diff --git a/cmd/acceptguard/report.go b/cmd/acceptguard/report.go index c82d902d5f..2b6c6e50d4 100644 --- a/cmd/acceptguard/report.go +++ b/cmd/acceptguard/report.go @@ -1,9 +1,12 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func writeJSON(path string, findings []finding) error { @@ -13,10 +16,9 @@ func writeJSON(path string, findings []finding) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(findings) + return json.MarshalEncode(enc, findings) } func printReport(findings []finding) { diff --git a/cmd/bdaudit/issues.go b/cmd/bdaudit/issues.go index 90a478ad36..fd1108e3cc 100644 --- a/cmd/bdaudit/issues.go +++ b/cmd/bdaudit/issues.go @@ -2,10 +2,12 @@ package main import ( "bufio" - "encoding/json" + "encoding/json/v2" "fmt" "os" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const statusClosed = "closed" @@ -61,7 +63,7 @@ func loadIssues(path string) (map[string]issue, error) { } var iss issue - if unmarshalErr := json.Unmarshal([]byte(text), &iss); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal([]byte(text), &iss, jsonx.V1()); unmarshalErr != nil { return nil, fmt.Errorf("%s:%d: %w", path, line, unmarshalErr) } if iss.ID == "" { diff --git a/cmd/bdaudit/report.go b/cmd/bdaudit/report.go index 416fab8122..4a11afecde 100644 --- a/cmd/bdaudit/report.go +++ b/cmd/bdaudit/report.go @@ -1,11 +1,14 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "os" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type report struct { @@ -23,10 +26,9 @@ func writeJSONReport(path string, rep report) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(rep); encErr != nil { + if encErr := json.MarshalEncode(enc, rep); encErr != nil { return fmt.Errorf("encode json: %w", encErr) } diff --git a/cmd/bodyclass/report.go b/cmd/bodyclass/report.go index b188fb33f1..5af8390219 100644 --- a/cmd/bodyclass/report.go +++ b/cmd/bodyclass/report.go @@ -1,10 +1,13 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type serviceReport struct { @@ -68,10 +71,9 @@ func writeJSON(path string, results []serviceReport) { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(results); encErr != nil { + if encErr := json.MarshalEncode(enc, results); encErr != nil { fatal(encErr) } diff --git a/cmd/cfnattrgen/main.go b/cmd/cfnattrgen/main.go index a4635c83b2..0742bd56f5 100644 --- a/cmd/cfnattrgen/main.go +++ b/cmd/cfnattrgen/main.go @@ -26,7 +26,8 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "go/ast" @@ -37,10 +38,12 @@ import ( "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type resourceTypeSpec struct { - Attributes map[string]json.RawMessage `json:"Attributes"` + Attributes map[string]jsontext.Value `json:"Attributes"` } type resourceSpec struct { @@ -158,15 +161,15 @@ func runTrimSpec(specPath, outPath string) error { continue } - attrs := make(map[string]json.RawMessage, len(rt.Attributes)) + attrs := make(map[string]jsontext.Value, len(rt.Attributes)) for attr := range rt.Attributes { - attrs[attr] = json.RawMessage("{}") + attrs[attr] = jsontext.Value("{}") } trimmed.ResourceTypes[name] = resourceTypeSpec{Attributes: attrs} } - out, err := json.MarshalIndent(trimmed, "", " ") + out, err := json.Marshal(trimmed, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return fmt.Errorf("marshal trimmed spec: %w", err) } @@ -187,7 +190,7 @@ func loadSpec(path string) (*resourceSpec, error) { } var spec resourceSpec - if unmarshalErr := json.Unmarshal(data, &spec); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &spec, jsonx.V1()); unmarshalErr != nil { return nil, unmarshalErr } @@ -295,7 +298,7 @@ func buildTable(spec *resourceSpec, supportedTypes map[string]struct{}) map[stri } func renderJSON(table map[string][]string) ([]byte, error) { - out, err := json.MarshalIndent(table, "", " ") + out, err := json.Marshal(table, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return nil, err } @@ -359,7 +362,7 @@ func buildServices(spec *resourceSpec, literals map[string]struct{}) []string { const minTypeNameParts = 3 func renderServices(services []string) ([]byte, error) { - out, err := json.MarshalIndent(services, "", " ") + out, err := json.Marshal(services, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return nil, err } diff --git a/cmd/cfnattrgen/regen_test.go b/cmd/cfnattrgen/regen_test.go index 34f33b3e74..8d788dc1b8 100644 --- a/cmd/cfnattrgen/regen_test.go +++ b/cmd/cfnattrgen/regen_test.go @@ -1,12 +1,14 @@ package main import ( - "encoding/json" + "encoding/json/v2" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -90,7 +92,7 @@ func TestRenderJSON_Deterministic(t *testing.T) { assert.Equal(t, first, second) var roundTrip map[string][]string - require.NoError(t, json.Unmarshal(first, &roundTrip)) + require.NoError(t, json.Unmarshal(first, &roundTrip, jsonx.V1())) assert.Equal(t, table, roundTrip) } diff --git a/cmd/cfnpropgen/main.go b/cmd/cfnpropgen/main.go index 59e8584569..fc0546e135 100644 --- a/cmd/cfnpropgen/main.go +++ b/cmd/cfnpropgen/main.go @@ -8,11 +8,14 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "os" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type propertySpec struct { @@ -53,7 +56,7 @@ func readJSON(path string, v any) error { return err } - return json.Unmarshal(data, v) + return json.Unmarshal(data, v, jsonx.V1()) } func run(specPath, typesPath, outPath, reqPath string) error { @@ -67,7 +70,12 @@ func run(specPath, typesPath, outPath, reqPath string) error { return fmt.Errorf("load types: %w", err) } - out, err := json.MarshalIndent(buildSchemas(&spec, types), "", " ") + out, err := json.Marshal( + buildSchemas(&spec, types), + jsonx.V1(), + jsontext.Multiline(true), + jsontext.WithIndent(" "), + ) if err != nil { return fmt.Errorf("render: %w", err) } @@ -80,7 +88,12 @@ func run(specPath, typesPath, outPath, reqPath string) error { return nil } - req, err := json.MarshalIndent(buildRequired(&spec, types), "", " ") + req, err := json.Marshal( + buildRequired(&spec, types), + jsonx.V1(), + jsontext.Multiline(true), + jsontext.WithIndent(" "), + ) if err != nil { return fmt.Errorf("render required: %w", err) } diff --git a/cmd/clientcoverage/main.go b/cmd/clientcoverage/main.go index aa8c7d7863..479738cd11 100644 --- a/cmd/clientcoverage/main.go +++ b/cmd/clientcoverage/main.go @@ -36,7 +36,8 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "go/ast" @@ -49,6 +50,8 @@ import ( "regexp" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -650,7 +653,7 @@ func loadOpcensus(path string) ([]opService, error) { } var services []opService - if unmarshalErr := json.Unmarshal(data, &services); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &services, jsonx.V1()); unmarshalErr != nil { return nil, fmt.Errorf("parse %s: %w", path, unmarshalErr) } @@ -692,7 +695,7 @@ func main() { rpt := buildReport(services, result) if *jsonPath != "" { - data, marshalErr := json.MarshalIndent(rpt, "", " ") + data, marshalErr := json.Marshal(rpt, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if marshalErr != nil { fmt.Fprintln(os.Stderr, "marshal report:", marshalErr) os.Exit(1) diff --git a/cmd/enumcheck/report.go b/cmd/enumcheck/report.go index aaad7c39e6..7d692353fb 100644 --- a/cmd/enumcheck/report.go +++ b/cmd/enumcheck/report.go @@ -1,9 +1,12 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func writeJSON(path string, findings []finding) error { @@ -12,10 +15,9 @@ func writeJSON(path string, findings []finding) error { return err } - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(findings); encErr != nil { + if encErr := json.MarshalEncode(enc, findings); encErr != nil { _ = f.Close() return encErr diff --git a/cmd/errcodeaudit/report.go b/cmd/errcodeaudit/report.go index 8d564cd3c2..ea37dc3a9e 100644 --- a/cmd/errcodeaudit/report.go +++ b/cmd/errcodeaudit/report.go @@ -1,9 +1,12 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func writeJSON(path string, findings []finding) error { @@ -13,10 +16,9 @@ func writeJSON(path string, findings []finding) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(findings) + return json.MarshalEncode(enc, findings) } func printReport(findings []finding) { diff --git a/cmd/errtargetaudit/report.go b/cmd/errtargetaudit/report.go index 6d8f01b33a..6e072770b2 100644 --- a/cmd/errtargetaudit/report.go +++ b/cmd/errtargetaudit/report.go @@ -1,11 +1,14 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // lowResolutionThreshold gates the implausible-resolution guard -- @@ -211,10 +214,9 @@ func writeJSON(path string, scans []serviceScan) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(scans) + return json.MarshalEncode(enc, scans) } func printServiceScan(sr serviceScan) { diff --git a/cmd/jsonv2mod/main.go b/cmd/jsonv2mod/main.go new file mode 100644 index 0000000000..2bfff044be --- /dev/null +++ b/cmd/jsonv2mod/main.go @@ -0,0 +1,141 @@ +// Command jsonv2mod rewrites encoding/json (v1) call sites to +// encoding/json/v2 plus jsontext, passing jsonx.V1() so behavior is unchanged. +// +// Usage: +// +// go run ./cmd/jsonv2mod [-n] [-skip frag,...] path... +// +// Each path is a Go file or a directory walked recursively. Files are +// rewritten in place (-n lists them instead). Constructs that cannot be +// rewritten safely are left untouched and printed as file:line: reason; the +// exit status is 1 when any are reported. See pkgs/jsonx for the mapping. +package main + +import ( + "flag" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" +) + +const ( + exitLeftovers = 1 + exitFailure = 2 + fileMode = 0o600 +) + +type options struct { + out, errOut io.Writer + skips []string + dry bool +} + +func main() { + dry := flag.Bool("n", false, "list files that would change without writing") + skip := flag.String("skip", "", "comma-separated path fragments to skip") + flag.Parse() + + opts := options{out: os.Stdout, errOut: os.Stderr, skips: strings.Split(*skip, ","), dry: *dry} + + sum, err := opts.run(flag.Args()) + if err != nil { + fmt.Fprintln(os.Stderr, "jsonv2mod:", err) + os.Exit(exitFailure) + } + + fmt.Fprintf(os.Stderr, "jsonv2mod: %d files changed, %d leftovers\n", sum.changed, sum.leftovers) + + if sum.leftovers > 0 { + os.Exit(exitLeftovers) + } +} + +type summary struct{ changed, leftovers int } + +func (o options) run(roots []string) (summary, error) { + var ( + files []string + sum summary + ) + + for _, root := range roots { + walkErr := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err == nil && !d.IsDir() && strings.HasSuffix(path, ".go") && !skipped(path, o.skips) { + files = append(files, path) + } + + return err + }) + if walkErr != nil { + return sum, walkErr + } + } + + for _, path := range files { + one, err := o.process(path) + if err != nil { + return sum, err + } + + sum.changed += one.changed + sum.leftovers += one.leftovers + } + + return sum, nil +} + +func (o options) process(path string) (summary, error) { + var sum summary + + res, err := processFile(path) + if err != nil { + return sum, err + } + + for _, f := range res.findings { + fmt.Fprintf(o.errOut, "%s:%d: %s\n", path, f.line, f.msg) + + sum.leftovers++ + } + + if res.out == nil { + return sum, nil + } + + sum.changed++ + + fmt.Fprintln(o.out, path) + + if o.dry { + return sum, nil + } + + return sum, os.WriteFile(path, res.out, fileMode) +} + +func skipped(path string, skips []string) bool { + slash := filepath.ToSlash(path) + if strings.Contains(slash, "pkgs/jsonx/") || strings.Contains(slash, "cmd/jsonv2mod/") { + return true + } + + for _, s := range skips { + if s != "" && strings.Contains(slash, s) { + return true + } + } + + return false +} + +func processFile(path string) (*result, error) { + src, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + return rewrite(path, src) +} diff --git a/cmd/jsonv2mod/rewrite.go b/cmd/jsonv2mod/rewrite.go new file mode 100644 index 0000000000..80e77dc8ad --- /dev/null +++ b/cmd/jsonv2mod/rewrite.go @@ -0,0 +1,653 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "sort" + "strconv" + "strings" +) + +const ( + v1Path = "encoding/json" + v2Path = "encoding/json/v2" + jsontextPath = "encoding/json/jsontext" + jsonxPath = "github.com/blackbirdworks/gopherstack/pkgs/jsonx" + localPrefix = "github.com/blackbirdworks/gopherstack" + optV1 = "jsonx.V1()" +) + +var errOverlap = errors.New("overlapping edits") + +type finding struct { + msg string + line int +} + +type result struct { + out []byte + findings []finding +} + +type edit struct { + text string + start, end int +} + +type stream struct { + call *ast.CallExpr + block *ast.BlockStmt + name string + opts []string + edits []edit + decodes []*ast.CallExpr + decoder bool + bad bool +} + +type rewriter struct { + fset *token.FileSet + file *ast.File + src []byte + jsonName string + consumed map[ast.Node]bool + streams map[string]*stream + edits []edit + findings []finding +} + +func (r *rewriter) off(p token.Pos) int { return r.fset.Position(p).Offset } + +func (r *rewriter) text(a, b ast.Node) string { return string(r.src[r.off(a.Pos()):r.off(b.End())]) } + +func (r *rewriter) add(start, end int, text string) { + r.edits = append(r.edits, edit{start: start, end: end, text: text}) +} + +func (r *rewriter) reportf(p token.Pos, format string, a ...any) { + r.findings = append(r.findings, finding{line: r.fset.Position(p).Line, msg: fmt.Sprintf(format, a...)}) +} + +func (r *rewriter) isJSON(e ast.Expr, name string) bool { + sel, ok := e.(*ast.SelectorExpr) + if !ok { + return false + } + + id, ok := sel.X.(*ast.Ident) + + return ok && id.Name == r.jsonName && (name == "" || sel.Sel.Name == name) +} + +func (r *rewriter) hasComment(start, end int) bool { + for _, cg := range r.file.Comments { + if r.off(cg.Pos()) < end && r.off(cg.End()) > start { + return true + } + } + + return false +} + +func rewrite(path string, src []byte) (*result, error) { + fset := token.NewFileSet() + + file, err := parser.ParseFile(fset, path, src, parser.ParseComments) + if err != nil { + return nil, err + } + + name, ok := jsonImportName(file) + if !ok { + return &result{}, nil + } + + r := &rewriter{fset: fset, file: file, src: src, jsonName: name, consumed: map[ast.Node]bool{}} + + for _, d := range file.Decls { + r.streams = map[string]*stream{} + r.collectStreams(d) + r.rewriteStreamUses(d) + r.rewriteCalls(d) + r.rewriteSelectors(d) + r.emitStreams() + } + + out, err := r.apply() + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + + out, err = fixImports(path, out, name) + if err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + + res := &result{findings: r.findings} + if !bytes.Equal(out, src) { + res.out = out + } + + return res, nil +} + +func jsonImportName(file *ast.File) (string, bool) { + for _, imp := range file.Imports { + if p, _ := strconv.Unquote(imp.Path.Value); p != v1Path { + continue + } + + if imp.Name == nil { + return "json", true + } + + if imp.Name.Name == "_" || imp.Name.Name == "." { + return "", false + } + + return imp.Name.Name, true + } + + return "", false +} + +func (r *rewriter) collectStreams(d ast.Decl) { + register := func(lhs ast.Expr, rhs ast.Expr, block *ast.BlockStmt) { + id, ok := lhs.(*ast.Ident) + call, isCall := rhs.(*ast.CallExpr) + + if !ok || !isCall || len(call.Args) != 1 { + return + } + + switch { + case r.isJSON(call.Fun, "NewDecoder"): + r.streams[id.Name] = &stream{name: id.Name, call: call, decoder: true, block: block} + case r.isJSON(call.Fun, "NewEncoder"): + r.streams[id.Name] = &stream{name: id.Name, call: call, block: block} + default: + return + } + + r.consumed[call.Fun] = true + } + + ast.Inspect(d, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.AssignStmt: + if len(x.Lhs) == 1 && len(x.Rhs) == 1 { + register(x.Lhs[0], x.Rhs[0], enclosing(d, x)) + } + case *ast.ValueSpec: + if len(x.Names) == 1 && len(x.Values) == 1 { + register(x.Names[0], x.Values[0], enclosing(d, x)) + } + } + + return true + }) +} + +// enclosing returns the innermost block statement of d containing n. +func enclosing(d ast.Decl, n ast.Node) *ast.BlockStmt { + var best *ast.BlockStmt + + ast.Inspect(d, func(c ast.Node) bool { + if b, ok := c.(*ast.BlockStmt); ok && b.Pos() <= n.Pos() && n.End() <= b.End() { + best = b + } + + return true + }) + + return best +} + +func (r *rewriter) declIdents(d ast.Decl) map[*ast.Ident]bool { + out := map[*ast.Ident]bool{} + + ast.Inspect(d, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.AssignStmt: + for _, l := range x.Lhs { + if id, ok := l.(*ast.Ident); ok && r.streams[id.Name] != nil { + out[id] = true + } + } + case *ast.ValueSpec: + for _, id := range x.Names { + out[id] = true + } + } + + return true + }) + + return out +} + +func (r *rewriter) rewriteStreamUses(d ast.Decl) { + if len(r.streams) == 0 { + return + } + + decls := r.declIdents(d) + + var stack []ast.Node + + ast.Inspect(d, func(n ast.Node) bool { + if n == nil { + stack = stack[:len(stack)-1] + + return true + } + + stack = append(stack, n) + + if id, ok := n.(*ast.Ident); ok && !decls[id] && r.streams[id.Name] != nil { + r.streamUse(r.streams[id.Name], id, stack) + } + + return true + }) +} + +// Stack offsets from the identifier at the top: selector, call, statement, block. +const ( + stackSel = 2 + stackCall = 3 + stackStmt = 4 + stackBlock = 5 +) + +func (r *rewriter) streamUse(s *stream, id *ast.Ident, stack []ast.Node) { + n := len(stack) + if n < stackCall { + s.bad = true + + return + } + + sel, isSel := stack[n-stackSel].(*ast.SelectorExpr) + call, isCall := stack[n-stackCall].(*ast.CallExpr) + + if !isSel || sel.X != id || !isCall || call.Fun != sel { + s.bad = true + + r.reportf(id.Pos(), "%s escapes (not a plain method call); rewrite by hand", s.name) + + return + } + + var stmt *ast.ExprStmt + + if n >= stackBlock && stack[n-stackBlock] == ast.Node(s.block) { + stmt, _ = stack[n-stackStmt].(*ast.ExprStmt) + } + + var handled bool + if s.decoder { + handled = r.decoderMethod(s, call, sel.Sel.Name, stmt) + } else { + handled = r.encoderMethod(s, call, sel.Sel.Name, stmt) + } + + if !handled { + s.bad = true + + r.reportf(call.Pos(), "%s.%s has no mechanical v2 equivalent; rewrite by hand", s.name, sel.Sel.Name) + } +} + +func (r *rewriter) decoderMethod(s *stream, call *ast.CallExpr, method string, stmt *ast.ExprStmt) bool { + argc := len(call.Args) + + switch { + case method == "Decode" && argc == 1: + s.decodes = append(s.decodes, call) + case method == "UseNumber" && argc == 0 && stmt != nil: + s.opts = append(s.opts, "jsonx.UseNumber()") + s.edits = append(s.edits, r.stmtEdit(stmt)) + case method == "DisallowUnknownFields" && argc == 0 && stmt != nil: + s.opts = append(s.opts, r.jsonName+".RejectUnknownMembers(true)") + s.edits = append(s.edits, r.stmtEdit(stmt)) + default: + return false + } + + return true +} + +func (r *rewriter) encoderMethod(s *stream, call *ast.CallExpr, method string, stmt *ast.ExprStmt) bool { + argc := len(call.Args) + + switch { + case method == "Encode" && argc == 1: + s.edits = append(s.edits, edit{ + start: r.off(call.Pos()), end: r.off(call.Args[0].Pos()), + text: r.jsonName + ".MarshalEncode(" + s.name + ", ", + }) + case method == "SetEscapeHTML" && argc == 1 && stmt != nil: + s.opts = append(s.opts, "jsontext.EscapeForHTML("+r.text(call.Args[0], call.Args[0])+")") + s.edits = append(s.edits, r.stmtEdit(stmt)) + case method == "SetIndent" && argc == 2 && stmt != nil: + s.opts = append(s.opts, r.indentOpts(call.Args[0], call.Args[1])...) + s.edits = append(s.edits, r.stmtEdit(stmt)) + default: + return false + } + + return true +} + +func (r *rewriter) indentOpts(prefix, indent ast.Expr) []string { + opts := []string{"jsontext.Multiline(true)"} + + if lit, ok := prefix.(*ast.BasicLit); !ok || lit.Value != `""` { + opts = append(opts, "jsontext.WithIndentPrefix("+r.text(prefix, prefix)+")") + } + + if lit, ok := indent.(*ast.BasicLit); !ok || lit.Value != `""` { + opts = append(opts, "jsontext.WithIndent("+r.text(indent, indent)+")") + } + + return opts +} + +func (r *rewriter) stmtEdit(s *ast.ExprStmt) edit { + start, end := r.off(s.Pos()), r.off(s.End()) + + for start > 0 && (r.src[start-1] == ' ' || r.src[start-1] == '\t') { + start-- + } + + if end < len(r.src) && r.src[end] == '\n' { + end++ + } + + return edit{start: start, end: end} +} + +func (r *rewriter) emitStreams() { + for _, s := range r.streams { + fun, ok := s.call.Fun.(*ast.SelectorExpr) + if s.bad || !ok { + continue + } + + r.edits = append(r.edits, s.edits...) + + opts := ", " + optV1 + if len(s.opts) > 0 { + opts += ", " + strings.Join(s.opts, ", ") + } + + if !s.decoder { + r.add(r.off(fun.Pos()), r.off(fun.End()), "jsontext.NewEncoder") + end := r.off(s.call.Args[0].End()) + r.add(end, end, opts) + + continue + } + + r.add(r.off(fun.Pos()), r.off(fun.End()), "jsontext.NewDecoder") + + for _, c := range s.decodes { + r.add(r.off(c.Pos()), r.off(c.Args[0].Pos()), r.jsonName+".UnmarshalDecode("+s.name+", ") + r.add(r.off(c.Args[0].End()), r.off(c.End()), opts+")") + } + } +} + +func (r *rewriter) rewriteCalls(d ast.Decl) { + ast.Inspect(d, func(n ast.Node) bool { + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } + + r.callExpr(call) + + return true + }) +} + +func (r *rewriter) callExpr(call *ast.CallExpr) { + argc := len(call.Args) + + switch { + case r.isJSON(call.Fun, "Marshal") && argc == 1, r.isJSON(call.Fun, "Unmarshal") && argc == 2: + r.consumed[call.Fun] = true + end := r.off(call.Args[len(call.Args)-1].End()) + r.add(end, end, ", "+optV1) + case r.isJSON(call.Fun, "MarshalIndent") && argc == 3: + r.marshalIndent(call) + case r.isJSON(call.Fun, "Valid") && argc == 1: + r.consumed[call.Fun] = true + r.add(r.off(call.Pos()), r.off(call.Args[0].Pos()), "jsontext.Value(") + r.add(r.off(call.Args[0].End()), r.off(call.End()), ").IsValid("+optV1+")") + default: + r.chain(call) + } +} + +func (r *rewriter) marshalIndent(call *ast.CallExpr) { + for _, arg := range call.Args[1:] { + lit, isLit := arg.(*ast.BasicLit) + if !isLit { + continue + } + + if s, err := strconv.Unquote(lit.Value); err == nil && strings.Trim(s, " \t\r\n") != "" { + r.reportf(call.Pos(), "MarshalIndent with non-whitespace indent %s; v2 panics, rewrite by hand", lit.Value) + + return + } + } + + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + + r.consumed[call.Fun] = true + r.add(r.off(sel.Sel.Pos()), r.off(sel.Sel.End()), "Marshal") + + opts := append([]string{optV1}, r.indentOpts(call.Args[1], call.Args[2])...) + r.add(r.off(call.Args[0].End()), r.off(call.Args[2].End()), ", "+strings.Join(opts, ", ")) +} + +func (r *rewriter) chain(call *ast.CallExpr) { + outer, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + + inner, ok := outer.X.(*ast.CallExpr) + if !ok || len(inner.Args) != 1 || len(call.Args) != 1 { + return + } + + start, end := r.off(inner.Args[0].End()), r.off(call.Args[0].Pos()) + + switch { + case r.isJSON(inner.Fun, "NewDecoder") && outer.Sel.Name == "Decode": + if r.hasComment(r.off(call.Pos()), r.off(call.End())) { + r.reportf(call.Pos(), "comment inside NewDecoder(...).Decode chain; rewrite by hand") + + return + } + + r.consumed[inner.Fun] = true + r.add(r.off(call.Pos()), r.off(inner.Args[0].Pos()), r.jsonName+".UnmarshalDecode(jsontext.NewDecoder(") + r.add(start, end, "), ") + r.add(r.off(call.Args[0].End()), r.off(call.End()), ", "+optV1+")") + case r.isJSON(inner.Fun, "NewEncoder") && outer.Sel.Name == "Encode": + if r.hasComment(r.off(call.Pos()), r.off(call.End())) { + r.reportf(call.Pos(), "comment inside NewEncoder(...).Encode chain; rewrite by hand") + + return + } + + r.consumed[inner.Fun] = true + r.add(r.off(call.Pos()), r.off(inner.Args[0].Pos()), r.jsonName+".MarshalEncode(jsontext.NewEncoder(") + r.add(start, end, ", "+optV1+"), ") + r.add(r.off(call.Args[0].End()), r.off(call.End()), ")") + } +} + +func (r *rewriter) rewriteSelectors(d ast.Decl) { + ast.Inspect(d, func(n ast.Node) bool { + sel, ok := n.(*ast.SelectorExpr) + if !ok || r.consumed[sel] || !r.isJSON(sel, "") { + return true + } + + switch sel.Sel.Name { + case "RawMessage": + r.add(r.off(sel.Pos()), r.off(sel.End()), "jsontext.Value") + case "Number": + r.add(r.off(sel.Pos()), r.off(sel.End()), "jsonx.Number") + case "Marshaler", "Unmarshaler": + default: + r.reportf(sel.Pos(), "%s.%s has no mechanical v2 equivalent; rewrite by hand", r.jsonName, sel.Sel.Name) + } + + return true + }) +} + +func (r *rewriter) apply() ([]byte, error) { + sort.SliceStable(r.edits, func(i, j int) bool { return r.edits[i].start > r.edits[j].start }) + + out := append([]byte(nil), r.src...) + prev := len(out) + 1 + + for _, e := range r.edits { + if e.end > prev { + return nil, fmt.Errorf("%w at offset %d", errOverlap, e.start) + } + + out = append(out[:e.start], append([]byte(e.text), out[e.end:]...)...) + prev = e.start + } + + return out, nil +} + +type importInfo struct { + v1Spec *ast.ImportSpec + firstLocal *ast.ImportSpec + last *ast.ImportSpec + have map[string]bool + used map[string]bool +} + +func scanImports(file *ast.File) importInfo { + info := importInfo{have: map[string]bool{}, used: map[string]bool{}} + + for _, imp := range file.Imports { + p, _ := strconv.Unquote(imp.Path.Value) + info.have[p] = true + info.last = imp + + if p == v1Path { + info.v1Spec = imp + } + + if info.firstLocal == nil && strings.HasPrefix(p, localPrefix) { + info.firstLocal = imp + } + } + + ast.Inspect(file, func(n ast.Node) bool { + if sel, ok := n.(*ast.SelectorExpr); ok { + if id, isID := sel.X.(*ast.Ident); isID { + info.used[id.Name] = true + } + } + + return true + }) + + return info +} + +// fixImports swaps the v1 import for v2 (or drops it when unused after the +// rewrite) and adds jsontext / jsonx when referenced. +func fixImports(path string, src []byte, jsonName string) ([]byte, error) { + fset := token.NewFileSet() + + file, err := parser.ParseFile(fset, path, src, parser.ParseComments) + if err != nil { + return nil, fmt.Errorf("rewritten file does not parse: %w", err) + } + + info := scanImports(file) + off := func(p token.Pos) int { return fset.Position(p).Offset } + spec := info.v1Spec + + if decl := importDecl(file, spec); !decl.Lparen.IsValid() { + block := "import (\n\t" + string(src[off(spec.Pos()):off(spec.End())]) + "\n)" + out := append(append(append([]byte(nil), src[:off(decl.Pos())]...), block...), src[off(decl.End()):]...) + + return fixImports(path, out, jsonName) + } + + var edits []edit + + if info.used[jsonName] { + edits = append(edits, edit{start: off(spec.Path.Pos()), end: off(spec.Path.End()), text: strconv.Quote(v2Path)}) + } else { + start, end := off(spec.Pos()), off(spec.End()) + for start > 0 && (src[start-1] == ' ' || src[start-1] == '\t') { + start-- + } + + if end < len(src) && src[end] == '\n' { + end++ + } + + edits = append(edits, edit{start: start, end: end}) + } + + if info.used["jsontext"] && !info.have[jsontextPath] { + edits = append( + edits, + edit{start: off(spec.End()), end: off(spec.End()), text: "\n\t" + strconv.Quote(jsontextPath)}, + ) + } + + if info.used["jsonx"] && !info.have[jsonxPath] { + at, text := off(info.last.End()), "\n\n\t"+strconv.Quote(jsonxPath) + if info.firstLocal != nil { + at, text = off(info.firstLocal.Pos()), strconv.Quote(jsonxPath)+"\n\t" + } + + edits = append(edits, edit{start: at, end: at, text: text}) + } + + sort.SliceStable(edits, func(i, j int) bool { return edits[i].start > edits[j].start }) + + out := append([]byte(nil), src...) + for _, e := range edits { + out = append(out[:e.start], append([]byte(e.text), out[e.end:]...)...) + } + + return format.Source(out) +} + +func importDecl(file *ast.File, spec *ast.ImportSpec) *ast.GenDecl { + for _, d := range file.Decls { + if g, ok := d.(*ast.GenDecl); ok && g.Tok == token.IMPORT && g.Pos() <= spec.Pos() && spec.End() <= g.End() { + return g + } + } + + return nil +} diff --git a/cmd/jsonv2mod/rewrite_test.go b/cmd/jsonv2mod/rewrite_test.go new file mode 100644 index 0000000000..3c8ad3ddd9 --- /dev/null +++ b/cmd/jsonv2mod/rewrite_test.go @@ -0,0 +1,185 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const header = `package p + +import ( + "encoding/json" + "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/arn" +) + +var _ = http.StatusOK +var _ = arn.X + +` + +func TestRewrite(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + body string + want []string + wantFindings int + }{ + { + name: "marshal unmarshal", + body: `func f(b []byte) { + _, _ = json.Marshal(b) + _ = json.Unmarshal(b, &b) +}`, + want: []string{ + "json.Marshal(b, jsonx.V1())", "json.Unmarshal(b, &b, jsonx.V1())", + `"encoding/json/v2"`, `pkgs/jsonx"`, + }, + }, + { + name: "multiline trailing comma", + body: `func f(b []byte) { + _, _ = json.Marshal( + b, + ) +}`, + want: []string{"b, jsonx.V1(),"}, + }, + { + name: "marshal indent", + body: `func f(v any) { _, _ = json.MarshalIndent(v, "", " ") }`, + want: []string{ + `json.Marshal(v, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" "))`, + "encoding/json/jsontext", + }, + }, + { + name: "decode chain", + body: `func f(r *http.Request, v any) error { return json.NewDecoder(r.Body).Decode(&v) }`, + want: []string{"json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &v, jsonx.V1())"}, + }, + { + name: "encode chain", + body: `func f(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) }`, + want: []string{"json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), v)"}, + }, + { + name: "decoder var", + body: `func f(r *http.Request, v any) error { + dec := json.NewDecoder(r.Body) + dec.UseNumber() + dec.DisallowUnknownFields() + + return dec.Decode(&v) +}`, + want: []string{ + "dec := jsontext.NewDecoder(r.Body)", + "json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber(), json.RejectUnknownMembers(true))", + }, + }, + { + name: "encoder var", + body: `func f(w http.ResponseWriter, v any) error { + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + + return enc.Encode(v) +}`, + want: []string{ + `jsontext.NewEncoder(w, jsonx.V1(), jsontext.EscapeForHTML(false), jsontext.Multiline(true), ` + + `jsontext.WithIndent("\t"))`, + "json.MarshalEncode(enc, v)", + }, + }, + { + name: "types", + body: `type T struct { + R json.RawMessage + N json.Number +} + +var _ json.Marshaler + +func f(b []byte) bool { return json.Valid(b) }`, + want: []string{ + "R jsontext.Value", + "N jsonx.Number", + "json.Marshaler", + "jsontext.Value(b).IsValid(jsonx.V1())", + }, + }, + { + name: "bad stream stays untouched", + body: `func f(r *http.Request, v any) { + dec := json.NewDecoder(r.Body) + _ = dec.Decode(&v) + _ = dec.Buffered() +}`, + want: []string{"_ = dec.Decode(&v)", "json.NewDecoder(r.Body)"}, + wantFindings: 1, + }, + { + name: "conditional setindent is not hoisted", + body: `func f(w http.ResponseWriter, v any, c bool) { + enc := json.NewEncoder(w) + if c { + enc.SetIndent("", " ") + } + _ = enc.Encode(v) +}`, + want: []string{"enc.SetIndent"}, + wantFindings: 1, + }, + { + name: "leftovers", + body: `func f(r *http.Request) { + d := json.NewDecoder(r.Body) + _ = d.More() + var e *json.SyntaxError + _ = e +}`, + want: []string{"json.NewDecoder(r.Body)"}, + wantFindings: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + res, err := rewrite("x.go", []byte(header+tt.body+"\n")) + require.NoError(t, err) + assert.Len(t, res.findings, tt.wantFindings) + require.NotNil(t, res.out) + + for _, w := range tt.want { + assert.Contains(t, string(res.out), w) + } + }) + } +} + +func TestRewriteSingleLineImport(t *testing.T) { + t.Parallel() + + src := "package p\n\nimport \"encoding/json\"\n\nfunc f(b []byte) bool { return json.Valid(b) }\n" + res, err := rewrite("x.go", []byte(src)) + require.NoError(t, err) + require.NotNil(t, res.out) + assert.Contains(t, string(res.out), "jsontext.Value(b).IsValid(jsonx.V1())") + assert.Contains(t, string(res.out), "encoding/json/jsontext") +} + +func TestRewriteNoV1Import(t *testing.T) { + t.Parallel() + + res, err := rewrite("x.go", []byte("package p\n\nimport \"fmt\"\n\nvar _ = fmt.Sprint\n")) + require.NoError(t, err) + assert.Nil(t, res.out) +} diff --git a/cmd/opcensus/main.go b/cmd/opcensus/main.go index 5e76a6ee15..aa2d4350c4 100644 --- a/cmd/opcensus/main.go +++ b/cmd/opcensus/main.go @@ -40,7 +40,8 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "go/ast" @@ -52,6 +53,8 @@ import ( "slices" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -210,9 +213,8 @@ func writeJSON(path string, results []serviceResult) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") - if encErr := enc.Encode(results); encErr != nil { + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) + if encErr := json.MarshalEncode(enc, results); encErr != nil { return fmt.Errorf("encode json: %w", encErr) } diff --git a/cmd/overwidecandidates/main.go b/cmd/overwidecandidates/main.go index e3bc546c5e..75ae2aab5a 100644 --- a/cmd/overwidecandidates/main.go +++ b/cmd/overwidecandidates/main.go @@ -26,7 +26,8 @@ package main import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "flag" "fmt" @@ -37,6 +38,8 @@ import ( "slices" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dirModuleOverride maps services/ to its aws-sdk-go-v2/service module @@ -370,10 +373,9 @@ func writeJSON(path string, results map[string]serviceResult) { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(results); encErr != nil { + if encErr := json.MarshalEncode(enc, results); encErr != nil { fmt.Fprintln(os.Stderr, "error:", encErr) } diff --git a/cmd/parityfmtcheck/report.go b/cmd/parityfmtcheck/report.go index c9c4127bd8..cdf6b66522 100644 --- a/cmd/parityfmtcheck/report.go +++ b/cmd/parityfmtcheck/report.go @@ -1,10 +1,13 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const jsonFileMode = 0o644 @@ -28,7 +31,7 @@ func writeJSON(path string, results []result) error { }) } - data, err := json.MarshalIndent(out, "", " ") + data, err := json.Marshal(out, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return fmt.Errorf("marshal results: %w", err) } diff --git a/cmd/parityfmtcheck/report_test.go b/cmd/parityfmtcheck/report_test.go index 4fdf65d14a..aa62f38eb9 100644 --- a/cmd/parityfmtcheck/report_test.go +++ b/cmd/parityfmtcheck/report_test.go @@ -2,13 +2,15 @@ package main import ( "bytes" - "encoding/json" + "encoding/json/v2" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPrintReport(t *testing.T) { @@ -68,7 +70,7 @@ func TestWriteJSON(t *testing.T) { require.NoError(t, err) var got []jsonResult - require.NoError(t, json.Unmarshal(data, &got)) + require.NoError(t, json.Unmarshal(data, &got, jsonx.V1())) require.Len(t, got, 2) assert.Equal(t, "dlm", got[0].Service) diff --git a/cmd/paritylint/report.go b/cmd/paritylint/report.go index d1684e217f..0873068351 100644 --- a/cmd/paritylint/report.go +++ b/cmd/paritylint/report.go @@ -1,10 +1,13 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func printReport(w io.Writer, findings []finding) { @@ -35,7 +38,7 @@ func printReport(w io.Writer, findings []finding) { } func writeJSON(path string, findings []finding) error { - data, err := json.MarshalIndent(findings, "", " ") + data, err := json.Marshal(findings, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return err } diff --git a/cmd/pgoload/stepfunctions.go b/cmd/pgoload/stepfunctions.go index 44a7b242f7..ee4e8e1e0b 100644 --- a/cmd/pgoload/stepfunctions.go +++ b/cmd/pgoload/stepfunctions.go @@ -2,7 +2,7 @@ package main import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "log/slog" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/sfn" sfntypes "github.com/aws/aws-sdk-go-v2/service/sfn/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const sfnStateMachineName = "pgoload-sm" @@ -30,7 +32,7 @@ func sfnDefinition() string { "States": map[string]any{ sfnPassState: map[string]any{"Type": "Pass", "End": true}, }, - }) + }, jsonx.V1()) return string(def) } diff --git a/cmd/reqfielddiff/main.go b/cmd/reqfielddiff/main.go index 2eab5a822f..8d1c0fd1a2 100644 --- a/cmd/reqfielddiff/main.go +++ b/cmd/reqfielddiff/main.go @@ -420,7 +420,8 @@ package main import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "os" @@ -428,6 +429,8 @@ import ( "path/filepath" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -586,8 +589,7 @@ func writeJSON(path string, reports []serviceReport) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(reports) + return json.MarshalEncode(enc, reports) } diff --git a/cmd/reqfieldscan/main.go b/cmd/reqfieldscan/main.go index 4ff94473bb..aaa25c397b 100644 --- a/cmd/reqfieldscan/main.go +++ b/cmd/reqfieldscan/main.go @@ -206,7 +206,8 @@ package main import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "os" @@ -214,6 +215,8 @@ import ( "path/filepath" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -335,8 +338,7 @@ func writeJSON(path string, reports []serviceReport) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(reports) + return json.MarshalEncode(enc, reports) } diff --git a/cmd/requiredoutputfields/main.go b/cmd/requiredoutputfields/main.go index fe90fbfb32..f912c4e4b3 100644 --- a/cmd/requiredoutputfields/main.go +++ b/cmd/requiredoutputfields/main.go @@ -23,7 +23,8 @@ package main import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "os" @@ -33,6 +34,8 @@ import ( "slices" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dirModuleOverride maps services/ to its aws-sdk-go-v2/service module @@ -395,10 +398,9 @@ func writeJSON(path string, results map[string]serviceResult) { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(results); encErr != nil { + if encErr := json.MarshalEncode(enc, results); encErr != nil { fmt.Fprintln(os.Stderr, "error:", encErr) } diff --git a/cmd/retypesgen/main.go b/cmd/retypesgen/main.go index 098d93e5a4..85dd0cc4b9 100644 --- a/cmd/retypesgen/main.go +++ b/cmd/retypesgen/main.go @@ -3,7 +3,8 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "flag" "fmt" @@ -16,6 +17,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const fetchTimeout = time.Minute @@ -88,7 +90,12 @@ func run(out string) (int, error) { types := parse(string(b)) - data, err := json.MarshalIndent(catalog{Source: source, Types: types}, "", " ") + data, err := json.Marshal( + catalog{Source: source, Types: types}, + jsonx.V1(), + jsontext.Multiline(true), + jsontext.WithIndent(" "), + ) if err != nil { return 0, err } diff --git a/cmd/routecollisions/main.go b/cmd/routecollisions/main.go index 7ae9f2918e..0847cd3198 100644 --- a/cmd/routecollisions/main.go +++ b/cmd/routecollisions/main.go @@ -39,7 +39,8 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "flag" "fmt" "go/ast" @@ -50,6 +51,8 @@ import ( "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const cliGoPath = "internal/app/cli.go" @@ -169,10 +172,9 @@ func writeJSONReport(path string, results []svcInfo) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(results) + return json.MarshalEncode(enc, results) } func listServiceDirs(servicesDir string) ([]string, error) { diff --git a/cmd/sfnschemagen/main.go b/cmd/sfnschemagen/main.go index 2394fd94ea..4b6191583e 100644 --- a/cmd/sfnschemagen/main.go +++ b/cmd/sfnschemagen/main.go @@ -8,7 +8,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "fmt" "go/format" "log" @@ -20,6 +20,7 @@ import ( "sort" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire/ir" ) @@ -208,7 +209,7 @@ func queryVersion(dir string) string { } func writeModel(key string, model *ir.Model) { - raw, err := json.Marshal(model) + raw, err := json.Marshal(model, jsonx.V1()) if err != nil { log.Fatal(err) } diff --git a/cmd/sqdocsgen/main.go b/cmd/sqdocsgen/main.go index a29490a8d8..2086d692eb 100644 --- a/cmd/sqdocsgen/main.go +++ b/cmd/sqdocsgen/main.go @@ -6,7 +6,8 @@ package main import ( "bytes" "compress/gzip" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "flag" "fmt" @@ -18,6 +19,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const fetchTimeout = time.Minute @@ -208,7 +210,7 @@ func main() { slices.SortFunc(svcs, func(a, b service) int { return strings.Compare(a.Code, b.Code) }) - b, err := json.MarshalIndent(svcs, "", " ") + b, err := json.Marshal(svcs, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/cmd/staleclaims/report.go b/cmd/staleclaims/report.go index 0dfc74f075..b040237b38 100644 --- a/cmd/staleclaims/report.go +++ b/cmd/staleclaims/report.go @@ -1,10 +1,13 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const disclaimer = `NOTE ON READING THIS REPORT @@ -59,10 +62,9 @@ func writeJSON(path string, findings []finding) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(findings); encErr != nil { + if encErr := json.MarshalEncode(enc, findings); encErr != nil { return fmt.Errorf("encode json: %w", encErr) } diff --git a/cmd/stampaudit/report.go b/cmd/stampaudit/report.go index 2d8bdca390..877d9b4a9a 100644 --- a/cmd/stampaudit/report.go +++ b/cmd/stampaudit/report.go @@ -1,10 +1,13 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -130,10 +133,9 @@ func writeJSON(path string, results []result) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(out); encErr != nil { + if encErr := json.MarshalEncode(enc, out); encErr != nil { return fmt.Errorf("encode json: %w", encErr) } diff --git a/cmd/structfielddiff/main.go b/cmd/structfielddiff/main.go index 56f3b0bfba..b20830bfdd 100644 --- a/cmd/structfielddiff/main.go +++ b/cmd/structfielddiff/main.go @@ -38,7 +38,8 @@ package main import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "flag" "fmt" @@ -50,6 +51,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/cmd/internal/sdkshape" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dirModuleOverride maps services/ to its aws-sdk-go-v2/service module @@ -261,10 +263,9 @@ func writeJSON(path string, dumps []opDump) { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(dumps); encErr != nil { + if encErr := json.MarshalEncode(enc, dumps); encErr != nil { fatal(encErr) } diff --git a/cmd/xmlitemwrap/report.go b/cmd/xmlitemwrap/report.go index b14492b0af..c978689606 100644 --- a/cmd/xmlitemwrap/report.go +++ b/cmd/xmlitemwrap/report.go @@ -1,9 +1,12 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func writeJSON(path string, findings []finding) error { @@ -13,10 +16,9 @@ func writeJSON(path string, findings []finding) error { } defer f.Close() - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(findings) + return json.MarshalEncode(enc, findings) } func printReport(findings []finding) { diff --git a/cmd/zeroguard/report.go b/cmd/zeroguard/report.go index 68ce9764d5..aaa85ea99f 100644 --- a/cmd/zeroguard/report.go +++ b/cmd/zeroguard/report.go @@ -1,9 +1,12 @@ package main import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func writeJSON(path string, findings []finding) error { @@ -12,10 +15,9 @@ func writeJSON(path string, findings []finding) error { return err } - enc := json.NewEncoder(f) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(f, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if encErr := enc.Encode(findings); encErr != nil { + if encErr := json.MarshalEncode(enc, findings); encErr != nil { _ = f.Close() return encErr diff --git a/dashboard/azureaks_proxy_test.go b/dashboard/azureaks_proxy_test.go index 19ad9faac0..8ebfa6a889 100644 --- a/dashboard/azureaks_proxy_test.go +++ b/dashboard/azureaks_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -120,13 +121,13 @@ func TestAzureAKSProxy(t *testing.T) { require.NoError(t, h.Handler()(echo.NewContext(httptest.NewRequest(http.MethodGet, pool, nil), rec))) var doc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1())) props, ok := doc["properties"].(map[string]any) require.True(t, ok) props["count"] = 5 - body, err := json.Marshal(map[string]any{"properties": props}) + body, err := json.Marshal(map[string]any{"properties": props}, jsonx.V1()) require.NoError(t, err) runGCPSteps(t, h, []gcpStep{ diff --git a/dashboard/azureapim_proxy.go b/dashboard/azureapim_proxy.go index e2cd376069..a8c83b5223 100644 --- a/dashboard/azureapim_proxy.go +++ b/dashboard/azureapim_proxy.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/tls" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -253,14 +255,14 @@ func (t *apimTry) subscriptionKey(ctx context.Context, svc, sub string) (string, Properties map[string]any `json:"properties"` } - if json.Unmarshal(rec.Body.Bytes(), &doc) != nil || doc.Properties["state"] != apimPxActive { + if json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1()) != nil || doc.Properties["state"] != apimPxActive { return "", http.StatusConflict, "Subscription is not active" } rec = t.arm(ctx, http.MethodPost, base+"/listSecrets") var keys map[string]any - if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &keys) != nil { + if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &keys, jsonx.V1()) != nil { return "", http.StatusConflict, "Subscription key is unavailable" } @@ -298,7 +300,7 @@ func (t *apimTry) apiInfo(ctx context.Context, svc, api string) apimAPIInfo { } `json:"properties"` } - if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &doc) != nil { + if rec.Code != http.StatusOK || json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1()) != nil { return def } diff --git a/dashboard/azureapim_proxy_test.go b/dashboard/azureapim_proxy_test.go index 4f0d2a1a71..9c6022ddeb 100644 --- a/dashboard/azureapim_proxy_test.go +++ b/dashboard/azureapim_proxy_test.go @@ -2,7 +2,8 @@ package dashboard_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -56,7 +58,10 @@ func newAPIMDashboard(t *testing.T) *apimEnv { be := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Echo-Key", r.Header.Get("Ocp-Apim-Subscription-Key")) - _ = json.NewEncoder(w).Encode(map[string]any{"path": r.URL.Path, "header": r.Header, "query": r.URL.RawQuery}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]any{"path": r.URL.Path, "header": r.Header, "query": r.URL.RawQuery}, + ) })) t.Cleanup(be.Close) @@ -99,7 +104,7 @@ func newAPIMDashboard(t *testing.T) *apimEnv { Secondary string `json:"secondaryKey"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &keys)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &keys, jsonx.V1())) require.NotEmpty(t, keys.Primary) dash := echo.New() @@ -222,7 +227,7 @@ func TestAzureAPIMTryIt(t *testing.T) { var o out if rec.Code == http.StatusOK { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &o)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &o, jsonx.V1())) } return rec, o diff --git a/dashboard/azurearm_inproc_proxy.go b/dashboard/azurearm_inproc_proxy.go index 6b036ebd61..a591e35a02 100644 --- a/dashboard/azurearm_inproc_proxy.go +++ b/dashboard/azurearm_inproc_proxy.go @@ -2,7 +2,7 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +11,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -152,7 +154,7 @@ func armProxyRelay(c *echo.Context, rec *httptest.ResponseRecorder, view func(an // armScrubBody applies the optional view and the generic scrub to a JSON body; anything else is returned as is. func armScrubBody(body []byte, view func(any)) []byte { var doc any - if json.Unmarshal(body, &doc) != nil { + if json.Unmarshal(body, &doc, jsonx.V1()) != nil { return body } @@ -162,7 +164,7 @@ func armScrubBody(body []byte, view func(any)) []byte { armScrub(doc) - if out, err := json.Marshal(doc); err == nil { + if out, err := json.Marshal(doc, jsonx.V1()); err == nil { return out } diff --git a/dashboard/azureflexdb_proxy.go b/dashboard/azureflexdb_proxy.go index 47a6b191df..17e3ea7f09 100644 --- a/dashboard/azureflexdb_proxy.go +++ b/dashboard/azureflexdb_proxy.go @@ -1,12 +1,14 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/netip" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -115,7 +117,7 @@ func flexProxyBody(method string, p []string, in []byte) ([]byte, bool) { return nil, false } - b, err := json.Marshal(map[string]any{"properties": props}) + b, err := json.Marshal(map[string]any{"properties": props}, jsonx.V1()) return b, err == nil } @@ -129,7 +131,7 @@ type flexPropsIn struct { func flexDatabaseProps(in []byte) map[string]any { var req flexPropsIn - if json.Unmarshal(in, &req) != nil { + if json.Unmarshal(in, &req, jsonx.V1()) != nil { return nil } @@ -153,7 +155,7 @@ func flexDatabaseProps(in []byte) map[string]any { func flexFirewallProps(in []byte) map[string]any { var req flexPropsIn - if json.Unmarshal(in, &req) != nil { + if json.Unmarshal(in, &req, jsonx.V1()) != nil { return nil } diff --git a/dashboard/azureinproc_proxy_test.go b/dashboard/azureinproc_proxy_test.go index c1faf8b550..6cef6221f2 100644 --- a/dashboard/azureinproc_proxy_test.go +++ b/dashboard/azureinproc_proxy_test.go @@ -2,7 +2,7 @@ package dashboard_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/dashboard" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -110,7 +111,7 @@ func jsonField(t *testing.T, raw []byte, path ...any) string { t.Helper() var doc any - require.NoError(t, json.Unmarshal(raw, &doc)) + require.NoError(t, json.Unmarshal(raw, &doc, jsonx.V1())) for _, p := range path { switch k := p.(type) { diff --git a/dashboard/azurekeyvault_proxy.go b/dashboard/azurekeyvault_proxy.go index 700a5d4876..ddfa9f5971 100644 --- a/dashboard/azurekeyvault_proxy.go +++ b/dashboard/azurekeyvault_proxy.go @@ -2,7 +2,8 @@ package dashboard import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "regexp" "slices" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" azurekeyvaultbackend "github.com/blackbirdworks/gopherstack/services/azurekeyvault" ) @@ -74,7 +76,7 @@ func azureKeyVaultInternalToken() string { claims, _ := json.Marshal(map[string]any{ "iss": "gopherstack-dashboard", "exp": time.Now().Add(azureKeyVaultTokenTTL).Unix(), - }) + }, jsonx.V1()) return header + "." + enc.EncodeToString(claims) + "." } @@ -88,8 +90,12 @@ func (h *DashboardHandler) azureKeyVaultCreateVault(c *echo.Context) error { Name string `json:"name"` } - if err := json.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, azureKeyVaultMaxBodyBytes)). - Decode(&in); err != nil || !azureKeyVaultNameRe.MatchString(in.Name) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, azureKeyVaultMaxBodyBytes)), + &in, + jsonx.V1(), + ); err != nil || + !azureKeyVaultNameRe.MatchString(in.Name) { return c.JSON(http.StatusBadRequest, map[string]string{ "error": "vault name must be 3-24 characters: letters, digits and hyphens, starting with a letter", }) diff --git a/dashboard/azuremonitor_proxy_test.go b/dashboard/azuremonitor_proxy_test.go index ae486bfc51..28c87568be 100644 --- a/dashboard/azuremonitor_proxy_test.go +++ b/dashboard/azuremonitor_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuremonitor" ) @@ -148,7 +149,7 @@ func TestAzureMonitorProxy(t *testing.T) { } `json:"innererror"` } `json:"error"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "BadArgumentError", body.Error.Code) assert.Equal(t, "SemanticError", body.Error.Innererror.Code) assert.NotEmpty(t, body.Error.Innererror.Innererror.Message) diff --git a/dashboard/cloud_json.go b/dashboard/cloud_json.go index eb8055b440..fb753f34d3 100644 --- a/dashboard/cloud_json.go +++ b/dashboard/cloud_json.go @@ -1,10 +1,13 @@ package dashboard import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -33,8 +36,8 @@ func cloudUnavailable(c *echo.Context, name string) error { // cloudJSONDecode reads a size-bounded JSON body into v; on failure it has already replied. func cloudJSONDecode(c *echo.Context, v any) bool { - dec := json.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, cloudJSONMaxBody)) - if err := dec.Decode(v); err != nil { + dec := jsontext.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, cloudJSONMaxBody)) + if err := json.UnmarshalDecode(dec, v, jsonx.V1()); err != nil { _ = cloudJSONFail(c, http.StatusBadRequest, cloudStatusBadArg, "invalid request body") return false diff --git a/dashboard/gcpapigateway_proxy.go b/dashboard/gcpapigateway_proxy.go index 42cfd4d2f6..275b00128b 100644 --- a/dashboard/gcpapigateway_proxy.go +++ b/dashboard/gcpapigateway_proxy.go @@ -2,7 +2,7 @@ package dashboard import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -17,6 +17,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpapigateway" ) @@ -194,7 +195,7 @@ func (a *gcpAPIGWProxy) tryIt(ctx context.Context, r gcpRESTCall) (proto.Message bad := status.Error(codes.InvalidArgument, "invalid API Gateway try-it request") var in gcpAPIGWTryIn - if len(r.body) > gcpAPIGWMaxBody || json.Unmarshal(r.body, &in) != nil { + if len(r.body) > gcpAPIGWMaxBody || json.Unmarshal(r.body, &in, jsonx.V1()) != nil { return nil, bad } @@ -257,7 +258,7 @@ func gcpAPIGWTryTarget(project, location, gateway string, in gcpAPIGWTryIn) (str // structOf converts a JSON-compatible value into the protobuf Struct the REST proxy renders. func structOf(v any) (proto.Message, error) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, status.Error(codes.Internal, "unreadable response") } diff --git a/dashboard/gcpapigateway_proxy_test.go b/dashboard/gcpapigateway_proxy_test.go index 4aeeb4659f..673c5f69a6 100644 --- a/dashboard/gcpapigateway_proxy_test.go +++ b/dashboard/gcpapigateway_proxy_test.go @@ -2,7 +2,8 @@ package dashboard_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpapigateway" ) @@ -42,7 +44,10 @@ func newAPIGatewayDashboard(t *testing.T) *dashboard.DashboardHandler { be := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Echo-Query", r.URL.RawQuery) - _ = json.NewEncoder(w).Encode(map[string]string{"path": r.URL.Path, "query": r.URL.RawQuery}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"path": r.URL.Path, "query": r.URL.RawQuery}, + ) })) t.Cleanup(be.Close) @@ -124,7 +129,7 @@ func TestGCPAPIGatewayProxy(t *testing.T) { } `json:"openapiDocuments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) require.Len(t, cfg.Docs, 1) raw, err := base64.StdEncoding.DecodeString(cfg.Docs[0].Document.Contents) @@ -211,7 +216,7 @@ func TestGCPAPIGatewayTryIt(t *testing.T) { var o out if rec.Code == http.StatusOK { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &o)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &o, jsonx.V1())) } return rec, o diff --git a/dashboard/gcpbigtable_proxy.go b/dashboard/gcpbigtable_proxy.go index 4eb2ddca46..9f647a052c 100644 --- a/dashboard/gcpbigtable_proxy.go +++ b/dashboard/gcpbigtable_proxy.go @@ -2,7 +2,7 @@ package dashboard import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "cloud.google.com/go/bigtable/admin/apiv2/adminpb" @@ -13,6 +13,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" gcpbigtablebackend "github.com/blackbirdworks/gopherstack/services/gcpbigtable" ) @@ -171,7 +172,7 @@ func (a *gcpBigtableAPI) readRows(r gcpRESTCall, table string) (proto.Message, e } var v any - if uErr := json.Unmarshal(b, &v); uErr != nil { + if uErr := json.Unmarshal(b, &v, jsonx.V1()); uErr != nil { return nil, status.Error(codes.Internal, uErr.Error()) } diff --git a/dashboard/gcpfunctions_proxy.go b/dashboard/gcpfunctions_proxy.go index b73380fcbe..c4278c8558 100644 --- a/dashboard/gcpfunctions_proxy.go +++ b/dashboard/gcpfunctions_proxy.go @@ -3,7 +3,7 @@ package dashboard import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "cloud.google.com/go/functions/apiv2/functionspb" @@ -12,6 +12,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpfunctions" ) @@ -139,7 +140,7 @@ type invokeInput struct { func (a *gcpFunctionsAPI) invoke(ctx context.Context, name string, raw []byte) (proto.Message, error) { var in invokeInput - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, status.Error(codes.InvalidArgument, "invalid request body: "+err.Error()) } diff --git a/dashboard/gcpidentitytoolkit_proxy.go b/dashboard/gcpidentitytoolkit_proxy.go index a9d25ddcab..5e9e96b23e 100644 --- a/dashboard/gcpidentitytoolkit_proxy.go +++ b/dashboard/gcpidentitytoolkit_proxy.go @@ -2,7 +2,7 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -147,7 +149,7 @@ func (p *gcpIDPRequest) call(method, path, rawQuery string, body any) (*httptest var payload []byte if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) if err != nil { return nil, err //nolint:wrapcheck // surfaced as a 400 by the caller } @@ -262,7 +264,7 @@ func (p *gcpIDPRequest) userAction(uid, action string) error { return ociInvalid(p.c, "claims must be a JSON object") } - raw, err := json.Marshal(in.Claims) + raw, err := json.Marshal(in.Claims, jsonx.V1()) if err != nil || len(raw) > gcpIDPMaxClaims { return ociInvalid(p.c, "claims must be a JSON object under 1000 bytes") } diff --git a/dashboard/gcpidentitytoolkit_views.go b/dashboard/gcpidentitytoolkit_views.go index 796dc2625a..6f56f26885 100644 --- a/dashboard/gcpidentitytoolkit_views.go +++ b/dashboard/gcpidentitytoolkit_views.go @@ -1,9 +1,11 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type idpProvider struct { @@ -45,7 +47,7 @@ func idpUsersView(raw []byte) (any, error) { Users []idpUserWire `json:"userInfo"` } - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // surfaced as a 502 by the caller } @@ -73,7 +75,7 @@ func idpLocalIDView(raw []byte) (any, error) { LocalID string `json:"localId"` } - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // surfaced as a 502 by the caller } @@ -92,7 +94,7 @@ func idpOOBView(raw []byte) (any, error) { } `json:"oobCodes"` } - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // surfaced as a 502 by the caller } @@ -123,7 +125,7 @@ func idpTenantsView(raw []byte) (any, error) { } `json:"tenants"` } - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // surfaced as a 502 by the caller } @@ -164,7 +166,7 @@ func idpConfigView(raw []byte) (any, error) { } `json:"signIn"` } - if err := json.Unmarshal(raw, &in); err != nil { + if err := json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // surfaced as a 502 by the caller } diff --git a/dashboard/gcpresourcemanager_proxy_test.go b/dashboard/gcpresourcemanager_proxy_test.go index 7471e3cd87..19a7196dca 100644 --- a/dashboard/gcpresourcemanager_proxy_test.go +++ b/dashboard/gcpresourcemanager_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/dashboard" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpresourcemanager" ) @@ -106,7 +107,7 @@ func TestGCPResourceManagerProxy(t *testing.T) { Etag string `json:"etag"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pol)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pol, jsonx.V1())) add := `{"role":"roles/viewer","member":"user:a@example.com","etag":"` + pol.Etag + `"}` @@ -122,7 +123,7 @@ func TestGCPResourceManagerProxy(t *testing.T) { Etag string `json:"etag"` } - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &cur)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &cur, jsonx.V1())) del := `{"role":"roles/viewer","member":"user:a@example.com","etag":"` + cur.Etag + `"}` runGCPSteps(t, h, []gcpStep{ diff --git a/dashboard/oci_inproc_proxy.go b/dashboard/oci_inproc_proxy.go index c6f96ed0c0..d5c6200e33 100644 --- a/dashboard/oci_inproc_proxy.go +++ b/dashboard/oci_inproc_proxy.go @@ -2,7 +2,8 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -10,6 +11,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -57,12 +60,20 @@ func ociDispatch(c *echo.Context, inner *echo.Echo, apiPath string, body []byte) func ociDecodeBody(c *echo.Context, v any) bool { r := c.Request() - return json.NewDecoder(http.MaxBytesReader(c.Response(), r.Body, ociInprocMaxBody)).Decode(v) == nil + return json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(c.Response(), r.Body, ociInprocMaxBody)), + v, + jsonx.V1(), + ) == nil } // ociDecodeBodyN is ociDecodeBody with an explicit size cap. func ociDecodeBodyN(c *echo.Context, v any, limit int64) bool { - return json.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, limit)).Decode(v) == nil + return json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, limit)), + v, + jsonx.V1(), + ) == nil } // ociInvalid answers 400 for a request outside the proxy's allow-list. diff --git a/dashboard/ociapigateway_proxy_test.go b/dashboard/ociapigateway_proxy_test.go index 8e546a6a4a..d56148759a 100644 --- a/dashboard/ociapigateway_proxy_test.go +++ b/dashboard/ociapigateway_proxy_test.go @@ -6,7 +6,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/json" + "encoding/json/v2" "encoding/pem" "math/big" "net/http" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ociapigateway" ) @@ -63,7 +64,7 @@ func newGatewayDashboard(t *testing.T) (*dashboard.DashboardHandler, string, str e.Any("/*", ops.Trusted()) post := func(path string, body map[string]any) []byte { - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rec := httptest.NewRecorder() @@ -79,7 +80,7 @@ func newGatewayDashboard(t *testing.T) (*dashboard.DashboardHandler, string, str require.NoError(t, json.Unmarshal(post("/20190501/gateways", map[string]any{ "compartmentId": gwComp, "endpointType": "PUBLIC", "displayName": "edge", "subnetId": "ocid1.subnet.oc1..s", - }), &gw)) + }), &gw, jsonx.V1())) var dep struct { ID string `json:"id"` @@ -91,7 +92,7 @@ func newGatewayDashboard(t *testing.T) (*dashboard.DashboardHandler, string, str "path": "/hello", "methods": []string{"GET"}, "backend": map[string]any{"type": "STOCK_RESPONSE_BACKEND", "status": 200, "body": "hi there"}, }}}, - }), &dep)) + }), &dep, jsonx.V1())) certPEM, keyPEM := gwCert(t) post("/20190501/certificates", map[string]any{ @@ -166,7 +167,7 @@ func TestOCIAPIGatewayProxy(t *testing.T) { Status int `json:"status"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, 200, out.Status) assert.Equal(t, "hi there", out.Body) assert.Equal(t, "utf8", out.Encoding) diff --git a/dashboard/ociartifacts_proxy.go b/dashboard/ociartifacts_proxy.go index 778c1bf188..d127b0fac0 100644 --- a/dashboard/ociartifacts_proxy.go +++ b/dashboard/ociartifacts_proxy.go @@ -2,7 +2,8 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -13,6 +14,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -306,7 +309,7 @@ func (in *ociArtReadmeIn) build() (map[string]any, bool) { } func ociArtMarshal(v map[string]any) ([]byte, bool) { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) return b, err == nil } @@ -383,7 +386,11 @@ func ociArtRestoreBody(c *echo.Context) ([]byte, bool) { Version *string `json:"version"` } - err := json.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, ociInprocMaxBody)).Decode(&in) + err := json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, ociInprocMaxBody)), + &in, + jsonx.V1(), + ) if err != nil && !errors.Is(err, io.EOF) { return nil, false } @@ -442,7 +449,7 @@ func ociArtScrubSignatures(raw []byte) []byte { Items []map[string]any `json:"items"` } - if json.Unmarshal(raw, &env) != nil { + if json.Unmarshal(raw, &env, jsonx.V1()) != nil { return []byte("{}") } @@ -455,7 +462,7 @@ func ociArtScrubSignatures(raw []byte) []byte { env.Items = []map[string]any{} } - out, err := json.Marshal(env) + out, err := json.Marshal(env, jsonx.V1()) if err != nil { return []byte("{}") } @@ -476,7 +483,7 @@ func ociArtDownload(c *echo.Context, inner *echo.Echo, contentPath string) error Size int64 `json:"sizeInBytes"` } - _ = json.Unmarshal(meta.Body.Bytes(), &art) + _ = json.Unmarshal(meta.Body.Bytes(), &art, jsonx.V1()) if art.Size > ociArtMaxDownload { return cloudJSONFail(c, http.StatusRequestEntityTooLarge, "PayloadTooLarge", diff --git a/dashboard/ociartifacts_proxy_test.go b/dashboard/ociartifacts_proxy_test.go index 8bfe628fe5..af15e1e844 100644 --- a/dashboard/ociartifacts_proxy_test.go +++ b/dashboard/ociartifacts_proxy_test.go @@ -2,7 +2,7 @@ package dashboard_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/ociartifacts" @@ -88,7 +89,7 @@ func newArtDashboard(t *testing.T) artFixture { } `json:"items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list, jsonx.V1())) require.Len(t, list.Items, 1) f.imageID = list.Items[0].ID @@ -105,7 +106,7 @@ func artTenancy(t *testing.T, f artFixture) string { Compartments []string `json:"compartments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Compartments) return out.Compartments[0] @@ -161,7 +162,7 @@ func TestOCIArtifactsProxyRepositoryActions(t *testing.T) { Immutable bool `json:"isImmutable"` } - require.NoError(t, json.Unmarshal(create.Body.Bytes(), &repo)) + require.NoError(t, json.Unmarshal(create.Body.Bytes(), &repo, jsonx.V1())) assert.NotEqual(t, "forged", repo.ID) assert.NotEqual(t, "forged", repo.CreatedBy) assert.Empty(t, repo.Tags) @@ -172,7 +173,7 @@ func TestOCIArtifactsProxyRepositoryActions(t *testing.T) { upd := artDo(t, f.h, http.MethodPut, artAPI+"/containerRepositories/"+repo.ID, `{"isImmutable":true,"isPublic":false,"freeformTags":{"forged":"yes"},"compartmentId":"x"}`) require.Equal(t, http.StatusOK, upd.Code, upd.Body.String()) - require.NoError(t, json.Unmarshal(upd.Body.Bytes(), &repo)) + require.NoError(t, json.Unmarshal(upd.Body.Bytes(), &repo, jsonx.V1())) assert.True(t, repo.Immutable) assert.False(t, repo.Public) assert.Empty(t, repo.Tags) @@ -222,7 +223,7 @@ func TestOCIArtifactsProxySignaturesAreScrubbed(t *testing.T) { "compartmentId": tenancy, "imageId": f.imageID, "kmsKeyId": "ocid1.key.oc1..kmskey0001", "kmsKeyVersionId": "ocid1.keyversion.oc1..ver", "message": enc([]byte(artMsg)), "signature": enc([]byte(artSig)), "signingAlgorithm": "SHA_256_RSA_PKCS_PSS", - }) + }, jsonx.V1()) require.NoError(t, err) made := f.direct(t, http.MethodPost, "/20160918/container/imageSignatures", string(body)) @@ -254,7 +255,7 @@ func TestOCIArtifactsProxyGenericLifecycleAndDownload(t *testing.T) { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(made.Body.Bytes(), &repo)) + require.NoError(t, json.Unmarshal(made.Body.Bytes(), &repo, jsonx.V1())) assert.NotEqual(t, "forged", repo.ID) put := f.direct(t, http.MethodPut, @@ -273,7 +274,7 @@ func TestOCIArtifactsProxyGenericLifecycleAndDownload(t *testing.T) { } `json:"items"` } - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &arts)) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &arts, jsonx.V1())) require.Len(t, arts.Items, 1) assert.NotEmpty(t, arts.Items[0].Sha256) diff --git a/dashboard/ocicontainerengine_proxy.go b/dashboard/ocicontainerengine_proxy.go index 8986db70ac..e71c32ae48 100644 --- a/dashboard/ocicontainerengine_proxy.go +++ b/dashboard/ocicontainerengine_proxy.go @@ -2,7 +2,8 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +12,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -131,7 +134,7 @@ func ociOKEResizeBody(in io.Reader) ([]byte, bool) { Quantity *int `json:"quantityPerSubnet"` } - if err := json.NewDecoder(in).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(in), &req, jsonx.V1()); err != nil { return nil, false } @@ -146,7 +149,7 @@ func ociOKEResizeBody(in io.Reader) ([]byte, bool) { return nil, false } - body, err := json.Marshal(out) + body, err := json.Marshal(out, jsonx.V1()) return body, err == nil } diff --git a/dashboard/ocicontainerengine_proxy_test.go b/dashboard/ocicontainerengine_proxy_test.go index ff1f28aa91..edf7683712 100644 --- a/dashboard/ocicontainerengine_proxy_test.go +++ b/dashboard/ocicontainerengine_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocicontainerengine" ) @@ -50,7 +51,7 @@ func newOKEDashboard(t *testing.T) (*dashboard.DashboardHandler, string, string) } require.NoError(t, json.Unmarshal(okeSeed(t, e, http.MethodGet, - "/20180222/clusters?compartmentId="+okeComp, ""), &clusters)) + "/20180222/clusters?compartmentId="+okeComp, ""), &clusters, jsonx.V1())) require.Len(t, clusters, 1) okeSeed(t, e, http.MethodPost, "/20180222/nodePools", @@ -66,7 +67,7 @@ func newOKEDashboard(t *testing.T) (*dashboard.DashboardHandler, string, string) } require.NoError(t, json.Unmarshal(okeSeed(t, e, http.MethodGet, - "/20180222/nodePools?compartmentId="+okeComp, ""), &pools)) + "/20180222/nodePools?compartmentId="+okeComp, ""), &pools, jsonx.V1())) require.Len(t, pools, 1) return dashboard.NewHandler(dashboard.Config{OCIContainerEngineOps: ops}), clusters[0].ID, pools[0].ID diff --git a/dashboard/ocicontainerinstances_proxy.go b/dashboard/ocicontainerinstances_proxy.go index 95cb0e859d..323a703670 100644 --- a/dashboard/ocicontainerinstances_proxy.go +++ b/dashboard/ocicontainerinstances_proxy.go @@ -3,7 +3,7 @@ package dashboard import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" idbackend "github.com/blackbirdworks/gopherstack/services/ociidentity" ) @@ -445,13 +446,13 @@ func ociCIScrubJSON(raw []byte) []byte { } var doc any - if json.Unmarshal(raw, &doc) != nil { + if json.Unmarshal(raw, &doc, jsonx.V1()) != nil { return []byte("{}") } ociCIScrub(doc) - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return []byte("{}") } @@ -610,7 +611,7 @@ func (h *DashboardHandler) ociCISubnets(c *echo.Context) error { } var subnets []map[string]any - if json.Unmarshal(rec.Body.Bytes(), &subnets) != nil { + if json.Unmarshal(rec.Body.Bytes(), &subnets, jsonx.V1()) != nil { return c.JSON(http.StatusOK, map[string]any{ociCIKeyItems: []any{}}) } diff --git a/dashboard/ocicontainerinstances_proxy_test.go b/dashboard/ocicontainerinstances_proxy_test.go index 19ad734e66..70eff49427 100644 --- a/dashboard/ocicontainerinstances_proxy_test.go +++ b/dashboard/ocicontainerinstances_proxy_test.go @@ -3,7 +3,7 @@ package dashboard_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/dashboard" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocicontainerinstances" "github.com/blackbirdworks/gopherstack/services/ocicore" "github.com/blackbirdworks/gopherstack/services/ociidentity" @@ -114,7 +115,7 @@ func (f ciFixture) settle(t *testing.T, id, state string) { State string `json:"lifecycleState"` } - return json.Unmarshal(rec.Body.Bytes(), &got) == nil && got.State == state + return json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1()) == nil && got.State == state }, 10*time.Second, 5*time.Millisecond) } @@ -141,7 +142,7 @@ func ciCreateDirect(t *testing.T, f ciFixture) string { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inst)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inst, jsonx.V1())) f.settle(t, inst.ID, "ACTIVE") return inst.ID @@ -164,7 +165,7 @@ func TestOCIContainerInstancesProxyScrubbing(t *testing.T) { rec := f.proxy(t, http.MethodGet, ciAPI+"/containers?compartmentId="+ciComp+"&containerInstanceId="+id, "") require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrList)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrList, jsonx.V1())) require.Len(t, ctrList.Items, 1) rawCtr := f.direct(t, http.MethodGet, "/20210415/containers/"+ctrList.Items[0].ID, "") @@ -240,7 +241,7 @@ func TestOCIContainerInstancesProxyCreateRebuild(t *testing.T) { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inst)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inst, jsonx.V1())) raw := f.direct(t, http.MethodGet, "/20210415/containerInstances/"+inst.ID, "").Body.String() for _, dropped := range []string{"mallory", "securityContext", "EMPTYDIR", "registryEndpoint", "runAsUser"} { @@ -410,7 +411,7 @@ func TestOCIContainerInstancesProxyLogsStateEngine(t *testing.T) { } rec := f.proxy(t, http.MethodGet, ciAPI+"/containers?compartmentId="+ciComp+"&containerInstanceId="+id, "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrs)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrs, jsonx.V1())) require.Len(t, ctrs.Items, 1) rec = f.proxy(t, http.MethodPost, ciAPI+"/containers/"+ctrs.Items[0].ID+"/actions/retrieveLogs", "") @@ -442,7 +443,7 @@ func TestOCIContainerInstancesProxyLogsDockerEngine(t *testing.T) { } rec := f.proxy(t, http.MethodGet, ciAPI+"/containers?compartmentId="+ciComp+"&containerInstanceId="+id, "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrs)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ctrs, jsonx.V1())) require.Len(t, ctrs.Items, 1) rec = f.proxy(t, http.MethodPost, ciAPI+"/containers/"+ctrs.Items[0].ID+"/actions/retrieveLogs", "") @@ -496,14 +497,14 @@ func TestOCIContainerInstancesProxySubnets(t *testing.T) { ) require.Nil(t, aerr) - vcnJSON, err := json.Marshal(vcn) + vcnJSON, err := json.Marshal(vcn, jsonx.V1()) require.NoError(t, err) var v struct { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(vcnJSON, &v)) + require.NoError(t, json.Unmarshal(vcnJSON, &v, jsonx.V1())) _, _, aerr = be.CreateSubnet(ocicore.CreateSubnetIn{ CompartmentID: ciComp, VcnID: v.ID, DisplayName: "sub-a", CidrBlock: "10.0.1.0/24", diff --git a/dashboard/ocicore_proxy.go b/dashboard/ocicore_proxy.go index 0436952892..192944e5bd 100644 --- a/dashboard/ocicore_proxy.go +++ b/dashboard/ocicore_proxy.go @@ -1,7 +1,7 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -9,6 +9,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -93,10 +95,10 @@ func ociCoreRelay(c *echo.Context, rec *httptest.ResponseRecorder) error { if rec.Code < http.StatusMultipleChoices && len(body) > 0 { var doc any - if err := json.Unmarshal(body, &doc); err == nil { + if err := json.Unmarshal(body, &doc, jsonx.V1()); err == nil { ociCoreScrub(doc) - if out, merr := json.Marshal(doc); merr == nil { + if out, merr := json.Marshal(doc, jsonx.V1()); merr == nil { body = out } } diff --git a/dashboard/ocievents_proxy.go b/dashboard/ocievents_proxy.go index 6e08fcb14a..292d9b9516 100644 --- a/dashboard/ocievents_proxy.go +++ b/dashboard/ocievents_proxy.go @@ -1,12 +1,14 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -161,7 +163,7 @@ func ociEventsCreateBody(c *echo.Context) ([]byte, bool) { out["description"] = in.Description } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } @@ -175,7 +177,7 @@ func ociEventsToggleBody(c *echo.Context) ([]byte, bool) { return nil, false } - b, err := json.Marshal(map[string]any{ociEventsEnabledKey: *in.IsEnabled}) + b, err := json.Marshal(map[string]any{ociEventsEnabledKey: *in.IsEnabled}, jsonx.V1()) return b, err == nil } diff --git a/dashboard/ocifunctions_proxy.go b/dashboard/ocifunctions_proxy.go index 334beef992..9528584260 100644 --- a/dashboard/ocifunctions_proxy.go +++ b/dashboard/ocifunctions_proxy.go @@ -2,7 +2,7 @@ package dashboard import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net" "net/http" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" fnbackend "github.com/blackbirdworks/gopherstack/services/ocifunctions" ) @@ -72,13 +73,13 @@ func (h *DashboardHandler) registerOCIFunctionsRoutes() { // maskedConfig renders a wire value as JSON with secret-looking config values replaced. func maskedConfig(v any) (map[string]any, error) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, err } out := map[string]any{} - if err = json.Unmarshal(raw, &out); err != nil { + if err = json.Unmarshal(raw, &out, jsonx.V1()); err != nil { return nil, err } diff --git a/dashboard/ocifunctions_proxy_test.go b/dashboard/ocifunctions_proxy_test.go index 99f0327c39..1085089838 100644 --- a/dashboard/ocifunctions_proxy_test.go +++ b/dashboard/ocifunctions_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocifunctions" ) @@ -36,7 +37,7 @@ func ociFnCall( require.NoError(t, h.Handler()(echo.NewContext(req, rec))) out := map[string]any{} - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) return rec, out } diff --git a/dashboard/ociloadbalancer_proxy.go b/dashboard/ociloadbalancer_proxy.go index 4dc6e13ad8..24fff74adb 100644 --- a/dashboard/ociloadbalancer_proxy.go +++ b/dashboard/ociloadbalancer_proxy.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "io" "maps" @@ -19,6 +19,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ociloadbalancer" ) @@ -241,7 +242,7 @@ func ociLBCreateBody(c *echo.Context) ([]byte, bool) { }} } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } @@ -311,7 +312,7 @@ func ociLBBackendBody(c *echo.Context, update bool) ([]byte, bool) { return nil, false } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } @@ -320,7 +321,7 @@ func ociLBBackendBody(c *echo.Context, update bool) ([]byte, bool) { // subjects and validity; PEM text never reaches the browser. func ociLBScrubCerts(raw []byte) []byte { var v any - if json.Unmarshal(raw, &v) != nil { + if json.Unmarshal(raw, &v, jsonx.V1()) != nil { return raw } @@ -337,7 +338,7 @@ func ociLBScrubCerts(raw []byte) []byte { } } - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return []byte("{}") } diff --git a/dashboard/ociloadbalancer_proxy_test.go b/dashboard/ociloadbalancer_proxy_test.go index cd91f7903a..1e409f5829 100644 --- a/dashboard/ociloadbalancer_proxy_test.go +++ b/dashboard/ociloadbalancer_proxy_test.go @@ -6,7 +6,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "math/big" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ociloadbalancer" ) @@ -123,7 +124,7 @@ func newLBDashboard(t *testing.T) lbFixture { "sslConfiguration": map[string]any{"certificateName": "tls"}, }, }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := httptest.NewRecorder() @@ -139,7 +140,7 @@ func newLBDashboard(t *testing.T) lbFixture { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &items), list.Body.String()) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &items, jsonx.V1()), list.Body.String()) require.Len(t, items, 1) f.id = items[0].ID @@ -287,7 +288,7 @@ func TestOCILoadBalancerProxyActions(t *testing.T) { } `json:"backendSets"` } - require.NoError(t, json.Unmarshal(lbDo(t, f.h, http.MethodGet, lb, "").Body.Bytes(), &detail)) + require.NoError(t, json.Unmarshal(lbDo(t, f.h, http.MethodGet, lb, "").Body.Bytes(), &detail, jsonx.V1())) var found bool @@ -320,7 +321,7 @@ func TestOCILoadBalancerProxyActions(t *testing.T) { DeleteProtected bool `json:"isDeleteProtectionEnabled"` } - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &items), list.Body.String()) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &items, jsonx.V1()), list.Body.String()) require.Len(t, items, 1) assert.Equal(t, "flexible", items[0].Shape) assert.False(t, items[0].DeleteProtected) @@ -349,7 +350,7 @@ func TestOCILoadBalancerProxyTryItRoundRobin(t *testing.T) { Status int `json:"status"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, 200, out.Status) assert.NotContains(t, out.Headers, "Connection") assert.NotContains(t, out.Body, "evil.example") diff --git a/dashboard/ocilogging_proxy.go b/dashboard/ocilogging_proxy.go index 4810406047..655593c604 100644 --- a/dashboard/ocilogging_proxy.go +++ b/dashboard/ocilogging_proxy.go @@ -1,13 +1,15 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" "github.com/google/uuid" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -95,7 +97,7 @@ func ociLoggingToggle(c *echo.Context, parts []string, raw string) (string, []by return "", nil, false } - b, err := json.Marshal(map[string]any{"isEnabled": *in.Enabled}) + b, err := json.Marshal(map[string]any{"isEnabled": *in.Enabled}, jsonx.V1()) return ociLoggingMgmt + raw, b, err == nil } @@ -140,7 +142,7 @@ func ociLoggingSearchRequest(c *echo.Context) (string, []byte, bool) { b, err := json.Marshal(map[string]any{ "timeStart": in.TimeStart, "timeEnd": in.TimeEnd, "searchQuery": in.SearchQuery, "isReturnFieldInfo": true, - }) + }, jsonx.V1()) return ociLoggingSearch, b, err == nil } @@ -161,7 +163,7 @@ func ociLoggingPushRequest(c *echo.Context, logID string) (string, []byte, bool) "source": "dashboard", "type": "dashboard.test", "defaultlogentrytime": now, "entries": []any{map[string]any{"data": in.Data, "id": uuid.NewString(), "time": now}}, }}, - }) + }, jsonx.V1()) return ociLoggingIngest + "/logs/" + logID + "/actions/push", b, err == nil } diff --git a/dashboard/ocilogging_proxy_test.go b/dashboard/ocilogging_proxy_test.go index 730f221f06..b6d29eb996 100644 --- a/dashboard/ocilogging_proxy_test.go +++ b/dashboard/ocilogging_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocilogging" ) @@ -49,7 +50,7 @@ func newLoggingDashboard(t *testing.T) (*dashboard.DashboardHandler, string, str } require.NoError(t, json.Unmarshal(logSeed(t, e, http.MethodGet, - "/20200531/logGroups?compartmentId="+logComp, ""), &groups)) + "/20200531/logGroups?compartmentId="+logComp, ""), &groups, jsonx.V1())) require.Len(t, groups, 1) logSeed(t, e, http.MethodPost, "/20200531/logGroups/"+groups[0].ID+"/logs", @@ -60,7 +61,7 @@ func newLoggingDashboard(t *testing.T) (*dashboard.DashboardHandler, string, str } require.NoError(t, json.Unmarshal(logSeed(t, e, http.MethodGet, - "/20200531/logGroups/"+groups[0].ID+"/logs", ""), &logs)) + "/20200531/logGroups/"+groups[0].ID+"/logs", ""), &logs, jsonx.V1())) require.Len(t, logs, 1) return dashboard.NewHandler(dashboard.Config{OCILoggingOps: ops}), groups[0].ID, logs[0].ID diff --git a/dashboard/ocimonitoring_proxy.go b/dashboard/ocimonitoring_proxy.go index 101a08faf2..44c441c88e 100644 --- a/dashboard/ocimonitoring_proxy.go +++ b/dashboard/ocimonitoring_proxy.go @@ -1,12 +1,14 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -91,7 +93,7 @@ func ociMonitoringListBody(c *echo.Context) ([]byte, bool) { b, err := json.Marshal(map[string]any{ ociNamespaceKey: in.Namespace, "name": in.Name, "resourceGroup": in.ResourceGroup, - }) + }, jsonx.V1()) return b, err == nil } @@ -110,7 +112,7 @@ func ociMonitoringSummarizeBody(c *echo.Context) ([]byte, bool) { return nil, false } - b, err := json.Marshal(in) + b, err := json.Marshal(in, jsonx.V1()) return b, err == nil } diff --git a/dashboard/ocimonitoring_proxy_test.go b/dashboard/ocimonitoring_proxy_test.go index d05bef329b..696c8c5a9c 100644 --- a/dashboard/ocimonitoring_proxy_test.go +++ b/dashboard/ocimonitoring_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocimonitoring" ) @@ -59,7 +60,7 @@ func newMonitoringDashboard(t *testing.T) (*dashboard.DashboardHandler, string) } require.NoError(t, json.Unmarshal(monSeed(t, e, http.MethodGet, - "/20180401/alarms?compartmentId="+monComp, ""), &alarms)) + "/20180401/alarms?compartmentId="+monComp, ""), &alarms, jsonx.V1())) require.Len(t, alarms, 1) return dashboard.NewHandler(dashboard.Config{OCIMonitoringOps: ops}), alarms[0].ID diff --git a/dashboard/ocimysql_proxy.go b/dashboard/ocimysql_proxy.go index 549ad8f913..2f1d816300 100644 --- a/dashboard/ocimysql_proxy.go +++ b/dashboard/ocimysql_proxy.go @@ -1,12 +1,14 @@ package dashboard import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -102,7 +104,7 @@ func ociMySQLBody(c *echo.Context, method string, parts []string) ([]byte, bool) out["displayName"] = in.DisplayName } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } diff --git a/dashboard/ocimysql_proxy_test.go b/dashboard/ocimysql_proxy_test.go index 3b9b6ce1f8..d390012fe9 100644 --- a/dashboard/ocimysql_proxy_test.go +++ b/dashboard/ocimysql_proxy_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ocimysql" ) @@ -50,7 +51,7 @@ func newMySQLDashboard(t *testing.T) (*dashboard.DashboardHandler, string) { } require.NoError(t, json.Unmarshal(mysqlSeed(t, e, http.MethodGet, - "/20190415/dbSystems?compartmentId="+mysqlComp, ""), &systems)) + "/20190415/dbSystems?compartmentId="+mysqlComp, ""), &systems, jsonx.V1())) require.Len(t, systems, 1) return dashboard.NewHandler(dashboard.Config{OCIMySQLOps: ops}), systems[0].ID diff --git a/dashboard/ocinosql_proxy.go b/dashboard/ocinosql_proxy.go index 43197493d6..760f5af674 100644 --- a/dashboard/ocinosql_proxy.go +++ b/dashboard/ocinosql_proxy.go @@ -2,11 +2,14 @@ package dashboard import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -135,16 +138,16 @@ func ociNoSQLCreateBody(c *echo.Context) ([]byte, bool) { b, err := json.Marshal(map[string]any{ ociKeyCompartment: in.CompartmentID, ociKeyName: in.Name, "ddlStatement": in.DDL, "tableLimits": limits, - }) + }, jsonx.V1()) return b, err == nil } func ociNoSQLRowBody(c *echo.Context) ([]byte, bool) { var in struct { - CompartmentID string `json:"compartmentId"` - Option string `json:"option"` - Value json.RawMessage `json:"value"` + CompartmentID string `json:"compartmentId"` + Option string `json:"option"` + Value jsontext.Value `json:"value"` } if !ociDecodeBody(c, &in) || in.CompartmentID == "" || !bytes.HasPrefix(bytes.TrimSpace(in.Value), []byte("{")) { @@ -160,7 +163,7 @@ func ociNoSQLRowBody(c *echo.Context) ([]byte, bool) { out["option"] = in.Option } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } @@ -182,7 +185,7 @@ func ociNoSQLQueryBody(c *echo.Context) ([]byte, bool) { out["consistency"] = in.Consistency } - b, err := json.Marshal(out) + b, err := json.Marshal(out, jsonx.V1()) return b, err == nil } diff --git a/dashboard/region_test.go b/dashboard/region_test.go index c8b9122648..a2cd4d4c4a 100644 --- a/dashboard/region_test.go +++ b/dashboard/region_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfigdata" ) @@ -30,7 +31,7 @@ func profileCount(t *testing.T, h *dashboard.DashboardHandler, query string) int Profiles []any `json:"profiles"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return len(out.Profiles) } diff --git a/dashboard/ui.go b/dashboard/ui.go index 21a764e3a4..2f213152f0 100644 --- a/dashboard/ui.go +++ b/dashboard/ui.go @@ -3,7 +3,8 @@ package dashboard import ( "context" "embed" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "html" "io" @@ -29,6 +30,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/chaos" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/pkgs/service" acmbackend "github.com/blackbirdworks/gopherstack/services/acm" @@ -846,7 +848,7 @@ func (h *DashboardHandler) logEmbeddedSPABuildStamp() { } var stamp spaBuildStamp - if jsonErr := json.Unmarshal(data, &stamp); jsonErr != nil { + if jsonErr := json.Unmarshal(data, &stamp, jsonx.V1()); jsonErr != nil { h.Logger.Warn("embedded dashboard SPA build-stamp.json is malformed", "error", jsonErr) return @@ -1087,7 +1089,7 @@ func (h *DashboardHandler) registerCodeStarConnectionsMutationRoutes() { ProviderType string `json:"providerType"` HostArn string `json:"hostArn"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1130,7 +1132,7 @@ func (h *DashboardHandler) registerCodeStarConnectionsMutationRoutes() { ProviderType string `json:"providerType"` ProviderEndpoint string `json:"providerEndpoint"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1178,7 +1180,7 @@ func (h *DashboardHandler) registerCognitoUserPoolRoutes() { var req struct { Name string `json:"name"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1239,7 +1241,7 @@ func (h *DashboardHandler) registerCognitoUserPoolClientRoutes() { var req struct { ClientName string `json:"clientName"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1304,7 +1306,7 @@ func (h *DashboardHandler) registerCognitoUserRoutes() { Username string `json:"username"` TempPassword string `json:"tempPassword"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1356,7 +1358,7 @@ func (h *DashboardHandler) registerCognitoUserLifecycleRoutes() { var req struct { Enabled bool `json:"enabled"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1409,7 +1411,7 @@ func (h *DashboardHandler) registerCognitoGroupRoutes() { GroupName string `json:"groupName"` Description string `json:"description"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1461,7 +1463,7 @@ func (h *DashboardHandler) registerCognitoUserPoolConfigRoutes() { var req struct { MfaConfiguration string `json:"mfaConfiguration"` } - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyError: err.Error()}) } @@ -1677,7 +1679,7 @@ func (h *DashboardHandler) registerAppConfigDataProfileRoutes() { } var req setProfileRequest - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, map[string]string{keyMessage: "invalid request body"}, diff --git a/dashboard/ui_test.go b/dashboard/ui_test.go index 21027f50b3..119b78fbf5 100644 --- a/dashboard/ui_test.go +++ b/dashboard/ui_test.go @@ -1,7 +1,7 @@ package dashboard_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "regexp" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/dashboard" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -72,7 +73,7 @@ func TestSystemSettings_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "123456789012", body["accountID"]) assert.Equal(t, "eu-west-1", body["region"]) @@ -134,7 +135,7 @@ func TestSystemSettings_NoSecretShapedKeys(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) secretLike := regexp.MustCompile(`(?i)secret|password|token|credential|accesskey`) for key := range body { @@ -184,7 +185,7 @@ func TestSystemRegions(t *testing.T) { var body struct { Regions []string `json:"regions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) for _, want := range tt.wantContains { assert.Contains(t, body.Regions, want) diff --git a/demo/wire.go b/demo/wire.go index ff1e4d9338..ad726732fd 100644 --- a/demo/wire.go +++ b/demo/wire.go @@ -2,12 +2,13 @@ package demo import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire" ) @@ -35,13 +36,13 @@ func NewWireCaller(key string, handler http.Handler, creds aws.CredentialsProvid } func (c *wireCaller) Call(ctx context.Context, op string, params any) (map[string]any, error) { - b, err := json.Marshal(params) + b, err := json.Marshal(params, jsonx.V1()) if err != nil { return nil, err } var in map[string]any - if err = json.Unmarshal(b, &in); err != nil { + if err = json.Unmarshal(b, &in, jsonx.V1()); err != nil { return nil, err } diff --git a/examples/azure-keyvault-app/azure-keyvault-app b/examples/azure-keyvault-app/azure-keyvault-app new file mode 100755 index 0000000000..369aba431c Binary files /dev/null and b/examples/azure-keyvault-app/azure-keyvault-app differ diff --git a/examples/azure-keyvault-app/main.go b/examples/azure-keyvault-app/main.go index 35a0d12d10..d3f598897c 100644 --- a/examples/azure-keyvault-app/main.go +++ b/examples/azure-keyvault-app/main.go @@ -9,7 +9,7 @@ import ( "crypto/sha256" "crypto/tls" "crypto/x509" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "math/big" diff --git a/examples/azure-servicebus-cosmos-worker/azure-servicebus-cosmos-worker b/examples/azure-servicebus-cosmos-worker/azure-servicebus-cosmos-worker new file mode 100755 index 0000000000..79988d5bdb Binary files /dev/null and b/examples/azure-servicebus-cosmos-worker/azure-servicebus-cosmos-worker differ diff --git a/examples/azure-servicebus-cosmos-worker/feed.go b/examples/azure-servicebus-cosmos-worker/feed.go index 2746facd95..01484efef5 100644 --- a/examples/azure-servicebus-cosmos-worker/feed.go +++ b/examples/azure-servicebus-cosmos-worker/feed.go @@ -2,7 +2,7 @@ package main import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" diff --git a/examples/azure-servicebus-cosmos-worker/servicebus.go b/examples/azure-servicebus-cosmos-worker/servicebus.go index 777f72d7f7..d6eeb8db53 100644 --- a/examples/azure-servicebus-cosmos-worker/servicebus.go +++ b/examples/azure-servicebus-cosmos-worker/servicebus.go @@ -2,7 +2,7 @@ package main import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" diff --git a/examples/azure-servicebus-cosmos-worker/worker.go b/examples/azure-servicebus-cosmos-worker/worker.go index 88e86c8be6..39474d1a2f 100644 --- a/examples/azure-servicebus-cosmos-worker/worker.go +++ b/examples/azure-servicebus-cosmos-worker/worker.go @@ -2,7 +2,7 @@ package main import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "time" diff --git a/examples/extensions/hello/main.go b/examples/extensions/hello/main.go index 62d69c0a5a..ebfec4c503 100644 --- a/examples/extensions/hello/main.go +++ b/examples/extensions/hello/main.go @@ -4,13 +4,16 @@ package main import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "log" "net" "net/http" "os" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -33,7 +36,7 @@ func main() { mux.HandleFunc("/hooks/s3/pre", func(_ http.ResponseWriter, r *http.Request) { var req hookRequest - _ = json.NewDecoder(r.Body).Decode(&req) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &req, jsonx.V1()) log.Printf("s3 request %q %q", req.Method, req.Path) }) @@ -66,7 +69,7 @@ func register() error { {"services": []string{"S3"}, "pre": "/hooks/s3/pre", "timeout_ms": hookTimeoutMs}, }, }, - }) + }, jsonx.V1()) if err != nil { return err } diff --git a/examples/gcp-run-pubsub-push/service/main.go b/examples/gcp-run-pubsub-push/service/main.go index 041882a684..7deaa6eaff 100644 --- a/examples/gcp-run-pubsub-push/service/main.go +++ b/examples/gcp-run-pubsub-push/service/main.go @@ -4,7 +4,7 @@ package main import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "log" "net/http" "os" @@ -33,7 +33,7 @@ type receiver struct { func (r *receiver) push(w http.ResponseWriter, req *http.Request) { var env pushEnvelope - if err := json.NewDecoder(req.Body).Decode(&env); err != nil { + if err := json.UnmarshalRead(req.Body, &env); err != nil { http.Error(w, "bad push envelope", http.StatusBadRequest) return @@ -62,7 +62,7 @@ func (r *receiver) list(w http.ResponseWriter, _ *http.Request) { r.mu.Unlock() w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(out) + _ = json.MarshalWrite(w, out) } func main() { diff --git a/internal/app/adapters_cosmos.go b/internal/app/adapters_cosmos.go index 97f06b39a7..3581308e24 100644 --- a/internal/app/adapters_cosmos.go +++ b/internal/app/adapters_cosmos.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -106,9 +106,9 @@ func (a *azureCosmosAdapter) DeleteDatabase(id string) error { func containerSpec(c azurearm.CosmosContainer) cosmosdb.ContainerSpec { return cosmosdb.ContainerSpec{ ID: c.ID, PartitionKeyPaths: c.PartitionKeyPaths, DefaultTTL: c.DefaultTTL, - IndexingPolicy: json.RawMessage(c.IndexingPolicy), - ConflictResolutionPolicy: json.RawMessage(c.ConflictResolutionPolicy), - UniqueKeyPolicy: json.RawMessage(c.UniqueKeyPolicy), + IndexingPolicy: jsontext.Value(c.IndexingPolicy), + ConflictResolutionPolicy: jsontext.Value(c.ConflictResolutionPolicy), + UniqueKeyPolicy: jsontext.Value(c.UniqueKeyPolicy), } } diff --git a/internal/app/adapters_cosmos_throughput_test.go b/internal/app/adapters_cosmos_throughput_test.go index 5b4d5abca2..bf3ad78dfb 100644 --- a/internal/app/adapters_cosmos_throughput_test.go +++ b/internal/app/adapters_cosmos_throughput_test.go @@ -2,7 +2,7 @@ package app import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -40,7 +41,7 @@ func armCosmosDo(t *testing.T, h http.Handler, method, path string, body any) (i if body != nil { var err error - raw, err = json.Marshal(body) + raw, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -53,7 +54,7 @@ func armCosmosDo(t *testing.T, h http.Handler, method, path string, body any) (i var out map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) } return rec.Code, out diff --git a/internal/app/app_inspector_fanout_test.go b/internal/app/app_inspector_fanout_test.go index 5256ff6622..365a519b36 100644 --- a/internal/app/app_inspector_fanout_test.go +++ b/internal/app/app_inspector_fanout_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/apptrace" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const fanoutQueueName = "fanout-queue" @@ -55,7 +56,7 @@ func (f *fanoutEnv) spans(t *testing.T, query string) []apptrace.Span { require.Equal(t, http.StatusOK, rec.Code) var res apptrace.ListResult - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &res)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &res, jsonx.V1())) return res.Items } diff --git a/internal/app/app_inspector_test.go b/internal/app/app_inspector_test.go index 508d8f6ee3..cbf8686132 100644 --- a/internal/app/app_inspector_test.go +++ b/internal/app/app_inspector_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/apptrace" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func inspectorServer(t *testing.T) (*echo.Echo, *apptrace.Tracer) { @@ -69,7 +70,7 @@ func TestAppInspectorEndpoints(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var res apptrace.ListResult - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &res)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &res, jsonx.V1())) assert.Equal(t, tc.total, res.Total) }) } @@ -93,7 +94,7 @@ func TestAppInspectorDetailTraceAndConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var detail appInspectorDetail - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detail)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detail, jsonx.V1())) require.NotNil(t, detail.Span.Detail) assert.NotContains(t, detail.Span.Detail.RequestBody, "hunter2") require.Len(t, detail.Trace, 2) diff --git a/internal/app/appsync_datasources_wiring.go b/internal/app/appsync_datasources_wiring.go index fc57744235..00919a74dc 100644 --- a/internal/app/appsync_datasources_wiring.go +++ b/internal/app/appsync_datasources_wiring.go @@ -3,7 +3,7 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -13,6 +13,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" appsyncbackend "github.com/blackbirdworks/gopherstack/services/appsync" eventbridgebackend "github.com/blackbirdworks/gopherstack/services/eventbridge" @@ -173,14 +174,14 @@ func (a appsyncRDSData) ExecuteRDSStatement( raw, err := json.Marshal(map[string]any{ "records": nonNil(records), "columnMetadata": nonNil(cols), "numberOfRecordsUpdated": updated, "generatedFields": nonNil(generated), - }) + }, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode result: %w", err) } var out map[string]any - return out, json.Unmarshal(raw, &out) + return out, json.Unmarshal(raw, &out, jsonx.V1()) } func nonNil[T any](s []T) []T { diff --git a/internal/app/appsync_datasources_wiring_test.go b/internal/app/appsync_datasources_wiring_test.go index fc773e9481..e3d012f116 100644 --- a/internal/app/appsync_datasources_wiring_test.go +++ b/internal/app/appsync_datasources_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -17,6 +17,8 @@ import ( sqstypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -95,7 +97,7 @@ func (f *asDSFixture) query(t *testing.T, field string, vars map[string]any) any query = "query(" + strings.Join(decls, ", ") + ") { " + field + " }" } - body, err := json.Marshal(map[string]any{"query": query, "variables": vars}) + body, err := json.Marshal(map[string]any{"query": query, "variables": vars}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/v1/apis/"+f.apiID+"/graphql", strings.NewReader(string(body))) @@ -112,7 +114,7 @@ func (f *asDSFixture) query(t *testing.T, field string, vars map[string]any) any Message string `json:"message"` } `json:"errors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out), rec.Body.String()) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()), rec.Body.String()) require.Empty(t, out.Errors, rec.Body.String()) name, _, _ := strings.Cut(field, "(") @@ -129,7 +131,7 @@ func asJSONValue(t *testing.T, v any) any { } var out any - require.NoError(t, json.Unmarshal([]byte(s), &out), s) + require.NoError(t, json.Unmarshal([]byte(s), &out, jsonx.V1()), s) return out } diff --git a/internal/app/appsync_dynamodb_resolvers_test.go b/internal/app/appsync_dynamodb_resolvers_test.go index a7c70f051f..613ec4fc2e 100644 --- a/internal/app/appsync_dynamodb_resolvers_test.go +++ b/internal/app/appsync_dynamodb_resolvers_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,8 @@ import ( ddbsdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const asDDBSchema = ` @@ -256,7 +258,7 @@ type asGQLResponse struct { func (f *asDDBFixture) do(t *testing.T, query string) asGQLResponse { t.Helper() - body, err := json.Marshal(map[string]any{"query": query}) + body, err := json.Marshal(map[string]any{"query": query}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/v1/apis/"+f.apiID+"/graphql", strings.NewReader(string(body))) @@ -268,7 +270,7 @@ func (f *asDDBFixture) do(t *testing.T, query string) asGQLResponse { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out asGQLResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out), rec.Body.String()) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()), rec.Body.String()) return out } diff --git a/internal/app/azureeventgrid_domains_sdk_test.go b/internal/app/azureeventgrid_domains_sdk_test.go index f4f52fdfb2..a82377c15c 100644 --- a/internal/app/azureeventgrid_domains_sdk_test.go +++ b/internal/app/azureeventgrid_domains_sdk_test.go @@ -1,7 +1,8 @@ package app import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" "github.com/blackbirdworks/gopherstack/services/azureeventgrid" @@ -42,9 +44,9 @@ func newEGReceiver(t *testing.T) *egReceiver { if req.Header.Get("Aeg-Event-Type") == "SubscriptionValidation" { var evs []map[string]any - _ = json.Unmarshal(body, &evs) + _ = json.Unmarshal(body, &evs, jsonx.V1()) code, _ := evs[0]["data"].(map[string]any)["validationCode"].(string) - _ = json.NewEncoder(w).Encode(map[string]string{"validationResponse": code}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{"validationResponse": code}) return } @@ -70,7 +72,7 @@ func (r *egReceiver) events(t *testing.T) ([]map[string]any, int) { for _, b := range r.bodies { var arr []map[string]any - require.NoError(t, json.Unmarshal(b, &arr)) + require.NoError(t, json.Unmarshal(b, &arr, jsonx.V1())) out = append(out, arr...) } diff --git a/internal/app/azureeventgrid_scopes_sdk_test.go b/internal/app/azureeventgrid_scopes_sdk_test.go index 1d16bb84dc..c9f7aec17b 100644 --- a/internal/app/azureeventgrid_scopes_sdk_test.go +++ b/internal/app/azureeventgrid_scopes_sdk_test.go @@ -3,7 +3,7 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "reflect" "strings" @@ -14,6 +14,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -25,7 +27,7 @@ const ( func (s *egDomainStack) armDo(t *testing.T, method, path string, body any) *http.Response { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), method, s.armSrv.URL+path+"?api-version=2021-04-01", diff --git a/internal/app/azureeventgrid_wiring_test.go b/internal/app/azureeventgrid_wiring_test.go index 6b456b3bb1..90bf09095d 100644 --- a/internal/app/azureeventgrid_wiring_test.go +++ b/internal/app/azureeventgrid_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "log/slog" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azureblob" @@ -129,7 +130,7 @@ func jsonField(t *testing.T, raw []byte, key string) string { var m map[string]any - require.NoError(t, json.Unmarshal(raw, &m)) + require.NoError(t, json.Unmarshal(raw, &m, jsonx.V1())) s, _ := m[key].(string) diff --git a/internal/app/azuregraph_wiring_test.go b/internal/app/azuregraph_wiring_test.go index 30ff349c3c..ea6b891ed1 100644 --- a/internal/app/azuregraph_wiring_test.go +++ b/internal/app/azuregraph_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" "github.com/blackbirdworks/gopherstack/services/azuregraph" @@ -86,7 +87,7 @@ func do( require.NoError(t, err) var out map[string]any - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) return resp.StatusCode, out } diff --git a/internal/app/cli.go b/internal/app/cli.go index acc58566b0..49a8b8004b 100644 --- a/internal/app/cli.go +++ b/internal/app/cli.go @@ -8,12 +8,14 @@ import ( sqssdk "github.com/blackbirdworks/gopherstack/internal/awswire/sqs" ssmsdk "github.com/blackbirdworks/gopherstack/internal/awswire/ssm" "github.com/blackbirdworks/gopherstack/internal/routegates" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/internal/cliconfig" "context" "crypto/tls" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "log/slog" @@ -1391,7 +1393,7 @@ func (c *CLI) SaveConfig() error { StepFunctions: c.StepFunctions, } - data, err := json.MarshalIndent(cfg, "", " ") + data, err := json.Marshal(cfg, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } @@ -1415,7 +1417,7 @@ func (c *CLI) LoadConfig() error { } var cfg cliconfig.PersistableConfig - if unmarshalErr := json.Unmarshal(data, &cfg); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &cfg, jsonx.V1()); unmarshalErr != nil { return fmt.Errorf("failed to unmarshal config: %w", unmarshalErr) } @@ -3287,7 +3289,7 @@ func buildResetHandler(services []service.Registerable) echo.HandlerFunc { // payload stays human-readable without an extra base64 layer. type snapshotBundle struct { // Services maps each registered service name to its raw JSON snapshot. - Services map[string]json.RawMessage `json:"services"` + Services map[string]jsontext.Value `json:"services"` // Format is a fixed version tag; currently "gopherstack-snapshot/v1". Format string `json:"format"` } @@ -3326,9 +3328,9 @@ func buildSnapshotHandler(m *persistence.Manager) echo.HandlerFunc { return func(c *echo.Context) error { raw := m.ExportAll() - services := make(map[string]json.RawMessage, len(raw)) + services := make(map[string]jsontext.Value, len(raw)) for name, data := range raw { - services[name] = json.RawMessage(data) + services[name] = jsontext.Value(data) } resp := snapshotResponse{ @@ -3352,7 +3354,7 @@ func buildLoadHandler(m *persistence.Manager) echo.HandlerFunc { return func(c *echo.Context) error { var bundle snapshotBundle - if err := json.NewDecoder(c.Request().Body).Decode(&bundle); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &bundle, jsonx.V1()); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid snapshot bundle: %s", err.Error())) } @@ -5969,7 +5971,7 @@ func buildECSRunInput( ) ecsbackend.RunTaskInput { var input map[string]any if len(payload) > 0 { - _ = json.Unmarshal(payload, &input) + _ = json.Unmarshal(payload, &input, jsonx.V1()) } runInput := ecsbackend.RunTaskInput{ @@ -6605,7 +6607,7 @@ func (a *cognitoLambdaTriggerAdapter) InvokeTrigger( functionARN string, event map[string]any, ) (map[string]any, error) { - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { return nil, err } @@ -6618,7 +6620,7 @@ func (a *cognitoLambdaTriggerAdapter) InvokeTrigger( } var resp map[string]any - if err = json.Unmarshal(result, &resp); err != nil { + if err = json.Unmarshal(result, &resp, jsonx.V1()); err != nil { return nil, err } @@ -14614,7 +14616,7 @@ func (a *iotAnalyticsThingShadowAdapter) GetThingShadow(thingName string) (map[s } var data map[string]any - if unmarshalErr := json.Unmarshal(doc, &data); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(doc, &data, jsonx.V1()); unmarshalErr != nil { return nil, fmt.Errorf("iotanalytics: unmarshal thing shadow document: %w", unmarshalErr) } @@ -14697,7 +14699,7 @@ func (a *ddbKinesisEmitterAdapter) EmitDynamoDBStreamRecord( ctx, streamName := kinesisRefContext(context.Background(), streamARN) partitionKey := tableName - if keys, err := json.Marshal(record.Keys); err == nil { + if keys, err := json.Marshal(record.Keys, jsonx.V1()); err == nil { partitionKey = string(keys) } @@ -14717,7 +14719,7 @@ func (a *ddbKinesisEmitterAdapter) EmitDynamoDBStreamRecord( ApproximateCreationDateTime: record.ApproximateCreationDateTime, SizeBytes: record.SizeBytes, }, - }) + }, jsonx.V1()) if err != nil { return } diff --git a/internal/app/cli_test.go b/internal/app/cli_test.go index c3403cb41f..59c5e09539 100644 --- a/internal/app/cli_test.go +++ b/internal/app/cli_test.go @@ -3,7 +3,8 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -21,6 +22,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/dns/dnstest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/service" svctags "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -895,7 +897,7 @@ func TestWireResourceGroupsTagging_CrossServiceResources(t *testing.T) { wafBk := wafv2backend.NewInMemoryBackend(accountID, region) webACL, err := wafBk.CreateWebACL( context.Background(), "wiring-test-acl", "REGIONAL", "", - json.RawMessage(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + jsontext.Value(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ) require.NoError(t, err) require.NoError(t, wafBk.TagResource( @@ -1527,7 +1529,7 @@ func TestWireResourceGroupsTagging_CrossServiceResources(t *testing.T) { "SubnetIds": []string{"subnet-1", "subnet-2"}, "SecurityGroupIds": []string{"sg-1"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPut, "/environments/wiring-test-env", bytes.NewReader(body)) @@ -2355,7 +2357,7 @@ func TestWireResourceGroupsTagging_TagResourcesRoundTrip(t *testing.T) { wafBk := wafv2backend.NewInMemoryBackend(accountID, region) webACL, err := wafBk.CreateWebACL( context.Background(), "roundtrip-acl", "REGIONAL", "", - json.RawMessage(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + jsontext.Value(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, ) require.NoError(t, err) @@ -3102,7 +3104,7 @@ func TestHealthEndpoint_GoroutineAndMemStats(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var body map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1())) // Verify the new observability fields are present. assert.Contains(t, body, "goroutines", "health response must include goroutines count") @@ -3208,7 +3210,8 @@ func TestLocalstackCompatibilityEndpoints(t *testing.T) { waitForEndpoint(t, fmt.Sprintf("http://localhost:%d/_localstack/init/ready", port), func(resp *http.Response) bool { var body map[string]any - return json.NewDecoder(resp.Body).Decode(&body) == nil && body["completed"] == true + return json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1()) == nil && + body["completed"] == true }) for _, tt := range tests { @@ -3219,7 +3222,7 @@ func TestLocalstackCompatibilityEndpoints(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var body map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1())) tt.validate(t, body) }) } @@ -3384,7 +3387,7 @@ func TestBuildSnapshotHandler_EmptyManager(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp snapshotResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, snapshotBundleFormat, resp.Format) assert.Equal(t, 0, resp.Exported) assert.Empty(t, resp.Services) @@ -3445,7 +3448,7 @@ func TestBuildSnapshotHandler(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp snapshotResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, snapshotBundleFormat, resp.Format) assert.Equal(t, "ok", resp.Status) @@ -3469,14 +3472,14 @@ func TestBuildSnapshotHandler_ResponseIsValidJSON(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp snapshotResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) // Service snapshot must be embedded as raw JSON, not base64. rawSvc, ok := resp.Services["svc"] require.True(t, ok, "svc key must be present") var parsed map[string]any - require.NoError(t, json.Unmarshal(rawSvc, &parsed)) + require.NoError(t, json.Unmarshal(rawSvc, &parsed, jsonx.V1())) assert.Equal(t, []any{float64(1), float64(2), float64(3)}, parsed["items"]) } @@ -3490,14 +3493,14 @@ func TestBuildLoadHandler_EmptyBundle(t *testing.T) { body, _ := json.Marshal(snapshotBundle{ Format: snapshotBundleFormat, - Services: map[string]json.RawMessage{}, - }) + Services: map[string]jsontext.Value{}, + }, jsonx.V1()) rec := postJSON(t, handler, body) assert.Equal(t, http.StatusOK, rec.Code) var resp loadResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "ok", resp.Status) assert.Equal(t, 0, resp.Loaded) } @@ -3505,7 +3508,7 @@ func TestBuildLoadHandler_EmptyBundle(t *testing.T) { func TestBuildLoadHandler(t *testing.T) { t.Parallel() - snap := json.RawMessage(`{"restored":true}`) + snap := jsontext.Value(`{"restored":true}`) tests := []struct { setupErr error @@ -3518,7 +3521,7 @@ func TestBuildLoadHandler(t *testing.T) { name: "single_service_loaded", bundle: snapshotBundle{ Format: snapshotBundleFormat, - Services: map[string]json.RawMessage{"svc": snap}, + Services: map[string]jsontext.Value{"svc": snap}, }, wantStatusCode: http.StatusOK, wantLoaded: 1, @@ -3527,7 +3530,7 @@ func TestBuildLoadHandler(t *testing.T) { name: "unknown_service_skipped", bundle: snapshotBundle{ Format: snapshotBundleFormat, - Services: map[string]json.RawMessage{"unknown": snap}, + Services: map[string]jsontext.Value{"unknown": snap}, }, wantStatusCode: http.StatusOK, wantLoaded: 1, @@ -3537,7 +3540,7 @@ func TestBuildLoadHandler(t *testing.T) { setupErr: errRestoreFailure, bundle: snapshotBundle{ Format: snapshotBundleFormat, - Services: map[string]json.RawMessage{"svc": snap}, + Services: map[string]jsontext.Value{"svc": snap}, }, wantStatusCode: http.StatusInternalServerError, }, @@ -3555,7 +3558,7 @@ func TestBuildLoadHandler(t *testing.T) { e.HTTPErrorHandler = buildHTTPErrorHandler() e.POST("/", handler) - body, marshalErr := json.Marshal(tt.bundle) + body, marshalErr := json.Marshal(tt.bundle, jsonx.V1()) require.NoError(t, marshalErr) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -3567,7 +3570,7 @@ func TestBuildLoadHandler(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp loadResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "ok", resp.Status) assert.Equal(t, tt.wantLoaded, resp.Loaded) } else { @@ -3649,7 +3652,7 @@ func TestSnapshotLoadRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, snapRec.Code) var snapResp snapshotResponse - require.NoError(t, json.NewDecoder(snapRec.Body).Decode(&snapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(snapRec.Body), &snapResp, jsonx.V1())) assert.Equal(t, len(tt.initial), snapResp.Exported) // Step 2: Reset all services. @@ -3662,7 +3665,7 @@ func TestSnapshotLoadRoundTrip(t *testing.T) { } // Step 3: POST /_gopherstack/load with the exported snapshot. - loadBody, err := json.Marshal(snapResp.snapshotBundle) + loadBody, err := json.Marshal(snapResp.snapshotBundle, jsonx.V1()) require.NoError(t, err) loadReq := httptest.NewRequest( @@ -3678,7 +3681,7 @@ func TestSnapshotLoadRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, loadRec.Code) var loadResp loadResponse - require.NoError(t, json.NewDecoder(loadRec.Body).Decode(&loadResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(loadRec.Body), &loadResp, jsonx.V1())) assert.Equal(t, "ok", loadResp.Status) assert.Equal(t, len(tt.initial), loadResp.Loaded) @@ -3711,10 +3714,10 @@ func TestSnapshotLoadRoundTrip_CrossManagerLoad(t *testing.T) { require.Equal(t, http.StatusOK, snapRec.Code) var snapResp snapshotResponse - require.NoError(t, json.NewDecoder(snapRec.Body).Decode(&snapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(snapRec.Body), &snapResp, jsonx.V1())) // Load into mgrB. - loadBody, err := json.Marshal(snapResp.snapshotBundle) + loadBody, err := json.Marshal(snapResp.snapshotBundle, jsonx.V1()) require.NoError(t, err) loadReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(loadBody)) @@ -3741,7 +3744,7 @@ func TestSnapshotLoadRoundTrip_EmptyServicesProduceEmptyBundle(t *testing.T) { require.Equal(t, http.StatusOK, snapRec.Code) var snapResp snapshotResponse - require.NoError(t, json.NewDecoder(snapRec.Body).Decode(&snapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(snapRec.Body), &snapResp, jsonx.V1())) assert.Equal(t, 0, snapResp.Exported, "nil-snapshot service must not be exported") assert.Empty(t, snapResp.Services) } @@ -3762,7 +3765,7 @@ func TestBuildEchoServer_SnapshotLoadRoutes(t *testing.T) { e.ServeHTTP(snapshotRec, snapshotReq) assert.NotEqual(t, http.StatusNotFound, snapshotRec.Code, "snapshot route must be registered") - loadBody, _ := json.Marshal(snapshotBundle{Format: snapshotBundleFormat, Services: nil}) + loadBody, _ := json.Marshal(snapshotBundle{Format: snapshotBundleFormat, Services: nil}, jsonx.V1()) loadReq := httptest.NewRequest(http.MethodPost, "/_gopherstack/load", bytes.NewReader(loadBody)) loadReq.Header.Set("Content-Type", "application/json") loadRec := httptest.NewRecorder() @@ -3791,7 +3794,7 @@ func TestBuildSnapshotHandler_MultipleServices_StatePreservedIndependently(t *te require.Equal(t, http.StatusOK, snapRec.Code) var snapResp snapshotResponse - require.NoError(t, json.NewDecoder(snapRec.Body).Decode(&snapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(snapRec.Body), &snapResp, jsonx.V1())) assert.Equal(t, 3, snapResp.Exported) // Reset. @@ -3800,7 +3803,7 @@ func TestBuildSnapshotHandler_MultipleServices_StatePreservedIndependently(t *te } // Load. - loadBody, err := json.Marshal(snapResp.snapshotBundle) + loadBody, err := json.Marshal(snapResp.snapshotBundle, jsonx.V1()) require.NoError(t, err) loadRec := httptest.NewRecorder() diff --git a/internal/app/cloudcontrol_handlers.go b/internal/app/cloudcontrol_handlers.go index 78b3e3bc6b..d46ebc30ad 100644 --- a/internal/app/cloudcontrol_handlers.go +++ b/internal/app/cloudcontrol_handlers.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -28,6 +28,7 @@ import ( "github.com/blackbirdworks/gopherstack/internal/awswire/cloudwatchlogs" "github.com/blackbirdworks/gopherstack/internal/awswire/sqs" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" cloudcontrolbackend "github.com/blackbirdworks/gopherstack/services/cloudcontrol" cfnbackend "github.com/blackbirdworks/gopherstack/services/cloudformation" @@ -82,12 +83,12 @@ func tagsProperty(m map[string]string) []map[string]string { func ccDecode[T any](raw any) (T, error) { var out T - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return out, fmt.Errorf("%w: %w", cloudcontrolbackend.ErrValidation, err) } - if err = json.Unmarshal(b, &out); err != nil { + if err = json.Unmarshal(b, &out, jsonx.V1()); err != nil { return out, fmt.Errorf("%w: %w", cloudcontrolbackend.ErrValidation, err) } @@ -147,13 +148,13 @@ func ccRejectUnsupportedChanges(current, desired map[string]any, allowed ...stri } func normalizeJSON(v any) any { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } var out any - if json.Unmarshal(b, &out) != nil { + if json.Unmarshal(b, &out, jsonx.V1()) != nil { return v } diff --git a/internal/app/cloudcontrol_handlers_security.go b/internal/app/cloudcontrol_handlers_security.go index dcdd987b3e..11524dd0c4 100644 --- a/internal/app/cloudcontrol_handlers_security.go +++ b/internal/app/cloudcontrol_handlers_security.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "math" "net/url" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/internal/awswire/kms" "github.com/blackbirdworks/gopherstack/internal/awswire/secretsmanager" "github.com/blackbirdworks/gopherstack/internal/awswire/ssm" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cloudcontrolbackend "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -78,7 +79,7 @@ func ccPolicyJSON(v any) (string, error) { return s, nil } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "", fmt.Errorf("%w: %w", cloudcontrolbackend.ErrValidation, err) } @@ -94,7 +95,7 @@ func ccPolicyObject(doc string, unescape bool) any { } var out any - if json.Unmarshal([]byte(doc), &out) != nil { + if json.Unmarshal([]byte(doc), &out, jsonx.V1()) != nil { return doc } diff --git a/internal/app/cloudcontrol_handlers_test.go b/internal/app/cloudcontrol_handlers_test.go index 3e43835ca7..fb235a2991 100644 --- a/internal/app/cloudcontrol_handlers_test.go +++ b/internal/app/cloudcontrol_handlers_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "net/netip" "slices" "strings" @@ -12,6 +12,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type ccDelegationCase struct { @@ -120,7 +122,7 @@ func ccAssertGet(t *testing.T, cc *cloudcontrol.Client, tt ccDelegationCase, id assert.Equal(t, id, aws.ToString(got.ResourceDescription.Identifier)) var props map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props, jsonx.V1())) for _, k := range tt.wantKeys { assert.Contains(t, props, k) @@ -206,7 +208,7 @@ func ccDeleteAndVerify(t *testing.T, cc *cloudcontrol.Client, fx *sfnFixture, tt } func jsonScalar(v any) string { - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return string(b) } @@ -215,9 +217,9 @@ func subsetJSON(t *testing.T, doc, key string) string { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(doc), &m)) + require.NoError(t, json.Unmarshal([]byte(doc), &m, jsonx.V1())) - b, err := json.Marshal(map[string]any{key: m[key]}) + b, err := json.Marshal(map[string]any{key: m[key]}, jsonx.V1()) require.NoError(t, err) return string(b) @@ -334,7 +336,10 @@ func TestCloudControlSubnetIPv6(t *testing.T) { require.NoError(t, err) var props map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props)) + require.NoError( + t, + json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props, jsonx.V1()), + ) assert.Equal(t, []any{v6}, props["Ipv6CidrBlocks"]) assert.Equal(t, azID, props["AvailabilityZoneId"]) assert.Equal(t, true, props["AssignIpv6AddressOnCreation"]) diff --git a/internal/app/cloudcontrol_provisioned_test.go b/internal/app/cloudcontrol_provisioned_test.go index cf6101d017..caa2de93e5 100644 --- a/internal/app/cloudcontrol_provisioned_test.go +++ b/internal/app/cloudcontrol_provisioned_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cfnbackend "github.com/blackbirdworks/gopherstack/services/cloudformation" ) @@ -42,7 +43,7 @@ func TestCloudControlProvisionedType_Lifecycle(t *testing.T) { require.NoError(t, err) var props map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(got.ResourceDescription.Properties)), &props, jsonx.V1())) assert.Equal(t, "cc-prov-user", props["UserName"]) listed, err := cc.ListResources(t.Context(), &cloudcontrol.ListResourcesInput{TypeName: aws.String(typeName)}) diff --git a/internal/app/cloudcontrol_queue_url_test.go b/internal/app/cloudcontrol_queue_url_test.go index bff2f402d8..323d48b0e6 100644 --- a/internal/app/cloudcontrol_queue_url_test.go +++ b/internal/app/cloudcontrol_queue_url_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sqs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudControlQueueURLMatchesSQS(t *testing.T) { @@ -68,7 +70,7 @@ func TestCloudFormationQueueURLMatchesSQS(t *testing.T) { "Q": map[string]any{"Type": "AWS::SQS::Queue", "Properties": map[string]any{"QueueName": "cfn-url-q"}}, }, "Outputs": map[string]any{"Url": map[string]any{"Value": map[string]any{"Ref": "Q"}}}, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/cloudcontrol_wire_client.go b/internal/app/cloudcontrol_wire_client.go index 05210b39f2..b8d7f5862e 100644 --- a/internal/app/cloudcontrol_wire_client.go +++ b/internal/app/cloudcontrol_wire_client.go @@ -2,12 +2,13 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cloudcontrolbackend "github.com/blackbirdworks/gopherstack/services/cloudcontrol" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire" ) @@ -40,12 +41,12 @@ func wireCall[O any](ctx context.Context, c *ccWireClient, op string, in any) (* params := map[string]any{} if in != nil { - b, err := json.Marshal(in) + b, err := json.Marshal(in, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: %w", cloudcontrolbackend.ErrValidation, err) } - if err = json.Unmarshal(b, ¶ms); err != nil { + if err = json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", cloudcontrolbackend.ErrValidation, err) } } @@ -57,12 +58,12 @@ func wireCall[O any](ctx context.Context, c *ccWireClient, op string, in any) (* var out O - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, err } - if err = json.Unmarshal(b, &out); err != nil { + if err = json.Unmarshal(b, &out, jsonx.V1()); err != nil { return nil, fmt.Errorf("decode %s response: %w", op, err) } diff --git a/internal/app/cloudformation_cloudhsm_wiring_test.go b/internal/app/cloudformation_cloudhsm_wiring_test.go index 29e03794d2..b88ea086f8 100644 --- a/internal/app/cloudformation_cloudhsm_wiring_test.go +++ b/internal/app/cloudformation_cloudhsm_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsCloudHSMCluster(t *testing.T) { @@ -42,7 +44,7 @@ func TestCloudFormationProvisionsCloudHSMCluster(t *testing.T) { "Outputs": map[string]any{ "Id": map[string]any{"Value": map[string]any{"Ref": "Cluster"}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) _, err = cfn.CreateStack(t.Context(), &cloudformation.CreateStackInput{ diff --git a/internal/app/cloudformation_connectfamily_wiring_test.go b/internal/app/cloudformation_connectfamily_wiring_test.go index 04eeda4073..85f6b53067 100644 --- a/internal/app/cloudformation_connectfamily_wiring_test.go +++ b/internal/app/cloudformation_connectfamily_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/connect" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsAppIntegrationsAndCodeGuru(t *testing.T) { @@ -44,7 +46,7 @@ func TestCloudFormationProvisionsAppIntegrationsAndCodeGuru(t *testing.T) { "EventArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Event", "EventIntegrationArn"}}}, "AssocArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Assoc", "AssociationArn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) @@ -122,7 +124,7 @@ func TestCloudFormationProvisionsConnect(t *testing.T) { "InstanceId": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Instance", "Id"}}}, "QueueArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Queue", "QueueArn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/cloudformation_directconnect_test.go b/internal/app/cloudformation_directconnect_test.go index 81540aecf5..51f8661272 100644 --- a/internal/app/cloudformation_directconnect_test.go +++ b/internal/app/cloudformation_directconnect_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/directconnect" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsDirectConnect(t *testing.T) { @@ -48,7 +50,7 @@ func TestCloudFormationProvisionsDirectConnect(t *testing.T) { "ConnArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Conn", "ConnectionArn"}}}, "VifArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Vif", "VirtualInterfaceArn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/cloudformation_networkmanager_test.go b/internal/app/cloudformation_networkmanager_test.go index 073b0cb4dc..c2c17ca7e8 100644 --- a/internal/app/cloudformation_networkmanager_test.go +++ b/internal/app/cloudformation_networkmanager_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/networkmanager" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsNetworkManager(t *testing.T) { @@ -55,7 +57,7 @@ func TestCloudFormationProvisionsNetworkManager(t *testing.T) { "SiteArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Site", "SiteArn"}}}, "GNArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"GN", "Arn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/cloudformation_oam_wiring_test.go b/internal/app/cloudformation_oam_wiring_test.go index 82e9605ee4..1e2e537653 100644 --- a/internal/app/cloudformation_oam_wiring_test.go +++ b/internal/app/cloudformation_oam_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/oam" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsOAMSink(t *testing.T) { @@ -25,7 +27,7 @@ func TestCloudFormationProvisionsOAMSink(t *testing.T) { "Outputs": map[string]any{ "Arn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Sink", "Arn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/cloudformation_rum_wiring_test.go b/internal/app/cloudformation_rum_wiring_test.go index b336061833..4c6d17b278 100644 --- a/internal/app/cloudformation_rum_wiring_test.go +++ b/internal/app/cloudformation_rum_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/rum" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCloudFormationProvisionsRUMAppMonitor(t *testing.T) { @@ -30,7 +32,7 @@ func TestCloudFormationProvisionsRUMAppMonitor(t *testing.T) { "Name": map[string]any{"Value": map[string]any{"Ref": "Mon"}}, "Id": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Mon", "Id"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/codepipeline_actions_test.go b/internal/app/codepipeline_actions_test.go index 14103e0709..f0a50e51e1 100644 --- a/internal/app/codepipeline_actions_test.go +++ b/internal/app/codepipeline_actions_test.go @@ -3,7 +3,7 @@ package app import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/v2" "io" "maps" "testing" @@ -21,6 +21,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const cfnPipelineTemplate = `{"Parameters":{"Env":{"Type":"String","Default":"dev"}},` + @@ -509,7 +511,7 @@ func TestCodePipelineECRSource(t *testing.T) { } var detail map[string]any - require.NoError(t, json.Unmarshal([]byte(env.object(t, "out/imageDetail.json")), &detail)) + require.NoError(t, json.Unmarshal([]byte(env.object(t, "out/imageDetail.json")), &detail, jsonx.V1())) imgs, err := ecrc.DescribeImages(ctx, &ecr.DescribeImagesInput{ RepositoryName: aws.String( diff --git a/internal/app/dev_endpoints_test.go b/internal/app/dev_endpoints_test.go index 522d97d25c..934aecdc01 100644 --- a/internal/app/dev_endpoints_test.go +++ b/internal/app/dev_endpoints_test.go @@ -2,7 +2,8 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "io" "net/http" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -143,7 +145,7 @@ func TestSESRetrospectionEndpoint(t *testing.T) { var got struct { Messages []map[string]any `json:"messages"` } - require.NoError(t, json.Unmarshal(body, &got)) + require.NoError(t, json.Unmarshal(body, &got, jsonx.V1())) require.Len(t, got.Messages, tt.wantN) if tt.wantN == 0 { @@ -225,7 +227,7 @@ func TestSQSInspectEndpoint(t *testing.T) { Body string `json:"Body"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(body, &got)) + require.NoError(t, json.Unmarshal(body, &got, jsonx.V1())) require.Len(t, got.Messages, 1) assert.Equal(t, "payload", got.Messages[0].Body) assert.Equal(t, "0", got.Messages[0].Attributes["ApproximateReceiveCount"]) @@ -316,7 +318,7 @@ func TestCloudWatchRawMetricsEndpoint(t *testing.T) { var got struct { Metrics []map[string]any `json:"metrics"` } - require.NoError(t, json.Unmarshal(body, &got)) + require.NoError(t, json.Unmarshal(body, &got, jsonx.V1())) var found map[string]any @@ -419,7 +421,7 @@ func TestLambdaRuntimesEndpoint(t *testing.T) { Runtimes []string `json:"Runtimes"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Contains(t, out.Runtimes, tt.has) if tt.hasNot != "" { diff --git a/internal/app/dms_engine_docker_test.go b/internal/app/dms_engine_docker_test.go index 888f126beb..8bc7014c10 100644 --- a/internal/app/dms_engine_docker_test.go +++ b/internal/app/dms_engine_docker_test.go @@ -3,7 +3,7 @@ package app import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net" @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -698,7 +699,7 @@ func TestDMSDockerMySQLToInProcessTargets(t *testing.T) { for _, r := range out.Records { var x dmsKinesisRec - require.NoError(t, json.Unmarshal(r.Data, &x)) + require.NoError(t, json.Unmarshal(r.Data, &x, jsonx.V1())) got = append(got, x) } diff --git a/internal/app/ecs_runtime_test.go b/internal/app/ecs_runtime_test.go index 75aead18e7..74c1a73544 100644 --- a/internal/app/ecs_runtime_test.go +++ b/internal/app/ecs_runtime_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -14,6 +14,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func newECSTaskFixture(t *testing.T, role string) (*sfnFixture, *ecs.Client, string) { @@ -87,7 +89,7 @@ func TestECSTaskStateEventsReachRuleTarget(t *testing.T) { for _, m := range out.Messages { var env map[string]any - uErr := json.Unmarshal([]byte(aws.ToString(m.Body)), &env) + uErr := json.Unmarshal([]byte(aws.ToString(m.Body)), &env, jsonx.V1()) if uErr != nil || env["detail-type"] != "ECS Task State Change" { continue } diff --git a/internal/app/error_wire_guards_test.go b/internal/app/error_wire_guards_test.go index 1f551b32b4..07a9689bc6 100644 --- a/internal/app/error_wire_guards_test.go +++ b/internal/app/error_wire_guards_test.go @@ -1,7 +1,8 @@ package app import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "fmt" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -41,7 +43,7 @@ func probeJSONBody() string { for _, k := range keys { m[k] = "gs-probe-missing" } - b, _ := json.Marshal(m) + b, _ := json.Marshal(m, jsonx.V1()) return string(b) } @@ -172,8 +174,8 @@ func parseErrorBody(p errorProbe) (parsedError, bool) { } func parseJSONError(p errorProbe, body string) (parsedError, bool) { - var m map[string]json.RawMessage - if json.Unmarshal([]byte(body), &m) != nil { + var m map[string]jsontext.Value + if json.Unmarshal([]byte(body), &m, jsonx.V1()) != nil { return parsedError{}, false } @@ -184,7 +186,7 @@ func parseJSONError(p errorProbe, body string) (parsedError, bool) { } var s string - _ = json.Unmarshal(raw, &s) + _ = json.Unmarshal(raw, &s, jsonx.V1()) return s, true } diff --git a/internal/app/extensions_test.go b/internal/app/extensions_test.go index 09700b9906..8695152bdc 100644 --- a/internal/app/extensions_test.go +++ b/internal/app/extensions_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/extensions" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -76,7 +77,7 @@ func TestExtensionRoutingAndHooks(t *testing.T) { "hooks": map[string]any{"request": []map[string]any{ {"services": []string{"Dash"}, "pre": "/hook/pre", "post": "/hook/post"}, }}, - }) + }, jsonx.V1()) require.NoError(t, err) require.NoError(t, os.WriteFile(regFile, reg, 0o600)) @@ -87,7 +88,7 @@ func TestExtensionRoutingAndHooks(t *testing.T) { cfgFile := filepath.Join(dir, "ext.json") cfg, err := json.Marshal(map[string]any{"extensions": []map[string]any{ {"name": "t", "command": []string{"sh", "-c", script}, "health_interval": "1h"}, - }}) + }}, jsonx.V1()) require.NoError(t, err) require.NoError(t, os.WriteFile(cfgFile, cfg, 0o600)) diff --git a/internal/app/fis_cloudwatch_stopcondition_wiring_test.go b/internal/app/fis_cloudwatch_stopcondition_wiring_test.go index af77938f0b..fc7d50adca 100644 --- a/internal/app/fis_cloudwatch_stopcondition_wiring_test.go +++ b/internal/app/fis_cloudwatch_stopcondition_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cwbackend "github.com/blackbirdworks/gopherstack/services/cloudwatch" fisbackend "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -25,7 +26,7 @@ func fisWiringRequest(t *testing.T, h *fisbackend.Handler, method, path string, var bodyBytes []byte if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyBytes = b } @@ -94,7 +95,7 @@ func TestWireFISStopConditions_AlarmStopsRealExperiment(t *testing.T) { ID string `json:"id"` } `json:"experimentTemplate"` } - require.NoError(t, json.Unmarshal(tplRec.Body.Bytes(), &tplResp)) + require.NoError(t, json.Unmarshal(tplRec.Body.Bytes(), &tplResp, jsonx.V1())) expRec := fisWiringRequest(t, fisH, http.MethodPost, "/experiments", map[string]any{ "experimentTemplateId": tplResp.ExperimentTemplate.ID, @@ -106,7 +107,7 @@ func TestWireFISStopConditions_AlarmStopsRealExperiment(t *testing.T) { ID string `json:"id"` } `json:"experiment"` } - require.NoError(t, json.Unmarshal(expRec.Body.Bytes(), &expResp)) + require.NoError(t, json.Unmarshal(expRec.Body.Bytes(), &expResp, jsonx.V1())) expID := expResp.Experiment.ID // Advance past the pending -> initiating -> running transition. This diff --git a/internal/app/gcpenforce_wiring_test.go b/internal/app/gcpenforce_wiring_test.go index 505d087570..9b97a00c42 100644 --- a/internal/app/gcpenforce_wiring_test.go +++ b/internal/app/gcpenforce_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "io" "log/slog" "net/http" @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc/status" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -233,7 +234,7 @@ func getREST(t *testing.T, url string) (int, restEnvelope) { var env restEnvelope - _ = json.Unmarshal(body, &env) + _ = json.Unmarshal(body, &env, jsonx.V1()) return resp.StatusCode, env } diff --git a/internal/app/gcpfunctions_events_wiring_test.go b/internal/app/gcpfunctions_events_wiring_test.go index 15da9b5bff..c07cde1c51 100644 --- a/internal/app/gcpfunctions_events_wiring_test.go +++ b/internal/app/gcpfunctions_events_wiring_test.go @@ -4,7 +4,8 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net" @@ -21,6 +22,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" gcpfirestorebackend "github.com/blackbirdworks/gopherstack/services/gcpfirestore" gcpfunctionsbackend "github.com/blackbirdworks/gopherstack/services/gcpfunctions" @@ -130,9 +132,9 @@ func TestWireGCPFunctions_StorageFirestoreAndSecrets(t *testing.T) { "GCPFunctions": fns, "GCPStorage": gcs, "GCPFirestore": fs, "GCPSecretManager": sm, }) - _, err := gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": json.RawMessage(`"src"`)}) + _, err := gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": jsontext.Value(`"src"`)}) require.NoError(t, err) - _, err = gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": json.RawMessage(`"inbox"`)}) + _, err = gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": jsontext.Value(`"inbox"`)}) require.NoError(t, err) _, err = sm.Backend.CreateSecret(&secretmanagerpb.CreateSecretRequest{ @@ -204,7 +206,7 @@ func TestWireGCPFunctions_StorageFirestoreAndSecrets(t *testing.T) { var obj map[string]any - require.NoError(t, json.Unmarshal(ev.body, &obj)) + require.NoError(t, json.Unmarshal(ev.body, &obj, jsonx.V1())) assert.Equal(t, "inbox", obj["bucket"]) assert.Equal(t, "a.txt", obj["name"]) diff --git a/internal/app/gcpfunctions_wiring_test.go b/internal/app/gcpfunctions_wiring_test.go index 10d37f049b..931bef9003 100644 --- a/internal/app/gcpfunctions_wiring_test.go +++ b/internal/app/gcpfunctions_wiring_test.go @@ -3,7 +3,7 @@ package app import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/jsontext" "testing" "cloud.google.com/go/functions/apiv2/functionspb" @@ -56,7 +56,7 @@ func TestWireGCPFunctions(t *testing.T) { _, err := ps.Backend.CreateTopic(&pubsubpb.Topic{Name: topic}) require.NoError(t, err) - _, err = gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": json.RawMessage(`"src-bucket"`)}) + _, err = gcs.Backend.CreateBucket("p", gcpstoragebackend.Patch{"name": jsontext.Value(`"src-bucket"`)}) require.NoError(t, err) var buf bytes.Buffer diff --git a/internal/app/globalaccelerator_wiring_test.go b/internal/app/globalaccelerator_wiring_test.go index bcb0011217..835bca1ffa 100644 --- a/internal/app/globalaccelerator_wiring_test.go +++ b/internal/app/globalaccelerator_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -17,6 +17,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func waitStack(t *testing.T, cfn *cloudformation.Client, name string) cfntypes.Stack { @@ -80,7 +82,7 @@ func TestCloudFormationProvisionsGlobalAccelerator(t *testing.T) { "GrpArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Grp", "EndpointGroupArn"}}}, "AttArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Att", "AttachmentArn"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) fx := newSFNFixture(t) diff --git a/internal/app/http_targets_wiring.go b/internal/app/http_targets_wiring.go index c3c0100fe6..66ed7fa7f6 100644 --- a/internal/app/http_targets_wiring.go +++ b/internal/app/http_targets_wiring.go @@ -2,11 +2,12 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" apigwbackend "github.com/blackbirdworks/gopherstack/services/apigateway" ebbackend "github.com/blackbirdworks/gopherstack/services/eventbridge" @@ -115,7 +116,7 @@ func (a *schedUniversalAdapter) InvokeUniversalTarget( ctx context.Context, region, roleARN, svc, action string, input []byte, ) error { var params any - if err := json.Unmarshal(input, ¶ms); err != nil { + if err := json.Unmarshal(input, ¶ms, jsonx.V1()); err != nil { return fmt.Errorf("universal target input is not valid JSON: %w", err) } diff --git a/internal/app/iam_policy_stream.go b/internal/app/iam_policy_stream.go index fd798e3b8b..c948530803 100644 --- a/internal/app/iam_policy_stream.go +++ b/internal/app/iam_policy_stream.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/iampolicystream" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" iambackend "github.com/blackbirdworks/gopherstack/services/iam" organizationsbackend "github.com/blackbirdworks/gopherstack/services/organizations" @@ -116,7 +117,7 @@ func followIAMPolicyStream(c *echo.Context, rec *iampolicystream.Recorder, f iam _ = rc.Flush() send := func(ev iampolicystream.Event) error { - b, err := json.Marshal(ev) + b, err := json.Marshal(ev, jsonx.V1()) if err != nil { return err } diff --git a/internal/app/iam_policy_stream_e2e_test.go b/internal/app/iam_policy_stream_e2e_test.go index 8486a83441..7ae78dd1e7 100644 --- a/internal/app/iam_policy_stream_e2e_test.go +++ b/internal/app/iam_policy_stream_e2e_test.go @@ -1,7 +1,8 @@ package app import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http/httptest" "testing" @@ -15,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/chaos" "github.com/blackbirdworks/gopherstack/pkgs/iampolicystream" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -114,7 +116,7 @@ func TestIAMPolicyStreamEndToEnd(t *testing.T) { var body struct { Events []iampolicystream.Event `json:"events"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1())) require.Len(t, body.Events, 2) allow, deny := body.Events[0], body.Events[1] diff --git a/internal/app/iam_policy_stream_test.go b/internal/app/iam_policy_stream_test.go index 8ca5ac932d..1d8df3357a 100644 --- a/internal/app/iam_policy_stream_test.go +++ b/internal/app/iam_policy_stream_test.go @@ -3,7 +3,7 @@ package app import ( "bufio" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/iampolicystream" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -73,7 +74,7 @@ func TestIAMPolicyStreamList(t *testing.T) { var body struct { Events []iampolicystream.Event `json:"events"` } - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body.Events, tt.wantCount) }) } @@ -88,7 +89,7 @@ func TestIAMPolicySuggest(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var s iampolicystream.Suggestion - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &s, jsonx.V1())) require.Len(t, s.Policy.Statement, 1) assert.Equal(t, []string{"s3:GetObject"}, s.Policy.Statement[0].Action) } diff --git a/internal/app/init_stages.go b/internal/app/init_stages.go index f3fc3f24d3..797370df5b 100644 --- a/internal/app/init_stages.go +++ b/internal/app/init_stages.go @@ -2,7 +2,8 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "log/slog" "net" @@ -13,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/inithooks" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -136,8 +138,11 @@ func buildInitRunHandler(st *localstackInitState) echo.HandlerFunc { } var req initRunRequest - if err := json.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, initRunMaxBody)). - Decode(&req); err != nil { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(c.Response(), c.Request().Body, initRunMaxBody)), + &req, + jsonx.V1(), + ); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{keyMessageField: "invalid request body"}) } diff --git a/internal/app/iot_rule_actions_test.go b/internal/app/iot_rule_actions_test.go index c6835607b1..994ded4ad3 100644 --- a/internal/app/iot_rule_actions_test.go +++ b/internal/app/iot_rule_actions_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "fmt" "io" "strings" @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" firehosebackend "github.com/blackbirdworks/gopherstack/services/firehose" iotbackend "github.com/blackbirdworks/gopherstack/services/iot" iotanalyticsbackend "github.com/blackbirdworks/gopherstack/services/iotanalytics" @@ -267,7 +268,7 @@ func iotCaseSNS(t *testing.T, f *iotRuleFixture, format, payload, want string) { Message string `json:"Message"` } - require.NoError(t, json.Unmarshal([]byte(f.bodies(t, url, 1)[0]), &env)) + require.NoError(t, json.Unmarshal([]byte(f.bodies(t, url, 1)[0]), &env, jsonx.V1())) assert.Contains(t, env.Message, want) } @@ -648,7 +649,7 @@ func iotCaseTemplateError(t *testing.T, f *iotRuleFixture) { f.publish(t, "tpl/topic", `{"present":1}`) var env map[string]string - require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env)) + require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env, jsonx.V1())) assert.Equal(t, "KinesisAction", env["failedAction"]) assert.Equal(t, "r", env["ruleName"]) assert.Empty(t, kinesisRecords(t, f.fx, "iot-tpl-stream")) diff --git a/internal/app/iot_rule_http_test.go b/internal/app/iot_rule_http_test.go index 2cf7c45a6e..d77943e8ae 100644 --- a/internal/app/iot_rule_http_test.go +++ b/internal/app/iot_rule_http_test.go @@ -2,7 +2,7 @@ package app import ( "cmp" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -109,7 +110,7 @@ func decodeConfirm(t *testing.T, hit iotHTTPHit) confirmBody { t.Helper() var b confirmBody - require.NoError(t, json.Unmarshal(hit.body, &b)) + require.NoError(t, json.Unmarshal(hit.body, &b, jsonx.V1())) return b } @@ -119,7 +120,7 @@ func (f *iotRuleFixture) confirmViaSDK(t *testing.T) func(hit iotHTTPHit) { return func(hit iotHTTPHit) { var b confirmBody - if json.Unmarshal(hit.body, &b) != nil { + if json.Unmarshal(hit.body, &b, jsonx.V1()) != nil { return } @@ -185,7 +186,7 @@ func TestIoTTopicRuleDestinationConfirmation(t *testing.T) { return func(hit iotHTTPHit) { var b confirmBody - if json.Unmarshal(hit.body, &b) != nil { + if json.Unmarshal(hit.body, &b, jsonx.V1()) != nil { return } @@ -427,7 +428,7 @@ func runIoTHTTPCase(t *testing.T, tt iotHTTPCase) { if tt.wantReason != "" { var env map[string]string - require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env)) + require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env, jsonx.V1())) assert.Equal(t, "HttpAction", env["failedAction"]) assert.Contains(t, env["failedActionReason"], tt.wantReason) } @@ -555,7 +556,7 @@ func TestIoTRuleHTTPActionSigV4Enforcement(t *testing.T) { if !tt.wantHit { var env map[string]string - require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env)) + require.NoError(t, json.Unmarshal([]byte(f.bodies(t, errURL, 1)[0]), &env, jsonx.V1())) assert.Equal(t, "HttpAction", env["failedAction"]) assert.Empty(t, ep.deliveries()) diff --git a/internal/app/iot_rule_lookups_test.go b/internal/app/iot_rule_lookups_test.go index 860d094f6f..8fea90a493 100644 --- a/internal/app/iot_rule_lookups_test.go +++ b/internal/app/iot_rule_lookups_test.go @@ -3,7 +3,7 @@ package app import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net" "strconv" "strings" @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" iotbackend "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -484,7 +485,7 @@ func TestIoTRuleConnectionFunctions(t *testing.T) { require.True(t, tok.WaitTimeout(authzDeadline)) var msg map[string]string - require.NoError(t, json.Unmarshal([]byte(f.bodies(t, url, 1)[0]), &msg)) + require.NoError(t, json.Unmarshal([]byte(f.bodies(t, url, 1)[0]), &msg, jsonx.V1())) assert.Equal(t, "device-7", msg["cid"]) assert.NotNil(t, net.ParseIP(msg["ip"])) diff --git a/internal/app/iotanalytics_lambda_iot_wiring_test.go b/internal/app/iotanalytics_lambda_iot_wiring_test.go index 50adaf0c81..01ad0894c3 100644 --- a/internal/app/iotanalytics_lambda_iot_wiring_test.go +++ b/internal/app/iotanalytics_lambda_iot_wiring_test.go @@ -2,8 +2,9 @@ package app import ( "github.com/blackbirdworks/gopherstack/internal/cliconfig" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http/httptest" "testing" @@ -116,7 +117,7 @@ func TestInitializeServices_IoTAnalyticsLambdaIoTWiring(t *testing.T) { require.Len(t, out.Payloads, 1) var decoded map[string]any - require.NoError(t, json.Unmarshal(out.Payloads[0], &decoded)) + require.NoError(t, json.Unmarshal(out.Payloads[0], &decoded, jsonx.V1())) registryData, isMap := decoded["registry"].(map[string]any) require.True(t, isMap, "registry attribute must be populated from the real IoT backend") @@ -155,7 +156,7 @@ func TestInitializeServices_IoTAnalyticsLambdaIoTWiring(t *testing.T) { require.Len(t, out.Payloads, 1) var decoded map[string]any - require.NoError(t, json.Unmarshal(out.Payloads[0], &decoded)) + require.NoError(t, json.Unmarshal(out.Payloads[0], &decoded, jsonx.V1())) shadowData, isMap := decoded["shadow"].(map[string]any) require.True(t, isMap, "shadow attribute must be populated from the real IoT backend") diff --git a/internal/app/lifecycle_cmd_test.go b/internal/app/lifecycle_cmd_test.go index a943b50c5a..a3781d22f4 100644 --- a/internal/app/lifecycle_cmd_test.go +++ b/internal/app/lifecycle_cmd_test.go @@ -3,7 +3,8 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -23,6 +24,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -104,7 +106,7 @@ func healthServer(t *testing.T, code int, h healthResponse) (*httptest.Server, s switch { case r.URL.Path == "/_gopherstack/health": w.WriteHeader(code) - _ = json.NewEncoder(w).Encode(h) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), h) case r.URL.Path == "/_gopherstack/reset" && r.Method == http.MethodPost: mu.Lock() posts = append(posts, r.URL.RawQuery) @@ -607,7 +609,7 @@ func TestLifecycleStatus(t *testing.T) { require.NoError(t, cmd.run(t.Context(), d)) var rep statusReport - require.NoError(t, json.Unmarshal(out.Bytes(), &rep)) + require.NoError(t, json.Unmarshal(out.Bytes(), &rep, jsonx.V1())) assert.True(t, rep.Running) assert.Equal(t, 4, rep.EnabledCount) assert.Equal(t, 1, rep.DisabledCount) diff --git a/internal/app/lifecycle_foreground_test.go b/internal/app/lifecycle_foreground_test.go index b9d30e8e1a..838aaa3c59 100644 --- a/internal/app/lifecycle_foreground_test.go +++ b/internal/app/lifecycle_foreground_test.go @@ -2,7 +2,8 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errKillFake = errors.New("no console") @@ -79,7 +81,7 @@ func readyServer(t *testing.T, ready *atomic.Bool) string { return } - _ = json.NewEncoder(w).Encode(healthResponse{Status: "ok"}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), healthResponse{Status: "ok"}) })) t.Cleanup(srv.Close) @@ -315,12 +317,15 @@ func TestLifecycleStatusServiceHealth(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/_gopherstack/health": - _ = json.NewEncoder(w).Encode(healthResponse{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), healthResponse{ Status: "ok", Version: "v1", UptimeSecs: 5, Goroutines: 12, HeapAllocB: 3 << 20, Services: []string{"S3", "SQS"}, Disabled: []string{"SNS"}, }) case "/_localstack/health": - _ = json.NewEncoder(w).Encode(localstackHealthResponse{Services: map[string]string{"s3": "available"}}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + localstackHealthResponse{Services: map[string]string{"s3": "available"}}, + ) default: http.NotFound(w, r) } @@ -335,7 +340,7 @@ func TestLifecycleStatusServiceHealth(t *testing.T) { require.NoError(t, cmd.run(t.Context(), d)) var rep statusReport - require.NoError(t, json.Unmarshal(out.Bytes(), &rep)) + require.NoError(t, json.Unmarshal(out.Bytes(), &rep, jsonx.V1())) got := map[string]serviceState{} for _, s := range rep.Clouds["aws"] { diff --git a/internal/app/lifecycle_run.go b/internal/app/lifecycle_run.go index 9ba2fe5363..d520e5681e 100644 --- a/internal/app/lifecycle_run.go +++ b/internal/app/lifecycle_run.go @@ -3,7 +3,8 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -17,6 +18,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type lifecycleState struct { @@ -41,7 +43,7 @@ func (d *lifecycleDeps) loadState(t LifecycleTarget) (lifecycleState, bool) { } var st lifecycleState - if json.Unmarshal(b, &st) != nil { + if json.Unmarshal(b, &st, jsonx.V1()) != nil { return lifecycleState{}, false } @@ -49,7 +51,7 @@ func (d *lifecycleDeps) loadState(t LifecycleTarget) (lifecycleState, bool) { } func (d *lifecycleDeps) saveState(t LifecycleTarget, st lifecycleState) error { - b, err := json.Marshal(st) + b, err := json.Marshal(st, jsonx.V1()) if err != nil { return fmt.Errorf("encode state: %w", err) } @@ -92,7 +94,7 @@ func (d *lifecycleDeps) health(ctx context.Context, endpoint string) (healthResp return h, fmt.Errorf("%w: health status %d", errLifecycleRequest, code) } - if err = json.Unmarshal(body, &h); err != nil { + if err = json.Unmarshal(body, &h, jsonx.V1()); err != nil { return h, fmt.Errorf("decode health: %w", err) } @@ -485,7 +487,7 @@ func (d *lifecycleDeps) serviceHealth(ctx context.Context, endpoint string) map[ } var r localstackHealthResponse - if json.Unmarshal(body, &r) != nil { + if json.Unmarshal(body, &r, jsonx.V1()) != nil { return nil } @@ -566,7 +568,10 @@ func (c *StatusCmd) run(ctx context.Context, d *lifecycleDeps) error { h, err := d.health(ctx, endpoint) if err != nil { if c.JSON { - _ = json.NewEncoder(d.Out).Encode(statusReport{Endpoint: endpoint, Managed: managed}) + _ = json.MarshalEncode( + jsontext.NewEncoder(d.Out, jsonx.V1()), + statusReport{Endpoint: endpoint, Managed: managed}, + ) } else { fmt.Fprintf(d.Out, "gopherstack is not running (%s)\n", endpoint) } @@ -576,10 +581,9 @@ func (c *StatusCmd) run(ctx context.Context, d *lifecycleDeps) error { rep := buildStatus(endpoint, h, managed, d.serviceHealth(ctx, endpoint)) if c.JSON { - enc := json.NewEncoder(d.Out) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(d.Out, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - return enc.Encode(rep) + return json.MarshalEncode(enc, rep) } rep.print(d.Out) @@ -652,7 +656,7 @@ func (c *ResetCmd) run(ctx context.Context, d *lifecycleDeps) error { Message string `json:"message"` } - _ = json.Unmarshal(body, &resp) + _ = json.Unmarshal(body, &resp, jsonx.V1()) fmt.Fprintln(d.Out, resp.Message) } diff --git a/internal/app/localstack_compat_test.go b/internal/app/localstack_compat_test.go index 1d0715c54b..4b186dd25d 100644 --- a/internal/app/localstack_compat_test.go +++ b/internal/app/localstack_compat_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/inithooks" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestLocalstackEnvPlan(t *testing.T) { @@ -355,7 +356,7 @@ func TestLocalstackInternalEndpoints(t *testing.T) { } var body map[string]any - require.NoError(t, json.Unmarshal(raw, &body)) + require.NoError(t, json.Unmarshal(raw, &body, jsonx.V1())) tt.check(t, body) }) } diff --git a/internal/app/logs_policy.go b/internal/app/logs_policy.go index b201342f5c..935fe01afd 100644 --- a/internal/app/logs_policy.go +++ b/internal/app/logs_policy.go @@ -2,9 +2,11 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cwlogsbackend "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -29,7 +31,7 @@ func (a *logsPolicyAdapter) GetResourcePolicy(_ context.Context, resourceARN str } var ( - statements []json.RawMessage + statements []jsontext.Value token string ) @@ -55,7 +57,7 @@ func (a *logsPolicyAdapter) GetResourcePolicy(_ context.Context, resourceARN str return "", nil } - doc, err := json.Marshal(map[string]any{"Version": "2012-10-17", "Statement": statements}) + doc, err := json.Marshal(map[string]any{"Version": "2012-10-17", "Statement": statements}, jsonx.V1()) if err != nil { return "", err } @@ -63,19 +65,19 @@ func (a *logsPolicyAdapter) GetResourcePolicy(_ context.Context, resourceARN str return string(doc), nil } -func policyStatements(doc string) []json.RawMessage { +func policyStatements(doc string) []jsontext.Value { var parsed struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if json.Unmarshal([]byte(doc), &parsed) != nil || len(parsed.Statement) == 0 { + if json.Unmarshal([]byte(doc), &parsed, jsonx.V1()) != nil || len(parsed.Statement) == 0 { return nil } - var list []json.RawMessage - if json.Unmarshal(parsed.Statement, &list) == nil { + var list []jsontext.Value + if json.Unmarshal(parsed.Statement, &list, jsonx.V1()) == nil { return list } - return []json.RawMessage{parsed.Statement} + return []jsontext.Value{parsed.Statement} } diff --git a/internal/app/mcp_wiring_test.go b/internal/app/mcp_wiring_test.go index 224befdc8e..76ac9713c3 100644 --- a/internal/app/mcp_wiring_test.go +++ b/internal/app/mcp_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "os" @@ -22,6 +22,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/chaos" "github.com/blackbirdworks/gopherstack/pkgs/iampolicystream" "github.com/blackbirdworks/gopherstack/pkgs/inithooks" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -127,7 +128,7 @@ func callTool(t *testing.T, cs *mcp.ClientSession, name string, args map[string] res, err := cs.CallTool(t.Context(), &mcp.CallToolParams{Name: name, Arguments: args}) require.NoError(t, err) - raw, err := json.Marshal(res) + raw, err := json.Marshal(res, jsonx.V1()) require.NoError(t, err) require.False(t, res.IsError, "tool %s: %s", name, raw) diff --git a/internal/app/persist_modes_test.go b/internal/app/persist_modes_test.go index a2dd217ea3..759a706677 100644 --- a/internal/app/persist_modes_test.go +++ b/internal/app/persist_modes_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -48,7 +49,7 @@ func (k *kvPersistSvc) Snapshot(context.Context) []byte { k.mu.Lock() defer k.mu.Unlock() - b, _ := json.Marshal(k.vals) + b, _ := json.Marshal(k.vals, jsonx.V1()) return b } @@ -57,7 +58,7 @@ func (k *kvPersistSvc) Restore(_ context.Context, data []byte) error { k.mu.Lock() defer k.mu.Unlock() - return json.Unmarshal(data, &k.vals) + return json.Unmarshal(data, &k.vals, jsonx.V1()) } func (k *kvPersistSvc) getA() string { diff --git a/internal/app/pods_apply.go b/internal/app/pods_apply.go index 2cd087d1af..da30e553c7 100644 --- a/internal/app/pods_apply.go +++ b/internal/app/pods_apply.go @@ -2,13 +2,14 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/pods" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -32,7 +33,7 @@ func podApplyHTTPError(err error) error { // validateSnaps rejects any section that is not well-formed JSON. func validateSnaps(snaps map[string][]byte) error { for n, d := range snaps { - if !json.Valid(d) { + if !jsontext.Value(d).IsValid(jsonx.V1()) { return podInvalidError{fmt.Errorf("%w: service %q section is not valid JSON", pods.ErrCorrupt, n)} } } diff --git a/internal/app/pods_cmd.go b/internal/app/pods_cmd.go index e43e23c55b..229e2745b3 100644 --- a/internal/app/pods_cmd.go +++ b/internal/app/pods_cmd.go @@ -3,7 +3,8 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const podCmdTimeout = 10 * time.Minute @@ -85,7 +87,7 @@ func (t PodTarget) call(method, path string, query url.Values, body any) error { var rdr io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) if err != nil { return fmt.Errorf("encode request: %w", err) } @@ -118,9 +120,9 @@ func (t PodTarget) call(method, path string, query url.Values, body any) error { return fmt.Errorf("%w: %d: %s", errPodServer, resp.StatusCode, strings.TrimSpace(string(out))) } - var pretty bytes.Buffer - if json.Indent(&pretty, out, "", " ") == nil { - out = pretty.Bytes() + pretty := jsontext.Value(bytes.Clone(out)) + if pretty.Indent(jsonx.V1(), jsontext.WithIndent(" ")) == nil { + out = pretty } fmt.Fprintln(os.Stdout, strings.TrimSpace(string(out))) diff --git a/internal/app/pods_localstack.go b/internal/app/pods_localstack.go index 6308cf8a2b..0239eadfa5 100644 --- a/internal/app/pods_localstack.go +++ b/internal/app/pods_localstack.go @@ -1,12 +1,14 @@ package app import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/pods" ) @@ -118,10 +120,10 @@ func (a *podAPI) lsDelete(c *echo.Context) error { func (a *podAPI) lsStateExport(c *echo.Context) error { raw := a.manager.ExportAll() - services := make(map[string]json.RawMessage, len(raw)) + services := make(map[string]jsontext.Value, len(raw)) for n, d := range raw { - services[n] = json.RawMessage(d) + services[n] = jsontext.Value(d) } return c.JSON(http.StatusOK, snapshotResponse{ @@ -131,7 +133,7 @@ func (a *podAPI) lsStateExport(c *echo.Context) error { func (a *podAPI) lsStateImport(c *echo.Context) error { var bundle snapshotBundle - if err := json.NewDecoder(c.Request().Body).Decode(&bundle); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &bundle, jsonx.V1()); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid snapshot bundle: "+err.Error()) } diff --git a/internal/app/region_wiring_test.go b/internal/app/region_wiring_test.go index f6cd071af2..7366bc5160 100644 --- a/internal/app/region_wiring_test.go +++ b/internal/app/region_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/pkgs/service" athenabackend "github.com/blackbirdworks/gopherstack/services/athena" @@ -59,7 +60,7 @@ func regionTargetCall( require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/internal/app/regional_platform_wiring_test.go b/internal/app/regional_platform_wiring_test.go index 22646b5770..dbc1b689a1 100644 --- a/internal/app/regional_platform_wiring_test.go +++ b/internal/app/regional_platform_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" awsconfigbackend "github.com/blackbirdworks/gopherstack/services/awsconfig" cfnbackend "github.com/blackbirdworks/gopherstack/services/cloudformation" codebuildbackend "github.com/blackbirdworks/gopherstack/services/codebuild" @@ -37,7 +38,7 @@ func regionRESTCall(t *testing.T, h echo.HandlerFunc, region, method, path, body out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out diff --git a/internal/app/registry_webhook_test.go b/internal/app/registry_webhook_test.go index f0c85c0922..c6ec715bdc 100644 --- a/internal/app/registry_webhook_test.go +++ b/internal/app/registry_webhook_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" ) @@ -31,7 +32,7 @@ func newHookSink(t *testing.T) *hookSink { raw, _ := io.ReadAll(r.Body) var m map[string]any - _ = json.Unmarshal(raw, &m) + _ = json.Unmarshal(raw, &m, jsonx.V1()) s.mu.Lock() s.got = append(s.got, m) diff --git a/internal/app/registry_wiring.go b/internal/app/registry_wiring.go index 4d7297309a..dd15fe638c 100644 --- a/internal/app/registry_wiring.go +++ b/internal/app/registry_wiring.go @@ -1,10 +1,11 @@ package app import ( - "encoding/json" + "encoding/json/v2" "errors" "path/filepath" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" acr "github.com/blackbirdworks/gopherstack/services/azurecontainerregistry" @@ -103,13 +104,13 @@ func (a *azureContainerRegistryAdapter) WebhookEvents(registry, name string) ([] out := make([]map[string]any, 0, len(events)) for _, ev := range events { - raw, marshalErr := json.Marshal(ev) + raw, marshalErr := json.Marshal(ev, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } var m map[string]any - if unmarshalErr := json.Unmarshal(raw, &m); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(raw, &m, jsonx.V1()); unmarshalErr != nil { return nil, unmarshalErr } diff --git a/internal/app/replicate_cmd.go b/internal/app/replicate_cmd.go index 8e65a403f7..a41b1c348a 100644 --- a/internal/app/replicate_cmd.go +++ b/internal/app/replicate_cmd.go @@ -2,7 +2,8 @@ package app import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -12,6 +13,7 @@ import ( "text/tabwriter" "github.com/blackbirdworks/gopherstack/internal/replicator" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -67,10 +69,9 @@ func (c *ReplicateCmd) run(ctx context.Context, out io.Writer, fn replicateFunc) } if c.JSON { - enc := json.NewEncoder(out) - enc.SetIndent("", " ") + enc := jsontext.NewEncoder(out, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) - if err = enc.Encode(rep); err != nil { + if err = json.MarshalEncode(enc, rep); err != nil { return fmt.Errorf("encode report: %w", err) } } else { diff --git a/internal/app/replicate_test.go b/internal/app/replicate_test.go index c67e62daa9..26fc865454 100644 --- a/internal/app/replicate_test.go +++ b/internal/app/replicate_test.go @@ -3,7 +3,7 @@ package app import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/internal/replicator" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func fakeReplicate(rep *replicator.Report, err error, seen *replicator.Config) replicateFunc { @@ -86,7 +87,7 @@ func TestReplicateAPIJobLifecycle(t *testing.T) { } var started map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) var job replicateJob @@ -94,7 +95,7 @@ func TestReplicateAPIJobLifecycle(t *testing.T) { r := replicateDo(e, http.MethodGet, replicateBasePath+"/"+started["id"], "") job = replicateJob{} - return r.Code == http.StatusOK && json.Unmarshal(r.Body.Bytes(), &job) == nil && + return r.Code == http.StatusOK && json.Unmarshal(r.Body.Bytes(), &job, jsonx.V1()) == nil && job.Status != replicateRunning }, 5*time.Second, 5*time.Millisecond) @@ -131,7 +132,7 @@ func TestReplicateAPIDoesNotEchoCredentials(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var started map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) got := replicateDo(e, http.MethodGet, replicateBasePath+"/"+started["id"], "") assert.NotContains(t, got.Body.String(), "topsecret") diff --git a/internal/app/requests_endpoint_test.go b/internal/app/requests_endpoint_test.go index d9626b1a6a..15c0bc8601 100644 --- a/internal/app/requests_endpoint_test.go +++ b/internal/app/requests_endpoint_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -64,14 +65,14 @@ func TestRequestsHandler(t *testing.T) { if tt.wantCode != http.StatusOK { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body["message"]) return } var got []logger.CapturedRequest - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) paths := make([]string, 0, len(got)) for _, r := range got { diff --git a/internal/app/s3_snapshot_options_test.go b/internal/app/s3_snapshot_options_test.go index 48d7885ac8..fd26aa31ad 100644 --- a/internal/app/s3_snapshot_options_test.go +++ b/internal/app/s3_snapshot_options_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -164,7 +165,7 @@ func TestPersistManualEndpoints(t *testing.T) { var out map[string]any - require.NoError(t, json.Unmarshal(load.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(load.Body.Bytes(), &out, jsonx.V1())) assert.InDelta(t, 1, out["loaded"], 0) assert.Equal(t, "7", h3.kvs[0].getA()) } diff --git a/internal/app/s3_sns_subject_wiring_test.go b/internal/app/s3_sns_subject_wiring_test.go index db4a12339e..853f7860c7 100644 --- a/internal/app/s3_sns_subject_wiring_test.go +++ b/internal/app/s3_sns_subject_wiring_test.go @@ -1,13 +1,14 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" s3backend "github.com/blackbirdworks/gopherstack/services/s3" snsbackend "github.com/blackbirdworks/gopherstack/services/sns" sqsbackend "github.com/blackbirdworks/gopherstack/services/sqs" @@ -83,7 +84,7 @@ func TestS3SNSPublisherAdapter_ForwardsSubject(t *testing.T) { var envelope struct { Subject string `json:"Subject"` } - require.NoError(t, json.Unmarshal([]byte(receivedBody), &envelope)) + require.NoError(t, json.Unmarshal([]byte(receivedBody), &envelope, jsonx.V1())) require.Equal(t, "S3Notification", envelope.Subject, "s3SNSPublisherAdapter must forward the S3 notification dispatcher's subject to SNS "+ "Publish instead of hardcoding an empty one") diff --git a/internal/app/s3tables_s3_wiring_test.go b/internal/app/s3tables_s3_wiring_test.go index dbdc75ccfa..bcd2b1d887 100644 --- a/internal/app/s3tables_s3_wiring_test.go +++ b/internal/app/s3tables_s3_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "io" "strings" "testing" @@ -12,6 +12,8 @@ import ( s3tablestypes "github.com/aws/aws-sdk-go-v2/service/s3tables/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestS3TablesInitialMetadataMaterialized(t *testing.T) { @@ -84,7 +86,7 @@ func TestS3TablesInitialMetadataMaterialized(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(raw, &doc)) + require.NoError(t, json.Unmarshal(raw, &doc, jsonx.V1())) assert.InDelta(t, 2, doc["format-version"], 0) assert.Equal(t, aws.ToString(loc.WarehouseLocation), doc["location"]) assert.InDelta(t, 2, doc["last-column-id"], 0) diff --git a/internal/app/scheduler_universal_targets_test.go b/internal/app/scheduler_universal_targets_test.go index 126c15b6c7..909b2065a0 100644 --- a/internal/app/scheduler_universal_targets_test.go +++ b/internal/app/scheduler_universal_targets_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/scheduler" schedtypes "github.com/aws/aws-sdk-go-v2/service/scheduler/types" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const universalPrefix = "arn:aws:scheduler:::aws-sdk:" @@ -46,7 +48,7 @@ func TestSchedulerUniversalTargets(t *testing.T) { t.Helper() url, _ := authzQueue(t, fx, "universal-q") - in, err := json.Marshal(map[string]string{"QueueUrl": url, "MessageBody": "hello"}) + in, err := json.Marshal(map[string]string{"QueueUrl": url, "MessageBody": "hello"}, jsonx.V1()) require.NoError(t, err) return string(in), url @@ -97,7 +99,7 @@ func TestSchedulerUniversalTargets(t *testing.T) { jd := setupBatchQueue(t, fx) in, err := json.Marshal(map[string]string{ "JobName": "universal-job", "JobQueue": "eb-queue", "JobDefinition": jd, - }) + }, jsonx.V1()) require.NoError(t, err) return string(in), "" diff --git a/internal/app/service_role_authz_test.go b/internal/app/service_role_authz_test.go index 6f2d10ceab..3508ce1c20 100644 --- a/internal/app/service_role_authz_test.go +++ b/internal/app/service_role_authz_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -182,7 +183,7 @@ func TestServiceRoleAuthzStepFunctionsLegacy(t *testing.T) { func jsonAtOptional(raw *string, path ...string) (string, bool) { var cur any - if raw == nil || json.Unmarshal([]byte(*raw), &cur) != nil { + if raw == nil || json.Unmarshal([]byte(*raw), &cur, jsonx.V1()) != nil { return "", false } diff --git a/internal/app/ses_inbound_wiring_test.go b/internal/app/ses_inbound_wiring_test.go index 1714c7a163..1a43609466 100644 --- a/internal/app/ses_inbound_wiring_test.go +++ b/internal/app/ses_inbound_wiring_test.go @@ -4,7 +4,7 @@ import ( "crypto/aes" "crypto/cipher" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/smtp" "testing" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" sesbackend "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -110,7 +111,7 @@ func TestSESInboundKMSAndAuthWiring(t *testing.T) { require.NoError(t, err) var matdesc map[string]string - require.NoError(t, json.Unmarshal([]byte(obj.Metadata["x-amz-matdesc"]), &matdesc)) + require.NoError(t, json.Unmarshal([]byte(obj.Metadata["x-amz-matdesc"]), &matdesc, jsonx.V1())) dec, err := kmsC.Decrypt(ctx, &kms.DecryptInput{CiphertextBlob: wrapped, EncryptionContext: matdesc}) require.NoError(t, err) diff --git a/internal/app/sesv2_sns_event_wiring_test.go b/internal/app/sesv2_sns_event_wiring_test.go index b4f0c02e8a..def710603b 100644 --- a/internal/app/sesv2_sns_event_wiring_test.go +++ b/internal/app/sesv2_sns_event_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -13,6 +13,8 @@ import ( sqstypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSESv2SNSEventDestinationWiring(t *testing.T) { @@ -114,7 +116,7 @@ func TestSESv2SNSEventDestinationWiring(t *testing.T) { Message string `json:"Message"` } - if json.Unmarshal([]byte(aws.ToString(m.Body)), &env) != nil { + if json.Unmarshal([]byte(aws.ToString(m.Body)), &env, jsonx.V1()) != nil { continue } @@ -122,7 +124,7 @@ func TestSESv2SNSEventDestinationWiring(t *testing.T) { EventType string `json:"eventType"` } - if json.Unmarshal([]byte(env.Message), &ev) == nil { + if json.Unmarshal([]byte(env.Message), &ev, jsonx.V1()) == nil { got[ev.EventType] = true } } diff --git a/internal/app/sfn_sdk_integration_test.go b/internal/app/sfn_sdk_integration_test.go index 348dc98d78..55eb090404 100644 --- a/internal/app/sfn_sdk_integration_test.go +++ b/internal/app/sfn_sdk_integration_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net" "net/http" @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" sfnbackend "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -157,7 +158,7 @@ func jsonAt(t *testing.T, raw *string, path ...string) any { t.Helper() var cur any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(raw)), &cur)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(raw)), &cur, jsonx.V1())) for _, p := range path { m, ok := cur.(map[string]any) diff --git a/internal/app/sns_wiring_test.go b/internal/app/sns_wiring_test.go index b4b3d9cab8..2772233fe2 100644 --- a/internal/app/sns_wiring_test.go +++ b/internal/app/sns_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "context" - "encoding/json" + "encoding/json/v2" "io" "testing" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" firehosebackend "github.com/blackbirdworks/gopherstack/services/firehose" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" snsbackend "github.com/blackbirdworks/gopherstack/services/sns" @@ -124,7 +125,7 @@ func TestWireSNSToLambdaFirehose_EndToEndDelivery(t *testing.T) { require.NoError(t, err) dlqARN := arn.Build("sqs", config.DefaultRegion, config.DefaultAccountID, "lambda-dlq") - redrivePolicy, err := json.Marshal(map[string]string{"deadLetterTargetArn": dlqARN}) + redrivePolicy, err := json.Marshal(map[string]string{"deadLetterTargetArn": dlqARN}, jsonx.V1()) require.NoError(t, err) require.NoError(t, snsBk.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy))) diff --git a/internal/app/sso_test.go b/internal/app/sso_test.go index 3cc196dbd4..d422c28f06 100644 --- a/internal/app/sso_test.go +++ b/internal/app/sso_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" identitystorebackend "github.com/blackbirdworks/gopherstack/services/identitystore" @@ -194,7 +195,7 @@ func writeSSOTokenCache(t *testing.T, l ssoLogin, expiresAt time.Time) string { "accessToken": l.accessToken, "expiresAt": expiresAt.UTC().Format(time.RFC3339), "refreshToken": l.refreshToken, "clientId": l.clientID, "clientSecret": l.clientSecret, "region": "us-east-1", "startUrl": ssoStartURL, - }) + }, jsonx.V1()) require.NoError(t, err) require.NoError(t, os.WriteFile(path, data, 0o600)) diff --git a/internal/app/textract_rekognition_s3_wiring_test.go b/internal/app/textract_rekognition_s3_wiring_test.go index c58743b500..039acde657 100644 --- a/internal/app/textract_rekognition_s3_wiring_test.go +++ b/internal/app/textract_rekognition_s3_wiring_test.go @@ -2,7 +2,7 @@ package app import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/pkgs/service" rekognitionbackend "github.com/blackbirdworks/gopherstack/services/rekognition" @@ -31,7 +32,7 @@ func doJSONTargetRequest( ) (int, map[string]any) { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -44,7 +45,7 @@ func doJSONTargetRequest( require.NoError(t, h(c)) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return rec.Code, resp } diff --git a/internal/app/timestreaminfluxdb_wiring_test.go b/internal/app/timestreaminfluxdb_wiring_test.go index 0f83425e6d..7aa0526db0 100644 --- a/internal/app/timestreaminfluxdb_wiring_test.go +++ b/internal/app/timestreaminfluxdb_wiring_test.go @@ -1,7 +1,7 @@ package app import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/timestreaminfluxdb/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTimestreamInfluxDBAuthSecretWiring(t *testing.T) { @@ -55,7 +57,7 @@ func TestTimestreamInfluxDBAuthSecretWiring(t *testing.T) { require.NoError(t, err) var secret map[string]string - require.NoError(t, json.Unmarshal([]byte(aws.ToString(val.SecretString)), &secret)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(val.SecretString)), &secret, jsonx.V1())) assert.Equal(t, tt.wantUsername, secret["username"]) assert.Equal(t, "Passw0rdPassw0rd", secret["password"]) assert.Len(t, secret["token"], 64) diff --git a/internal/app/verifiedpermissions_jwt_test.go b/internal/app/verifiedpermissions_jwt_test.go index 681d4dc078..c2598cabe9 100644 --- a/internal/app/verifiedpermissions_jwt_test.go +++ b/internal/app/verifiedpermissions_jwt_test.go @@ -6,7 +6,8 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "log/slog" "math/big" @@ -28,6 +29,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/chaos" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" cognitoidpbackend "github.com/blackbirdworks/gopherstack/services/cognitoidp" verifiedpermissionsbackend "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" @@ -304,11 +306,14 @@ func newOIDCServer(t *testing.T) *oidcServer { t.Cleanup(o.srv.Close) mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]string{"issuer": o.srv.URL, "jwks_uri": o.srv.URL + "/keys"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"issuer": o.srv.URL, "jwks_uri": o.srv.URL + "/keys"}, + ) }) mux.HandleFunc("/keys", func(w http.ResponseWriter, _ *http.Request) { o.jwksHits.Add(1) - _ = json.NewEncoder(w).Encode(map[string]any{"keys": []map[string]string{{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{"keys": []map[string]string{{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "k1", "n": base64.RawURLEncoding.EncodeToString(key.N.Bytes()), "e": base64.RawURLEncoding.EncodeToString(big.NewInt(int64(key.E)).Bytes()), diff --git a/internal/awstest/awstest.go b/internal/awstest/awstest.go index 827c45e81f..6ef1be0d87 100644 --- a/internal/awstest/awstest.go +++ b/internal/awstest/awstest.go @@ -8,13 +8,15 @@ package awstest import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http/httptest" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // NewContext builds an echo context and response recorder for an httptest @@ -38,7 +40,7 @@ func NewJSONContext( var body io.Reader if payload != nil { - raw, err := json.Marshal(payload) + raw, err := json.Marshal(payload, jsonx.V1()) require.NoError(t, err) body = bytes.NewReader(raw) } @@ -53,5 +55,5 @@ func NewJSONContext( func DecodeJSON(t *testing.T, rec *httptest.ResponseRecorder, v any) { t.Helper() - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1())) } diff --git a/internal/awswire/awswire.go b/internal/awswire/awswire.go index f06527c661..e1a016f3a9 100644 --- a/internal/awswire/awswire.go +++ b/internal/awswire/awswire.go @@ -5,7 +5,7 @@ package awswire import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -17,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire" ) @@ -142,12 +143,12 @@ func Do[O any](ctx context.Context, c *Core, op string, in any) (*O, error) { params := map[string]any{} if in != nil { - b, err := json.Marshal(in) + b, err := json.Marshal(in, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode %s request: %w", op, err) } - if err = json.Unmarshal(b, ¶ms); err != nil { + if err = json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return nil, fmt.Errorf("encode %s request: %w", op, err) } } @@ -159,12 +160,12 @@ func Do[O any](ctx context.Context, c *Core, op string, in any) (*O, error) { var out O - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, err } - if err = json.Unmarshal(b, &out); err != nil { + if err = json.Unmarshal(b, &out, jsonx.V1()); err != nil { return nil, fmt.Errorf("decode %s response: %w", op, err) } diff --git a/internal/awswire/awswire_test.go b/internal/awswire/awswire_test.go index e6b05e068f..80dd480feb 100644 --- a/internal/awswire/awswire_test.go +++ b/internal/awswire/awswire_test.go @@ -2,7 +2,7 @@ package awswire_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -25,6 +25,7 @@ import ( "github.com/blackbirdworks/gopherstack/internal/awswire/kms" "github.com/blackbirdworks/gopherstack/internal/awswire/lambda" "github.com/blackbirdworks/gopherstack/internal/awswire/sqs" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func testConfig(base string) aws.Config { @@ -56,7 +57,7 @@ func TestClientSignsAndDecodes(t *testing.T) { auth, token = r.Header.Get("Authorization"), r.Header.Get("X-Amz-Security-Token") target = r.Header.Get("X-Amz-Target") raw, _ := io.ReadAll(r.Body) - _ = json.Unmarshal(raw, &body) + _ = json.Unmarshal(raw, &body, jsonx.V1()) w.Header().Set("Content-Type", "application/x-amz-json-1.1") _, _ = w.Write([]byte(`{"KeyMetadata":{"KeyId":"k-1","Arn":"arn:aws:kms:us-west-2:1:key/k-1","Enabled":true}}`)) })) @@ -166,7 +167,7 @@ func TestPaginator(t *testing.T) { raw, _ := io.ReadAll(r.Body) var in map[string]any - _ = json.Unmarshal(raw, &in) + _ = json.Unmarshal(raw, &in, jsonx.V1()) marker, _ := in["Marker"].(string) seen = append(seen, marker) diff --git a/internal/dockercompat/client/client.go b/internal/dockercompat/client/client.go index 7c6119f4ff..97ab333273 100644 --- a/internal/dockercompat/client/client.go +++ b/internal/dockercompat/client/client.go @@ -3,7 +3,8 @@ package client import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -19,6 +20,7 @@ import ( compatcontainer "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/container" compatimage "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/image" compatnetwork "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/network" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Opt aliases the Moby client option type. @@ -104,13 +106,13 @@ var errPushRejected = errors.New("image push failed") // pushAnonymousAuth is the base64 of "{}": the X-Registry-Auth header for a registry without credentials. const pushAnonymousAuth = "e30=" -func pushDigest(aux *json.RawMessage, statusLine, prev string) string { +func pushDigest(aux *jsontext.Value, statusLine, prev string) string { if aux != nil { var a struct { Digest string `json:"Digest"` } - if json.Unmarshal(*aux, &a) == nil && a.Digest != "" { + if json.Unmarshal(*aux, &a, jsonx.V1()) == nil && a.Digest != "" { return a.Digest } } diff --git a/internal/dockercompat/client/client_runtime_test.go b/internal/dockercompat/client/client_runtime_test.go index 4a2bf77a1d..39a75ae8d1 100644 --- a/internal/dockercompat/client/client_runtime_test.go +++ b/internal/dockercompat/client/client_runtime_test.go @@ -1,7 +1,8 @@ package client_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +14,7 @@ import ( compatcontainer "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/container" "github.com/blackbirdworks/gopherstack/internal/dockercompat/client" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type fakeDaemon struct { @@ -26,7 +28,7 @@ func (f *fakeDaemon) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case strings.HasSuffix(r.URL.Path, "/containers/create"): f.mu.Lock() - _ = json.NewDecoder(r.Body).Decode(&f.createBody) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &f.createBody, jsonx.V1()) f.mu.Unlock() w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"Id":"abc","Warnings":[]}`)) diff --git a/internal/mcpserver/backend.go b/internal/mcpserver/backend.go index be0feaa9c2..bafc7b6b7f 100644 --- a/internal/mcpserver/backend.go +++ b/internal/mcpserver/backend.go @@ -4,13 +4,15 @@ package mcpserver import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -40,7 +42,7 @@ func (b *backend) call(ctx context.Context, method, path string, q url.Values, i var body io.Reader = http.NoBody if in != nil { - raw, err := json.Marshal(in) + raw, err := json.Marshal(in, jsonx.V1()) if err != nil { return fmt.Errorf("encode request: %w", err) } @@ -81,7 +83,7 @@ func (b *backend) call(ctx context.Context, method, path string, q url.Values, i return nil } - if err = json.Unmarshal(raw, out); err != nil { + if err = json.Unmarshal(raw, out, jsonx.V1()); err != nil { return fmt.Errorf("decode response: %w", err) } diff --git a/internal/mcpserver/server.go b/internal/mcpserver/server.go index 4a748d7aec..78e18adac3 100644 --- a/internal/mcpserver/server.go +++ b/internal/mcpserver/server.go @@ -2,10 +2,12 @@ package mcpserver import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/modelcontextprotocol/go-sdk/mcp" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -51,7 +53,7 @@ func (b *backend) infoResource(ctx context.Context, req *mcp.ReadResourceRequest return nil, err } - raw, err := json.Marshal(h) + raw, err := json.Marshal(h, jsonx.V1()) if err != nil { return nil, err } diff --git a/internal/replicator/sqs.go b/internal/replicator/sqs.go index 516303ed42..bca02c3b0d 100644 --- a/internal/replicator/sqs.go +++ b/internal/replicator/sqs.go @@ -2,7 +2,7 @@ package replicator import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "path" "slices" @@ -10,6 +10,7 @@ import ( sqstypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" "github.com/blackbirdworks/gopherstack/internal/awswire/sqs" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func sqsCreateAttrs() []sqstypes.QueueAttributeName { @@ -79,7 +80,7 @@ func dlqName(redrive string) string { Arn string `json:"deadLetterTargetArn"` } - if json.Unmarshal([]byte(redrive), &rp) != nil || rp.Arn == "" { + if json.Unmarshal([]byte(redrive), &rp, jsonx.V1()) != nil || rp.Arn == "" { return "" } diff --git a/pkgs/apptrace/middleware.go b/pkgs/apptrace/middleware.go index 84f503ed5d..8afaa1187d 100644 --- a/pkgs/apptrace/middleware.go +++ b/pkgs/apptrace/middleware.go @@ -2,7 +2,7 @@ package apptrace import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -317,7 +318,7 @@ func (r *redactor) errorCode(header string, body []byte) string { if b[0] == '{' { var env errEnvelope - if json.Unmarshal(b, &env) == nil { + if json.Unmarshal(b, &env, jsonx.V1()) == nil { return cleanCode(env.pick()) } diff --git a/pkgs/awserr/respond.go b/pkgs/awserr/respond.go index 9679259561..6fc0f5e0fb 100644 --- a/pkgs/awserr/respond.go +++ b/pkgs/awserr/respond.go @@ -1,7 +1,7 @@ package awserr import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "hash/crc32" @@ -9,6 +9,8 @@ import ( "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Protocol identifies the AWS wire protocol used to encode an error envelope. @@ -129,7 +131,7 @@ func Write(c *echo.Context, proto Protocol, e APIError) error { } func writeJSON(c *echo.Context, contentType string, e APIError) error { - body, _ := json.Marshal(jsonErrorEnvelope{Type: e.Code, Message: e.Message, MessageCap: e.Message}) + body, _ := json.Marshal(jsonErrorEnvelope{Type: e.Code, Message: e.Message, MessageCap: e.Message}, jsonx.V1()) checksum := crc32.ChecksumIEEE(body) c.Response().Header().Set("X-Amz-Crc32", strconv.FormatUint(uint64(checksum), 10)) c.Response().Header().Set("Content-Type", contentType) diff --git a/pkgs/awserr/respond_test.go b/pkgs/awserr/respond_test.go index 0fd7806b93..4c5caa2a0a 100644 --- a/pkgs/awserr/respond_test.go +++ b/pkgs/awserr/respond_test.go @@ -1,7 +1,7 @@ package awserr_test import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestClassifyMatchesWrappedSentinel(t *testing.T) { @@ -67,7 +68,7 @@ func TestWriteJSON10(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", env.Type) assert.Equal(t, "nope", env.Message) } diff --git a/pkgs/awstime/awstime_test.go b/pkgs/awstime/awstime_test.go index b7d24971ff..2e016c78ef 100644 --- a/pkgs/awstime/awstime_test.go +++ b/pkgs/awstime/awstime_test.go @@ -1,11 +1,12 @@ package awstime_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEpoch(t *testing.T) { @@ -61,7 +62,7 @@ func TestEpochTimeJSON(t *testing.T) { in := doc{At: awstime.NewEpochTime(time.Unix(1_700_000_000, 500_000_000).UTC())} - raw, err := json.Marshal(in) + raw, err := json.Marshal(in, jsonx.V1()) if err != nil { t.Fatal(err) } @@ -71,7 +72,7 @@ func TestEpochTimeJSON(t *testing.T) { } var out doc - if err = json.Unmarshal(raw, &out); err != nil { + if err = json.Unmarshal(raw, &out, jsonx.V1()); err != nil { t.Fatal(err) } @@ -79,7 +80,7 @@ func TestEpochTimeJSON(t *testing.T) { t.Fatalf("round trip lost data: %+v", out) } - if err = json.Unmarshal([]byte(`{"at":"2026-01-01"}`), &out); err == nil { + if err = json.Unmarshal([]byte(`{"at":"2026-01-01"}`), &out, jsonx.V1()); err == nil { t.Fatal("string timestamp must be rejected") } } diff --git a/pkgs/chaos/handler.go b/pkgs/chaos/handler.go index 2f15e1ada6..4ec74576c9 100644 --- a/pkgs/chaos/handler.go +++ b/pkgs/chaos/handler.go @@ -1,11 +1,13 @@ package chaos import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -83,7 +85,7 @@ func (h *apiHandler) getFaults(c *echo.Context) error { // postFaults replaces the entire fault configuration. func (h *apiHandler) postFaults(c *echo.Context) error { var rules []FaultRule - if err := json.NewDecoder(c.Request().Body).Decode(&rules); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &rules, jsonx.V1()); err != nil { return c.String(http.StatusBadRequest, "invalid JSON: "+err.Error()) } @@ -99,7 +101,7 @@ func (h *apiHandler) postFaults(c *echo.Context) error { // patchFaults appends rules to the existing fault configuration. func (h *apiHandler) patchFaults(c *echo.Context) error { var rules []FaultRule - if err := json.NewDecoder(c.Request().Body).Decode(&rules); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &rules, jsonx.V1()); err != nil { return c.String(http.StatusBadRequest, "invalid JSON: "+err.Error()) } @@ -115,7 +117,7 @@ func (h *apiHandler) patchFaults(c *echo.Context) error { // deleteFaults removes rules matching the provided list. func (h *apiHandler) deleteFaults(c *echo.Context) error { var rules []FaultRule - if err := json.NewDecoder(c.Request().Body).Decode(&rules); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &rules, jsonx.V1()); err != nil { return c.String(http.StatusBadRequest, "invalid JSON: "+err.Error()) } @@ -132,7 +134,7 @@ func (h *apiHandler) getEffects(c *echo.Context) error { // postEffects updates the network effect configuration. func (h *apiHandler) postEffects(c *echo.Context) error { var effects NetworkEffects - if err := json.NewDecoder(c.Request().Body).Decode(&effects); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &effects, jsonx.V1()); err != nil { return c.String(http.StatusBadRequest, "invalid JSON: "+err.Error()) } @@ -246,7 +248,7 @@ func (h *apiHandler) clearFaults(c *echo.Context) error { // deleteFaultByIndex removes the fault rule at the given index. func (h *apiHandler) deleteFaultByIndex(c *echo.Context) error { var req deleteByIndexRequest - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { return c.String(http.StatusBadRequest, "invalid JSON: "+err.Error()) } diff --git a/pkgs/chaos/handler_test.go b/pkgs/chaos/handler_test.go index 44233f6838..c984319328 100644 --- a/pkgs/chaos/handler_test.go +++ b/pkgs/chaos/handler_test.go @@ -2,7 +2,8 @@ package chaos_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/chaos" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -77,7 +79,7 @@ func TestChaosHandler_Faults_GetEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var rules []chaos.FaultRule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules, jsonx.V1())) assert.Empty(t, rules) } @@ -138,14 +140,14 @@ func TestChaosHandler_Faults_PostReplacesRules(t *testing.T) { reg := service.NewRegistry() e := buildChaosAPI(t, store, reg) - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := callGroup(t, e, http.MethodPost, "/_gopherstack/chaos/faults", bodyBytes) assert.Equal(t, tt.wantStatus, rec.Code) var rules []chaos.FaultRule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules, jsonx.V1())) assert.Equal(t, tt.wantRules, rules) }) } @@ -188,14 +190,14 @@ func TestChaosHandler_Faults_PatchAppendsRules(t *testing.T) { reg := service.NewRegistry() e := buildChaosAPI(t, store, reg) - bodyBytes, err := json.Marshal(tt.patch) + bodyBytes, err := json.Marshal(tt.patch, jsonx.V1()) require.NoError(t, err) rec := callGroup(t, e, http.MethodPatch, "/_gopherstack/chaos/faults", bodyBytes) assert.Equal(t, tt.wantStatus, rec.Code) var rules []chaos.FaultRule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules, jsonx.V1())) assert.Len(t, rules, tt.wantCount) }) } @@ -242,14 +244,14 @@ func TestChaosHandler_Faults_DeleteRemovesRules(t *testing.T) { reg := service.NewRegistry() e := buildChaosAPI(t, store, reg) - bodyBytes, err := json.Marshal(tt.delete) + bodyBytes, err := json.Marshal(tt.delete, jsonx.V1()) require.NoError(t, err) rec := callGroup(t, e, http.MethodDelete, "/_gopherstack/chaos/faults", bodyBytes) assert.Equal(t, tt.wantStatus, rec.Code) var rules []chaos.FaultRule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rules, jsonx.V1())) assert.Len(t, rules, tt.wantCount) }) } @@ -301,7 +303,7 @@ func TestChaosHandler_Effects(t *testing.T) { e := buildChaosAPI(t, store, reg) if tt.postBody != nil { - bodyBytes, err := json.Marshal(tt.postBody) + bodyBytes, err := json.Marshal(tt.postBody, jsonx.V1()) require.NoError(t, err) postRec := callGroup(t, e, http.MethodPost, "/_gopherstack/chaos/effects", bodyBytes) @@ -312,7 +314,7 @@ func TestChaosHandler_Effects(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var effects chaos.NetworkEffects - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &effects)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &effects, jsonx.V1())) assert.Equal(t, tt.wantEffects, effects) }) } @@ -407,7 +409,7 @@ func TestChaosHandler_Targets(t *testing.T) { } var resp targetsResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := make([]string, 0, len(resp.Services)) for _, s := range resp.Services { @@ -503,7 +505,7 @@ func TestChaosHandler_TargetsMerge(t *testing.T) { } var resp targetsResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Find the entry for the expected service name. var found *targetEntry @@ -599,8 +601,8 @@ func TestChaosHandler_Activity_GetEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) - var events []json.RawMessage - require.NoError(t, json.NewDecoder(rec.Body).Decode(&events)) + var events []jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &events, jsonx.V1())) assert.Empty(t, events) } @@ -655,7 +657,7 @@ func TestChaosHandler_Activity_GetWithEvents(t *testing.T) { var events []struct { Service string `json:"service"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&events)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &events, jsonx.V1())) require.Len(t, events, tt.wantCount) if tt.wantFirst != "" { @@ -676,11 +678,11 @@ func TestChaosHandler_Query_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Faults []json.RawMessage `json:"faults"` - Effects json.RawMessage `json:"effects"` - Activity []json.RawMessage `json:"activity"` + Faults []jsontext.Value `json:"faults"` + Effects jsontext.Value `json:"effects"` + Activity []jsontext.Value `json:"activity"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Empty(t, resp.Faults) assert.Empty(t, resp.Activity) } @@ -704,7 +706,7 @@ func TestChaosHandler_Query_WithData(t *testing.T) { Effects chaos.NetworkEffects `json:"effects"` Activity []chaos.ActivityEvent `json:"activity"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.Faults, 1) assert.Equal(t, "s3", resp.Faults[0].Service) require.NotNil(t, resp.Faults[0].Probability) @@ -730,7 +732,7 @@ func TestChaosHandler_ClearFaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var rules []chaos.FaultRule - require.NoError(t, json.NewDecoder(rec.Body).Decode(&rules)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &rules, jsonx.V1())) assert.Empty(t, rules) // Confirm store is empty. @@ -801,7 +803,7 @@ func TestChaosHandler_DeleteFaultByIndex(t *testing.T) { if tt.wantCode == http.StatusOK { var rules []chaos.FaultRule - require.NoError(t, json.NewDecoder(rec.Body).Decode(&rules)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &rules, jsonx.V1())) assert.Len(t, rules, tt.wantCount) } }) @@ -821,7 +823,7 @@ func TestChaosHandler_ResetEffects(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var effects chaos.NetworkEffects - require.NoError(t, json.NewDecoder(rec.Body).Decode(&effects)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &effects, jsonx.V1())) assert.Zero(t, effects.Latency) assert.Zero(t, effects.Jitter) diff --git a/pkgs/chaos/localstack.go b/pkgs/chaos/localstack.go index 07c601ce66..48c99f693c 100644 --- a/pkgs/chaos/localstack.go +++ b/pkgs/chaos/localstack.go @@ -1,11 +1,14 @@ package chaos import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -41,7 +44,7 @@ func (h *lsHandler) rules() []FaultRule { return h.store.GetRules() } func decodeRules(c *echo.Context) ([]FaultRule, error) { var rules []FaultRule - if err := json.NewDecoder(c.Request().Body).Decode(&rules); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &rules, jsonx.V1()); err != nil { return nil, err } @@ -111,7 +114,12 @@ func (h *lsHandler) getEffects(c *echo.Context) error { func (h *lsHandler) postEffects(c *echo.Context) error { var in localstackEffects - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil || in.Latency < 0 { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &in, + jsonx.V1(), + ); err != nil || + in.Latency < 0 { return badRequest(c, errInvalidEffects) } diff --git a/pkgs/container/credentials_resources_test.go b/pkgs/container/credentials_resources_test.go index 6221e76a26..a1a20df73f 100644 --- a/pkgs/container/credentials_resources_test.go +++ b/pkgs/container/credentials_resources_test.go @@ -3,7 +3,7 @@ package container_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "strings" "testing" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/image" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type authRecordingAPI struct { @@ -54,7 +55,7 @@ func TestPullImageWithCredentials(t *testing.T) { var got map[string]string - require.NoError(t, json.Unmarshal(raw, &got)) + require.NoError(t, json.Unmarshal(raw, &got, jsonx.V1())) assert.Equal(t, "puller", got["username"]) assert.Equal(t, "s3cret", got["password"]) assert.Contains(t, got["serveraddress"], strings.TrimPrefix(tc.wantServer, "docker.io")) diff --git a/pkgs/deltalog/deltalog.go b/pkgs/deltalog/deltalog.go index c2f896ebf2..dbfe6c1381 100644 --- a/pkgs/deltalog/deltalog.go +++ b/pkgs/deltalog/deltalog.go @@ -7,7 +7,8 @@ import ( "bufio" "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/url" @@ -16,6 +17,7 @@ import ( "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -82,7 +84,7 @@ type action struct { type addAction struct { PartitionValues map[string]*string `json:"partitionValues"` Path string `json:"path"` - DeletionVector json.RawMessage `json:"deletionVector"` + DeletionVector jsontext.Value `json:"deletionVector"` Size int64 `json:"size"` } @@ -210,7 +212,7 @@ func (s *state) loadCheckpoint(ctx context.Context, st Storage, cps map[int64][] Version int64 `json:"version"` } - if json.Unmarshal(raw, &lc) == nil { + if json.Unmarshal(raw, &lc, jsonx.V1()) == nil { if _, ok := cps[lc.Version]; ok { best = lc.Version } @@ -260,13 +262,13 @@ func (s *state) applyCheckpoint(body []byte) error { continue } - raw, merr := json.Marshal(v) + raw, merr := json.Marshal(v, jsonx.V1()) if merr != nil { return merr } part := map[int]string{0: "add", 1: "metaData", 2: "protocol", 3: "remove"}[i] - if uerr := json.Unmarshal([]byte(`{"`+part+`":`+string(raw)+`}`), &a); uerr != nil { + if uerr := json.Unmarshal([]byte(`{"`+part+`":`+string(raw)+`}`), &a, jsonx.V1()); uerr != nil { return fmt.Errorf("%w: %w", ErrBadLog, uerr) } } @@ -291,7 +293,7 @@ func (s *state) applyCommit(body []byte) error { } var a action - if err := json.Unmarshal(line, &a); err != nil { + if err := json.Unmarshal(line, &a, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", ErrBadLog, err) } @@ -362,9 +364,9 @@ func (s *state) snapshot() (*Snapshot, error) { } type schemaField struct { - Metadata map[string]any `json:"metadata"` - Name string `json:"name"` - Type json.RawMessage `json:"type"` + Metadata map[string]any `json:"metadata"` + Name string `json:"name"` + Type jsontext.Value `json:"type"` } func parseSchema(s, mode string) ([]Column, error) { @@ -372,7 +374,7 @@ func parseSchema(s, mode string) ([]Column, error) { Fields []schemaField `json:"fields"` } - if err := json.Unmarshal([]byte(s), &st); err != nil { + if err := json.Unmarshal([]byte(s), &st, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: schemaString: %w", ErrBadLog, err) } @@ -402,21 +404,21 @@ func parseSchema(s, mode string) ([]Column, error) { return cols, nil } -func hiveType(raw json.RawMessage) (string, error) { +func hiveType(raw jsontext.Value) (string, error) { var prim string - if err := json.Unmarshal(raw, &prim); err == nil { + if err := json.Unmarshal(raw, &prim, jsonx.V1()); err == nil { return hivePrimitive(prim) } var n struct { - Type string `json:"type"` - ElementType json.RawMessage `json:"elementType"` - KeyType json.RawMessage `json:"keyType"` - ValueType json.RawMessage `json:"valueType"` - Fields []schemaField `json:"fields"` + Type string `json:"type"` + ElementType jsontext.Value `json:"elementType"` + KeyType jsontext.Value `json:"keyType"` + ValueType jsontext.Value `json:"valueType"` + Fields []schemaField `json:"fields"` } - if err := json.Unmarshal(raw, &n); err != nil { + if err := json.Unmarshal(raw, &n, jsonx.V1()); err != nil { return "", fmt.Errorf("%w: type %s", ErrBadLog, raw) } diff --git a/pkgs/dmsengine/kinesis_target.go b/pkgs/dmsengine/kinesis_target.go index 377ca82500..67e6a014a1 100644 --- a/pkgs/dmsengine/kinesis_target.go +++ b/pkgs/dmsengine/kinesis_target.go @@ -3,12 +3,15 @@ package dmsengine import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errNoStreamARN = errors.New("KinesisSettings.StreamArn is required") @@ -36,7 +39,7 @@ func newKinesisTarget(ep EndpointConfig, store StreamStore) (Target, error) { var o kinesisOptions if ep.KinesisSettings != "" { - if err := json.Unmarshal([]byte(ep.KinesisSettings), &o); err != nil { + if err := json.Unmarshal([]byte(ep.KinesisSettings), &o, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid KinesisSettings: %w", err) } } @@ -85,7 +88,7 @@ func (t *kinesisTarget) value(v any, k Kind) any { if isNumeric(k) { if _, err := strconv.ParseFloat(x, 64); err == nil { - return json.Number(x) + return jsonx.Number(x) } } @@ -163,14 +166,14 @@ type recordOut struct { func (t *kinesisTarget) encode(data, meta map[string]any) ([]byte, error) { var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) - + opts := []jsontext.Options{jsonx.V1(), jsontext.EscapeForHTML(false)} if !strings.EqualFold(t.opts.MessageFormat, "json-unformatted") { - enc.SetIndent("", " ") + opts = append(opts, jsontext.Multiline(true), jsontext.WithIndent(" ")) } - if err := enc.Encode(recordOut{Data: data, Metadata: meta}); err != nil { + enc := jsontext.NewEncoder(&buf, opts...) + + if err := json.MarshalEncode(enc, recordOut{Data: data, Metadata: meta}); err != nil { return nil, err } diff --git a/pkgs/dmsengine/mapping.go b/pkgs/dmsengine/mapping.go index ee83aa71d7..9abc2d3ba2 100644 --- a/pkgs/dmsengine/mapping.go +++ b/pkgs/dmsengine/mapping.go @@ -1,13 +1,15 @@ package dmsengine import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrMapping reports an invalid table-mappings document. @@ -30,15 +32,15 @@ const ( type flexString string func (f *flexString) UnmarshalJSON(b []byte) error { - var n json.Number - if err := json.Unmarshal(b, &n); err == nil { + var n jsonx.Number + if err := json.Unmarshal(b, &n, jsonx.V1()); err == nil { *f = flexString(n.String()) return nil } var s string - if err := json.Unmarshal(b, &s); err != nil { + if err := json.Unmarshal(b, &s, jsonx.V1()); err != nil { return err } @@ -75,7 +77,7 @@ func ParseMappings(doc string) (Mappings, error) { return m, nil } - if err := json.Unmarshal([]byte(doc), &m); err != nil { + if err := json.Unmarshal([]byte(doc), &m, jsonx.V1()); err != nil { return m, fmt.Errorf("%w: %w", ErrMapping, err) } diff --git a/pkgs/dmsengine/s3_target.go b/pkgs/dmsengine/s3_target.go index dd650cf20a..c6a2d373f0 100644 --- a/pkgs/dmsengine/s3_target.go +++ b/pkgs/dmsengine/s3_target.go @@ -4,12 +4,14 @@ import ( "bytes" "context" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" "sync" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -71,7 +73,7 @@ func newS3Target(ep EndpointConfig, store ObjectStore) (Target, error) { var o s3Options if ep.S3Settings != "" { - if err := json.Unmarshal([]byte(ep.S3Settings), &o); err != nil { + if err := json.Unmarshal([]byte(ep.S3Settings), &o, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid S3Settings: %w", err) } } diff --git a/pkgs/dmsengine/settings.go b/pkgs/dmsengine/settings.go index 62a678aa5b..be03f552b3 100644 --- a/pkgs/dmsengine/settings.go +++ b/pkgs/dmsengine/settings.go @@ -1,10 +1,12 @@ package dmsengine import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrInvalidSettings reports an unusable ReplicationTaskSettings document. @@ -34,7 +36,7 @@ func ParseTaskSettings(doc string) (TaskSettings, error) { } `json:"FullLoadSettings"` } - if err := json.Unmarshal([]byte(doc), &raw); err != nil { + if err := json.Unmarshal([]byte(doc), &raw, jsonx.V1()); err != nil { return ts, fmt.Errorf("%w: %w", ErrInvalidSettings, err) } diff --git a/pkgs/dmsengine/targets_test.go b/pkgs/dmsengine/targets_test.go index 0e17e83597..86e20204ec 100644 --- a/pkgs/dmsengine/targets_test.go +++ b/pkgs/dmsengine/targets_test.go @@ -2,7 +2,7 @@ package dmsengine_test import ( "context" - "encoding/json" + "encoding/json/v2" "sort" "strings" "sync" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/dmsengine" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -239,9 +240,9 @@ func TestKinesisTargetRecords(t *testing.T) { var first, second, del map[string]map[string]any - require.NoError(t, json.Unmarshal([]byte(store.recs[0]), &first)) - require.NoError(t, json.Unmarshal([]byte(store.recs[1]), &second)) - require.NoError(t, json.Unmarshal([]byte(store.recs[2]), &del)) + require.NoError(t, json.Unmarshal([]byte(store.recs[0]), &first, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(store.recs[1]), &second, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(store.recs[2]), &del, jsonx.V1())) assert.Equal(t, map[string]any{"id": float64(1), "name": "alpha"}, first["data"]) assert.Equal(t, map[string]any{"id": float64(2)}, second["data"], "nulls are omitted by default") diff --git a/pkgs/docker/pull.go b/pkgs/docker/pull.go index 11b6650f85..d52ac6ba48 100644 --- a/pkgs/docker/pull.go +++ b/pkgs/docker/pull.go @@ -1,10 +1,14 @@ package docker import ( - "encoding/json" + "bytes" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrImagePull wraps failures the daemon reports in an image pull stream. @@ -20,12 +24,12 @@ type pullMessage struct { // ConsumePullStream reads an ImagePull response to EOF (closing early cancels // the pull) and returns the first error the daemon reports in the stream. func ConsumePullStream(r io.Reader) error { - dec := json.NewDecoder(r) + dec := jsontext.NewDecoder(r) for { var msg pullMessage - err := dec.Decode(&msg) + err := json.UnmarshalDecode(dec, &msg, jsonx.V1()) if errors.Is(err, io.EOF) { return nil } @@ -37,13 +41,13 @@ func ConsumePullStream(r io.Reader) error { } if msg.ErrorDetail != nil && msg.ErrorDetail.Message != "" { - _, _ = io.Copy(io.Discard, io.MultiReader(dec.Buffered(), r)) + _, _ = io.Copy(io.Discard, io.MultiReader(bytes.NewReader(dec.UnreadBuffer()), r)) return fmt.Errorf("%w: %s", ErrImagePull, msg.ErrorDetail.Message) } if msg.Error != "" { - _, _ = io.Copy(io.Discard, io.MultiReader(dec.Buffered(), r)) + _, _ = io.Copy(io.Discard, io.MultiReader(bytes.NewReader(dec.UnreadBuffer()), r)) return fmt.Errorf("%w: %s", ErrImagePull, msg.Error) } diff --git a/pkgs/dynamoattr/attr.go b/pkgs/dynamoattr/attr.go index 39e782c0c1..d0e3b4d4cd 100644 --- a/pkgs/dynamoattr/attr.go +++ b/pkgs/dynamoattr/attr.go @@ -1,12 +1,13 @@ package dynamoattr import ( - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "strings" "github.com/blackbirdworks/gopherstack/pkgs/dynamonum" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // UnwrapAttributeValue converts a DynamoDB wire attribute map into a bare value when possible. @@ -163,7 +164,7 @@ func ToString(v any) string { case nil: return "" default: - b, err := json.Marshal(s) + b, err := json.Marshal(s, jsonx.V1()) if err != nil { return fmt.Sprintf("%v", s) } diff --git a/pkgs/extensions/control.go b/pkgs/extensions/control.go index 0036f52527..a4c4ee3d34 100644 --- a/pkgs/extensions/control.go +++ b/pkgs/extensions/control.go @@ -2,7 +2,8 @@ package extensions import ( "crypto/subtle" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net" @@ -11,6 +12,8 @@ import ( "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -115,8 +118,8 @@ func (m *Manager) ServeRegister(w http.ResponseWriter, r *http.Request) { var reg Registration - dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxRegistrationBytes)) - if err := dec.Decode(®); err != nil { + dec := jsontext.NewDecoder(http.MaxBytesReader(w, r.Body, maxRegistrationBytes)) + if err := json.UnmarshalDecode(dec, ®, jsonx.V1()); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{keyMessage: "invalid JSON: " + err.Error()}) return @@ -287,5 +290,5 @@ func overlap(a, b []string) bool { func writeJSON(w http.ResponseWriter, code int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - _ = json.NewEncoder(w).Encode(v) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), v) } diff --git a/pkgs/extensions/hooks.go b/pkgs/extensions/hooks.go index 4490e254f7..d45ff90f5c 100644 --- a/pkgs/extensions/hooks.go +++ b/pkgs/extensions/hooks.go @@ -3,7 +3,7 @@ package extensions import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -11,6 +11,7 @@ import ( "net/url" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -320,12 +321,12 @@ func (h *HookSet) Post(r *http.Request, rec *Recorder) *HookResponse { func (h *HookSet) call(ctx context.Context, b boundHook, path string, req hookRequest, rep any) *HookResponse { req.Extension = b.name - payload, err := json.Marshal(req) + payload, err := json.Marshal(req, jsonx.V1()) if err == nil { var out []byte if out, err = h.m.post(ctx, b.ep, path, payload, b.timeout); err == nil && len(bytes.TrimSpace(out)) > 0 { - err = json.Unmarshal(out, rep) + err = json.Unmarshal(out, rep, jsonx.V1()) } } diff --git a/pkgs/extensions/hooks_internal_test.go b/pkgs/extensions/hooks_internal_test.go index d7be9176cb..cc12696239 100644 --- a/pkgs/extensions/hooks_internal_test.go +++ b/pkgs/extensions/hooks_internal_test.go @@ -1,7 +1,8 @@ package extensions import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -10,6 +11,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func hookManager(t *testing.T, failOpen *bool, hook RequestHook, handler http.HandlerFunc) *Manager { @@ -129,7 +132,7 @@ func TestPreHookPayload(t *testing.T) { var got hookRequest m := hookManager(t, nil, RequestHook{Pre: "/pre"}, func(_ http.ResponseWriter, r *http.Request) { - _ = json.NewDecoder(r.Body).Decode(&got) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &got, jsonx.V1()) }) req := httptest.NewRequest(http.MethodPut, "/bkt/key?x=1", strings.NewReader("data")) @@ -172,7 +175,7 @@ func TestPostHook(t *testing.T) { var seen hookRequest m := hookManager(t, nil, RequestHook{Post: "/post"}, func(w http.ResponseWriter, r *http.Request) { - _ = json.NewDecoder(r.Body).Decode(&seen) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &seen, jsonx.V1()) _, _ = io.WriteString(w, tt.reply) }) diff --git a/pkgs/extensions/main_internal_test.go b/pkgs/extensions/main_internal_test.go index 6daa0cd011..93302615e3 100644 --- a/pkgs/extensions/main_internal_test.go +++ b/pkgs/extensions/main_internal_test.go @@ -2,7 +2,7 @@ package extensions import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net" "net/http" @@ -12,6 +12,7 @@ import ( "testing" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testleak" ) @@ -90,7 +91,7 @@ func runHelper() { } func mustJSON(v any) string { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { panic(err) } diff --git a/pkgs/gcpdispatch/gcpdispatch.go b/pkgs/gcpdispatch/gcpdispatch.go index 28f1c349db..acbf61761e 100644 --- a/pkgs/gcpdispatch/gcpdispatch.go +++ b/pkgs/gcpdispatch/gcpdispatch.go @@ -3,12 +3,14 @@ package gcpdispatch import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "math" "net/http" "time" "google.golang.org/genproto/googleapis/rpc/code" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -65,7 +67,7 @@ func Backoff(lo, hi time.Duration, doublings, failures int32) time.Duration { } func b64(v any) string { - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(raw) } diff --git a/pkgs/gcpdispatch/gcpdispatch_test.go b/pkgs/gcpdispatch/gcpdispatch_test.go index 6a5ac8b8aa..1be6edd4eb 100644 --- a/pkgs/gcpdispatch/gcpdispatch_test.go +++ b/pkgs/gcpdispatch/gcpdispatch_test.go @@ -2,7 +2,7 @@ package gcpdispatch_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -13,6 +13,7 @@ import ( "google.golang.org/genproto/googleapis/rpc/code" "github.com/blackbirdworks/gopherstack/pkgs/gcpdispatch" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHTTPToRPC(t *testing.T) { @@ -81,7 +82,7 @@ func TestTokens(t *testing.T) { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) assert.Equal(t, "https://x.test/y", claims["aud"]) assert.Equal(t, "sa@p.iam", claims["email"]) diff --git a/pkgs/gcplro/store.go b/pkgs/gcplro/store.go index 82353d6b47..508a2fe6ee 100644 --- a/pkgs/gcplro/store.go +++ b/pkgs/gcplro/store.go @@ -4,7 +4,7 @@ package gcplro import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "strings" @@ -103,11 +103,11 @@ func (s *Store) Reset() { } // Snapshot serialises every operation as protojson. -func (s *Store) Snapshot() map[string]json.RawMessage { +func (s *Store) Snapshot() map[string]jsontext.Value { s.mu.RLock("Snapshot") defer s.mu.RUnlock() - out := make(map[string]json.RawMessage, len(s.ops)) + out := make(map[string]jsontext.Value, len(s.ops)) for name, op := range s.ops { if raw, err := protojson.Marshal(op); err == nil { @@ -119,7 +119,7 @@ func (s *Store) Snapshot() map[string]json.RawMessage { } // Restore replaces the stored operations with a Snapshot result. -func (s *Store) Restore(snap map[string]json.RawMessage) error { +func (s *Store) Restore(snap map[string]jsontext.Value) error { ops := make(map[string]*longrunningpb.Operation, len(snap)) for name, raw := range snap { diff --git a/pkgs/gcprest/errors.go b/pkgs/gcprest/errors.go index 1a87408b98..1792922049 100644 --- a/pkgs/gcprest/errors.go +++ b/pkgs/gcprest/errors.go @@ -1,12 +1,15 @@ package gcprest import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type codeInfo struct { @@ -40,10 +43,10 @@ type errorBody struct { } type errorDetail struct { - Message string `json:"message"` - Status string `json:"status"` - Details []json.RawMessage `json:"details"` - Code int `json:"code"` + Message string `json:"message"` + Status string `json:"status"` + Details []jsontext.Value `json:"details"` + Code int `json:"code"` } // HTTPStatus maps a gRPC code to the HTTP status Google APIs use for it. @@ -63,7 +66,7 @@ func writeError(w http.ResponseWriter, err error) { info = codeInfo{"UNKNOWN", http.StatusInternalServerError} } - details := make([]json.RawMessage, 0, len(st.Proto().GetDetails())) + details := make([]jsontext.Value, 0, len(st.Proto().GetDetails())) for _, d := range st.Proto().GetDetails() { if b, mErr := protojson.Marshal(d); mErr == nil { @@ -77,7 +80,7 @@ func writeError(w http.ResponseWriter, err error) { } func writeJSON(w http.ResponseWriter, code int, v any) { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkgs/gcpserve/logentry.go b/pkgs/gcpserve/logentry.go index c3ba1b2a5c..986ad7c253 100644 --- a/pkgs/gcpserve/logentry.go +++ b/pkgs/gcpserve/logentry.go @@ -1,7 +1,7 @@ package gcpserve import ( - "encoding/json" + "encoding/json/v2" "maps" "strconv" "strings" @@ -9,6 +9,8 @@ import ( "cloud.google.com/go/logging/apiv2/loggingpb" ltype "google.golang.org/genproto/googleapis/logging/type" "google.golang.org/protobuf/types/known/structpb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Special JSON fields the structured-logging agent lifts out of jsonPayload. @@ -49,7 +51,7 @@ func jsonObject(line string) (map[string]any, bool) { } var obj map[string]any - if err := json.Unmarshal([]byte(s), &obj); err != nil || obj == nil { + if err := json.Unmarshal([]byte(s), &obj, jsonx.V1()); err != nil || obj == nil { return nil, false } diff --git a/pkgs/handler/handler_test.go b/pkgs/handler/handler_test.go index 7d59017efe..507292359b 100644 --- a/pkgs/handler/handler_test.go +++ b/pkgs/handler/handler_test.go @@ -2,7 +2,7 @@ package handler_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/handler" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- OperationContext --- @@ -158,7 +159,7 @@ func TestWriteJSON_SetsHeadersAndBody(t *testing.T) { assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) var got map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "value", got["key"]) } diff --git a/pkgs/handler/response.go b/pkgs/handler/response.go index f91d696f21..1968af6d81 100644 --- a/pkgs/handler/response.go +++ b/pkgs/handler/response.go @@ -2,20 +2,21 @@ package handler import ( "context" - "encoding/json" + "encoding/json/v2" "hash/crc32" "net/http" "strconv" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) // WriteJSON marshals the payload to JSON, sets standard headers, and writes the response. // Sets Content-Type to "application/json" and Content-Length. func WriteJSON(w http.ResponseWriter, code int, payload any) error { - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) if err != nil { return err } @@ -33,7 +34,7 @@ func WriteJSON(w http.ResponseWriter, code int, payload any) error { // and writes the response. The checksum is required by the DynamoDB wire protocol. // Sets Content-Type to "application/x-amz-json-1.0" and X-Amz-Crc32. func WriteJSONWithChecksum(w http.ResponseWriter, code int, payload any) error { - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) if err != nil { return err } diff --git a/pkgs/handler/wire_golden_test.go b/pkgs/handler/wire_golden_test.go new file mode 100644 index 0000000000..5264b79685 --- /dev/null +++ b/pkgs/handler/wire_golden_test.go @@ -0,0 +1,141 @@ +package handler_test + +import ( + "encoding/json/jsontext" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/handler" +) + +type goldenAttr struct { + S *string `json:"S,omitempty"` + N *string `json:"N,omitempty"` + B []byte `json:"B,omitempty"` + NULL *bool `json:"NULL,omitempty"` + M map[string]*goldenAttr `json:"M,omitempty"` + L []*goldenAttr `json:"L,omitempty"` +} + +type goldenScanOutput struct { + LastEvaluatedKey map[string]*goldenAttr `json:"LastEvaluatedKey,omitempty"` + Items []map[string]*goldenAttr `json:"Items"` + Count int32 `json:"Count"` + ScannedCount int32 `json:"ScannedCount"` +} + +type goldenSQSMessage struct { + Attributes map[string]string `json:"Attributes"` + MessageID string `json:"MessageId"` + Body string `json:"Body"` + MD5 string `json:"MD5OfBody"` +} + +type goldenSQSOutput struct { + Messages []goldenSQSMessage `json:"Messages"` +} + +type goldenMetricResult struct { + ID string `json:"Id"` + Label string `json:"Label"` + StatusCode string `json:"StatusCode"` + Timestamps []float64 `json:"Timestamps"` + Values []float64 `json:"Values"` + Messages []string `json:"Messages"` + Extra jsontext.Value `json:"Extra,omitempty"` +} + +type goldenMetricOutput struct { + NextToken *string `json:"NextToken,omitempty"` + Results []goldenMetricResult `json:"MetricDataResults"` +} + +func TestWireGoldenJSONRPC(t *testing.T) { + t.Parallel() + + ts := time.Date(2025, 3, 4, 5, 6, 7, 123_000_000, time.UTC) + yes := true + + tests := []struct { + payload any + name string + body string + crc string + }{ + { + name: "dynamodb scan", + payload: goldenScanOutput{ + Items: []map[string]*goldenAttr{{ + "id": {S: new("a&c
\"q\"")}, + "n": {N: new("1.50")}, + "bin": {B: []byte{1, 2, 3}}, + "nul": {NULL: &yes}, + "m": {M: map[string]*goldenAttr{"z": {S: new("1")}, "a": {S: new("2")}}}, + "list": {L: []*goldenAttr{{S: new("x")}, {N: new("2")}}}, + }}, + Count: 1, + ScannedCount: 1, + }, + body: `{"Items":[{"bin":{"B":"AQID"},"id":{"S":"a\u003cb\u003e\u0026c\u2028\"q\""},` + + `"list":{"L":[{"S":"x"},{"N":"2"}]},"m":{"M":{"a":{"S":"2"},"z":{"S":"1"}}},` + + `"n":{"N":"1.50"},"nul":{"NULL":true}}],"Count":1,"ScannedCount":1}`, + crc: "2457307943", + }, + { + name: "dynamodb empty scan", + payload: goldenScanOutput{}, + body: `{"Items":null,"Count":0,"ScannedCount":0}`, + crc: "4196347728", + }, + { + name: "sqs receive", + payload: goldenSQSOutput{Messages: []goldenSQSMessage{{ + MessageID: "m-1", + Body: `{"k":""}`, + MD5: "abc", + }}}, + body: `{"Messages":[{"Attributes":null,"MessageId":"m-1",` + + `"Body":"{\"k\":\"\u003cv\u003e\"}","MD5OfBody":"abc"}]}`, + crc: "2547812670", + }, + { + name: "sqs empty", + payload: goldenSQSOutput{}, + body: `{"Messages":null}`, + crc: "1433687114", + }, + { + name: "cloudwatch get metric data", + payload: goldenMetricOutput{Results: []goldenMetricResult{{ + ID: "q1", + Label: "CPU ", + StatusCode: "Complete", + Timestamps: []float64{awstime.Epoch(ts), awstime.Epoch(ts.Add(time.Minute))}, + Values: []float64{1.5, 2, 1e21, 0.000001, 1e-7, 100, 123456789012}, + Extra: jsontext.Value(` { "a" : [1, 2] } `), + }}}, + body: `{"MetricDataResults":[{"Id":"q1","Label":"CPU \u003cavg\u003e",` + + `"StatusCode":"Complete","Timestamps":[1741064767.1230001,1741064827.1230001],` + + `"Values":[1.5,2,1e+21,0.000001,1e-7,100,123456789012],"Messages":null,` + + `"Extra":{"a":[1,2]}}]}`, + crc: "2076035819", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rec := httptest.NewRecorder() + require.NoError(t, handler.WriteJSONWithChecksum(rec, 200, tt.payload)) + + assert.Equal(t, tt.body, rec.Body.String()) + assert.Equal(t, tt.crc, rec.Header().Get("X-Amz-Crc32")) + }) + } +} diff --git a/pkgs/httputils/httputils.go b/pkgs/httputils/httputils.go index cd7f4741e8..dd328e7244 100644 --- a/pkgs/httputils/httputils.go +++ b/pkgs/httputils/httputils.go @@ -3,7 +3,7 @@ package httputils import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "hash/crc32" "io" @@ -18,6 +18,7 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -163,7 +164,7 @@ func ParseFormBody(r *http.Request) (url.Values, error) { func WriteJSON(ctx context.Context, w http.ResponseWriter, code int, payload any) { log := logger.Load(ctx) - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) if err != nil { log.ErrorContext(ctx, "failed to marshal JSON response", "error", err) http.Error(w, "internal server error", http.StatusInternalServerError) @@ -209,7 +210,7 @@ func WriteXML(ctx context.Context, w http.ResponseWriter, code int, payload any) func WriteDynamoDBResponse(ctx context.Context, w http.ResponseWriter, code int, payload any) { log := logger.Load(ctx) - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) if err != nil { log.ErrorContext(ctx, "failed to marshal DynamoDB response", "error", err) http.Error(w, @@ -644,3 +645,19 @@ func ReadBodyLimit(r *http.Request, limit int64) ([]byte, error) { return body, nil } + +// WriteJSONBytes writes an already-encoded JSON body with a JSON content type. +func WriteJSONBytes(w http.ResponseWriter, code int, body []byte) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(code) + _, _ = w.Write(body) +} + +// WriteAmzJSON11Bytes writes an already-encoded body as application/x-amz-json-1.1. +func WriteAmzJSON11Bytes(w http.ResponseWriter, code int, body []byte) { + w.Header().Set("Content-Type", "application/x-amz-json-1.1") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(code) + _, _ = w.Write(body) +} diff --git a/pkgs/httputils/httputils_test.go b/pkgs/httputils/httputils_test.go index 468845aaa2..1cebcab8f8 100644 --- a/pkgs/httputils/httputils_test.go +++ b/pkgs/httputils/httputils_test.go @@ -458,3 +458,43 @@ func TestWriteS3ErrorResponse(t *testing.T) { assert.Equal(t, "application/xml", w.Header().Get("Content-Type")) assert.Contains(t, w.Body.String(), "NoSuchKey") } + +func TestWriteJSONBytes(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + wantBody string + body []byte + code int + }{ + {name: "with_body", body: []byte(`{"a":1}`), wantBody: `{"a":1}`, code: http.StatusCreated}, + {name: "no_body", wantBody: "", code: http.StatusOK}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rec := httptest.NewRecorder() + httputils.WriteJSONBytes(rec, tt.code, tt.body) + + assert.Equal(t, tt.code, rec.Code) + assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) + assert.Equal(t, "nosniff", rec.Header().Get("X-Content-Type-Options")) + assert.Equal(t, tt.wantBody, rec.Body.String()) + }) + } +} + +func TestWriteAmzJSON11Bytes(t *testing.T) { + t.Parallel() + + rec := httptest.NewRecorder() + httputils.WriteAmzJSON11Bytes(rec, http.StatusAccepted, []byte(`{}`)) + + assert.Equal(t, http.StatusAccepted, rec.Code) + assert.Equal(t, "application/x-amz-json-1.1", rec.Header().Get("Content-Type")) + assert.Equal(t, "nosniff", rec.Header().Get("X-Content-Type-Options")) + assert.JSONEq(t, `{}`, rec.Body.String()) +} diff --git a/pkgs/iceberg/iceberg_test.go b/pkgs/iceberg/iceberg_test.go index 92f85282af..08905f07e4 100644 --- a/pkgs/iceberg/iceberg_test.go +++ b/pkgs/iceberg/iceberg_test.go @@ -1,7 +1,8 @@ package iceberg_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/iceberg" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTransformSpecVectors(t *testing.T) { @@ -184,8 +186,8 @@ func TestMetadataPreservesUnknownFields(t *testing.T) { out, err := iceberg.EncodeMetadata(m) require.NoError(t, err) - var generic map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out, &generic)) + var generic map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out, &generic, jsonx.V1())) assert.Contains(t, generic, "statistics") _, err = iceberg.ParseMetadata([]byte(`{"format-version":9}`)) @@ -206,7 +208,7 @@ func TestHiveType(t *testing.T) { t.Run(tc.want, func(t *testing.T) { t.Parallel() - got, err := iceberg.HiveType(json.RawMessage(tc.in)) + got, err := iceberg.HiveType(jsontext.Value(tc.in)) require.NoError(t, err) assert.Equal(t, tc.want, got) }) diff --git a/pkgs/iceberg/manifest.go b/pkgs/iceberg/manifest.go index 867264f0aa..23224e48aa 100644 --- a/pkgs/iceberg/manifest.go +++ b/pkgs/iceberg/manifest.go @@ -2,13 +2,15 @@ package iceberg import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" "github.com/iskorotkov/avro/v2" "github.com/iskorotkov/avro/v2/ocf" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Manifest entry statuses and content types from the table spec. @@ -399,7 +401,7 @@ func manifestSchema(meta *TableMetadata, spec PartitionSpec) (string, error) { }, } - b, err := json.Marshal(schema) + b, err := json.Marshal(schema, jsonx.V1()) return string(b), err } @@ -415,8 +417,8 @@ func EncodeManifest(meta *TableMetadata, spec PartitionSpec, content int, entrie return nil, fmt.Errorf("%w: %w", ErrInvalidManifest, err) } - cur, _ := json.Marshal(meta.CurrentSchema()) - specJSON, _ := json.Marshal(spec.Fields) + cur, _ := json.Marshal(meta.CurrentSchema(), jsonx.V1()) + specJSON, _ := json.Marshal(spec.Fields, jsonx.V1()) contentName := "data" if content != ContentData { diff --git a/pkgs/iceberg/metadata.go b/pkgs/iceberg/metadata.go index b977bf3ee4..8f0c3c74b8 100644 --- a/pkgs/iceberg/metadata.go +++ b/pkgs/iceberg/metadata.go @@ -3,7 +3,8 @@ package iceberg import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -12,6 +13,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -35,11 +38,11 @@ var ( // Field is one schema field; Type is a primitive name or a nested type object. type Field struct { - Name string `json:"name"` - Doc string `json:"doc,omitempty"` - Type json.RawMessage `json:"type"` - ID int `json:"id"` - Required bool `json:"required"` + Name string `json:"name"` + Doc string `json:"doc,omitempty"` + Type jsontext.Value `json:"type"` + ID int `json:"id"` + Required bool `json:"required"` } // Schema is an Iceberg struct schema. @@ -98,12 +101,12 @@ type Ref struct { type TableMetadata struct { Properties map[string]string `json:"properties"` Refs map[string]Ref `json:"refs,omitempty"` - extra map[string]json.RawMessage + extra map[string]jsontext.Value TableUUID string `json:"table-uuid"` Location string `json:"location"` Schemas []Schema `json:"schemas"` PartitionSpecs []PartitionSpec `json:"partition-specs"` - SortOrders []json.RawMessage `json:"sort-orders"` + SortOrders []jsontext.Value `json:"sort-orders"` Snapshots []Snapshot `json:"snapshots"` SnapshotLog []SnapshotLogEntry `json:"snapshot-log"` MetadataLog []MetadataLogEntry `json:"metadata-log"` @@ -140,11 +143,11 @@ func (m *TableMetadata) UnmarshalJSON(b []byte) error { a.CurrentSnapshotID = NoSnapshot - if err := json.Unmarshal(b, &a); err != nil { + if err := json.Unmarshal(b, &a, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", ErrInvalidMetadata, err) } - if err := json.Unmarshal(b, &v1); err != nil { + if err := json.Unmarshal(b, &v1, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", ErrInvalidMetadata, err) } @@ -157,8 +160,8 @@ func (m *TableMetadata) UnmarshalJSON(b []byte) error { a.PartitionSpecs = []PartitionSpec{{SpecID: 0, Fields: v1.PartitionSpecV1}} } - raw := map[string]json.RawMessage{} - _ = json.Unmarshal(b, &raw) + raw := map[string]jsontext.Value{} + _ = json.Unmarshal(b, &raw, jsonx.V1()) for _, k := range modeledKeys() { delete(raw, k) @@ -174,28 +177,28 @@ func (m *TableMetadata) UnmarshalJSON(b []byte) error { func (m *TableMetadata) MarshalJSON() ([]byte, error) { out := maps.Clone(m.extra) if out == nil { - out = map[string]json.RawMessage{} + out = map[string]jsontext.Value{} } - typed, err := json.Marshal(metadataAlias(*m)) + typed, err := json.Marshal(metadataAlias(*m), jsonx.V1()) if err != nil { return nil, err } - var fields map[string]json.RawMessage - if err = json.Unmarshal(typed, &fields); err != nil { + var fields map[string]jsontext.Value + if err = json.Unmarshal(typed, &fields, jsonx.V1()); err != nil { return nil, err } maps.Copy(out, fields) - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } // ParseMetadata decodes and validates a metadata.json document. func ParseMetadata(data []byte) (*TableMetadata, error) { var m TableMetadata - if err := json.Unmarshal(data, &m); err != nil { + if err := json.Unmarshal(data, &m, jsonx.V1()); err != nil { return nil, err } @@ -310,7 +313,7 @@ type NewParams struct { Now time.Time Properties map[string]string Location string - SortOrder json.RawMessage + SortOrder jsontext.Value Schema Schema Spec PartitionSpec } @@ -333,7 +336,7 @@ func NewTableMetadata(p NewParams) *TableMetadata { order := p.SortOrder if order == nil { - order = json.RawMessage(`{"order-id":0,"fields":[]}`) + order = jsontext.Value(`{"order-id":0,"fields":[]}`) } props := map[string]string{} @@ -349,7 +352,7 @@ func NewTableMetadata(p NewParams) *TableMetadata { DefaultSpecID: p.Spec.SpecID, PartitionSpecs: []PartitionSpec{p.Spec}, LastPartitionID: lastPart, - SortOrders: []json.RawMessage{order}, + SortOrders: []jsontext.Value{order}, Properties: props, CurrentSnapshotID: NoSnapshot, Snapshots: []Snapshot{}, @@ -359,12 +362,12 @@ func NewTableMetadata(p NewParams) *TableMetadata { } } -func sortOrderID(order json.RawMessage) int { +func sortOrderID(order jsontext.Value) int { var o struct { ID int `json:"order-id"` } - _ = json.Unmarshal(order, &o) + _ = json.Unmarshal(order, &o, jsonx.V1()) return o.ID } diff --git a/pkgs/iceberg/types.go b/pkgs/iceberg/types.go index 0cb5bdded9..8d5565a26a 100644 --- a/pkgs/iceberg/types.go +++ b/pkgs/iceberg/types.go @@ -1,39 +1,42 @@ package iceberg import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrUnsupportedType is returned for a type that cannot be converted or written. var ErrUnsupportedType = errors.New("unsupported iceberg type") type nestedType struct { - Element json.RawMessage `json:"element"` - Key json.RawMessage `json:"key"` - Value json.RawMessage `json:"value"` - Type string `json:"type"` - Fields []Field `json:"fields"` - ElementID int `json:"element-id"` - KeyID int `json:"key-id"` - ValueID int `json:"value-id"` - ElementNeeded bool `json:"element-required"` - ValueNeeded bool `json:"value-required"` + Element jsontext.Value `json:"element"` + Key jsontext.Value `json:"key"` + Value jsontext.Value `json:"value"` + Type string `json:"type"` + Fields []Field `json:"fields"` + ElementID int `json:"element-id"` + KeyID int `json:"key-id"` + ValueID int `json:"value-id"` + ElementNeeded bool `json:"element-required"` + ValueNeeded bool `json:"value-required"` } // HiveType renders an Iceberg type as the Athena/Hive type name used in the // Glue catalog (long -> bigint, list -> array<..>, struct -> struct). -func HiveType(raw json.RawMessage) (string, error) { +func HiveType(raw jsontext.Value) (string, error) { var prim string - if err := json.Unmarshal(raw, &prim); err == nil { + if err := json.Unmarshal(raw, &prim, jsonx.V1()); err == nil { return hivePrimitive(prim) } var n nestedType - if err := json.Unmarshal(raw, &n); err != nil { + if err := json.Unmarshal(raw, &n, jsonx.V1()); err != nil { return "", fmt.Errorf("%w: %s", ErrUnsupportedType, raw) } @@ -105,9 +108,9 @@ func hivePrimitive(p string) (string, error) { } // PrimitiveName returns the primitive type name of a field type ("" for nested types). -func PrimitiveName(raw json.RawMessage) string { +func PrimitiveName(raw jsontext.Value) string { var prim string - if err := json.Unmarshal(raw, &prim); err != nil { + if err := json.Unmarshal(raw, &prim, jsonx.V1()); err != nil { return "" } @@ -176,7 +179,7 @@ func SchemaFromColumns(cols []ColumnSpec) (Schema, error) { return Schema{}, err } - raw, _ := json.Marshal(t) + raw, _ := json.Marshal(t, jsonx.V1()) fields = append(fields, Field{ID: i + 1, Name: c.Name, Type: raw, Doc: c.Comment}) } diff --git a/pkgs/idempotency/idempotency.go b/pkgs/idempotency/idempotency.go index 97cc663498..9d755b4792 100644 --- a/pkgs/idempotency/idempotency.go +++ b/pkgs/idempotency/idempotency.go @@ -3,11 +3,12 @@ package idempotency import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -64,7 +65,7 @@ func New(name string, opts ...Option) *Memo { // Fingerprint renders request parameters for comparison across retries. func Fingerprint(v any) string { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Sprintf("%+v", v) } diff --git a/pkgs/jsonx/jsonx.go b/pkgs/jsonx/jsonx.go new file mode 100644 index 0000000000..b8fbc13ca0 --- /dev/null +++ b/pkgs/jsonx/jsonx.go @@ -0,0 +1,222 @@ +// Package jsonx holds the shared option sets for encoding/json/v2 and the few +// helpers that have no 1:1 v2 equivalent. +// +// Every call site uses encoding/json/v2 and encoding/json/jsontext directly; +// V1 is passed so a mechanical swap from encoding/json keeps byte-identical +// wire output and error text (nil slices and maps as null, HTML escaping, +// case-insensitive member matching, tolerated duplicate names and invalid +// UTF-8, v1 error messages). Drop V1 per call only where the v2 default is +// wanted and verified. +// +// This is the only package allowed to import the v1 encoding/json package: +// DefaultOptionsV1 and the legacy-semantics options are exported from there. +// +// v1 to v2 mapping: +// +// json.Marshal(v) json.Marshal(v, jsonx.V1()) +// json.Unmarshal(b, &v) json.Unmarshal(b, &v, jsonx.V1()) +// json.MarshalIndent(v, p, i) json.Marshal(v, jsonx.V1(), jsontext.Multiline(true), +// jsontext.WithIndentPrefix(p), jsontext.WithIndent(i)) +// json.NewDecoder(r).Decode(&v) json.UnmarshalDecode(jsontext.NewDecoder(r), &v, jsonx.V1()) +// json.NewEncoder(w).Encode(v) json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), v) +// enc.SetIndent / SetEscapeHTML jsontext.Multiline, WithIndent, EscapeForHTML on NewEncoder +// dec.DisallowUnknownFields() json.RejectUnknownMembers(true) +// dec.UseNumber(), json.Number jsonx.UseNumber(), jsonx.Number +// json.RawMessage jsontext.Value +// dec.Token() dec.ReadToken() (Kind: '{' '[' '"' '0' 't' 'f' 'n') +// dec.More() inside a container dec.PeekKind() not '}' or ']'; at top level +// compare InputOffset against the input (PeekKind()!=0 accepts garbage) +// dec.Buffered() bytes.NewReader(dec.UnreadBuffer()) +// json.Delim jsontext.Kind +// json.Valid(b) jsontext.Value(b).IsValid(jsonx.V1()) +// json.Compact / Indent jsontext.Value.Compact / Indent +// json.Marshaler, Unmarshaler same names in v2 (MarshalJSON / UnmarshalJSON still honored) +// *json.SyntaxError jsonx.SyntaxError / IsSyntaxError +// *json.UnmarshalTypeError jsonx.TypeError / IsTypeError +// +// Error types: with V1 the v2 engine returns the v1 types (*json.SyntaxError, +// *json.UnmarshalTypeError), not jsontext.SyntacticError or json.SemanticError, +// so errors.As on the v2 types silently misses and turns client errors into +// 500s. Always classify with SyntaxError, IsSyntaxError, TypeError and +// IsTypeError, which accept both families. +// +// A jsontext.Token is invalidated by the next decoder call; copy String() +// before reading on. Whitespace options (Multiline, WithIndent) belong on +// jsontext.NewEncoder, not on MarshalEncode. SDKDecoder returns a v1 Decoder +// for SDK helpers that require one. +// +// Use UnmarshalRead instead of UnmarshalDecode only when trailing data after +// the first value must be an error; v1 Decoder.Decode ignored it. +package jsonx + +import ( + jsonv1 "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +var ( + errInvalidNumber = errors.New("json: invalid number literal") + errNotNumber = errors.New("json: cannot unmarshal into jsonx.Number") +) + +// V1 is the full option set that reproduces encoding/json v1 behavior. +func V1() json.Options { + return jsonv1.DefaultOptionsV1() +} + +// SyntaxError reports whether err is a JSON syntax error and its byte offset. +// With V1 options the v2 engine returns the v1 error types, so both are accepted. +func SyntaxError(err error) (int64, bool) { + if se, ok := errors.AsType[*jsonv1.SyntaxError](err); ok { + return se.Offset, true + } + + if se, ok := errors.AsType[*jsontext.SyntacticError](err); ok { + return se.ByteOffset, true + } + + return 0, false +} + +// IsSyntaxError reports whether err is a JSON syntax error. +func IsSyntaxError(err error) bool { + _, ok := SyntaxError(err) + + return ok +} + +// TypeError reports whether err is a JSON value/type mismatch and the dotted +// path of the offending member (the v1 UnmarshalTypeError.Field form). +func TypeError(err error) (string, bool) { + if te, ok := errors.AsType[*jsonv1.UnmarshalTypeError](err); ok { + return te.Field, true + } + + if se, ok := errors.AsType[*json.SemanticError](err); ok { + return strings.ReplaceAll(strings.TrimPrefix(string(se.JSONPointer), "/"), "/", "."), true + } + + return "", false +} + +// IsTypeError reports whether err is a JSON value/type mismatch. +func IsTypeError(err error) bool { + _, ok := TypeError(err) + + return ok +} + +// SDKDecoder returns a v1 decoder for AWS SDK helpers that still take +// *encoding/json.Decoder (e.g. restjson.GetErrorInfo). +func SDKDecoder(r io.Reader) *jsonv1.Decoder { + return jsonv1.NewDecoder(r) +} + +// Number is a JSON number kept in its textual form, the v2 stand-in for +// v1 json.Number. It is produced for untyped numbers when UseNumber is set. +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { return strconv.ParseFloat(string(n), 64) } + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { return strconv.ParseInt(string(n), 10, 64) } + +// Int returns the number as an int, failing when it is out of int range. +func (n Number) Int() (int, error) { return strconv.Atoi(string(n)) } + +// MarshalJSON emits the number literal; an empty Number is 0 like v1. +func (n Number) MarshalJSON() ([]byte, error) { + if n == "" { + return []byte("0"), nil + } + + if !jsontext.Value(n).IsValid() || jsontext.Value(n).Kind() != '0' { + return nil, fmt.Errorf("%w %q", errInvalidNumber, string(n)) + } + + return []byte(n), nil +} + +// UnmarshalJSON accepts a JSON number, or a string holding a valid number as v1 did. +func (n *Number) UnmarshalJSON(b []byte) error { + v := jsontext.Value(b) + + switch v.Kind() { + case '0': + *n = Number(b) + case '"': + s, err := jsontext.AppendUnquote(nil, b) + if err != nil { + return err + } + + if lit := jsontext.Value(s); !lit.IsValid() || lit.Kind() != '0' { + return fmt.Errorf("%w %q", errInvalidNumber, string(s)) + } + + *n = Number(s) + case 'n': + default: + return fmt.Errorf("%w: %s", errNotNumber, v.Kind()) + } + + return nil +} + +// UseNumber makes every untyped (any) JSON number decode as Number instead of +// float64, replacing v1 Decoder.UseNumber. +func UseNumber() json.Options { + return json.WithUnmarshalers(json.UnmarshalFromFunc(unmarshalAnyNumber)) +} + +func unmarshalAnyNumber(dec *jsontext.Decoder, v *any) error { + switch dec.PeekKind() { + case '0': + val, err := dec.ReadValue() + if err != nil { + return err + } + + *v = Number(val) + case '{': + var m map[string]any + if err := json.UnmarshalDecode(dec, &m, V1(), UseNumber()); err != nil { + return err + } + + *v = m + case '[': + var s []any + if err := json.UnmarshalDecode(dec, &s, V1(), UseNumber()); err != nil { + return err + } + + *v = s + default: + tok, err := dec.ReadToken() + if err != nil { + return err + } + + switch tok.Kind() { + case 'n': + *v = nil + case 't', 'f': + *v = tok.Bool() + default: + *v = tok.String() + } + } + + return nil +} diff --git a/pkgs/jsonx/jsonx_test.go b/pkgs/jsonx/jsonx_test.go new file mode 100644 index 0000000000..8393123ace --- /dev/null +++ b/pkgs/jsonx/jsonx_test.go @@ -0,0 +1,192 @@ +package jsonx_test + +import ( + "encoding/json/jsontext" + "encoding/json/v2" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) + +func TestV1Semantics(t *testing.T) { + t.Parallel() + + type in struct { + H string `json:"h"` + M map[string]int `json:"m"` + S []int `json:"s"` + } + + b, err := json.Marshal(in{H: "<&>"}, jsonx.V1()) + require.NoError(t, err) + assert.JSONEq(t, `{"s":null,"m":null,"h":"<&>"}`, string(b)) + assert.Contains(t, string(b), "\\u003c") + + var out in + + require.NoError(t, json.Unmarshal([]byte(`{"S":[1],"H":"x","dup":1,"dup":2}`), &out, jsonx.V1())) + assert.Equal(t, []int{1}, out.S) + assert.Equal(t, "x", out.H) + + err = json.Unmarshal([]byte(`{"s":`), &out, jsonx.V1()) + require.EqualError(t, err, "unexpected end of JSON input") +} + +func TestUseNumber(t *testing.T) { + t.Parallel() + + var got any + + doc := `{"a":1.50,"b":[2,"x",true,null,{"c":3}],"d":"s"}` + err := json.Unmarshal([]byte(doc), &got, jsonx.V1(), jsonx.UseNumber()) + require.NoError(t, err) + + m, ok := got.(map[string]any) + require.True(t, ok) + assert.Equal(t, jsonx.Number("1.50"), m["a"]) + assert.Equal(t, "s", m["d"]) + assert.Equal(t, []any{jsonx.Number("2"), "x", true, nil, map[string]any{"c": jsonx.Number("3")}}, m["b"]) + + var typed struct{ N int } + + require.NoError(t, json.Unmarshal([]byte(`{"N":7}`), &typed, jsonx.V1(), jsonx.UseNumber())) + assert.Equal(t, 7, typed.N) +} + +func TestNumber(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in string + want string + wantErr bool + }{ + {name: "number", in: `12.5`, want: "12.5"}, + {name: "quoted number", in: `"7"`, want: "7"}, + {name: "quoted junk", in: `"x"`, wantErr: true}, + {name: "bool", in: `true`, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var n jsonx.Number + + err := json.Unmarshal([]byte(tt.in), &n, jsonx.V1()) + if tt.wantErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, n.String()) + + out, err := json.Marshal(n, jsonx.V1()) + require.NoError(t, err) + assert.Equal(t, tt.want, string(out)) + }) + } + + out, err := json.Marshal(jsonx.Number(""), jsonx.V1()) + require.NoError(t, err) + assert.Equal(t, "0", string(out)) +} + +func TestDecoderStreamTolerantOfTrailingData(t *testing.T) { + t.Parallel() + + var v map[string]int + + dec := jsontext.NewDecoder(strings.NewReader(`{"a":1} trailing`)) + require.NoError(t, json.UnmarshalDecode(dec, &v, jsonx.V1())) + assert.Equal(t, map[string]int{"a": 1}, v) +} + +func TestErrorClassifiers(t *testing.T) { + t.Parallel() + + type doc struct { + A struct{ B int } `json:"a"` + } + + tests := []struct { + name string + body string + field string + wantSyn bool + wantType bool + wantNoErr bool + }{ + {name: "garbage", body: `not json`, wantSyn: true}, + {name: "truncated", body: `{"a":`, wantSyn: true}, + {name: "type_mismatch", body: `{"a":{"B":"x"}}`, wantType: true, field: "a.B"}, + {name: "ok", body: `{"a":{"B":1}}`, wantNoErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var d doc + + err := json.Unmarshal([]byte(tt.body), &d, jsonx.V1()) + if tt.wantNoErr { + require.NoError(t, err) + + return + } + + require.Error(t, err) + assert.Equal(t, tt.wantSyn, jsonx.IsSyntaxError(err)) + assert.Equal(t, tt.wantType, jsonx.IsTypeError(err)) + + if tt.wantType { + field, _ := jsonx.TypeError(err) + assert.Equal(t, tt.field, field) + } + }) + } + + _, ok := jsonx.SyntaxError(json.Unmarshal([]byte(`{"a":1}x`), new(doc), jsonx.V1())) + assert.True(t, ok) +} + +func TestNumberInt(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in jsonx.Number + want int + wantErr bool + }{ + {name: "positive", in: "42", want: 42}, + {name: "negative", in: "-7", want: -7}, + {name: "fraction", in: "1.5", wantErr: true}, + {name: "overflow", in: "99999999999999999999", wantErr: true}, + {name: "empty", in: "", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := tt.in.Int() + if tt.wantErr { + require.Error(t, err) + + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkgs/jsruntime/convert.go b/pkgs/jsruntime/convert.go index 0fb0f7be5c..1ffb658642 100644 --- a/pkgs/jsruntime/convert.go +++ b/pkgs/jsruntime/convert.go @@ -1,11 +1,13 @@ package jsruntime import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "github.com/dop251/goja" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrNotJSON means a value could not be converted to or from JSON. @@ -13,7 +15,7 @@ var ErrNotJSON = errors.New("value is not JSON-serializable") // ToJS builds a fresh JS value from plain Go data via JSON.parse. func (s *Sandbox) ToJS(v any) (goja.Value, error) { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: %w", ErrNotJSON, err) } @@ -56,7 +58,7 @@ func (s *Sandbox) FromJS(v goja.Value) (any, error) { } var out any - if err = json.Unmarshal([]byte(str), &out); err != nil { + if err = json.Unmarshal([]byte(str), &out, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrNotJSON, err) } diff --git a/pkgs/ociregistry/client.go b/pkgs/ociregistry/client.go index 1acc9e8f7f..c2a15a2d2f 100644 --- a/pkgs/ociregistry/client.go +++ b/pkgs/ociregistry/client.go @@ -3,13 +3,15 @@ package ociregistry import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" "net/http" "net/url" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrNotFound is returned when a repository, manifest or blob does not exist. @@ -140,7 +142,7 @@ func (c Client) pages(ctx context.Context, base string, pick func(listResponse) } var lr listResponse - if err = json.Unmarshal(rec.body.Bytes(), &lr); err != nil { + if err = json.Unmarshal(rec.body.Bytes(), &lr, jsonx.V1()); err != nil { return nil, fmt.Errorf("ociregistry: decode list: %w", err) } diff --git a/pkgs/ociregistry/describe.go b/pkgs/ociregistry/describe.go index 34ef8ad382..4cfaeae79c 100644 --- a/pkgs/ociregistry/describe.go +++ b/pkgs/ociregistry/describe.go @@ -2,8 +2,10 @@ package ociregistry import ( "context" - "encoding/json" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxConfigBytes = 1 << 20 @@ -44,7 +46,7 @@ func (c Client) Describe(ctx context.Context, repo string, m Manifest) Info { info := Info{Digest: m.Digest, MediaType: m.MediaType} var doc manifestDoc - if json.Unmarshal(m.Body, &doc) != nil { + if json.Unmarshal(m.Body, &doc, jsonx.V1()) != nil { return info } @@ -85,7 +87,7 @@ func (c Client) platform(ctx context.Context, repo, configDigest string) (string OS string `json:"os"` } - if json.Unmarshal(raw, &cfg) != nil { + if json.Unmarshal(raw, &cfg, jsonx.V1()) != nil { return "", "" } diff --git a/pkgs/ociregistry/ocitest/ocitest.go b/pkgs/ociregistry/ocitest/ocitest.go index 48bdc3f699..e0a7df4eb1 100644 --- a/pkgs/ociregistry/ocitest/ocitest.go +++ b/pkgs/ociregistry/ocitest/ocitest.go @@ -6,7 +6,8 @@ import ( "bytes" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -20,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -174,7 +176,7 @@ func (c Client) PushImage(repo, tag string) Image { cfg, err := json.Marshal(map[string]any{ "architecture": "amd64", "os": "linux", "rootfs": map[string]any{"type": "layers", "diff_ids": []string{layerDigest}}, - }) + }, jsonx.V1()) require.NoError(c.T, err) cfgDigest := c.PushBlob(repo, cfg, configChunks) @@ -188,7 +190,7 @@ func (c Client) PushImage(repo, tag string) Image { keyMediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": len(layer), "digest": layerDigest, }}, - }) + }, jsonx.V1()) require.NoError(c.T, err) put := c.Do( @@ -215,11 +217,11 @@ func (c Client) Names(path, field string, n int) []string { resp := c.Do(http.MethodGet, next, nil, nil) require.Equal(c.T, http.StatusOK, resp.Status, string(resp.Body)) - var doc map[string]json.RawMessage - require.NoError(c.T, json.Unmarshal(resp.Body, &doc)) + var doc map[string]jsontext.Value + require.NoError(c.T, json.Unmarshal(resp.Body, &doc, jsonx.V1())) var page []string - require.NoError(c.T, json.Unmarshal(doc[field], &page)) + require.NoError(c.T, json.Unmarshal(doc[field], &page, jsonx.V1())) all = append(all, page...) diff --git a/pkgs/odatatable/batchwire.go b/pkgs/odatatable/batchwire.go index 267c8e62a0..d0be53c09c 100644 --- a/pkgs/odatatable/batchwire.go +++ b/pkgs/odatatable/batchwire.go @@ -2,7 +2,7 @@ package odatatable import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -12,6 +12,8 @@ import ( "net/url" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -456,5 +458,5 @@ func FailureBody(index int, f BatchFailure, requestID, timestamp string) ([]byte env.Error.Message.Lang = "en-US" env.Error.Message.Value = fmt.Sprintf("%d:%s\nRequestId:%s\nTime:%s", index, f.Message, requestID, timestamp) - return json.Marshal(env) + return json.Marshal(env, jsonx.V1()) } diff --git a/pkgs/odatatable/codec.go b/pkgs/odatatable/codec.go index c5bd574d8d..93f5c15b67 100644 --- a/pkgs/odatatable/codec.go +++ b/pkgs/odatatable/codec.go @@ -2,12 +2,15 @@ package odatatable import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // OData metadata level names, negotiated by each caller from its own @@ -66,9 +69,9 @@ func ParseTop(raw string) (int, error) { // supplied value never overwrites it -- the server always wins). Any // "odata.*"/"@odata.type" metadata key is skipped. func DecodeEntityBody(body []byte) (string, string, bool, bool, map[string]EntityProperty, error) { - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value - if err := json.Unmarshal(body, &raw); err != nil { + if err := json.Unmarshal(body, &raw, jsonx.V1()); err != nil { return "", "", false, false, nil, fmt.Errorf("%w: %w", ErrInvalidEntityProperty, err) } @@ -92,14 +95,14 @@ func DecodeEntityBody(body []byte) (string, string, bool, bool, map[string]Entit // decodeSystemKeyProperty extracts the string-valued system property name // (PartitionKey or RowKey) from raw, if present. -func decodeSystemKeyProperty(raw map[string]json.RawMessage, name string) (string, bool, error) { +func decodeSystemKeyProperty(raw map[string]jsontext.Value, name string) (string, bool, error) { rawVal, ok := raw[name] if !ok { return "", false, nil } var s string - if err := json.Unmarshal(rawVal, &s); err != nil { + if err := json.Unmarshal(rawVal, &s, jsonx.V1()); err != nil { return "", false, fmt.Errorf("%w: %s must be a string", ErrInvalidEntityProperty, name) } @@ -125,7 +128,7 @@ func isSystemOrMetadataKey(key string) bool { // decodeCustomProperties decodes every user-defined (non-system, // non-metadata) property in raw into its typed EntityProperty. -func decodeCustomProperties(raw map[string]json.RawMessage) (map[string]EntityProperty, error) { +func decodeCustomProperties(raw map[string]jsontext.Value) (map[string]EntityProperty, error) { props := make(map[string]EntityProperty, len(raw)) for key, rawVal := range raw { @@ -136,7 +139,7 @@ func decodeCustomProperties(raw map[string]json.RawMessage) (map[string]EntityPr edmType := "" if annRaw, ok := raw[key+"@odata.type"]; ok { - if err := json.Unmarshal(annRaw, &edmType); err != nil { + if err := json.Unmarshal(annRaw, &edmType, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: malformed %s@odata.type", ErrInvalidEntityProperty, key) } } @@ -154,7 +157,7 @@ func decodeCustomProperties(raw map[string]json.RawMessage) (map[string]EntityPr // decodeProperty decodes raw into a typed EntityProperty per its (possibly // empty) "@odata.type" annotation value edmType. -func decodeProperty(edmType string, raw json.RawMessage) (EntityProperty, error) { +func decodeProperty(edmType string, raw jsontext.Value) (EntityProperty, error) { switch EdmType(edmType) { case EdmString, EdmInt32, EdmDouble, EdmBoolean: return decodeScalarProperty(EdmType(edmType), raw) @@ -169,32 +172,32 @@ func decodeProperty(edmType string, raw json.RawMessage) (EntityProperty, error) // decodeScalarProperty decodes the four EDM types whose wire representation // is a bare, natively-typed JSON value (string/number/number/bool). -func decodeScalarProperty(edmType EdmType, raw json.RawMessage) (EntityProperty, error) { +func decodeScalarProperty(edmType EdmType, raw jsontext.Value) (EntityProperty, error) { switch edmType { case EdmString: var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return EntityProperty{}, fmt.Errorf("%w: not a string", ErrInvalidEntityProperty) } return EntityProperty{Type: EdmString, Value: s}, nil case EdmInt32: var n int32 - if err := json.Unmarshal(raw, &n); err != nil { + if err := json.Unmarshal(raw, &n, jsonx.V1()); err != nil { return EntityProperty{}, fmt.Errorf("%w: not an Int32", ErrInvalidEntityProperty) } return EntityProperty{Type: EdmInt32, Value: n}, nil case EdmDouble: var f float64 - if err := json.Unmarshal(raw, &f); err != nil { + if err := json.Unmarshal(raw, &f, jsonx.V1()); err != nil { return EntityProperty{}, fmt.Errorf("%w: not a Double", ErrInvalidEntityProperty) } return EntityProperty{Type: EdmDouble, Value: f}, nil case EdmBoolean: var b bool - if err := json.Unmarshal(raw, &b); err != nil { + if err := json.Unmarshal(raw, &b, jsonx.V1()); err != nil { return EntityProperty{}, fmt.Errorf("%w: not a Boolean", ErrInvalidEntityProperty) } @@ -208,9 +211,9 @@ func decodeScalarProperty(edmType EdmType, raw json.RawMessage) (EntityProperty, // representation is always a JSON string carrying an encoded value (decimal // digits, an RFC3339 timestamp, a UUID, or base64), requiring the // "@odata.type" annotation to disambiguate from Edm.String. -func decodeStringEncodedProperty(edmType EdmType, raw json.RawMessage) (EntityProperty, error) { +func decodeStringEncodedProperty(edmType EdmType, raw jsontext.Value) (EntityProperty, error) { var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return EntityProperty{}, fmt.Errorf("%w: %s must be a string", ErrInvalidEntityProperty, edmType) } @@ -247,24 +250,24 @@ func decodeStringEncodedProperty(edmType EdmType, raw json.RawMessage) (EntityPr // mirroring aztables' own client-side inference exactly: try Int32 first // (so a decimal-point-free number becomes Edm.Int32), then fall back to the // JSON value's natural Go type. -func decodeUnannotatedProperty(raw json.RawMessage) (EntityProperty, error) { +func decodeUnannotatedProperty(raw jsontext.Value) (EntityProperty, error) { var i32 int32 - if err := json.Unmarshal(raw, &i32); err == nil { + if err := json.Unmarshal(raw, &i32, jsonx.V1()); err == nil { return EntityProperty{Type: EdmInt32, Value: i32}, nil } var f float64 - if err := json.Unmarshal(raw, &f); err == nil { + if err := json.Unmarshal(raw, &f, jsonx.V1()); err == nil { return EntityProperty{Type: EdmDouble, Value: f}, nil } var b bool - if err := json.Unmarshal(raw, &b); err == nil { + if err := json.Unmarshal(raw, &b, jsonx.V1()); err == nil { return EntityProperty{Type: EdmBoolean, Value: b}, nil } var s string - if err := json.Unmarshal(raw, &s); err == nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err == nil { return EntityProperty{Type: EdmString, Value: s}, nil } diff --git a/pkgs/odatatable/models.go b/pkgs/odatatable/models.go index 88040ae50d..99dfea3fd9 100644 --- a/pkgs/odatatable/models.go +++ b/pkgs/odatatable/models.go @@ -19,12 +19,14 @@ package odatatable import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "math" "strconv" "sync/atomic" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // System entity property names, shared across path-predicate parsing @@ -106,7 +108,7 @@ func (p EntityProperty) MarshalJSON() ([]byte, error) { return nil, err } - return json.Marshal(entityPropertyWire{Type: p.Type, Value: v}) + return json.Marshal(entityPropertyWire{Type: p.Type, Value: v}, jsonx.V1()) } // marshalValue dispatches to marshalScalarValue or marshalStringEncodedValue @@ -219,7 +221,7 @@ func (p EntityProperty) marshalStringEncodedValue() (any, error) { func (p *EntityProperty) UnmarshalJSON(data []byte) error { var wire entityPropertyWire - if err := json.Unmarshal(data, &wire); err != nil { + if err := json.Unmarshal(data, &wire, jsonx.V1()); err != nil { return err } @@ -428,7 +430,7 @@ const entityCompositeKeyElemCount = 2 // encoding can't reintroduce the same collision class the struct key itself // was introduced to close. func (k entityCompositeKey) MarshalText() ([]byte, error) { - return json.Marshal([entityCompositeKeyElemCount]string{k.PartitionKey, k.RowKey}) + return json.Marshal([entityCompositeKeyElemCount]string{k.PartitionKey, k.RowKey}, jsonx.V1()) } // UnmarshalText implements encoding.TextUnmarshaler, the inverse of @@ -442,7 +444,7 @@ func (k *entityCompositeKey) UnmarshalText(text []byte) error { // two elements". var pair []string - if err := json.Unmarshal(text, &pair); err != nil { + if err := json.Unmarshal(text, &pair, jsonx.V1()); err != nil { return fmt.Errorf("odatatable: malformed entity composite key %q: %w", text, err) } diff --git a/pkgs/persistence/manager_test.go b/pkgs/persistence/manager_test.go index 3506b97446..eca03bf1af 100644 --- a/pkgs/persistence/manager_test.go +++ b/pkgs/persistence/manager_test.go @@ -2,7 +2,7 @@ package persistence_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "sync" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -821,7 +822,7 @@ func (s *statefulPersistable) Restore(_ context.Context, data []byte) error { State string `json:"state"` } - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &v, jsonx.V1()); err != nil { return err } diff --git a/pkgs/persistence/modes_test.go b/pkgs/persistence/modes_test.go index 9d3c241e8e..4463e18cea 100644 --- a/pkgs/persistence/modes_test.go +++ b/pkgs/persistence/modes_test.go @@ -2,7 +2,7 @@ package persistence_test import ( "context" - "encoding/json" + "encoding/json/v2" "path/filepath" "sync" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -40,7 +41,7 @@ func (c *counterSvc) Snapshot(context.Context) []byte { c.mu.Lock() defer c.mu.Unlock() - b, _ := json.Marshal(c.vals) + b, _ := json.Marshal(c.vals, jsonx.V1()) return b } @@ -49,7 +50,7 @@ func (c *counterSvc) Restore(_ context.Context, data []byte) error { c.mu.Lock() defer c.mu.Unlock() - return json.Unmarshal(data, &c.vals) + return json.Unmarshal(data, &c.vals, jsonx.V1()) } func newMgr( diff --git a/pkgs/persistence/snapshot.go b/pkgs/persistence/snapshot.go index aa56ce56c9..968e7be6f9 100644 --- a/pkgs/persistence/snapshot.go +++ b/pkgs/persistence/snapshot.go @@ -2,8 +2,9 @@ package persistence import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -15,7 +16,7 @@ import ( // persistence Manager). Centralising this here keeps every backend's Snapshot // method consistent and gives the ctx parameter a purpose. func MarshalSnapshot(ctx context.Context, service string, v any) []byte { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "persistence: snapshot marshal failed", @@ -37,7 +38,7 @@ func MarshalSnapshot(ctx context.Context, service string, v any) []byte { // the decode error. Centralising this here keeps every backend's Restore method // consistent and gives the ctx parameter a purpose. func UnmarshalSnapshot(ctx context.Context, service string, data []byte, v any) error { - if err := json.Unmarshal(data, v); err != nil { + if err := json.Unmarshal(data, v, jsonx.V1()); err != nil { logger.Load(ctx).WarnContext(ctx, "persistence: snapshot unmarshal failed", "service", service, "error", err) diff --git a/pkgs/persistence/snapshotversion_guard_test.go b/pkgs/persistence/snapshotversion_guard_test.go index 16adc151e8..7997b7e934 100644 --- a/pkgs/persistence/snapshotversion_guard_test.go +++ b/pkgs/persistence/snapshotversion_guard_test.go @@ -2,7 +2,8 @@ package persistence_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "flag" "fmt" @@ -20,6 +21,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errNoVersionStruct = errors.New("no struct with an int-typed Version field") @@ -480,7 +483,7 @@ func loadGolden(t *testing.T) map[string]snapshotInfo { var golden map[string]snapshotInfo - require.NoError(t, json.Unmarshal(data, &golden)) + require.NoError(t, json.Unmarshal(data, &golden, jsonx.V1())) return golden } @@ -488,7 +491,7 @@ func loadGolden(t *testing.T) map[string]snapshotInfo { func writeGolden(t *testing.T, live map[string]snapshotInfo) { t.Helper() - data, err := json.MarshalIndent(live, "", " ") + data, err := json.Marshal(live, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) require.NoError(t, err) require.NoError(t, os.WriteFile(goldenFile, append(data, '\n'), 0o644)) diff --git a/pkgs/persistence/store_test.go b/pkgs/persistence/store_test.go index 0c3a091fc4..f3361ea76f 100644 --- a/pkgs/persistence/store_test.go +++ b/pkgs/persistence/store_test.go @@ -1,7 +1,7 @@ package persistence_test import ( - "encoding/json" + "encoding/json/v2" "os" "path/filepath" "sync" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -220,7 +221,7 @@ func TestFileStore_ConcurrentAccess(t *testing.T) { go func(i int) { defer wg.Done() - data, _ := json.Marshal(map[string]int{"i": i}) + data, _ := json.Marshal(map[string]int{"i": i}, jsonx.V1()) if err := s.Save("concurrent", "snap", data); err != nil { errs <- err } @@ -239,7 +240,7 @@ func TestFileStore_ConcurrentAccess(t *testing.T) { require.NoError(t, err) var out map[string]int - require.NoError(t, json.Unmarshal(data, &out)) + require.NoError(t, json.Unmarshal(data, &out, jsonx.V1())) assert.Contains(t, out, "i") } diff --git a/pkgs/persistence/testdata/snapshot_inventory.json b/pkgs/persistence/testdata/snapshot_inventory.json index 298ca89f5d..6ed03a42f5 100644 --- a/pkgs/persistence/testdata/snapshot_inventory.json +++ b/pkgs/persistence/testdata/snapshot_inventory.json @@ -2,7 +2,7 @@ "accessanalyzer": { "fields": [ "AccessPreview.AnalyzerArn string", - "AccessPreview.Configurations map[string]json.RawMessage", + "AccessPreview.Configurations map[string]jsontext.Value", "AccessPreview.CreatedAt time.Time", "AccessPreview.ID string", "AccessPreview.Status AccessPreviewStatus", @@ -14,7 +14,7 @@ "AnalyzedResource.ResourceType string", "AnalyzedResource.UpdatedAt time.Time", "Analyzer.Arn string `json:\"arn\"`", - "Analyzer.Configuration json.RawMessage `json:\"configuration,omitempty\"`", + "Analyzer.Configuration jsontext.Value `json:\"configuration,omitempty\"`", "Analyzer.CreatedAt time.Time `json:\"createdAt\"`", "Analyzer.LastResourceAnalyzed string `json:\"lastResourceAnalyzed,omitempty\"`", "Analyzer.LastResourceAnalyzedAt *time.Time `json:\"lastResourceAnalyzedAt,omitempty\"`", @@ -69,7 +69,7 @@ "archiveRuleSnapshot.UpdatedAt time.Time `json:\"updatedAt\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -108,7 +108,7 @@ "backendSnapshot.PrimaryEmailUpdateStatus PrimaryEmailUpdateStatus `json:\"primaryEmailUpdateStatus,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Regions []*Region `json:\"regions\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 2 }, @@ -228,7 +228,7 @@ "backendSnapshot.EndpointIdempotency map[string]map[string]acmeIdempotencyEntry `json:\"endpointIdempotency,omitempty\"`", "backendSnapshot.IdempotencyMap map[string]map[string]certIdempotencyEntry `json:\"idempotencyMap,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "certIdempotencyEntry.ARN string `json:\"arn\"`", "certIdempotencyEntry.CreatedAt time.Time `json:\"createdAt\"`", "certTagsEntry.ResourceID string", @@ -361,7 +361,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Policies map[string]map[string]string `json:\"policies\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "caDTO.ARN string `json:\"arn\"`", "caDTO.PrivKeyPEM string `json:\"privKeyPEM,omitempty\"`", "caDTO.Region string `json:\"region\"`", @@ -479,7 +479,7 @@ "WorkspaceConfig.RuleQueryOffsetSet bool `json:\"ruleQueryOffsetSet,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -613,7 +613,7 @@ "Webhook.WebhookURL string `json:\"webhookUrl\"`", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -871,7 +871,7 @@ "authorizerSnapshot.RestAPIID string `json:\"restApiId\"`", "authorizerSnapshot.Type string `json:\"type\"`", "backendSnapshot.Account *Account `json:\"account,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UsageOverrides map[string]map[string]int64 `json:\"usageOverrides,omitempty\"`", "deploymentSnapshot.APISummary map[string]map[string]MethodSnapshot `json:\"apiSummary,omitempty\"`", "deploymentSnapshot.Config *DeploymentConfig `json:\"config,omitempty\"`", @@ -1212,7 +1212,7 @@ "authorizerSnapshot.JwtConfiguration *JwtConfiguration `json:\"jwtConfiguration,omitempty\"`", "authorizerSnapshot.Name string `json:\"name\"`", "backendSnapshot.PortalProductSharingPolicies map[string]string `json:\"portalProductSharingPolicies,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "deploymentSnapshot.APIID string `json:\"apiId\"`", "deploymentSnapshot.AutoDeployed bool `json:\"autoDeployed\"`", "deploymentSnapshot.CreatedDate isoTime `json:\"createdDate\"`", @@ -1488,7 +1488,7 @@ "backendSnapshot.ExperimentRunEvents map[string][]ExperimentRunEvent `json:\"experimentRunEvents\"`", "backendSnapshot.FetchedConfigs map[string]*HostedConfigurationVersion `json:\"fetchedConfigs,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TagsByArn map[string]map[string]string `json:\"tagsByArn\"`", "backendSnapshot.VersionCounters map[string]map[string]int32 `json:\"versionCounters\"`" ], @@ -1526,7 +1526,7 @@ "Session.PreviousContentHash string `json:\"previousContentHash\"`", "Session.Token string `json:\"token\"`", "Session.TokenFamilyID string `json:\"tokenFamilyId\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "graceEntry.Content []byte", "graceEntry.ContentHash string", "graceEntry.ContentType string", @@ -1621,7 +1621,7 @@ "Subscription.Event string `json:\"Event\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -1674,7 +1674,7 @@ "SuspendedState.ScheduledScalingSuspended bool `json:\"scheduledScalingSuspended\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -1683,12 +1683,12 @@ "GatewayRoute.GatewayRouteName string", "GatewayRoute.MeshName string", "GatewayRoute.Meta ResourceMeta", - "GatewayRoute.Spec json.RawMessage", + "GatewayRoute.Spec jsontext.Value", "GatewayRoute.Status string", "GatewayRoute.VirtualGatewayName string", "Mesh.Meta ResourceMeta", "Mesh.Name string", - "Mesh.Spec json.RawMessage", + "Mesh.Spec jsontext.Value", "Mesh.Status string", "ResourceMeta.Arn string", "ResourceMeta.CreatedAt time.Time", @@ -1699,32 +1699,32 @@ "Route.MeshName string", "Route.Meta ResourceMeta", "Route.RouteName string", - "Route.Spec json.RawMessage", + "Route.Spec jsontext.Value", "Route.Status string", "Route.VirtualRouterName string", "VirtualGateway.MeshName string", "VirtualGateway.Meta ResourceMeta", - "VirtualGateway.Spec json.RawMessage", + "VirtualGateway.Spec jsontext.Value", "VirtualGateway.Status string", "VirtualGateway.VirtualGatewayName string", "VirtualNode.MeshName string", "VirtualNode.Meta ResourceMeta", - "VirtualNode.Spec json.RawMessage", + "VirtualNode.Spec jsontext.Value", "VirtualNode.Status string", "VirtualNode.VirtualNodeName string", "VirtualRouter.MeshName string", "VirtualRouter.Meta ResourceMeta", - "VirtualRouter.Spec json.RawMessage", + "VirtualRouter.Spec jsontext.Value", "VirtualRouter.Status string", "VirtualRouter.VirtualRouterName string", "VirtualService.MeshName string", "VirtualService.Meta ResourceMeta", - "VirtualService.Spec json.RawMessage", + "VirtualService.Spec jsontext.Value", "VirtualService.Status string", "VirtualService.VirtualServiceName string", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -1773,7 +1773,7 @@ "SourceConfig.ConnectionArn string `json:\"connectionArn,omitempty\"`", "SourceConfig.ImageRepository *ImageSource `json:\"imageRepository,omitempty\"`", "backendSnapshot.CustomDomains map[string][]*storedCustomDomain `json:\"customDomains\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedAutoScalingConfiguration.AutoScalingConfigurationArn string `json:\"arn\"`", "storedAutoScalingConfiguration.AutoScalingConfigurationName string `json:\"name\"`", @@ -1902,7 +1902,7 @@ "backendSnapshot.ExportTaskSeq int `json:\"exportTaskSeq\"`", "backendSnapshot.SessionSeq int `json:\"sessionSeq\"`", "backendSnapshot.SoftwareAssoc map[string]map[string]bool `json:\"softwareAssoc\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "backendSnapshot.UsageReport *storedUsageReportSubscription `json:\"usageReport\"`", "backendSnapshot.UserStackAssoc map[string]map[string]bool `json:\"userStackAssoc\"`", @@ -2316,7 +2316,7 @@ "UserPoolConfig.DefaultAction string `json:\"defaultAction\"`", "UserPoolConfig.UserPoolID string `json:\"userPoolId\"`", "backendSnapshot.APIKeys map[string]map[string]*APIKey `json:\"apiKeys,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 2 }, @@ -2343,7 +2343,7 @@ "Shift.Resource string `json:\"resource\"`", "Shift.StartTime time.Time `json:\"startTime\"`", "Shift.Type string `json:\"type\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -2539,7 +2539,7 @@ "backendSnapshot.QueryResults map[string]*sqlResultSnapshot `json:\"queryResults,omitempty\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags,omitempty\"`", "backendSnapshot.TableData map[string][]map[string]any `json:\"tableData,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "databaseSnapshot.Catalog string `json:\"catalog\"`", "databaseSnapshot.Record Database `json:\"record\"`", "sqlColumnSnapshot.Name string `json:\"name\"`", @@ -2829,7 +2829,7 @@ "backendSnapshot.DeletedActivities []ScalingActivity `json:\"deletedActivities,omitempty\"`", "backendSnapshot.InstanceRefreshes map[string][]*InstanceRefresh `json:\"instanceRefreshes\"`", "backendSnapshot.NotificationConfigs map[string][]*NotificationConfiguration `json:\"notificationConfigs\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "pendingHookAction.DefaultResult string", "pendingHookAction.Disposition terminationDisposition", "pendingHookAction.GroupName string", @@ -3038,7 +3038,7 @@ "StoredQuery.QueryArn string `json:\"QueryArn,omitempty\"`", "StoredQuery.QueryID string `json:\"QueryId,omitempty\"`", "StoredQuery.QueryName string `json:\"QueryName\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "deliveryChannelStatusState.ChannelName string `json:\"channelName\"`", "deliveryChannelStatusState.Snapshot *exportDeliveryState `json:\"snapshot,omitempty\"`", "deliveryChannelStatusState.Stream *streamDeliveryState `json:\"stream,omitempty\"`", @@ -3122,7 +3122,7 @@ "aciContainerPropsWire.Command []string `json:\"command,omitempty\"`", "aciContainerPropsWire.EnvironmentVariables []aciEnvWire `json:\"environmentVariables,omitempty\"`", "aciContainerPropsWire.Image string `json:\"image\"`", - "aciContainerPropsWire.InstanceView json.RawMessage `json:\"instanceView,omitempty\"`", + "aciContainerPropsWire.InstanceView jsontext.Value `json:\"instanceView,omitempty\"`", "aciContainerPropsWire.LivenessProbe *aciProbeWire `json:\"livenessProbe,omitempty\"`", "aciContainerPropsWire.Ports []aciPortWire `json:\"ports,omitempty\"`", "aciContainerPropsWire.ReadinessProbe *aciProbeWire `json:\"readinessProbe,omitempty\"`", @@ -3158,7 +3158,7 @@ "aciInitPropsWire.Command []string `json:\"command,omitempty\"`", "aciInitPropsWire.EnvironmentVariables []aciEnvWire `json:\"environmentVariables,omitempty\"`", "aciInitPropsWire.Image string `json:\"image\"`", - "aciInitPropsWire.InstanceView json.RawMessage `json:\"instanceView,omitempty\"`", + "aciInitPropsWire.InstanceView jsontext.Value `json:\"instanceView,omitempty\"`", "aciInitPropsWire.VolumeMounts []aciMountWire `json:\"volumeMounts,omitempty\"`", "aciInitWire.Name string `json:\"name\"`", "aciInitWire.Properties aciInitPropsWire `json:\"properties\"`", @@ -3185,7 +3185,7 @@ "aciPropsWire.IPAddress *aciIPWire `json:\"ipAddress,omitempty\"`", "aciPropsWire.ImageRegistryCredentials []aciCredWire `json:\"imageRegistryCredentials,omitempty\"`", "aciPropsWire.InitContainers []aciInitWire `json:\"initContainers,omitempty\"`", - "aciPropsWire.InstanceView json.RawMessage `json:\"instanceView,omitempty\"`", + "aciPropsWire.InstanceView jsontext.Value `json:\"instanceView,omitempty\"`", "aciPropsWire.OSType string `json:\"osType,omitempty\"`", "aciPropsWire.Priority string `json:\"priority,omitempty\"`", "aciPropsWire.ProvisioningState string `json:\"provisioningState,omitempty\"`", @@ -3200,7 +3200,7 @@ "aciSubnetWire.ID string `json:\"id\"`", "aciSubnetWire.Name string `json:\"name,omitempty\"`", "aciVolumeWire.AzureFile *aciAzureFileWire `json:\"azureFile,omitempty\"`", - "aciVolumeWire.EmptyDir *json.RawMessage `json:\"emptyDir,omitempty\"`", + "aciVolumeWire.EmptyDir *jsontext.Value `json:\"emptyDir,omitempty\"`", "aciVolumeWire.GitRepo *aciGitRepoWire `json:\"gitRepo,omitempty\"`", "aciVolumeWire.Name string `json:\"name\"`", "aciVolumeWire.Secret map[string]string `json:\"secret,omitempty\"`", @@ -3216,8 +3216,8 @@ "acrRecord.ResourceGroup string `json:\"resourceGroup\"`", "acrRecord.SKU string `json:\"sku,omitempty\"`", "acrRecord.Tags map[string]string `json:\"tags,omitempty\"`", - "apimSnapshot.Operations json.RawMessage `json:\"operations,omitempty\"`", - "apimSnapshot.Services json.RawMessage `json:\"services,omitempty\"`", + "apimSnapshot.Operations jsontext.Value `json:\"operations,omitempty\"`", + "apimSnapshot.Services jsontext.Value `json:\"services,omitempty\"`", "appConfigDeletedRecord.DeletedAt time.Time `json:\"deletedAt\"`", "appConfigDeletedRecord.Location string `json:\"location\"`", "appConfigDeletedRecord.Name string `json:\"name\"`", @@ -3819,7 +3819,7 @@ "backendSnapshot.Topics map[string]*topic `json:\"topics\"`", "delivery.Attempts int `json:\"attempts\"`", "delivery.Due time.Time `json:\"due\"`", - "delivery.Event json.RawMessage `json:\"event\"`", + "delivery.Event jsontext.Value `json:\"event\"`", "delivery.ID string `json:\"id\"`", "delivery.InFlight bool `json:\"-\"`", "delivery.LastAttempt time.Time `json:\"lastAttempt\"`", @@ -3848,7 +3848,7 @@ "nsTopic.Subs map[string]*pullSub `json:\"subs\"`", "pullEvent.AvailableAt time.Time `json:\"availableAt\"`", "pullEvent.Count int `json:\"count\"`", - "pullEvent.Event json.RawMessage `json:\"event\"`", + "pullEvent.Event jsontext.Value `json:\"event\"`", "pullEvent.ExpiresAt time.Time `json:\"expiresAt\"`", "pullEvent.LockToken string `json:\"lockToken,omitempty\"`", "pullEvent.LockedUntil time.Time `json:\"lockedUntil\"`", @@ -4059,28 +4059,28 @@ "AppRoleAssignment.ResourceDisplayName string `json:\"resourceDisplayName\"`", "AppRoleAssignment.ResourceID string `json:\"resourceId\"`", "Application.API APIApplication `json:\"api\"`", - "Application.AddIns json.RawMessage `json:\"addIns,omitempty\"`", + "Application.AddIns jsontext.Value `json:\"addIns,omitempty\"`", "Application.AppID string `json:\"appId\"`", "Application.AppRoles []AppRole `json:\"appRoles\"`", "Application.ApplicationTemplateID string `json:\"applicationTemplateId,omitempty\"`", - "Application.AuthenticationBehaviors json.RawMessage `json:\"authenticationBehaviors,omitempty\"`", - "Application.Certification json.RawMessage `json:\"certification,omitempty\"`", + "Application.AuthenticationBehaviors jsontext.Value `json:\"authenticationBehaviors,omitempty\"`", + "Application.Certification jsontext.Value `json:\"certification,omitempty\"`", "Application.CreatedDateTime time.Time `json:\"createdDateTime\"`", "Application.DefaultRedirectURI string `json:\"defaultRedirectUri,omitempty\"`", "Application.Description string `json:\"description,omitempty\"`", - "Application.DisabledByMicrosoftStatus json.RawMessage `json:\"disabledByMicrosoftStatus,omitempty\"`", + "Application.DisabledByMicrosoftStatus jsontext.Value `json:\"disabledByMicrosoftStatus,omitempty\"`", "Application.DisplayName string `json:\"displayName\"`", "Application.GroupMembershipClaims string `json:\"groupMembershipClaims,omitempty\"`", "Application.ID string `json:\"id\"`", "Application.IdentifierURIs []string `json:\"identifierUris\"`", - "Application.Info json.RawMessage `json:\"info,omitempty\"`", + "Application.Info jsontext.Value `json:\"info,omitempty\"`", "Application.IsDeviceOnlyAuthSupported *bool `json:\"isDeviceOnlyAuthSupported\"`", "Application.IsFallbackPublicClient bool `json:\"isFallbackPublicClient\"`", "Application.KeyCredentials []KeyCredential `json:\"keyCredentials\"`", "Application.Notes string `json:\"notes,omitempty\"`", "Application.OAuth2RequirePostResponse bool `json:\"oauth2RequirePostResponse\"`", - "Application.OptionalClaims json.RawMessage `json:\"optionalClaims,omitempty\"`", - "Application.ParentalControlSettings json.RawMessage `json:\"parentalControlSettings,omitempty\"`", + "Application.OptionalClaims jsontext.Value `json:\"optionalClaims,omitempty\"`", + "Application.ParentalControlSettings jsontext.Value `json:\"parentalControlSettings,omitempty\"`", "Application.PasswordCredentials []PasswordCredential `json:\"passwordCredentials\"`", "Application.PublicClient RedirectFacet `json:\"publicClient\"`", "Application.PublisherDomain string `json:\"publisherDomain\"`", @@ -4092,7 +4092,7 @@ "Application.Tags []string `json:\"tags\"`", "Application.TokenEncryptionKeyID string `json:\"tokenEncryptionKeyId,omitempty\"`", "Application.UniqueName string `json:\"uniqueName,omitempty\"`", - "Application.VerifiedPublisher json.RawMessage `json:\"verifiedPublisher,omitempty\"`", + "Application.VerifiedPublisher jsontext.Value `json:\"verifiedPublisher,omitempty\"`", "Application.Web WebApplication `json:\"web\"`", "Group.Classification string `json:\"classification,omitempty\"`", "Group.CreatedDateTime time.Time `json:\"createdDateTime\"`", @@ -4153,7 +4153,7 @@ "ResourceAccess.ID string `json:\"id\"`", "ResourceAccess.Type string `json:\"type\"`", "ServicePrincipal.AccountEnabled bool `json:\"accountEnabled\"`", - "ServicePrincipal.AddIns json.RawMessage `json:\"addIns,omitempty\"`", + "ServicePrincipal.AddIns jsontext.Value `json:\"addIns,omitempty\"`", "ServicePrincipal.AlternativeNames []string `json:\"alternativeNames\"`", "ServicePrincipal.AppDescription string `json:\"appDescription,omitempty\"`", "ServicePrincipal.AppDisplayName string `json:\"appDisplayName\"`", @@ -4163,13 +4163,13 @@ "ServicePrincipal.AppRoles []AppRole `json:\"appRoles\"`", "ServicePrincipal.ApplicationTemplateID string `json:\"applicationTemplateId,omitempty\"`", "ServicePrincipal.CreatedDateTime time.Time `json:\"createdDateTime\"`", - "ServicePrincipal.CustomSecurityAttributes json.RawMessage `json:\"customSecurityAttributes,omitempty\"`", + "ServicePrincipal.CustomSecurityAttributes jsontext.Value `json:\"customSecurityAttributes,omitempty\"`", "ServicePrincipal.Description string `json:\"description,omitempty\"`", - "ServicePrincipal.DisabledByMicrosoftStatus json.RawMessage `json:\"disabledByMicrosoftStatus,omitempty\"`", + "ServicePrincipal.DisabledByMicrosoftStatus jsontext.Value `json:\"disabledByMicrosoftStatus,omitempty\"`", "ServicePrincipal.DisplayName string `json:\"displayName\"`", "ServicePrincipal.Homepage string `json:\"homepage,omitempty\"`", "ServicePrincipal.ID string `json:\"id\"`", - "ServicePrincipal.Info json.RawMessage `json:\"info,omitempty\"`", + "ServicePrincipal.Info jsontext.Value `json:\"info,omitempty\"`", "ServicePrincipal.KeyCredentials []KeyCredential `json:\"keyCredentials\"`", "ServicePrincipal.LoginURL string `json:\"loginUrl,omitempty\"`", "ServicePrincipal.LogoutURL string `json:\"logoutUrl,omitempty\"`", @@ -4177,20 +4177,20 @@ "ServicePrincipal.NotificationEmailAddresses []string `json:\"notificationEmailAddresses\"`", "ServicePrincipal.OAuth2PermissionScopes []PermissionScope `json:\"oauth2PermissionScopes\"`", "ServicePrincipal.PasswordCredentials []PasswordCredential `json:\"passwordCredentials\"`", - "ServicePrincipal.PasswordSSOSettings json.RawMessage `json:\"passwordSingleSignOnSettings,omitempty\"`", - "ServicePrincipal.PreferredKeyEndDateTime json.RawMessage `json:\"preferredTokenSigningKeyEndDateTime,omitempty\"`", + "ServicePrincipal.PasswordSSOSettings jsontext.Value `json:\"passwordSingleSignOnSettings,omitempty\"`", + "ServicePrincipal.PreferredKeyEndDateTime jsontext.Value `json:\"preferredTokenSigningKeyEndDateTime,omitempty\"`", "ServicePrincipal.PreferredSingleSignOnMode string `json:\"preferredSingleSignOnMode,omitempty\"`", "ServicePrincipal.PreferredTokenSigningKeyThumb string `json:\"preferredTokenSigningKeyThumbprint,omitempty\"`", "ServicePrincipal.ReplyURLs []string `json:\"replyUrls\"`", - "ServicePrincipal.ResourceSpecificPermissions json.RawMessage `json:\"resourceSpecificApplicationPermissions,omitempty\"`", + "ServicePrincipal.ResourceSpecificPermissions jsontext.Value `json:\"resourceSpecificApplicationPermissions,omitempty\"`", "ServicePrincipal.SAMLMetadataURL string `json:\"samlMetadataUrl,omitempty\"`", - "ServicePrincipal.SAMLSingleSignOnSettings json.RawMessage `json:\"samlSingleSignOnSettings,omitempty\"`", + "ServicePrincipal.SAMLSingleSignOnSettings jsontext.Value `json:\"samlSingleSignOnSettings,omitempty\"`", "ServicePrincipal.ServicePrincipalNames []string `json:\"servicePrincipalNames\"`", "ServicePrincipal.ServicePrincipalType string `json:\"servicePrincipalType\"`", "ServicePrincipal.SignInAudience string `json:\"signInAudience\"`", "ServicePrincipal.Tags []string `json:\"tags\"`", "ServicePrincipal.TokenEncryptionKeyID string `json:\"tokenEncryptionKeyId,omitempty\"`", - "ServicePrincipal.VerifiedPublisher json.RawMessage `json:\"verifiedPublisher,omitempty\"`", + "ServicePrincipal.VerifiedPublisher jsontext.Value `json:\"verifiedPublisher,omitempty\"`", "User.AccountEnabled bool `json:\"accountEnabled\"`", "User.BusinessPhones []string `json:\"businessPhones\"`", "User.City string `json:\"city,omitempty\"`", @@ -4633,7 +4633,7 @@ "RecoveryPoint.ResourceArn string `json:\"resourceArn,omitempty\"`", "RecoveryPoint.ResourceName string `json:\"resourceName,omitempty\"`", "RecoveryPoint.ResourceType string `json:\"resourceType,omitempty\"`", - "RecoveryPoint.Snapshot json.RawMessage `json:\"snapshot,omitempty\"`", + "RecoveryPoint.Snapshot jsontext.Value `json:\"snapshot,omitempty\"`", "RecoveryPoint.SourceBackupVaultArn string `json:\"sourceBackupVaultArn,omitempty\"`", "RecoveryPoint.Status string `json:\"status\"`", "RecoveryPoint.StorageClass string `json:\"storageClass,omitempty\"`", @@ -4809,11 +4809,11 @@ "backendSnapshot.RecoveryPointIndexStatus map[string]string `json:\"recoveryPointIndexStatus,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.RegionSettings *RegionSettings `json:\"regionSettings,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "ddbSnapshot.Attributes []ddbtypes.AttributeDefinition `json:\"attributes\"`", "ddbSnapshot.BillingMode ddbtypes.BillingMode `json:\"billingMode\"`", "ddbSnapshot.GSIs []ddbtypes.GlobalSecondaryIndex `json:\"gsis,omitempty\"`", - "ddbSnapshot.Items []map[string]json.RawMessage `json:\"items\"`", + "ddbSnapshot.Items []map[string]jsontext.Value `json:\"items\"`", "ddbSnapshot.KeySchema []ddbtypes.KeySchemaElement `json:\"keySchema\"`", "ddbSnapshot.LSIs []ddbtypes.LocalSecondaryIndex `json:\"lsis,omitempty\"`", "ddbSnapshot.Provisioned *ddbtypes.ProvisionedThroughput `json:\"provisioned,omitempty\"`", @@ -5237,7 +5237,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.JobDefRevisions map[string]map[string]int32 `json:\"jobDefRevisions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "nodeRun.attempts []JobAttempt", "nodeRun.startedAt *int64", "nodeRun.status string", @@ -5279,7 +5279,7 @@ "Export.Tags map[string]string `json:\"tags\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -5313,7 +5313,7 @@ "AdvancedPromptOptimizationJob.OutputConfig AdvancedPromptOptimizationOutputConfig", "AdvancedPromptOptimizationJob.Tags []Tag", "AdvancedPromptOptimizationOutputConfig.S3URI string `json:\"s3Uri\"`", - "AutomatedEvaluationConfig.CustomMetricConfig json.RawMessage `json:\"customMetricConfig,omitempty\"`", + "AutomatedEvaluationConfig.CustomMetricConfig jsontext.Value `json:\"customMetricConfig,omitempty\"`", "AutomatedEvaluationConfig.DatasetMetricConfigs []EvaluationDatasetMetricConfig `json:\"datasetMetricConfigs,omitempty\"`", "AutomatedEvaluationConfig.EvaluatorModelConfig *EvaluatorModelConfig `json:\"evaluatorModelConfig,omitempty\"`", "AutomatedReasoningPolicy.CreatedAt time.Time `json:\"createdAt\"`", @@ -5322,7 +5322,7 @@ "AutomatedReasoningPolicy.KmsKeyArn string `json:\"kmsKeyArn,omitempty\"`", "AutomatedReasoningPolicy.Name string `json:\"name\"`", "AutomatedReasoningPolicy.PolicyArn string `json:\"policyArn\"`", - "AutomatedReasoningPolicy.PolicyDefinition json.RawMessage `json:\"policyDefinition,omitempty\"`", + "AutomatedReasoningPolicy.PolicyDefinition jsontext.Value `json:\"policyDefinition,omitempty\"`", "AutomatedReasoningPolicy.Status string `json:\"status\"`", "AutomatedReasoningPolicy.Tags []Tag `json:\"tags,omitempty\"`", "AutomatedReasoningPolicy.UpdatedAt time.Time `json:\"updatedAt\"`", @@ -5330,7 +5330,7 @@ "AutomatedReasoningPolicyBuildWorkflow.BuildWorkflowType string `json:\"buildWorkflowType,omitempty\"`", "AutomatedReasoningPolicyBuildWorkflow.CreatedAt time.Time `json:\"createdAt\"`", "AutomatedReasoningPolicyBuildWorkflow.PolicyArn string `json:\"policyArn\"`", - "AutomatedReasoningPolicyBuildWorkflow.SourceContent json.RawMessage `json:\"sourceContent,omitempty\"`", + "AutomatedReasoningPolicyBuildWorkflow.SourceContent jsontext.Value `json:\"sourceContent,omitempty\"`", "AutomatedReasoningPolicyBuildWorkflow.Status string `json:\"status\"`", "AutomatedReasoningPolicyBuildWorkflow.UpdatedAt time.Time `json:\"updatedAt\"`", "AutomatedReasoningPolicyTestCase.ConfidenceThreshold *float64 `json:\"confidenceThreshold,omitempty\"`", @@ -5584,8 +5584,8 @@ "ProvisionedModelThroughput.ProvisionedModelName string `json:\"provisionedModelName\"`", "ProvisionedModelThroughput.Status string `json:\"status\"`", "ProvisionedModelThroughput.Tags []Tag `json:\"tags,omitempty\"`", - "RAGConfig.KnowledgeBaseConfig json.RawMessage `json:\"knowledgeBaseConfig,omitempty\"`", - "RAGConfig.PrecomputedRagSourceConfig json.RawMessage `json:\"precomputedRagSourceConfig,omitempty\"`", + "RAGConfig.KnowledgeBaseConfig jsontext.Value `json:\"knowledgeBaseConfig,omitempty\"`", + "RAGConfig.PrecomputedRagSourceConfig jsontext.Value `json:\"precomputedRagSourceConfig,omitempty\"`", "ResourcePolicy.CreatedAt time.Time", "ResourcePolicy.PolicyDocument string", "ResourcePolicy.ResourceArn string", @@ -5639,7 +5639,7 @@ "backendSnapshot.ProvisionedCounter int `json:\"provisionedCounter\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicyRevisionCounter int `json:\"resourcePolicyRevisionCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UseCaseFormData []byte `json:\"useCaseFormData,omitempty\"`" ], "version": 3 @@ -5879,7 +5879,7 @@ "backendSnapshot.PromptVersionCtrs map[string]int `json:\"promptVersionCtrs\"`", "backendSnapshot.PromptsByName map[string]string `json:\"promptsByName\"`", "backendSnapshot.ResourcePolicyCounter int `json:\"resourcePolicyCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 3 @@ -5927,7 +5927,7 @@ "ToolSession.Token string `json:\"clientToken,omitempty\"`", "ToolSession.ToolID string `json:\"toolId\"`", "ToolSession.Updated time.Time `json:\"updated\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -5961,7 +5961,7 @@ "Version.Doc map[string]any `json:\"doc\"`", "Version.ID string `json:\"id\"`", "Version.Parents []string `json:\"parents,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -5987,7 +5987,7 @@ "backendSnapshot.AsyncInvokeCounter int `json:\"asyncInvokeCounter\"`", "backendSnapshot.Invocations []*Invocation `json:\"invocations\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TokenIndex map[string]string `json:\"tokenIndex\"`" ], "version": 1 @@ -6097,7 +6097,7 @@ "ValuesFilter.Values []string `json:\"Values,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -6179,7 +6179,7 @@ "backendSnapshot.AnomalyTTL time.Duration `json:\"anomalyTTL\"`", "backendSnapshot.BackfillJobs []*BackfillJob `json:\"backfillJobs\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "ceCostCategoryValues.Key string `json:\"Key\"`", "ceCostCategoryValues.Values []string `json:\"Values\"`", "ceDimensionValues.Key string `json:\"Key\"`", @@ -6497,7 +6497,7 @@ "SchemaAnalysisRule.UpdateTime float64 `json:\"updateTime,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TagsByArn map[string]map[string]string `json:\"tagsByArn\"`" ], "version": 1 @@ -6522,7 +6522,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.ClientTokens map[string]clientTokenEntry `json:\"clientTokens\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "clientTokenEntry.CreatedAt time.Time `json:\"createdAt,omitzero\"`", "clientTokenEntry.Fingerprint string `json:\"fingerprint\"`", "clientTokenEntry.RequestToken string `json:\"requestToken\"`", @@ -6771,7 +6771,7 @@ "backendSnapshot.StackPolicies map[string]string `json:\"stackPolicies\"`", "backendSnapshot.StackSetOpResults map[string]map[string][]StackSetOperationResult `json:\"stackSetOpResults\"`", "backendSnapshot.StackSetOperations map[string]map[string]*StackSetOperation `json:\"stackSetOperations\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TypeConfigs map[string]string `json:\"typeConfigs\"`", "backendSnapshot.TypeVersions map[string][]*RegisteredTypeVersion `json:\"typeVersions\"`", "pendingStackOp.DisableValidation bool `json:\"disableValidation,omitempty\"`", @@ -7106,7 +7106,7 @@ "backendSnapshot.MonitoringSubscriptions map[string]*MonitoringSubscription `json:\"monitoringSubscriptions,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicies map[string]*resourcePolicyEntry `json:\"resourcePolicies,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "invalidationSnapshot.CallerRef string `json:\"callerRef,omitempty\"`", "invalidationSnapshot.CreateTime time.Time `json:\"createTime\"`", "invalidationSnapshot.ID string `json:\"id\"`", @@ -7178,7 +7178,7 @@ "Hsm.Zone string `json:\"zone\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -7331,7 +7331,7 @@ "backendSnapshot.ImportCounter int `json:\"importCounter\"`", "backendSnapshot.QueryCounter int `json:\"queryCounter\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -7526,7 +7526,7 @@ "backendSnapshot.AlarmHistory map[string][]AlarmHistoryItem `json:\"alarmHistory\"`", "backendSnapshot.Metrics map[string]map[string]*metricRecord `json:\"metrics\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TotalMetrics int `json:\"totalMetrics\"`", "dashboardRecord.Body string `json:\"Body\"`", "dashboardRecord.LastModified time.Time `json:\"LastModified\"`", @@ -7802,7 +7802,7 @@ "Transformer.Processors []map[string]any `json:\"transformerConfig\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "cwlIntegrationSnapshot.CreatedAt time.Time `json:\"createdAt,omitzero\"`", "cwlIntegrationSnapshot.Name string `json:\"integrationName\"`", "cwlIntegrationSnapshot.OpenSearchResourceConfig *OpenSearchResourceConfig `json:\"openSearchResourceConfig,omitempty\"`", @@ -7938,7 +7938,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.ExternalConnections map[string]map[string][]ExternalConnection `json:\"externalConnections\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "domainPolicyDTO.DomainName string `json:\"domainName\"`", "domainPolicyDTO.Region string `json:\"region\"`", "domainPolicyDTO.Value *DomainPermissionsPolicy `json:\"value\"`", @@ -8303,7 +8303,7 @@ "backendSnapshot.BuildBatchNumbers map[string]int64 `json:\"buildBatchNumbers\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicies map[string]string `json:\"resourcePolicies\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "reportDetail.Coverages []CodeCoverage `json:\"coverages,omitempty\"`", "reportDetail.ReportArn string `json:\"reportArn\"`", "reportDetail.TestCases []TestCase `json:\"testCases,omitempty\"`" @@ -8419,7 +8419,7 @@ "backendSnapshot.PROverrides map[string]bool `json:\"prOverrides\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.RepoTemplateAssoc map[string]map[string]struct{} `json:\"repoTemplateAssoc\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Triggers map[string][]RepositoryTrigger `json:\"triggers\"`", "commentSnapshot.AfterCommitID string `json:\"afterCommitId,omitempty\"`", "commentSnapshot.AuthorARN string `json:\"authorArn\"`", @@ -8508,7 +8508,7 @@ "VpcConfiguration.VpcID string `json:\"vpcId\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -8658,7 +8658,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.GitHubTokens []string `json:\"githubTokens,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "deploymentGroupSnapshot.TagsMap map[string]string `json:\"tagsMap\"`", "onPremisesInstanceSnapshot.TagsMap map[string]string `json:\"tagsMap\"`" ], @@ -8709,7 +8709,7 @@ "associationRecord.Updated awstime.EpochTime `json:\"updated\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "feedbackRecord.CodeReviewARN string `json:\"codeReviewArn\"`", "feedbackRecord.Created awstime.EpochTime `json:\"created\"`", "feedbackRecord.Reactions []string `json:\"reactions\"`", @@ -8984,7 +8984,7 @@ "backendSnapshot.ConditionRuns []conditionRunEntry `json:\"conditionRuns,omitempty\"`", "backendSnapshot.Executions []executionEntry `json:\"executions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "conditionRunEntry.PipelineName string `json:\"pipelineName\"`", "conditionRunEntry.Region string `json:\"region\"`", "conditionRunEntry.Runs []*ConditionRun `json:\"runs\"`", @@ -9067,7 +9067,7 @@ "VpcConfiguration.VpcID string `json:\"VpcId\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "repositorySyncStatusDTO.Branch string `json:\"branch\"`", "repositorySyncStatusDTO.Region string `json:\"region\"`", "repositorySyncStatusDTO.RepositoryLinkID string `json:\"repositoryLinkID\"`", @@ -9125,7 +9125,7 @@ "RulesConfiguration.Rules []MappingRule `json:\"rules\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "principalTagDTO.PoolID string `json:\"poolID\"`", "principalTagDTO.ProviderName string `json:\"providerName\"`", "principalTagDTO.Region string `json:\"region\"`", @@ -9387,7 +9387,7 @@ "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags,omitempty\"`", "backendSnapshot.RevokedChains map[string]time.Time `json:\"revokedChains,omitempty\"`", "backendSnapshot.RiskConfigurations map[string]*RiskConfiguration `json:\"riskConfigurations,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables,omitempty\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables,omitempty\"`", "backendSnapshot.TokenRevokedBefore map[string]time.Time `json:\"tokenRevokedBefore,omitempty\"`", "backendSnapshot.TokenRevokedBeforeSeq map[string]int64 `json:\"tokenRevokedBeforeSeq,omitempty\"`", "backendSnapshot.TokenSeq int64 `json:\"tokenSeq,omitempty\"`", @@ -9503,7 +9503,7 @@ "backendSnapshot.PolicyModifiedAt map[string]time.Time `json:\"policyModifiedAt\"`", "backendSnapshot.PolicyRevisions map[string]string `json:\"policyRevisions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -9570,7 +9570,7 @@ "StorageAssociation.ResourceType string `json:\"ResourceType\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "hierarchyStructureRec.InstanceID string `json:\"instanceId\"`", "hierarchyStructureRec.Structure HierarchyStructure `json:\"structure\"`", "instanceRec.Attributes map[string]string `json:\"attributes\"`", @@ -9606,7 +9606,7 @@ "ThroughputSpec.Manual int `json:\"Throughput,omitempty\"`", "backendSnapshot.Databases map[string]*storedDatabase `json:\"databases\"`", "backendSnapshot.Regions *AccountRegions `json:\"regions,omitempty\"`", - "backendSnapshot.TableAPI json.RawMessage `json:\"tableApi,omitempty\"`", + "backendSnapshot.TableAPI jsontext.Value `json:\"tableApi,omitempty\"`", "changeRecord.Current *storedDocument `json:\"Current,omitempty\"`", "changeRecord.ID string `json:\"ID\"`", "changeRecord.Op string `json:\"Op\"`", @@ -9652,15 +9652,15 @@ "storedContainer.ChangeFloor int64 `json:\"ChangeFloor,omitempty\"`", "storedContainer.ChangeLog []*changeRecord `json:\"ChangeLog,omitempty\"`", "storedContainer.ChangeSeq int64 `json:\"ChangeSeq,omitempty\"`", - "storedContainer.ComputedProperties json.RawMessage `json:\"ComputedProperties,omitempty\"`", - "storedContainer.ConflictResolutionPolicy json.RawMessage `json:\"ConflictResolutionPolicy,omitempty\"`", + "storedContainer.ComputedProperties jsontext.Value `json:\"ComputedProperties,omitempty\"`", + "storedContainer.ConflictResolutionPolicy jsontext.Value `json:\"ConflictResolutionPolicy,omitempty\"`", "storedContainer.Conflicts []*conflictRecord `json:\"Conflicts,omitempty\"`", "storedContainer.DefaultTTL *int `json:\"DefaultTTL,omitempty\"`", "storedContainer.Documents map[documentCompositeKey]*storedDocument", - "storedContainer.FullTextPolicy json.RawMessage `json:\"FullTextPolicy,omitempty\"`", - "storedContainer.GeospatialConfig json.RawMessage `json:\"GeospatialConfig,omitempty\"`", + "storedContainer.FullTextPolicy jsontext.Value `json:\"FullTextPolicy,omitempty\"`", + "storedContainer.GeospatialConfig jsontext.Value `json:\"GeospatialConfig,omitempty\"`", "storedContainer.ID string", - "storedContainer.IndexingPolicy json.RawMessage `json:\"IndexingPolicy,omitempty\"`", + "storedContainer.IndexingPolicy jsontext.Value `json:\"IndexingPolicy,omitempty\"`", "storedContainer.Modified time.Time `json:\"Modified\"`", "storedContainer.Offer *storedOffer `json:\"Offer,omitempty\"`", "storedContainer.PKLayout *pkLayout `json:\"PKLayout,omitempty\"`", @@ -9669,8 +9669,8 @@ "storedContainer.Sprocs map[string]*storedScript `json:\"Sprocs,omitempty\"`", "storedContainer.Triggers map[string]*storedScript `json:\"Triggers,omitempty\"`", "storedContainer.UDFs map[string]*storedScript `json:\"UDFs,omitempty\"`", - "storedContainer.UniqueKeyPolicy json.RawMessage `json:\"UniqueKeyPolicy,omitempty\"`", - "storedContainer.VectorEmbeddingPolicy json.RawMessage `json:\"VectorEmbeddingPolicy,omitempty\"`", + "storedContainer.UniqueKeyPolicy jsontext.Value `json:\"UniqueKeyPolicy,omitempty\"`", + "storedContainer.VectorEmbeddingPolicy jsontext.Value `json:\"VectorEmbeddingPolicy,omitempty\"`", "storedContainer.keep time.Duration", "storedContainer.repl *replState", "storedContainer.sorted atomic.Pointer[[]*storedDocument]", @@ -9694,7 +9694,7 @@ "storedPermission.Link string", "storedPermission.Mode string", "storedPermission.Modified time.Time `json:\"Modified\"`", - "storedPermission.PartitionKey json.RawMessage `json:\"PartitionKey,omitempty\"`", + "storedPermission.PartitionKey jsontext.Value `json:\"PartitionKey,omitempty\"`", "storedPermission.PartitionKeyJSON string `json:\"PartitionKeyJSON,omitempty\"`", "storedPermission.Resource string", "storedScript.Body string", @@ -9732,7 +9732,7 @@ "Report.TimeUnit string `json:\"timeUnit\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -9940,7 +9940,7 @@ "backendSnapshot.JobRuns map[string]map[string][]*JobRun `json:\"jobRuns\"`", "backendSnapshot.RecipeVersions map[string]map[string][]*Recipe `json:\"recipeVersions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -9951,7 +9951,7 @@ "PrivateLinkConfig.VpcEndpointID string `json:\"vpcEndpointId,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedAgent.AgentArn string `json:\"agentArn\"`", "storedAgent.CreationTime time.Time `json:\"creationTime\"`", @@ -10164,7 +10164,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Events []*Event `json:\"events\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -10175,7 +10175,7 @@ "backendSnapshot.Datasources map[string]map[string]string `json:\"datasources\"`", "backendSnapshot.OrgAdmins []*storedOrgAdmin `json:\"orgAdmins\"`", "backendSnapshot.OrgConfigs map[string]bool `json:\"orgConfigs\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedGraph.Arn string `json:\"arn\"`", "storedGraph.CreatedTime time.Time `json:\"createdTime\"`", @@ -10362,7 +10362,7 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.NextASN int64 `json:\"nextAmazonSideAsn\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -10386,7 +10386,7 @@ "backendSnapshot.DirSettings map[string]map[string][]*storedDirectorySetting `json:\"dirSettings\"`", "backendSnapshot.IPRoutes map[string]map[string][]storedIpRoute `json:\"ipRoutes\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UpdateInfoEntries map[string]map[string][]*storedUpdateInfo `json:\"updateInfoEntries\"`", "storedADAssessment.AssessType string `json:\"assessmentType\"`", "storedADAssessment.AssessmentID string `json:\"assessmentId\"`", @@ -10588,7 +10588,7 @@ "dlm": { "fields": [ "backendSnapshot.Counter int `json:\"counter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "managedResource.ArchivedAt time.Time `json:\"archivedAt,omitzero\"`", "managedResource.CopyOf string `json:\"copyOf,omitempty\"`", "managedResource.CreatedAt time.Time `json:\"createdAt\"`", @@ -10883,7 +10883,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -11041,7 +11041,7 @@ "backendSnapshot.EventsLog map[string][]Event `json:\"eventsLog\"`", "backendSnapshot.PendingMaintenanceActions map[string]map[string]PendingMaintenanceAction `json:\"pendingMaintenanceActions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string][]Tag `json:\"tags\"`", "xmlAvailabilityZoneList.Members []string `xml:\"AvailabilityZone\"`", "xmlDBClusterSnapshot.AvailabilityZones xmlAvailabilityZoneList `xml:\"AvailabilityZones\"`", @@ -11095,7 +11095,7 @@ "Stream.Target *StreamTarget", "StreamTarget.RoleArn string", "StreamTarget.StreamArn string", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -13056,7 +13056,7 @@ "backendSnapshot.SpotFleetHistory map[string][]SpotFleetHistoryRecord `json:\"spotFleetHistory\"`", "backendSnapshot.SubnetCIDRAssociations map[string][]*SubnetCIDRAssociation `json:\"subnetCIDRAssociations\"`", "backendSnapshot.SubnetCIDRReservations map[string][]*SubnetCIDRReservation `json:\"subnetCIDRReservations\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`", "backendSnapshot.TgwRTPropagations map[string]map[string]*snapTGWRTProp `json:\"tgwRTPropagations,omitempty\"`", "backendSnapshot.UsageReportEntries map[string][]*UsageReportEntry `json:\"usageReportEntries,omitempty\"`", @@ -13234,7 +13234,7 @@ "backendSnapshot.ReplicationConfig *ReplicationConfig `json:\"replicationConfig,omitempty\"`", "backendSnapshot.RepoTags map[string]map[string]string `json:\"repoTags,omitempty\"`", "backendSnapshot.SigningConfig *SigningSettings `json:\"signingConfig,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UploadedLayers map[string]map[string]int64 `json:\"uploadedLayers,omitempty\"`", "lifecyclePolicyEntry.PolicyText string `json:\"policyText\"`", "lifecyclePolicyEntry.RepositoryName string `json:\"repositoryName\"`", @@ -13271,7 +13271,7 @@ "Repository.RepositoryURI string", "Repository.Tags map[string]string", "backendSnapshot.RegistryCatalogData RegistryCatalogData `json:\"registryCatalogData\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TagIndex map[string]map[string]tagBinding `json:\"tagIndex,omitempty\"`", "backendSnapshot.UploadedLayers map[string]map[string]int64 `json:\"uploadedLayers,omitempty\"`", "tagBinding.CreatedAt time.Time", @@ -13881,7 +13881,7 @@ "backendSnapshot.Attributes map[string]map[string]*Attribute `json:\"attributes\"`", "backendSnapshot.DaemonTaskDefinitions map[string][]*DaemonTaskDefinition `json:\"daemonTaskDefinitions\"`", "backendSnapshot.ResourceTags map[string][]Tag `json:\"resourceTags\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TaskDefinitions map[string][]*TaskDefinition `json:\"taskDefinitions\"`", "rollingPlan.currentLive int", "rollingPlan.runningTotal int", @@ -13974,7 +13974,7 @@ "backendSnapshot.FileSystemPolicies map[string]map[string]string `json:\"fileSystemPolicies\"`", "backendSnapshot.LifecyclePolicies map[string]map[string][]LifecyclePolicy `json:\"lifecyclePolicies\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -14079,7 +14079,7 @@ "Cluster.CertificateAuthority string `json:\"certificateAuthority,omitempty\"`", "Cluster.ClusterLogging []ClusterLogEntry `json:\"clusterLogging,omitempty\"`", "Cluster.ComputeConfig *ComputeConfig `json:\"computeConfig,omitempty\"`", - "Cluster.ConfigBlocks map[string]json.RawMessage `json:\"configBlocks,omitempty\"`", + "Cluster.ConfigBlocks map[string]jsontext.Value `json:\"configBlocks,omitempty\"`", "Cluster.ConnectorConfig *ConnectorConfig `json:\"connectorConfig,omitempty\"`", "Cluster.CreatedAt time.Time `json:\"createdAt\"`", "Cluster.CreatorARN string `json:\"creatorArn,omitempty\"`", @@ -14238,8 +14238,8 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.EncryptionConfigs map[string][]EncryptionConfig `json:\"encryptionConfigs,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", - "idempotencyRecord.Body json.RawMessage `json:\"body\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", + "idempotencyRecord.Body jsontext.Value `json:\"body\"`", "idempotencyRecord.CreatedAt time.Time `json:\"createdAt\"`", "idempotencyRecord.Fingerprint string `json:\"fingerprint\"`", "idempotencyRecord.Op string `json:\"op\"`", @@ -14528,7 +14528,7 @@ "backendSnapshot.ServerlessCaches map[string][]*ServerlessCache `json:\"serverlessCaches,omitempty\"`", "backendSnapshot.Snapshots map[string][]*CacheSnapshot `json:\"snapshots\"`", "backendSnapshot.SubnetGroups map[string][]*CacheSubnetGroup `json:\"subnetGroups\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UserGroups map[string][]*UserGroup `json:\"userGroups,omitempty\"`", "backendSnapshot.Users map[string][]*User `json:\"users,omitempty\"`", "clusterGroupState.AZMode string `json:\"azMode,omitempty\"`", @@ -14679,7 +14679,7 @@ "backendSnapshot.Events map[string][]*EventRecord `json:\"events,omitempty\"`", "backendSnapshot.ManagedActionHistory map[string]map[string][]*ManagedActionHistory `json:\"managedActionHistory,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -14822,7 +14822,7 @@ "backendSnapshot.PackageAssociations map[string]map[string][]string `json:\"packageAssociations\"`", "backendSnapshot.PackagesByName map[string]map[string]string `json:\"packagesByName\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.VpcAccess map[string]map[string][]string `json:\"vpcAccess\"`" ], "version": 1 @@ -15071,7 +15071,7 @@ "backendSnapshot.ResourcePolicies map[string]string `json:\"resourcePolicies\"`", "backendSnapshot.RevocationIDCounter int64 `json:\"revocationIdCounter\"`", "backendSnapshot.RuleCounter int `json:\"ruleCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TargetDrainingUntil map[string]map[string]time.Time `json:\"targetDrainingUntil\"`", "backendSnapshot.TargetReadyAt map[string]map[string]time.Time `json:\"targetReadyAt\"`" ], @@ -15325,7 +15325,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.ArnIndex map[string]map[string]string `json:\"arnIndex\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "blockPublicAccessMeta.CreatedByArn string `json:\"CreatedByArn,omitempty\"`", "blockPublicAccessMeta.CreationDateTime float64 `json:\"CreationDateTime\"`", "blockPublicAccessMeta.region string", @@ -15397,7 +15397,7 @@ "backendSnapshot.JobRunTokens map[string]map[string]string `json:\"jobRunTokens\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SessionTokens map[string]map[string]string `json:\"sessionTokens\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -15431,7 +15431,7 @@ "backendSnapshot.BusPolicies map[string]map[string]*EventBusPolicy `json:\"busPolicies,omitempty\"`", "backendSnapshot.EventLog []EventLogEntry `json:\"eventLog\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "handlerSnapshot.Backend []byte `json:\"backend\"`", "handlerSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`" ], @@ -15718,7 +15718,7 @@ "VpcConfigurationDescription.VpcID string `json:\"VpcId\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "flushSnapshot.backupRecords [][]byte", "flushSnapshot.elasticsearchDest *ElasticsearchDestinationDescription", "flushSnapshot.httpDest *HTTPEndpointDestinationDescription", @@ -15864,7 +15864,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SafetyLever *SafetyLever `json:\"safetyLever\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -15891,7 +15891,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Evaluations map[string][]MonitorEvaluation `json:\"evaluations\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -15948,7 +15948,7 @@ "backendSnapshot.Aliases map[string][]string `json:\"aliases\"`", "backendSnapshot.CreateFileSystemTokens map[string]fsCreateTokenEntry `json:\"createFileSystemTokens\"`", "backendSnapshot.SharedVpcEnabled string `json:\"sharedVpcEnabled\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "fsCreateTokenEntry.CreatedAt time.Time `json:\"createdAt,omitzero\"`", "fsCreateTokenEntry.FileSystemID string `json:\"fileSystemId\"`", @@ -16044,16 +16044,16 @@ "storedFileSystem.WeeklyMaintenanceStartTime string `json:\"weeklyMaintenanceStartTime,omitempty\"`", "storedLustreExtras.AutoImportPolicy string `json:\"autoImportPolicy,omitempty\"`", "storedLustreExtras.DataCompressionType string `json:\"dataCompression,omitempty\"`", - "storedLustreExtras.DataReadCacheConfiguration json.RawMessage `json:\"dataReadCache,omitempty\"`", + "storedLustreExtras.DataReadCacheConfiguration jsontext.Value `json:\"dataReadCache,omitempty\"`", "storedLustreExtras.DriveCacheType string `json:\"driveCache,omitempty\"`", "storedLustreExtras.EfaEnabled bool `json:\"efa,omitempty\"`", "storedLustreExtras.ExportPath string `json:\"exportPath,omitempty\"`", "storedLustreExtras.ImportPath string `json:\"importPath,omitempty\"`", "storedLustreExtras.ImportedFileChunkSize int32 `json:\"importedChunk,omitempty\"`", - "storedLustreExtras.LogConfiguration json.RawMessage `json:\"log,omitempty\"`", - "storedLustreExtras.MetadataConfiguration json.RawMessage `json:\"metadata,omitempty\"`", + "storedLustreExtras.LogConfiguration jsontext.Value `json:\"log,omitempty\"`", + "storedLustreExtras.MetadataConfiguration jsontext.Value `json:\"metadata,omitempty\"`", "storedLustreExtras.PerUnitStorageThroughput int32 `json:\"perUnitThroughput,omitempty\"`", - "storedLustreExtras.RootSquashConfiguration json.RawMessage `json:\"rootSquash,omitempty\"`", + "storedLustreExtras.RootSquashConfiguration jsontext.Value `json:\"rootSquash,omitempty\"`", "storedS3AccessPoint.Alias string `json:\"alias\"`", "storedS3AccessPoint.CreationTime time.Time `json:\"creationTime\"`", "storedS3AccessPoint.Lifecycle string `json:\"lifecycle\"`", @@ -16119,8 +16119,8 @@ "opRec.Target string `json:\"target\"`", "opRec.Verb string `json:\"verb\"`", "recSnap.Life life `json:\"life\"`", - "recSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", - "recSnap.Resource json.RawMessage `json:\"resource\"`" + "recSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", + "recSnap.Resource jsontext.Value `json:\"resource\"`" ], "version": 1 }, @@ -16131,20 +16131,20 @@ "backendSnapshot.GenericVers map[string]*resMeta `json:\"genericVersions,omitempty\"`", "backendSnapshot.Ledger ociregistry.Ledger `json:\"ledger\"`", "backendSnapshot.Meta map[string]*resMeta `json:\"meta,omitempty\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.PackageTags map[string]map[string]string `json:\"packageTags,omitempty\"`", "backendSnapshot.Repositories map[string]repoSnap `json:\"repositories\"`", - "backendSnapshot.Rules map[string]json.RawMessage `json:\"rules,omitempty\"`", - "backendSnapshot.Settings map[string]json.RawMessage `json:\"projectSettings,omitempty\"`", - "backendSnapshot.VPCSC map[string]json.RawMessage `json:\"vpcscConfigs,omitempty\"`", + "backendSnapshot.Rules map[string]jsontext.Value `json:\"rules,omitempty\"`", + "backendSnapshot.Settings map[string]jsontext.Value `json:\"projectSettings,omitempty\"`", + "backendSnapshot.VPCSC map[string]jsontext.Value `json:\"vpcscConfigs,omitempty\"`", "genFile.Annotations map[string]string `json:\"annotations,omitempty\"`", "genFile.Created time.Time `json:\"created\"`", "genFile.Data []byte `json:\"data\"`", "genFile.Doc []byte `json:\"doc,omitempty\"`", "genFile.Owner string `json:\"owner\"`", "genFile.Updated time.Time `json:\"updated\"`", - "repoSnap.IAM json.RawMessage `json:\"iam,omitempty\"`", - "repoSnap.Repository json.RawMessage `json:\"repository\"`", + "repoSnap.IAM jsontext.Value `json:\"iam,omitempty\"`", + "repoSnap.Repository jsontext.Value `json:\"repository\"`", "resMeta.Annotations map[string]string `json:\"annotations,omitempty\"`", "resMeta.Created time.Time `json:\"created\"`", "resMeta.Description string `json:\"description,omitempty\"`", @@ -16177,7 +16177,7 @@ "jobRec.TaskRun int64 `json:\"taskRun\"`", "jobRec.Tasks []*taskRec `json:\"tasks\"`", "jobRec.pb *batchpb.Job", - "jobSnap.Job json.RawMessage `json:\"job\"`", + "jobSnap.Job jsontext.Value `json:\"job\"`", "jobSnap.Rec *jobRec `json:\"rec\"`", "opRec.Created time.Time `json:\"created\"`", "opRec.EndAt time.Time `json:\"endAt\"`", @@ -16240,10 +16240,10 @@ "gcpbigtable": { "fields": [ "backendSnapshot.Instances map[string]instSnap `json:\"instances\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.Seq uint64 `json:\"seq\"`", - "backupSnap.Backup json.RawMessage `json:\"backup\"`", - "backupSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", + "backupSnap.Backup jsontext.Value `json:\"backup\"`", + "backupSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", "backupSnap.Table tableSnap `json:\"table\"`", "cellSnap.TS int64 `json:\"ts\"`", "cellSnap.Val []byte `json:\"val,omitempty\"`", @@ -16251,43 +16251,43 @@ "columnSnap.Family string `json:\"family\"`", "columnSnap.Qualifier []byte `json:\"qualifier,omitempty\"`", "instSnap.Backups map[string]backupSnap `json:\"backups,omitempty\"`", - "instSnap.Clusters map[string]json.RawMessage `json:\"clusters,omitempty\"`", - "instSnap.Instance json.RawMessage `json:\"instance\"`", - "instSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", - "instSnap.Profiles map[string]json.RawMessage `json:\"profiles,omitempty\"`", + "instSnap.Clusters map[string]jsontext.Value `json:\"clusters,omitempty\"`", + "instSnap.Instance jsontext.Value `json:\"instance\"`", + "instSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", + "instSnap.Profiles map[string]jsontext.Value `json:\"profiles,omitempty\"`", "instSnap.Tables map[string]tableSnap `json:\"tables,omitempty\"`", "rowSnap.Columns []columnSnap `json:\"columns\"`", "rowSnap.Key []byte `json:\"key\"`", - "tableSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", + "tableSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", "tableSnap.Rows []rowSnap `json:\"rows,omitempty\"`", "tableSnap.Splits [][]byte `json:\"splits,omitempty\"`", - "tableSnap.Table json.RawMessage `json:\"table\"`" + "tableSnap.Table jsontext.Value `json:\"table\"`" ], "version": 1 }, "gcpcloudbuild": { "fields": [ "backendSnapshot.Builds map[string]buildSnap `json:\"builds\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", - "backendSnapshot.Pools map[string]json.RawMessage `json:\"workerPools\"`", - "backendSnapshot.Triggers map[string]json.RawMessage `json:\"triggers\"`", - "buildSnap.Build json.RawMessage `json:\"build\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", + "backendSnapshot.Pools map[string]jsontext.Value `json:\"workerPools\"`", + "backendSnapshot.Triggers map[string]jsontext.Value `json:\"triggers\"`", + "buildSnap.Build jsontext.Value `json:\"build\"`", "buildSnap.OpName string `json:\"opName\"`", - "buildSnap.Orig json.RawMessage `json:\"orig\"`" + "buildSnap.Orig jsontext.Value `json:\"orig\"`" ], "version": 1 }, "gcpcloudtasks": { "fields": [ - "backendSnapshot.Cmek map[string]json.RawMessage `json:\"cmek\"`", + "backendSnapshot.Cmek map[string]jsontext.Value `json:\"cmek\"`", "backendSnapshot.EtagSeq uint64 `json:\"etagSeq\"`", - "backendSnapshot.Ops map[string]json.RawMessage `json:\"operations,omitempty\"`", - "backendSnapshot.Policies map[string]json.RawMessage `json:\"policies\"`", + "backendSnapshot.Ops map[string]jsontext.Value `json:\"operations,omitempty\"`", + "backendSnapshot.Policies map[string]jsontext.Value `json:\"policies\"`", "backendSnapshot.Queues map[string]queueSnap `json:\"queues\"`", "backendSnapshot.TaskSeq uint64 `json:\"taskSeq\"`", "backendSnapshot.Tombstones map[string]time.Time `json:\"tombstones\"`", - "queueSnap.Queue json.RawMessage `json:\"queue\"`", - "queueSnap.Tasks map[string]json.RawMessage `json:\"tasks,omitempty\"`" + "queueSnap.Queue jsontext.Value `json:\"queue\"`", + "queueSnap.Tasks map[string]jsontext.Value `json:\"tasks,omitempty\"`" ], "version": 1 }, @@ -16353,7 +16353,7 @@ "backendSnapshot.Clusters map[string]clusterSnap `json:\"clusters\"`", "backendSnapshot.Ops map[string]opSnap `json:\"operations\"`", "backendSnapshot.Seq uint64 `json:\"seq\"`", - "clusterSnap.Cluster json.RawMessage `json:\"cluster\"`", + "clusterSnap.Cluster jsontext.Value `json:\"cluster\"`", "clusterSnap.DeleteOp string `json:\"deleteOp\"`", "clusterSnap.Failure string `json:\"failure\"`", "clusterSnap.Pools []poolSnap `json:\"pools\"`", @@ -16366,11 +16366,11 @@ "opSnap.Kind opKind `json:\"kind\"`", "opSnap.Location string `json:\"location\"`", "opSnap.Manual bool `json:\"manual\"`", - "opSnap.Op json.RawMessage `json:\"op\"`", + "opSnap.Op jsontext.Value `json:\"op\"`", "opSnap.Pool string `json:\"pool\"`", "opSnap.Project string `json:\"project\"`", "poolSnap.DeleteOp string `json:\"deleteOp\"`", - "poolSnap.Pool json.RawMessage `json:\"pool\"`", + "poolSnap.Pool jsontext.Value `json:\"pool\"`", "poolSnap.PrevVersion string `json:\"prevVersion\"`", "poolSnap.Size int32 `json:\"size\"`", "rotationRec.Credentials bool", @@ -16381,13 +16381,13 @@ "gcpdatastore": { "fields": [ "backendSnapshot.Entities map[string]entitySnap `json:\"entities\"`", - "backendSnapshot.Indexes map[string]json.RawMessage `json:\"indexes\"`", + "backendSnapshot.Indexes map[string]jsontext.Value `json:\"indexes\"`", "backendSnapshot.LastCommit time.Time `json:\"lastCommit\"`", "backendSnapshot.NextIndex int64 `json:\"nextIndex\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.UsedIDs map[string][]int64 `json:\"usedIds\"`", "entitySnap.Created time.Time `json:\"created\"`", - "entitySnap.Entity json.RawMessage `json:\"entity\"`", + "entitySnap.Entity jsontext.Value `json:\"entity\"`", "entitySnap.Updated time.Time `json:\"updated\"`" ], "version": 1 @@ -16411,22 +16411,22 @@ "gcpeventarc": { "fields": [ "backendSnapshot.Channels map[string]channelSnap `json:\"channels\"`", - "backendSnapshot.Configs map[string]json.RawMessage `json:\"configs\"`", - "backendSnapshot.Connections map[string]json.RawMessage `json:\"connections\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Configs map[string]jsontext.Value `json:\"configs\"`", + "backendSnapshot.Connections map[string]jsontext.Value `json:\"connections\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.Seq uint64 `json:\"seq\"`", "backendSnapshot.Triggers map[string]triggerSnap `json:\"triggers\"`", - "channelSnap.Channel json.RawMessage `json:\"channel\"`", - "channelSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", + "channelSnap.Channel jsontext.Value `json:\"channel\"`", + "channelSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", "triggerSnap.OwnedTopic bool `json:\"ownedTopic,omitempty\"`", - "triggerSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", - "triggerSnap.Trigger json.RawMessage `json:\"trigger\"`" + "triggerSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", + "triggerSnap.Trigger jsontext.Value `json:\"trigger\"`" ], "version": 1 }, "gcpfirestore": { "fields": [ - "backendSnapshot.Docs map[string]json.RawMessage `json:\"docs\"`", + "backendSnapshot.Docs map[string]jsontext.Value `json:\"docs\"`", "backendSnapshot.LastCommit time.Time `json:\"lastCommit\"`" ], "version": 1 @@ -16434,10 +16434,10 @@ "gcpfunctions": { "fields": [ "backendSnapshot.Functions map[string]fnSnap `json:\"functions\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.Uploads map[string]uploadSnap `json:\"uploads\"`", "fnSnap.Failure string `json:\"failure\"`", - "fnSnap.Function json.RawMessage `json:\"function\"`", + "fnSnap.Function jsontext.Value `json:\"function\"`", "fnSnap.RevSeq int `json:\"revSeq\"`", "fnSnap.SubName string `json:\"subName\"`", "fnSnap.Zip []byte `json:\"zip\"`", @@ -16448,20 +16448,20 @@ }, "gcpiam": { "fields": [ - "accountSnap.Account json.RawMessage `json:\"account\"`", + "accountSnap.Account jsontext.Value `json:\"account\"`", "accountSnap.DeletedAt time.Time `json:\"deletedAt\"`", "accountSnap.Keys map[string]keySnap `json:\"keys,omitempty\"`", - "accountSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", + "accountSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", "accountSnap.System *keySnap `json:\"system,omitempty\"`", "backendSnapshot.Accounts map[string]accountSnap `json:\"accounts\"`", "backendSnapshot.EtagSeq uint64 `json:\"etagSeq\"`", "backendSnapshot.Roles map[string]roleSnap `json:\"roles\"`", "keySnap.Cert []byte `json:\"cert,omitempty\"`", - "keySnap.Key json.RawMessage `json:\"key\"`", + "keySnap.Key jsontext.Value `json:\"key\"`", "keySnap.Private []byte `json:\"private,omitempty\"`", "keySnap.Public []byte `json:\"public,omitempty\"`", "roleSnap.DeletedAt time.Time `json:\"deletedAt\"`", - "roleSnap.Role json.RawMessage `json:\"role\"`" + "roleSnap.Role jsontext.Value `json:\"role\"`" ], "version": 1 }, @@ -16529,13 +16529,13 @@ "backendSnapshot.EtagSeq uint64 `json:\"etagSeq\"`", "backendSnapshot.Jobs map[string]jobSnap `json:\"jobs\"`", "backendSnapshot.Keys map[string]keySnap `json:\"keys\"`", - "backendSnapshot.Ops map[string]json.RawMessage `json:\"operations,omitempty\"`", - "backendSnapshot.Policies map[string]json.RawMessage `json:\"policies\"`", - "backendSnapshot.Retired map[string]json.RawMessage `json:\"retired,omitempty\"`", - "backendSnapshot.Rings map[string]json.RawMessage `json:\"rings\"`", - "jobSnap.Job json.RawMessage `json:\"job\"`", + "backendSnapshot.Ops map[string]jsontext.Value `json:\"operations,omitempty\"`", + "backendSnapshot.Policies map[string]jsontext.Value `json:\"policies\"`", + "backendSnapshot.Retired map[string]jsontext.Value `json:\"retired,omitempty\"`", + "backendSnapshot.Rings map[string]jsontext.Value `json:\"rings\"`", + "jobSnap.Job jsontext.Value `json:\"job\"`", "jobSnap.Private []byte `json:\"private,omitempty\"`", - "keySnap.Key json.RawMessage `json:\"key\"`", + "keySnap.Key jsontext.Value `json:\"key\"`", "keySnap.Primary int64 `json:\"primary\"`", "keySnap.Versions []versionSnap `json:\"versions,omitempty\"`", "versionSnap.Deleted bool `json:\"deleted,omitempty\"`", @@ -16546,20 +16546,20 @@ "gcplogging": { "fields": [ "backendSnapshot.Buckets map[string]bucketSnap `json:\"buckets\"`", - "backendSnapshot.Cmek map[string]json.RawMessage `json:\"cmek\"`", - "backendSnapshot.Entries []json.RawMessage `json:\"entries\"`", - "backendSnapshot.Exclusions map[string]json.RawMessage `json:\"exclusions\"`", + "backendSnapshot.Cmek map[string]jsontext.Value `json:\"cmek\"`", + "backendSnapshot.Entries []jsontext.Value `json:\"entries\"`", + "backendSnapshot.Exclusions map[string]jsontext.Value `json:\"exclusions\"`", "backendSnapshot.Metrics map[string]metricSnap `json:\"metrics\"`", - "backendSnapshot.Ops map[string]json.RawMessage `json:\"ops\"`", + "backendSnapshot.Ops map[string]jsontext.Value `json:\"ops\"`", "backendSnapshot.Seeded []string `json:\"seeded\"`", "backendSnapshot.Seq uint64 `json:\"seq\"`", - "backendSnapshot.Settings map[string]json.RawMessage `json:\"settings\"`", - "backendSnapshot.Sinks map[string]json.RawMessage `json:\"sinks\"`", - "backendSnapshot.Views map[string]json.RawMessage `json:\"views\"`", - "bucketSnap.Bucket json.RawMessage `json:\"bucket\"`", + "backendSnapshot.Settings map[string]jsontext.Value `json:\"settings\"`", + "backendSnapshot.Sinks map[string]jsontext.Value `json:\"sinks\"`", + "backendSnapshot.Views map[string]jsontext.Value `json:\"views\"`", + "bucketSnap.Bucket jsontext.Value `json:\"bucket\"`", "bucketSnap.DeleteRequested time.Time `json:\"deleteRequested\"`", "bucketSnap.Entries []int `json:\"entries,omitempty\"`", - "metricSnap.Metric json.RawMessage `json:\"metric\"`", + "metricSnap.Metric jsontext.Value `json:\"metric\"`", "metricSnap.Series []seriesSnap `json:\"series,omitempty\"`", "seriesSnap.Buckets []int64 `json:\"buckets,omitempty\"`", "seriesSnap.Count int64 `json:\"count\"`", @@ -16589,23 +16589,23 @@ "Incident.State string `json:\"state\"`", "Incident.Threshold float64 `json:\"threshold\"`", "backendSnapshot.Channels map[string]channelSnap `json:\"channels\"`", - "backendSnapshot.Descriptors map[string]json.RawMessage `json:\"descriptors\"`", - "backendSnapshot.Groups map[string]json.RawMessage `json:\"groups\"`", + "backendSnapshot.Descriptors map[string]jsontext.Value `json:\"descriptors\"`", + "backendSnapshot.Groups map[string]jsontext.Value `json:\"groups\"`", "backendSnapshot.Incidents []Incident `json:\"incidents\"`", - "backendSnapshot.Policies map[string]json.RawMessage `json:\"policies\"`", - "backendSnapshot.SLOs map[string]json.RawMessage `json:\"slos,omitempty\"`", + "backendSnapshot.Policies map[string]jsontext.Value `json:\"policies\"`", + "backendSnapshot.SLOs map[string]jsontext.Value `json:\"slos,omitempty\"`", "backendSnapshot.Series []seriesSnap `json:\"series\"`", - "backendSnapshot.Services map[string]json.RawMessage `json:\"services,omitempty\"`", - "backendSnapshot.Snoozes map[string]json.RawMessage `json:\"snoozes,omitempty\"`", + "backendSnapshot.Services map[string]jsontext.Value `json:\"services,omitempty\"`", + "backendSnapshot.Snoozes map[string]jsontext.Value `json:\"snoozes,omitempty\"`", "backendSnapshot.Uptime map[string]uptimeSnap `json:\"uptime\"`", - "channelSnap.Channel json.RawMessage `json:\"channel\"`", + "channelSnap.Channel jsontext.Value `json:\"channel\"`", "channelSnap.Code string `json:\"code,omitempty\"`", "channelSnap.CodeAt time.Time `json:\"codeAt\"`", "channelSnap.Sensitive map[string]string `json:\"sensitive,omitempty\"`", "seriesSnap.Project string `json:\"project\"`", - "seriesSnap.Series json.RawMessage `json:\"series\"`", + "seriesSnap.Series jsontext.Value `json:\"series\"`", "seriesSnap.Unit string `json:\"unit,omitempty\"`", - "uptimeSnap.Config json.RawMessage `json:\"config\"`", + "uptimeSnap.Config jsontext.Value `json:\"config\"`", "uptimeSnap.LastRun time.Time `json:\"lastRun\"`" ], "version": 1 @@ -16614,7 +16614,7 @@ "fields": [ "backendSnapshot.EtagSeq uint64 `json:\"etagSeq,omitempty\"`", "backendSnapshot.LastMsg int64 `json:\"lastMsg\"`", - "backendSnapshot.Policies map[string]json.RawMessage `json:\"policies,omitempty\"`", + "backendSnapshot.Policies map[string]jsontext.Value `json:\"policies,omitempty\"`", "backendSnapshot.Schemas map[string]*schemaRec `json:\"schemas\"`", "backendSnapshot.Snapshots map[string]pbSnapSnap `json:\"snapshots\"`", "backendSnapshot.Subs map[string]subSnap `json:\"subs\"`", @@ -16625,7 +16625,7 @@ "message.OrderingKey string `json:\"orderingKey,omitempty\"`", "message.PublishTime time.Time `json:\"publishTime\"`", "pbSnapSnap.Created time.Time `json:\"created\"`", - "pbSnapSnap.Snapshot json.RawMessage `json:\"snapshot\"`", + "pbSnapSnap.Snapshot jsontext.Value `json:\"snapshot\"`", "pbSnapSnap.Unacked []string `json:\"unacked,omitempty\"`", "schemaRec.Name string `json:\"name\"`", "schemaRec.Revisions []schemaRevision `json:\"revisions\"`", @@ -16645,18 +16645,18 @@ "subMsg.owner string", "subSnap.LastActive time.Time `json:\"lastActive\"`", "subSnap.Msgs []*subMsg `json:\"msgs,omitempty\"`", - "subSnap.Sub json.RawMessage `json:\"sub\"`", + "subSnap.Sub jsontext.Value `json:\"sub\"`", "topicSnap.Retained []*message `json:\"retained,omitempty\"`", - "topicSnap.Topic json.RawMessage `json:\"topic\"`" + "topicSnap.Topic jsontext.Value `json:\"topic\"`" ], "version": 1 }, "gcpredis": { "fields": [ "backendSnapshot.Instances map[string]instSnap `json:\"instances\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "instSnap.AuthString string `json:\"authString,omitempty\"`", - "instSnap.Instance json.RawMessage `json:\"instance\"`", + "instSnap.Instance jsontext.Value `json:\"instance\"`", "instSnap.Keyspace []byte `json:\"keyspace,omitempty\"`" ], "version": 1 @@ -16666,8 +16666,8 @@ "backendSnapshot.Bindings map[string]*bindingRec `json:\"tagBindings\"`", "backendSnapshot.Enabled map[string]map[string]time.Time `json:\"enabledServices\"`", "backendSnapshot.Folders map[string]*folderRec `json:\"folders\"`", - "backendSnapshot.Ops map[string]json.RawMessage `json:\"operations\"`", - "backendSnapshot.Policies map[string]json.RawMessage `json:\"policies\"`", + "backendSnapshot.Ops map[string]jsontext.Value `json:\"operations\"`", + "backendSnapshot.Policies map[string]jsontext.Value `json:\"policies\"`", "backendSnapshot.PolicySeq uint64 `json:\"policySeq\"`", "backendSnapshot.Projects map[string]*projectRec `json:\"projects\"`", "backendSnapshot.Seed string `json:\"seed\"`", @@ -16702,25 +16702,25 @@ }, "gcprun": { "fields": [ - "backendSnapshot.DeletedExe map[string]json.RawMessage `json:\"deletedExecutions\"`", - "backendSnapshot.DeletedJob map[string]json.RawMessage `json:\"deletedJobs\"`", - "backendSnapshot.DeletedSvc map[string]json.RawMessage `json:\"deletedServices\"`", - "backendSnapshot.Executions map[string]json.RawMessage `json:\"executions\"`", - "backendSnapshot.IAM map[string]json.RawMessage `json:\"iamPolicies\"`", + "backendSnapshot.DeletedExe map[string]jsontext.Value `json:\"deletedExecutions\"`", + "backendSnapshot.DeletedJob map[string]jsontext.Value `json:\"deletedJobs\"`", + "backendSnapshot.DeletedSvc map[string]jsontext.Value `json:\"deletedServices\"`", + "backendSnapshot.Executions map[string]jsontext.Value `json:\"executions\"`", + "backendSnapshot.IAM map[string]jsontext.Value `json:\"iamPolicies\"`", "backendSnapshot.IAMSeq uint64 `json:\"iamSeq\"`", - "backendSnapshot.Jobs map[string]json.RawMessage `json:\"jobs\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Jobs map[string]jsontext.Value `json:\"jobs\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.Revisions map[string]revSnap `json:\"revisions\"`", "backendSnapshot.Services map[string]svcSnap `json:\"services\"`", "backendSnapshot.Tasks map[string]taskSnap `json:\"tasks\"`", - "revSnap.Revision json.RawMessage `json:\"revision\"`", + "revSnap.Revision jsontext.Value `json:\"revision\"`", "revSnap.Seq int `json:\"seq\"`", "revSnap.StartErr string `json:\"startErr\"`", "svcSnap.Failure string `json:\"failure\"`", "svcSnap.RevSeq int `json:\"revSeq\"`", - "svcSnap.Service json.RawMessage `json:\"service\"`", + "svcSnap.Service jsontext.Value `json:\"service\"`", "taskSnap.State string `json:\"state\"`", - "taskSnap.Task json.RawMessage `json:\"task\"`" + "taskSnap.Task jsontext.Value `json:\"task\"`" ], "version": 1 }, @@ -16730,7 +16730,7 @@ "backendSnapshot.Jobs map[string]jobSnap `json:\"jobs\"`", "jobSnap.ExecStart time.Time `json:\"execStart\"`", "jobSnap.Failures int32 `json:\"failures\"`", - "jobSnap.Job json.RawMessage `json:\"job\"`", + "jobSnap.Job jsontext.Value `json:\"job\"`", "jobSnap.RetryAt time.Time `json:\"retryAt\"`" ], "version": 1 @@ -16740,8 +16740,8 @@ "backendSnapshot.EtagSeq uint64 `json:\"etagSeq\"`", "backendSnapshot.Secrets map[string]secretSnap `json:\"secrets\"`", "secretSnap.NextVer int64 `json:\"nextVer\"`", - "secretSnap.Policy json.RawMessage `json:\"policy,omitempty\"`", - "secretSnap.Secret json.RawMessage `json:\"secret\"`", + "secretSnap.Policy jsontext.Value `json:\"policy,omitempty\"`", + "secretSnap.Secret jsontext.Value `json:\"secret\"`", "secretSnap.Versions []versionSnap `json:\"versions,omitempty\"`", "versionSnap.Data []byte `json:\"data,omitempty\"`", "versionSnap.Num int64 `json:\"num\"`" @@ -16752,12 +16752,12 @@ "fields": [ "backendSnapshot.EtagSeq uint64 `json:\"etagSeq\"`", "backendSnapshot.Instances map[string]instSnap `json:\"instances\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "dbSnap.DDL []string `json:\"ddl,omitempty\"`", - "dbSnap.Database json.RawMessage `json:\"database\"`", + "dbSnap.Database jsontext.Value `json:\"database\"`", "instSnap.Databases map[string]dbSnap `json:\"databases,omitempty\"`", - "instSnap.Instance json.RawMessage `json:\"instance\"`", - "instSnap.Policy json.RawMessage `json:\"policy,omitempty\"`" + "instSnap.Instance jsontext.Value `json:\"instance\"`", + "instSnap.Policy jsontext.Value `json:\"policy,omitempty\"`" ], "version": 1 }, @@ -16901,7 +16901,7 @@ "ACLEntry.Entity string `json:\"entity\"`", "ACLEntry.Role string `json:\"role\"`", "Bucket.ACL []ACLEntry `json:\"acl,omitempty\"`", - "Bucket.Config map[string]json.RawMessage `json:\"config,omitempty\"`", + "Bucket.Config map[string]jsontext.Value `json:\"config,omitempty\"`", "Bucket.Created time.Time `json:\"created\"`", "Bucket.DefaultEventBasedHold bool `json:\"defaultEventBasedHold,omitempty\"`", "Bucket.DefaultObjectACL []ACLEntry `json:\"defaultObjectAcl,omitempty\"`", @@ -16915,7 +16915,7 @@ "Bucket.StorageClass string `json:\"storageClass\"`", "Bucket.Updated time.Time `json:\"updated\"`", "Bucket.Versioning bool `json:\"versioning,omitempty\"`", - "IAMBinding.Condition json.RawMessage `json:\"condition,omitempty\"`", + "IAMBinding.Condition jsontext.Value `json:\"condition,omitempty\"`", "IAMBinding.Members []string `json:\"members\"`", "IAMBinding.Role string `json:\"role\"`", "IAMPolicy.Bindings []IAMBinding `json:\"bindings\"`", @@ -16949,10 +16949,10 @@ }, "gcpworkflows": { "fields": [ - "backendSnapshot.Executions map[string]json.RawMessage `json:\"executions\"`", - "backendSnapshot.Operations map[string]json.RawMessage `json:\"operations\"`", + "backendSnapshot.Executions map[string]jsontext.Value `json:\"executions\"`", + "backendSnapshot.Operations map[string]jsontext.Value `json:\"operations\"`", "backendSnapshot.Workflows map[string]workflowSnap `json:\"workflows\"`", - "workflowSnap.Revisions []json.RawMessage `json:\"revisions\"`" + "workflowSnap.Revisions []jsontext.Value `json:\"revisions\"`" ], "version": 1 }, @@ -17027,7 +17027,7 @@ "backendSnapshot.DataRetrievalPolicies map[string]string `json:\"dataRetrievalPolicies,omitempty\"`", "backendSnapshot.MultipartParts []multipartPartSnapshot `json:\"multipartParts\"`", "backendSnapshot.ProvisionedCapacity []provisionedCapacitySnapshot `json:\"provisionedCapacity\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "csvInputDTO.Comments string `json:\"Comments,omitempty\"`", "csvInputDTO.FieldDelimiter string `json:\"FieldDelimiter,omitempty\"`", "csvInputDTO.FileHeaderInfo string `json:\"FileHeaderInfo,omitempty\"`", @@ -17156,7 +17156,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Seq int64 `json:\"seq\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -17551,7 +17551,7 @@ "JSONClassifier.JSONPath string `json:\"JsonPath\"`", "JSONClassifier.Name string `json:\"Name\"`", "Job.ARN string `json:\"Arn,omitempty\"`", - "Job.CodeGenConfigurationNodes map[string]json.RawMessage `json:\"CodeGenConfigurationNodes,omitempty\"`", + "Job.CodeGenConfigurationNodes map[string]jsontext.Value `json:\"CodeGenConfigurationNodes,omitempty\"`", "Job.Command JobCommand `json:\"Command,omitzero\"`", "Job.Connections ConnectionsList `json:\"Connections,omitzero\"`", "Job.CreatedOn float64 `json:\"CreatedOn,omitempty\"`", @@ -17924,7 +17924,7 @@ "backendSnapshot.SchemaVersions map[string][]*SchemaVersion `json:\"schemaVersions\"`", "backendSnapshot.SessionStatements map[string][]*Statement `json:\"sessionStatements\"`", "backendSnapshot.TableColumnStats map[string]*ColumnStatistics `json:\"tableColumnStats\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.WorkflowRuns map[string][]*WorkflowRun `json:\"workflowRuns\"`", "resourcePolicyEntry.CreateTime float64 `json:\"CreateTime,omitempty\"`", "resourcePolicyEntry.EnableHybrid string `json:\"EnableHybrid,omitempty\"`", @@ -18017,7 +18017,7 @@ "backendSnapshot.NextServiceAccountID uint64 `json:\"nextServiceAccountId\"`", "backendSnapshot.NextTokenID uint64 `json:\"nextTokenId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -18201,7 +18201,7 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.MemberSeq int64 `json:\"memberSeq\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 2 @@ -18401,7 +18401,7 @@ "backendSnapshot.RoleByARN map[string]string `json:\"roleByARN,omitempty\"`", "backendSnapshot.RoleInlinePolicies map[string]map[string]string `json:\"roleInlinePolicies,omitempty\"`", "backendSnapshot.RolePolicies map[string][]string `json:\"rolePolicies,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UserInlinePolicies map[string]map[string]string `json:\"userInlinePolicies,omitempty\"`", "backendSnapshot.UserPolicies map[string][]string `json:\"userPolicies,omitempty\"`", "comprehensiveSnapshot.AccessAdvisorJobs map[string]*accessAdvisorJob `json:\"accessAdvisorJobs,omitempty\"`", @@ -18409,8 +18409,8 @@ "comprehensiveSnapshot.OrgReportJobs map[string]time.Time `json:\"orgReportJobs,omitempty\"`", "comprehensiveSnapshot.SSHPublicKeys map[string]SSHPublicKey `json:\"sshPublicKeys,omitempty\"`", "comprehensiveSnapshot.ServiceLastAccessed map[string]map[string]ServiceLastAccessedDetail `json:\"serviceLastAccessed,omitempty\"`", - "handlerSnapshot.Accounts map[string]json.RawMessage `json:\"accounts,omitempty\"`", - "handlerSnapshot.Backend json.RawMessage `json:\"backend,omitempty\"`", + "handlerSnapshot.Accounts map[string]jsontext.Value `json:\"accounts,omitempty\"`", + "handlerSnapshot.Backend jsontext.Value `json:\"backend,omitempty\"`", "handlerSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`", "policyAttachmentRefs.groups map[string]struct{}", "policyAttachmentRefs.roles map[string]struct{}", @@ -18498,7 +18498,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Counter int `json:\"counter\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -18762,7 +18762,7 @@ "backendSnapshot.OrgConfig OrgConfiguration `json:\"orgConfig\"`", "backendSnapshot.OrgEc2Config OrgEc2DeepInspectionConfig `json:\"orgEc2Config\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "codeSnippet.EndLine int32 `json:\"endLine,omitempty\"`", "codeSnippet.FindingArn string `json:\"findingArn\"`", @@ -19230,7 +19230,7 @@ "RoleAlias.RoleAliasARN string `json:\"roleAliasArn\"`", "RoleAlias.Tags map[string]string `json:\"tags,omitempty\"`", "RuleAction.Lambda *LambdaAction `json:\"lambda,omitempty\"`", - "RuleAction.Other map[string]json.RawMessage `json:\"-\"`", + "RuleAction.Other map[string]jsontext.Value `json:\"-\"`", "RuleAction.SNS *SNSAction `json:\"sns,omitempty\"`", "RuleAction.SQS *SQSAction `json:\"sqs,omitempty\"`", "RuleAction.{MarshalJSON}", @@ -19428,7 +19428,7 @@ "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags\"`", "backendSnapshot.SbomValidationResults map[string][]*SbomValidationResult `json:\"sbomValidationResults\"`", "backendSnapshot.SecurityProfileTargets map[string][]string `json:\"securityProfileTargets\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.ThingBillingGroups map[string]string `json:\"thingBillingGroups\"`", "backendSnapshot.ThingConnectivity map[string]*ThingConnectivityData `json:\"thingConnectivity\"`", "backendSnapshot.ThingGroupIndexingConfiguration *ThingGroupIndexingConfiguration `json:\"thingGroupIndexingConfiguration\"`", @@ -19650,7 +19650,7 @@ "backendSnapshot.ChannelMessages map[string][]ChannelMessage `json:\"channelMessages\"`", "backendSnapshot.DatasetContents map[string][]*DatasetContent `json:\"datasetContents\"`", "backendSnapshot.LoggingOptions *LoggingOptions `json:\"loggingOptions\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 2 @@ -19662,7 +19662,7 @@ "RetainedMessage.Qos int32", "RetainedMessage.Topic string", "RetainedMessage.UserProperties []byte", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "connectionEntry.clientID string", "connectionEntry.connectedAt time.Time", "connectionEntry.disconnectReason string", @@ -19674,19 +19674,19 @@ "connectionEntrySnap.DisconnectedAt time.Time `json:\"disconnectedAt,omitzero\"`", "connectionEntrySnap.SourceIP string `json:\"sourceIp,omitempty\"`", "shadowEntry.deleted bool", - "shadowEntry.desired map[string]json.RawMessage", + "shadowEntry.desired map[string]jsontext.Value", "shadowEntry.metaDesired map[string]int64", "shadowEntry.metaReported map[string]int64", - "shadowEntry.reported map[string]json.RawMessage", + "shadowEntry.reported map[string]jsontext.Value", "shadowEntry.shadowName string", "shadowEntry.thingName string", "shadowEntry.updatedAt time.Time", "shadowEntry.version int", "shadowEntrySnap.Deleted bool `json:\"deleted,omitempty\"`", - "shadowEntrySnap.Desired map[string]json.RawMessage `json:\"desired,omitempty\"`", + "shadowEntrySnap.Desired map[string]jsontext.Value `json:\"desired,omitempty\"`", "shadowEntrySnap.MetaDesired map[string]int64 `json:\"metaDesired,omitempty\"`", "shadowEntrySnap.MetaReported map[string]int64 `json:\"metaReported,omitempty\"`", - "shadowEntrySnap.Reported map[string]json.RawMessage `json:\"reported,omitempty\"`", + "shadowEntrySnap.Reported map[string]jsontext.Value `json:\"reported,omitempty\"`", "shadowEntrySnap.ShadowName string `json:\"shadowName\"`", "shadowEntrySnap.ThingName string `json:\"thingName\"`", "shadowEntrySnap.UpdatedAt time.Time `json:\"updatedAt\"`" @@ -19974,7 +19974,7 @@ "backendSnapshot.QueuedMessages map[string][]QueuedMessage `json:\"queuedMessages,omitempty\"`", "backendSnapshot.ResourceLogLevels map[string]string `json:\"resourceLogLevels,omitempty\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.WirelessDeviceThings map[string]string `json:\"wirelessDeviceThings,omitempty\"`", "backendSnapshot.WirelessGatewayCerts map[string]string `json:\"wirelessGatewayCerts,omitempty\"`", "backendSnapshot.WirelessGatewayThings map[string]string `json:\"wirelessGatewayThings,omitempty\"`", @@ -20273,7 +20273,7 @@ "backendSnapshot.PolicyVersions map[string]string `json:\"clusterPolicyVersions,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ScramSecrets map[string][]string `json:\"scramSecrets\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -20366,7 +20366,7 @@ "WorkerLogDelivery.CloudWatchLogs *CloudWatchLogsDelivery", "WorkerLogDelivery.Firehose *FirehoseDelivery", "WorkerLogDelivery.S3 *S3LogDelivery", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -20469,7 +20469,7 @@ "backendSnapshot.OnDemandStreamCountLimit int `json:\"onDemandStreamCountLimit,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicies map[string]map[string]string `json:\"resourcePolicies,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "channelBufferSnapshot.Failed []failedChannelRecordSnapshot `json:\"failed,omitempty\"`", "channelBufferSnapshot.Records [][]byte `json:\"records,omitempty\"`", "channelFlushSnapshot.channel *Channel", @@ -20565,7 +20565,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.NextID int64 `json:\"next_id\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -20717,7 +20717,7 @@ "ZeppelinMonitoringConfigDesc.LogLevel string `json:\"LogLevel\"`", "backendSnapshot.FlinkStopSaves map[string]persistedStopSave `json:\"flink_stop_saves,omitempty\"`", "backendSnapshot.NextID int64 `json:\"next_id\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "persistedApplication.ApplicationARN string `json:\"ApplicationARN\"`", "persistedApplication.ApplicationDescription string `json:\"ApplicationDescription,omitempty\"`", "persistedApplication.ApplicationMode string `json:\"ApplicationMode,omitempty\"`", @@ -20822,7 +20822,7 @@ "Stream.PendingUntil time.Time", "Stream.Status string", "Stream.Tags map[string]string", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -20904,8 +20904,8 @@ "backendSnapshot.PendingMaterials map[string]map[string]serializedKeyMaterial `json:\"pending_key_materials,omitempty\"`", "backendSnapshot.Policies map[string]map[string]string `json:\"policies\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", - "handlerSnapshot.Backend json.RawMessage `json:\"backend,omitempty\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", + "handlerSnapshot.Backend jsontext.Value `json:\"backend,omitempty\"`", "handlerSnapshot.HandlerFormat int `json:\"handlerFormat\"`", "handlerSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`", "serializedKeyMaterial.PrivKeyDER []byte `json:\"priv_key_der,omitempty\"`", @@ -21028,7 +21028,7 @@ "backendSnapshot.Queries map[string]string `json:\"queries,omitempty\"`", "backendSnapshot.ResourceLFTags map[string][]LFTagPair `json:\"resourceLFTags\"`", "backendSnapshot.TableStorageOptimizers map[string][]StorageOptimizer `json:\"tableStorageOptimizers,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "transactionInfo.EndTime string `json:\"EndTime,omitempty\"`", "transactionInfo.ID string `json:\"-\"`", "transactionInfo.LastExtended string `json:\"LastExtended,omitempty\"`", @@ -21312,7 +21312,7 @@ "backendSnapshot.Layers map[string][]*LayerVersion `json:\"layers\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicyOverrides map[string]*ResourcePolicyOverride `json:\"resourcePolicyOverrides,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.VersionCounters map[string]int `json:\"versionCounters\"`", "backendSnapshot.Versions map[string][]*FunctionVersion `json:\"versions\"`", "functionConfigurationSnapshot.CreatedAt time.Time `json:\"createdAt,omitzero\"`", @@ -21794,7 +21794,7 @@ "backendSnapshot.NextAccessKeySeq int `json:\"nextAccessKeySeq\"`", "backendSnapshot.Partner *PartnerProfile `json:\"partner,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.VpcPeered bool `json:\"vpcPeered\"`", "instanceHealth.Reason string", "instanceHealth.State string" @@ -22011,7 +22011,7 @@ "backendSnapshot.RetrievalConfig *RetrievalConfiguration `json:\"retrievalConfig,omitempty\"`", "backendSnapshot.RevealConfig *RevealConfiguration `json:\"revealConfig,omitempty\"`", "backendSnapshot.Session *Session `json:\"session,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 3 @@ -22126,7 +22126,7 @@ "ProposalVote.proposalID string", "RemoveAction.MemberID string `json:\"memberId\"`", "VotingPolicy.ApprovalThresholdPolicy *ApprovalThresholdPolicy `json:\"approvalThresholdPolicy,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "proposalVoteDTO.ProposalID string `json:\"proposalID\"`", "proposalVoteDTO.Value *ProposalVote `json:\"value\"`" ], @@ -22147,7 +22147,7 @@ "Value.StringValue *string `json:\"StringValue,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -22187,7 +22187,7 @@ "UsageAllocation.Tags []Tag `json:\"Tags,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -22295,7 +22295,7 @@ "backendSnapshot.Certificates map[string]struct{} `json:\"certificates\"`", "backendSnapshot.Policy *Policy `json:\"policy,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "counterSnapshot.Counter queueJobCounter `json:\"counter\"`", "counterSnapshot.QueueArn string `json:\"queueArn\"`", @@ -23225,7 +23225,7 @@ "backendSnapshot.AccountKmsKeyID string `json:\"accountKmsKeyId\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ScheduleActions map[string][]*storedScheduleAction `json:\"scheduleActions\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "phase.label string", "phase.until time.Time", @@ -23449,7 +23449,7 @@ "StreamSelection.StreamOrder string `json:\"streamOrder,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedChannel.ARN string `json:\"arn\"`", "storedChannel.CreatedAt string `json:\"createdAt\"`", @@ -23637,7 +23637,7 @@ "TimeSignalMessage.SegmentationDescriptors []map[string]any", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedChannel.ARN string `json:\"arn\"`", "storedChannel.Audiences []string `json:\"audiences,omitempty\"`", @@ -23815,7 +23815,7 @@ "backendSnapshot.ReservedNodes map[string][]*ReservedNode `json:\"reservedNodes\"`", "backendSnapshot.Snapshots map[string][]*Snapshot `json:\"snapshots\"`", "backendSnapshot.SubnetGroups map[string][]*SubnetGroup `json:\"subnetGroups\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Users map[string][]*User `json:\"users\"`", "recurringChargeObject.RecurringChargeAmount float64 `json:\"RecurringChargeAmount,omitempty\"`", "recurringChargeObject.RecurringChargeFrequency string `json:\"RecurringChargeFrequency,omitempty\"`", @@ -24222,7 +24222,7 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ServiceInitialized bool `json:\"serviceInitialized\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "countPair.CreatedCount int64", "countPair.ModifiedCount int64", "ssmDocument.ActionName string", @@ -24344,7 +24344,7 @@ "WeeklyStartTime.TimeZone string `json:\"timeZone,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -24418,7 +24418,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Metrics map[string]map[string][]MetricDatum `json:\"metrics\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -24599,11 +24599,11 @@ "backendSnapshot.ClusterParameterOverrides map[string]map[string]ParameterValue `json:\"clusterParameterOverrides\"`", "backendSnapshot.ClusterRoles map[string]map[string][]string `json:\"clusterRoles\"`", "backendSnapshot.EventsLog map[string][]Event `json:\"eventsLog\"`", - "backendSnapshot.OpenCypherGraphs map[string]json.RawMessage `json:\"openCypherGraphs,omitempty\"`", + "backendSnapshot.OpenCypherGraphs map[string]jsontext.Value `json:\"openCypherGraphs,omitempty\"`", "backendSnapshot.ParameterOverrides map[string]map[string]ParameterValue `json:\"parameterOverrides\"`", "backendSnapshot.PendingMaintenanceActions map[string]map[string]PendingMaintenanceAction `json:\"pendingMaintenanceActions\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string][]Tag `json:\"tags\"`", "xmlDBClusterSnapshot.AllocatedStorage int `xml:\"AllocatedStorage,omitempty\"`", "xmlDBClusterSnapshot.ClusterCreateTime string `xml:\"ClusterCreateTime,omitempty\"`", @@ -24756,7 +24756,7 @@ "analysisReportRecord.Key string `json:\"key\"`", "analysisReportRecord.ReportTime time.Time `json:\"reportTime\"`", "analysisReportRecord.Type string `json:\"type\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "containerRecord.ARN string `json:\"arn\"`", "containerRecord.Configs []ContainerMonitoringConfiguration `json:\"configs\"`", "containerRecord.DeleteUntil time.Time `json:\"deleteUntil\"`", @@ -25100,7 +25100,7 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.OrgStatus *organizationStatus `json:\"orgStatus\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "networkResourceMetadata.Metadata map[string]string", "networkResourceMetadata.ResourceArn string", "organizationStatus.AccountStatusList []accountStatus", @@ -25137,7 +25137,7 @@ "Probe.Tags map[string]string `json:\"tags,omitempty\"`", "Probe.VpcID string `json:\"vpcId,omitempty\"`", "backendSnapshot.NextProbeSeq int64 `json:\"next_probe_seq\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -25167,7 +25167,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Seq int64 `json:\"seq\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -25179,7 +25179,7 @@ "Certificate.Etag string `json:\"etag\"`", "Certificate.ID string `json:\"id\"`", "Certificate.Intermediate string `json:\"intermediateCertificates,omitempty\"`", - "Certificate.Locks json.RawMessage `json:\"locks,omitempty\"`", + "Certificate.Locks jsontext.Value `json:\"locks,omitempty\"`", "Certificate.State string `json:\"state\"`", "Certificate.SubjectNames []string `json:\"subjectNames\"`", "Certificate.Tags Tags `json:\"tags\"`", @@ -25192,15 +25192,15 @@ "Deployment.Etag string `json:\"etag\"`", "Deployment.GatewayID string `json:\"gatewayId\"`", "Deployment.ID string `json:\"id\"`", - "Deployment.Locks json.RawMessage `json:\"locks,omitempty\"`", + "Deployment.Locks jsontext.Value `json:\"locks,omitempty\"`", "Deployment.PathPrefix string `json:\"pathPrefix\"`", - "Deployment.Spec json.RawMessage `json:\"specification\"`", + "Deployment.Spec jsontext.Value `json:\"specification\"`", "Deployment.State string `json:\"state\"`", "Deployment.Tags Tags `json:\"tags\"`", "Deployment.TimeCreated time.Time `json:\"timeCreated\"`", "Deployment.TimeUpdated time.Time `json:\"timeUpdated\"`", "Deployment.TransitionAt *time.Time `json:\"transitionAt,omitempty\"`", - "Gateway.CaBundles json.RawMessage `json:\"caBundles,omitempty\"`", + "Gateway.CaBundles jsontext.Value `json:\"caBundles,omitempty\"`", "Gateway.CertificateID string `json:\"certificateId,omitempty\"`", "Gateway.CompartmentID string `json:\"compartmentId\"`", "Gateway.DisplayName string `json:\"displayName\"`", @@ -25209,9 +25209,9 @@ "Gateway.Hostname string `json:\"hostname\"`", "Gateway.ID string `json:\"id\"`", "Gateway.IPMode string `json:\"ipMode,omitempty\"`", - "Gateway.Locks json.RawMessage `json:\"locks,omitempty\"`", + "Gateway.Locks jsontext.Value `json:\"locks,omitempty\"`", "Gateway.NSGs []string `json:\"networkSecurityGroupIds,omitempty\"`", - "Gateway.ResponseCache json.RawMessage `json:\"responseCache,omitempty\"`", + "Gateway.ResponseCache jsontext.Value `json:\"responseCache,omitempty\"`", "Gateway.State string `json:\"state\"`", "Gateway.SubnetID string `json:\"subnetId\"`", "Gateway.Tags Tags `json:\"tags\"`", @@ -25356,8 +25356,8 @@ "ClusterOptions.Admission *Admission `json:\"admissionControllerOptions,omitempty\"`", "ClusterOptions.IPFamilies []string `json:\"ipFamilies,omitempty\"`", "ClusterOptions.K8sNetwork *NetworkConfig `json:\"kubernetesNetworkConfig,omitempty\"`", - "ClusterOptions.OIDCAuth json.RawMessage `json:\"openIdConnectTokenAuthenticationConfig,omitempty\"`", - "ClusterOptions.OIDCDiscovery json.RawMessage `json:\"openIdConnectDiscovery,omitempty\"`", + "ClusterOptions.OIDCAuth jsontext.Value `json:\"openIdConnectTokenAuthenticationConfig,omitempty\"`", + "ClusterOptions.OIDCDiscovery jsontext.Value `json:\"openIdConnectDiscovery,omitempty\"`", "ClusterOptions.PersistentVolume *TagCfg `json:\"persistentVolumeConfig,omitempty\"`", "ClusterOptions.ServiceLb *TagCfg `json:\"serviceLbConfig,omitempty\"`", "ClusterOptions.ServiceLbSubnetID []string `json:\"serviceLbSubnetIds,omitempty\"`", @@ -25393,9 +25393,9 @@ "NodeConfig.Size int `json:\"size\"`", "NodePool.ClusterID string `json:\"clusterId\"`", "NodePool.CompartmentID string `json:\"compartmentId\"`", - "NodePool.Cycling json.RawMessage `json:\"cycling,omitempty\"`", + "NodePool.Cycling jsontext.Value `json:\"cycling,omitempty\"`", "NodePool.Etag string `json:\"etag\"`", - "NodePool.Eviction json.RawMessage `json:\"eviction,omitempty\"`", + "NodePool.Eviction jsontext.Value `json:\"eviction,omitempty\"`", "NodePool.ID string `json:\"id\"`", "NodePool.ImageName string `json:\"imageName,omitempty\"`", "NodePool.Labels []KeyValue `json:\"labels,omitempty\"`", @@ -25420,7 +25420,7 @@ "Placement.AvailabilityDom string `json:\"availabilityDomain\"`", "Placement.CapacityReserveID string `json:\"capacityReservationId,omitempty\"`", "Placement.FaultDomains []string `json:\"faultDomains,omitempty\"`", - "Placement.PreemptibleConfig json.RawMessage `json:\"preemptibleNodeConfig,omitempty\"`", + "Placement.PreemptibleConfig jsontext.Value `json:\"preemptibleNodeConfig,omitempty\"`", "Placement.SubnetID string `json:\"subnetId\"`", "PodNet.CniType string `json:\"cniType\"`", "PodNet.MaxPodsPerNode *int `json:\"maxPodsPerNode,omitempty\"`", @@ -25471,7 +25471,7 @@ "Container.NoResourcePrn bool `json:\"isResourcePrincipalDisabled\"`", "Container.Resource *ResourceConfig `json:\"resourceConfig,omitempty\"`", "Container.RestartCount int `json:\"restartCount\"`", - "Container.Security json.RawMessage `json:\"securityContext,omitempty\"`", + "Container.Security jsontext.Value `json:\"securityContext,omitempty\"`", "Container.State string `json:\"lifecycleState\"`", "Container.Terminated *time.Time `json:\"timeTerminated,omitempty\"`", "Container.Updated *time.Time `json:\"timeUpdated,omitempty\"`", @@ -25511,7 +25511,7 @@ "Instance.PullSecrets []PullSecret `json:\"imagePullSecrets,omitempty\"`", "Instance.Restart string `json:\"containerRestartPolicy\"`", "Instance.RetryToken string `json:\"retryToken,omitempty\"`", - "Instance.Security json.RawMessage `json:\"securityContext,omitempty\"`", + "Instance.Security jsontext.Value `json:\"securityContext,omitempty\"`", "Instance.Shape string `json:\"shape\"`", "Instance.ShapeConfig ShapeConfig `json:\"shapeConfig\"`", "Instance.State string `json:\"lifecycleState\"`", @@ -25898,7 +25898,7 @@ "Function.Config map[string]string `json:\"config,omitempty\"`", "Function.Detached *int `json:\"detachedModeTimeoutInSeconds,omitempty\"`", "Function.Etag string `json:\"etag\"`", - "Function.Failure json.RawMessage `json:\"failureDestination,omitempty\"`", + "Function.Failure jsontext.Value `json:\"failureDestination,omitempty\"`", "Function.ID string `json:\"id\"`", "Function.Image string `json:\"image\"`", "Function.ImageDigest string `json:\"imageDigest,omitempty\"`", @@ -25907,7 +25907,7 @@ "Function.Provisioned Concurrency `json:\"provisionedConcurrencyConfig\"`", "Function.Seq int64 `json:\"seq\"`", "Function.State string `json:\"state\"`", - "Function.Success json.RawMessage `json:\"successDestination,omitempty\"`", + "Function.Success jsontext.Value `json:\"successDestination,omitempty\"`", "Function.Tags Tags `json:\"tags\"`", "Function.TimeCreated time.Time `json:\"timeCreated\"`", "Function.TimeUpdated time.Time `json:\"timeUpdated\"`", @@ -26201,7 +26201,7 @@ "AgentConfig.ID string `json:\"id\"`", "AgentConfig.Name string `json:\"displayName\"`", "AgentConfig.Seq int64 `json:\"seq\"`", - "AgentConfig.ServiceConfig json.RawMessage `json:\"serviceConfiguration\"`", + "AgentConfig.ServiceConfig jsontext.Value `json:\"serviceConfiguration\"`", "AgentConfig.State string `json:\"state\"`", "AgentConfig.Tags Tags `json:\"tags\"`", "AgentConfig.TimeCreated time.Time `json:\"timeCreated\"`", @@ -26285,7 +26285,7 @@ "Alarm.MetricCompartmentID string `json:\"metricCompartmentId\"`", "Alarm.Name string `json:\"displayName\"`", "Alarm.Namespace string `json:\"namespace\"`", - "Alarm.Overrides []json.RawMessage `json:\"overrides,omitempty\"`", + "Alarm.Overrides []jsontext.Value `json:\"overrides,omitempty\"`", "Alarm.PendingDuration string `json:\"pendingDuration\"`", "Alarm.PendingSince *time.Time `json:\"pendingSince,omitempty\"`", "Alarm.PerDimension *bool `json:\"isNotificationsPerMetricDimensionEnabled,omitempty\"`", @@ -26350,7 +26350,7 @@ "Backup.TimeCreated time.Time `json:\"timeCreated\"`", "Backup.TimeUpdated time.Time `json:\"timeUpdated\"`", "Backup.TransitionAt *time.Time `json:\"transitionAt,omitempty\"`", - "BackupPolicy.CopyPolicies json.RawMessage `json:\"copyPolicies,omitempty\"`", + "BackupPolicy.CopyPolicies jsontext.Value `json:\"copyPolicies,omitempty\"`", "BackupPolicy.IsEnabled bool `json:\"isEnabled\"`", "BackupPolicy.PitrEnabled bool `json:\"pitrEnabled\"`", "BackupPolicy.RetentionInDays int `json:\"retentionInDays\"`", @@ -26373,7 +26373,7 @@ "Channel.TimeCreated time.Time `json:\"timeCreated\"`", "Channel.TimeUpdated time.Time `json:\"timeUpdated\"`", "Channel.TransitionAt *time.Time `json:\"transitionAt,omitempty\"`", - "ChannelSource.Extra map[string]json.RawMessage `json:\"extra,omitempty\"`", + "ChannelSource.Extra map[string]jsontext.Value `json:\"extra,omitempty\"`", "ChannelSource.HasPassword bool `json:\"hasPassword\"`", "ChannelSource.Hostname string `json:\"hostname\"`", "ChannelSource.Port int `json:\"port\"`", @@ -26384,7 +26384,7 @@ "ChannelTarget.ChannelName string `json:\"channelName\"`", "ChannelTarget.DBSystemID string `json:\"dbSystemId\"`", "ChannelTarget.DelayInSeconds int `json:\"delayInSeconds\"`", - "ChannelTarget.Filters json.RawMessage `json:\"filters,omitempty\"`", + "ChannelTarget.Filters jsontext.Value `json:\"filters,omitempty\"`", "ChannelTarget.TablesWithoutPrimaryKeyHandling string `json:\"tablesWithoutPrimaryKeyHandling\"`", "ChannelTarget.TargetType string `json:\"targetType\"`", "Configuration.CompartmentID string `json:\"compartmentId\"`", @@ -26421,7 +26421,7 @@ "DBSystem.EngineHost string `json:\"engineHost,omitempty\"`", "DBSystem.EnginePort int `json:\"enginePort,omitempty\"`", "DBSystem.Etag string `json:\"etag\"`", - "DBSystem.Extra map[string]json.RawMessage `json:\"extra,omitempty\"`", + "DBSystem.Extra map[string]jsontext.Value `json:\"extra,omitempty\"`", "DBSystem.FD string `json:\"faultDomain\"`", "DBSystem.HA bool `json:\"isHighlyAvailable\"`", "DBSystem.Hostname string `json:\"hostnameLabel,omitempty\"`", @@ -26474,7 +26474,7 @@ "ocinosql": { "fields": [ "Column.AsUUID bool `json:\"asUuid,omitempty\"`", - "Column.Default json.RawMessage `json:\"default,omitempty\"`", + "Column.Default jsontext.Value `json:\"default,omitempty\"`", "Column.Gen bool `json:\"generated,omitempty\"`", "Column.Name string `json:\"name\"`", "Column.NotNull bool `json:\"notNull,omitempty\"`", @@ -26497,7 +26497,7 @@ "Limits.StorageGB int `json:\"storageGb\"`", "Limits.WriteUnits int `json:\"writeUnits\"`", "Row.Expire *time.Time `json:\"expire,omitempty\"`", - "Row.Value json.RawMessage `json:\"value\"`", + "Row.Value jsontext.Value `json:\"value\"`", "Table.AutoReclaim bool `json:\"autoReclaimable,omitempty\"`", "Table.Columns []Column `json:\"columns\"`", "Table.CompartmentID string `json:\"compartmentId\"`", @@ -26877,7 +26877,7 @@ "Secret.Name string `json:\"name\"`", "Secret.Pending int64 `json:\"pending\"`", "Secret.Previous int64 `json:\"previous\"`", - "Secret.Rules []json.RawMessage `json:\"rules,omitempty\"`", + "Secret.Rules []jsontext.Value `json:\"rules,omitempty\"`", "Secret.Seq int64 `json:\"seq\"`", "Secret.State string `json:\"state\"`", "Secret.TimeCreated time.Time `json:\"timeCreated\"`", @@ -27270,7 +27270,7 @@ "backendSnapshot.ReadSetBytes map[string]map[string][]byte `json:\"readSetBytes\"`", "backendSnapshot.ReferenceBytes map[string]map[string][]byte `json:\"referenceBytes\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "backendSnapshot.UploadPartData map[string]map[string]map[string]map[int][]byte `json:\"uploadPartData\"`", "backendSnapshot.UploadParts map[string]map[string][]*ReadSetUploadPart `json:\"uploadParts\"`" @@ -27340,7 +27340,7 @@ "CollectionGroupCapacityLimits.MaxSearchCapacityInOCU *float64 `json:\"maxSearchCapacityInOCU,omitempty\"`", "CollectionGroupCapacityLimits.MinIndexingCapacityInOCU *float64 `json:\"minIndexingCapacityInOCU,omitempty\"`", "CollectionGroupCapacityLimits.MinSearchCapacityInOCU *float64 `json:\"minSearchCapacityInOCU,omitempty\"`", - "DataSource.DataSourceType json.RawMessage `json:\"dataSourceType,omitempty\"`", + "DataSource.DataSourceType jsontext.Value `json:\"dataSourceType,omitempty\"`", "DataSource.Description string `json:\"description\"`", "DataSource.DomainName string `json:\"-\"`", "DataSource.Name string `json:\"name\"`", @@ -27352,7 +27352,7 @@ "DataSourceAttachment.Status string `json:\"status\"`", "DirectQueryDataSource.DataSourceAccessPolicy string `json:\"dataSourceAccessPolicy,omitempty\"`", "DirectQueryDataSource.DataSourceArn string `json:\"dataSourceArn\"`", - "DirectQueryDataSource.DataSourceType json.RawMessage `json:\"dataSourceType,omitempty\"`", + "DirectQueryDataSource.DataSourceType jsontext.Value `json:\"dataSourceType,omitempty\"`", "DirectQueryDataSource.Description string `json:\"description\"`", "DirectQueryDataSource.Name string `json:\"name\"`", "DirectQueryDataSource.OpenSearchArns []string `json:\"openSearchArns\"`", @@ -27645,7 +27645,7 @@ "backendSnapshot.SlCollGroupCounter int `json:\"slCollGroupCounter\"`", "backendSnapshot.SlSecConfigCounter int `json:\"slSecConfigCounter\"`", "backendSnapshot.SlVpcEndpointCounter int `json:\"slVpcEndpointCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.UpgradeHistory map[string][]*UpgradeHistory `json:\"upgradeHistory\"`", "backendSnapshot.VpcAuthorizations map[string][]AuthorizedPrincipal `json:\"vpcAuthorizations\"`", "backendSnapshot.VpcEndpointCounter int `json:\"vpcEndpointCounter\"`", @@ -27655,7 +27655,7 @@ "dataSourceAttachmentSnapshot.CreatedAt time.Time `json:\"createdAt,omitzero\"`", "dataSourceAttachmentSnapshot.DataSourceArn string `json:\"dataSourceArn\"`", "dataSourceAttachmentSnapshot.Status string `json:\"status\"`", - "dataSourceSnapshot.DataSourceType json.RawMessage `json:\"dataSourceType,omitempty\"`", + "dataSourceSnapshot.DataSourceType jsontext.Value `json:\"dataSourceType,omitempty\"`", "dataSourceSnapshot.Description string `json:\"description\"`", "dataSourceSnapshot.DomainName string `json:\"domainName\"`", "dataSourceSnapshot.Name string `json:\"name\"`", @@ -27748,17 +27748,17 @@ "LayerSettings.Attributes map[string]string `json:\"Attributes,omitempty\"`", "LayerSettings.AutoAssignElasticIps *bool `json:\"AutoAssignElasticIps,omitempty\"`", "LayerSettings.AutoAssignPublicIps *bool `json:\"AutoAssignPublicIps,omitempty\"`", - "LayerSettings.CloudWatchLogsConfiguration json.RawMessage `json:\"CloudWatchLogsConfiguration,omitempty\"`", + "LayerSettings.CloudWatchLogsConfiguration jsontext.Value `json:\"CloudWatchLogsConfiguration,omitempty\"`", "LayerSettings.CustomInstanceProfileArn string `json:\"CustomInstanceProfileArn,omitempty\"`", "LayerSettings.CustomJSON string `json:\"CustomJson,omitempty\"`", - "LayerSettings.CustomRecipes json.RawMessage `json:\"CustomRecipes,omitempty\"`", + "LayerSettings.CustomRecipes jsontext.Value `json:\"CustomRecipes,omitempty\"`", "LayerSettings.CustomSecurityGroupIDs []string `json:\"CustomSecurityGroupIds,omitempty\"`", "LayerSettings.EnableAutoHealing *bool `json:\"EnableAutoHealing,omitempty\"`", "LayerSettings.InstallUpdatesOnBoot *bool `json:\"InstallUpdatesOnBoot,omitempty\"`", - "LayerSettings.LifecycleEventConfiguration json.RawMessage `json:\"LifecycleEventConfiguration,omitempty\"`", + "LayerSettings.LifecycleEventConfiguration jsontext.Value `json:\"LifecycleEventConfiguration,omitempty\"`", "LayerSettings.Packages []string `json:\"Packages,omitempty\"`", "LayerSettings.UseEbsOptimizedInstances *bool `json:\"UseEbsOptimizedInstances,omitempty\"`", - "LayerSettings.VolumeConfigurations json.RawMessage `json:\"VolumeConfigurations,omitempty\"`", + "LayerSettings.VolumeConfigurations jsontext.Value `json:\"VolumeConfigurations,omitempty\"`", "ScalingParameters.CPUThreshold float64 `json:\"CpuThreshold\"`", "ScalingParameters.IgnoreMetricsTime int32 `json:\"IgnoreMetricsTime\"`", "ScalingParameters.InstanceCount int32 `json:\"InstanceCount\"`", @@ -27769,7 +27769,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.MyUserProfileSSHKey string `json:\"myUserProfileSshKey,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedApp.AppID string `json:\"appId\"`", "storedApp.Arn string `json:\"arn\"`", @@ -28004,7 +28004,7 @@ "backendSnapshot.ResourcePolicy *ResourcePolicy `json:\"resourcePolicy,omitempty\"`", "backendSnapshot.Root *Root `json:\"root\"`", "backendSnapshot.StatusCounter int `json:\"statusCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "backendSnapshot.TargetPolicies map[string][]string `json:\"targetPolicies\"`", "delegatedAdminSnapshot.Admin DelegatedAdmin `json:\"admin\"`", @@ -28166,7 +28166,7 @@ "VpcInformation.VpcID string `json:\"vpcId,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "runningInstance.AccountID string", "runningInstance.AssetID string", "runningInstance.InstanceID string", @@ -28391,7 +28391,7 @@ "TrainingDataConfig.IncludedDatasetColumns map[string][]string `json:\"includedDatasetColumns,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "lifecycle.delay time.Duration", "storedFeatureTransformation.ARN string", @@ -28605,9 +28605,9 @@ "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SegmentVersions map[string][]*Segment `json:\"segmentVersions\"`", "backendSnapshot.SentMessages map[string]int `json:\"sentMessages\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TemplateActiveVersion map[string]string `json:\"templateActiveVersion\"`", - "backendSnapshot.TemplateVersionData map[string]map[string]json.RawMessage `json:\"templateVersionData\"`", + "backendSnapshot.TemplateVersionData map[string]map[string]jsontext.Value `json:\"templateVersionData\"`", "backendSnapshot.TemplateVersionHistory map[string][]templateVersionItem `json:\"templateVersionHistory\"`", "campaignActivity.ApplicationID string `json:\"ApplicationId\"`", "campaignActivity.CampaignID string `json:\"CampaignId\"`", @@ -28862,7 +28862,7 @@ "TimestreamSingleMeasureMapping.MeasureValueType string `json:\"MeasureValueType,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "enrichmentCounter.Count int64 `json:\"count\"`", "enrichmentCounter.Name string `json:\"name\"`" ], @@ -28899,7 +28899,7 @@ "SynthesisOptions.VoiceID string", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 2 }, @@ -28987,7 +28987,7 @@ "backendSnapshot.RoleMemberships map[string]bool `json:\"roleMemberships\"`", "backendSnapshot.SPICECapacity map[string]string `json:\"spiceCapacity\"`", "backendSnapshot.SelfUpgradeConfig map[string]string `json:\"selfUpgradeConfig\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "backendSnapshot.UserCustomPermissions map[string]string `json:\"userCustomPermissions\"`", "storedAccountCustomization.DefaultEmailCustomizationTemplate string `json:\"defaultEmailCustomizationTemplate,omitempty\"`", @@ -29442,7 +29442,7 @@ "backendSnapshot.Associations []*ResourceShareAssociation `json:\"associations\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SharePermissions map[string]map[string]int32 `json:\"sharePermissions\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -29890,7 +29890,7 @@ "backendSnapshot.ProxyTargets map[string][]DBProxyTarget `json:\"proxyTargets\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SnapshotTenantDatabases map[string][]*DBSnapshotTenantDatabase `json:\"snapshotTenantDatabases\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string][]Tag `json:\"tags\"`", "xmlDBClusterSnapshot.AllocatedStorage int `xml:\"AllocatedStorage,omitempty\"`", "xmlDBClusterSnapshot.ClusterCreateTime string `xml:\"ClusterCreateTime,omitempty\"`", @@ -30256,13 +30256,13 @@ "ServerlessScheduledAction.EndTime time.Time `json:\"endTime,omitzero\"`", "ServerlessScheduledAction.NamespaceName string `json:\"namespaceName,omitempty\"`", "ServerlessScheduledAction.RoleArn string `json:\"roleArn,omitempty\"`", - "ServerlessScheduledAction.Schedule json.RawMessage `json:\"schedule,omitempty\"`", + "ServerlessScheduledAction.Schedule jsontext.Value `json:\"schedule,omitempty\"`", "ServerlessScheduledAction.ScheduledActionDescription string `json:\"scheduledActionDescription,omitempty\"`", "ServerlessScheduledAction.ScheduledActionName string `json:\"scheduledActionName\"`", "ServerlessScheduledAction.ScheduledActionUUID string `json:\"scheduledActionUuid,omitempty\"`", "ServerlessScheduledAction.StartTime time.Time `json:\"startTime,omitzero\"`", "ServerlessScheduledAction.State string `json:\"state\"`", - "ServerlessScheduledAction.TargetAction json.RawMessage `json:\"targetAction,omitempty\"`", + "ServerlessScheduledAction.TargetAction jsontext.Value `json:\"targetAction,omitempty\"`", "ServerlessSnapshot.AccountsWithRestoreAccess []string `json:\"accountsWithRestoreAccess,omitempty\"`", "ServerlessSnapshot.AdminUsername string `json:\"adminUsername,omitempty\"`", "ServerlessSnapshot.NamespaceArn string `json:\"namespaceArn\"`", @@ -30379,7 +30379,7 @@ "backendSnapshot.LoggingStatuses map[string]*LoggingStatus `json:\"loggingStatuses\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SnapshotCopyConfigs map[string]*SnapshotCopyConfig `json:\"snapshotCopyConfigs\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "slResourceTagSet.ResourceArn string `json:\"resourceArn\"`", "slResourceTagSet.Tags map[string]string `json:\"tags\"`", "xmlAccountWithRestoreAccess.AccountAlias string `xml:\"AccountAlias,omitempty\"`", @@ -30491,7 +30491,7 @@ "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.DatasetEntries map[string][]string `json:\"datasetEntries\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "storedAsyncJob.ClientRequestToken string `json:\"clientRequestToken,omitempty\"`", "storedAsyncJob.CollectionID string `json:\"collectionId\"`", @@ -30755,7 +30755,7 @@ "TerraformSource.S3StateFileURL string", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -30797,7 +30797,7 @@ "View.Region string `json:\"region\"`", "View.Scope string `json:\"scope\"`", "View.Tags map[string]string `json:\"tags,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -30824,7 +30824,7 @@ "backendSnapshot.GroupResources map[string]map[string][]string `json:\"groupResources\"`", "backendSnapshot.GroupingStatuses map[string]map[string][]GroupingStatusItem `json:\"groupingStatuses\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "groupSnapshot.ARN string `json:\"arn\"`", "groupSnapshot.ApplicationTag map[string]string `json:\"applicationTag,omitempty\"`", "groupSnapshot.Criticality int `json:\"criticality,omitempty\"`", @@ -30841,7 +30841,7 @@ "fields": [ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "reportCreationState.Region string `json:\"-\"`", "reportCreationState.S3Location string `json:\"s3Location\"`", "reportCreationState.StartDate string `json:\"startDate\"`", @@ -30910,7 +30910,7 @@ "TrustAnchorSource.SourceType string `json:\"sourceType\"`", "backendSnapshot.AttributeMappings map[string]map[string][]AttributeMapping `json:\"attributeMappings\"`", "backendSnapshot.NotificationSettings map[string]map[string][]NotificationSetting `json:\"notificationSettings\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string][]TagEntry `json:\"tags\"`" ], "version": 2 @@ -31031,7 +31031,7 @@ "TrafficPolicyInstance.TrafficPolicyVersion int32 `json:\"trafficPolicyVersion\"`", "VPCAssociationAuthorization.VPCID string `json:\"vpcId\"`", "VPCAssociationAuthorization.VPCRegion string `json:\"vpcRegion\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]*svcTags.Tags `json:\"tags,omitempty\"`", "backendSnapshot.TrafficPolicies map[string][]*TrafficPolicy `json:\"trafficPolicies,omitempty\"`", "backendSnapshot.VPCAssocAuthorizations map[string][]VPCAssociationAuthorization `json:\"vpcAssocAuthorizations,omitempty\"`", @@ -31083,7 +31083,7 @@ "SafetyRule.Targets []string `json:\"targets,omitempty\"`", "SafetyRule.Threshold int32 `json:\"threshold\"`", "SafetyRule.WaitPeriodMs int32 `json:\"waitPeriodMs\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tokens map[string]string `json:\"tokens,omitempty\"`" ], "version": 1 @@ -31123,7 +31123,7 @@ "ResourceSet.Type string `json:\"type\"`", "TargetResource.NLB *NLBResource `json:\"nLBResource,omitempty\"`", "TargetResource.R53 *R53Resource `json:\"r53Resource,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -31304,7 +31304,7 @@ "backendSnapshot.QueryLogConfigPolicies map[string]map[string]string `json:\"queryLogConfigPolicies\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResolverRulePolicies map[string]map[string]string `json:\"resolverRulePolicies\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string][]svcTags.KV `json:\"tags\"`" ], "version": 1 @@ -31368,7 +31368,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Seq int64 `json:\"seq\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -31523,7 +31523,7 @@ "StoredPart.PartNumber int32 `json:\"partNumber\"`", "StoredPart.Size int64 `json:\"size\"`", "backendSnapshot.DefaultRegion string `json:\"defaultRegion\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string][]types.Tag `json:\"tags\"`", "keyIndex.chunks [][]string", "multipartObjectHeaders.CacheControl string `json:\"cacheControl,omitempty\"`", @@ -31659,7 +31659,7 @@ "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags\"`", "backendSnapshot.StorageLensConfigTags map[string]TagSet `json:\"storageLensConfigTags\"`", "backendSnapshot.StorageLensConfigs map[string]string `json:\"storageLensConfigs\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "mrapRequestSnapshot.Request MultiRegionAccessPointRequest `json:\"request\"`", "mrapRequestSnapshot.Token string `json:\"token\"`" ], @@ -31720,7 +31720,7 @@ "SyncConfiguration.ImportDataRules []ImportDataRule `json:\"importDataRules\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -31777,7 +31777,7 @@ "TableReplicationConfig.VersionToken string `json:\"versionToken\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 2 @@ -31813,7 +31813,7 @@ "Vector.Meta map[string]any `json:\"meta,omitempty\"`", "VectorSet.Key string `json:\"key\"`", "VectorSet.Vectors map[string]*Vector `json:\"vectors\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -31823,12 +31823,12 @@ "AIBenchmarkJob.AIBenchmarkJobName string `json:\"AIBenchmarkJobName\"`", "AIBenchmarkJob.AIBenchmarkJobStatus string `json:\"AIBenchmarkJobStatus\"`", "AIBenchmarkJob.AIWorkloadConfigIdentifier string `json:\"AIWorkloadConfigIdentifier\"`", - "AIBenchmarkJob.BenchmarkTarget json.RawMessage `json:\"BenchmarkTarget\"`", + "AIBenchmarkJob.BenchmarkTarget jsontext.Value `json:\"BenchmarkTarget\"`", "AIBenchmarkJob.CreationTime time.Time `json:\"CreationTime\"`", "AIBenchmarkJob.EndTime *time.Time `json:\"EndTime,omitempty\"`", "AIBenchmarkJob.FailureReason string `json:\"FailureReason,omitempty\"`", - "AIBenchmarkJob.NetworkConfig json.RawMessage `json:\"NetworkConfig,omitempty\"`", - "AIBenchmarkJob.OutputConfig json.RawMessage `json:\"OutputConfig\"`", + "AIBenchmarkJob.NetworkConfig jsontext.Value `json:\"NetworkConfig,omitempty\"`", + "AIBenchmarkJob.OutputConfig jsontext.Value `json:\"OutputConfig\"`", "AIBenchmarkJob.RoleArn string `json:\"RoleArn\"`", "AIBenchmarkJob.StartTime *time.Time `json:\"StartTime,omitempty\"`", "AIBenchmarkJob.Tags map[string]string `json:\"Tags,omitempty\"`", @@ -31838,16 +31838,16 @@ "AIRecommendationJob.AIRecommendationJobName string `json:\"AIRecommendationJobName\"`", "AIRecommendationJob.AIRecommendationJobStatus string `json:\"AIRecommendationJobStatus\"`", "AIRecommendationJob.AIWorkloadConfigIdentifier string `json:\"AIWorkloadConfigIdentifier\"`", - "AIRecommendationJob.AdapterSource json.RawMessage `json:\"AdapterSource,omitempty\"`", - "AIRecommendationJob.ComputeSpec json.RawMessage `json:\"ComputeSpec,omitempty\"`", + "AIRecommendationJob.AdapterSource jsontext.Value `json:\"AdapterSource,omitempty\"`", + "AIRecommendationJob.ComputeSpec jsontext.Value `json:\"ComputeSpec,omitempty\"`", "AIRecommendationJob.CreationTime time.Time `json:\"CreationTime\"`", "AIRecommendationJob.EndTime *time.Time `json:\"EndTime,omitempty\"`", "AIRecommendationJob.FailureReason string `json:\"FailureReason,omitempty\"`", - "AIRecommendationJob.InferenceSpecification json.RawMessage `json:\"InferenceSpecification,omitempty\"`", - "AIRecommendationJob.ModelSource json.RawMessage `json:\"ModelSource\"`", + "AIRecommendationJob.InferenceSpecification jsontext.Value `json:\"InferenceSpecification,omitempty\"`", + "AIRecommendationJob.ModelSource jsontext.Value `json:\"ModelSource\"`", "AIRecommendationJob.OptimizeModel *bool `json:\"OptimizeModel,omitempty\"`", - "AIRecommendationJob.OutputConfig json.RawMessage `json:\"OutputConfig\"`", - "AIRecommendationJob.PerformanceTarget json.RawMessage `json:\"PerformanceTarget,omitempty\"`", + "AIRecommendationJob.OutputConfig jsontext.Value `json:\"OutputConfig\"`", + "AIRecommendationJob.PerformanceTarget jsontext.Value `json:\"PerformanceTarget,omitempty\"`", "AIRecommendationJob.RoleArn string `json:\"RoleArn\"`", "AIRecommendationJob.StartTime *time.Time `json:\"StartTime,omitempty\"`", "AIRecommendationJob.Tags map[string]string `json:\"Tags,omitempty\"`", @@ -31856,9 +31856,9 @@ "AIWorkloadConfig.AIWorkloadConfigArn string `json:\"AIWorkloadConfigArn\"`", "AIWorkloadConfig.AIWorkloadConfigName string `json:\"AIWorkloadConfigName\"`", "AIWorkloadConfig.CreationTime time.Time `json:\"CreationTime\"`", - "AIWorkloadConfig.DatasetConfig json.RawMessage `json:\"DatasetConfig,omitempty\"`", + "AIWorkloadConfig.DatasetConfig jsontext.Value `json:\"DatasetConfig,omitempty\"`", "AIWorkloadConfig.Tags map[string]string `json:\"Tags,omitempty\"`", - "AIWorkloadConfig.WorkloadSpec json.RawMessage `json:\"AIWorkloadConfigs,omitempty\"`", + "AIWorkloadConfig.WorkloadSpec jsontext.Value `json:\"AIWorkloadConfigs,omitempty\"`", "AIWorkloadConfig.{MarshalJSON}", "AIWorkloadConfig.{UnmarshalJSON}", "AcceleratorPartitionConfig.Count int32 `json:\"Count\"`", @@ -31884,11 +31884,11 @@ "Algorithm.AlgorithmStatusDetails AlgorithmStatusDetails `json:\"AlgorithmStatusDetails\"`", "Algorithm.CertifyForMarketplace bool `json:\"CertifyForMarketplace,omitempty\"`", "Algorithm.CreationTime time.Time `json:\"CreationTime\"`", - "Algorithm.InferenceSpecification json.RawMessage `json:\"InferenceSpecification,omitempty\"`", + "Algorithm.InferenceSpecification jsontext.Value `json:\"InferenceSpecification,omitempty\"`", "Algorithm.ProductID string `json:\"ProductId,omitempty\"`", "Algorithm.Tags map[string]string `json:\"Tags,omitempty\"`", - "Algorithm.TrainingSpecification json.RawMessage `json:\"TrainingSpecification,omitempty\"`", - "Algorithm.ValidationSpecification json.RawMessage `json:\"ValidationSpecification,omitempty\"`", + "Algorithm.TrainingSpecification jsontext.Value `json:\"TrainingSpecification,omitempty\"`", + "Algorithm.ValidationSpecification jsontext.Value `json:\"ValidationSpecification,omitempty\"`", "AlgorithmSpecification.AlgorithmName string `json:\"AlgorithmName,omitempty\"`", "AlgorithmSpecification.ContainerArguments []string `json:\"ContainerArguments,omitempty\"`", "AlgorithmSpecification.ContainerEntrypoint []string `json:\"ContainerEntrypoint,omitempty\"`", @@ -31907,17 +31907,17 @@ "App.CreationTime time.Time `json:\"CreationTime\"`", "App.DomainID string `json:\"DomainId\"`", "App.RecoveryMode bool `json:\"RecoveryMode,omitempty\"`", - "App.ResourceSpec json.RawMessage `json:\"ResourceSpec,omitempty\"`", + "App.ResourceSpec jsontext.Value `json:\"ResourceSpec,omitempty\"`", "App.SpaceName string `json:\"SpaceName,omitempty\"`", "App.Status string `json:\"Status\"`", "App.Tags map[string]string `json:\"Tags,omitempty\"`", "App.UserProfileName string `json:\"UserProfileName,omitempty\"`", "AppImageConfig.AppImageConfigArn string `json:\"AppImageConfigArn\"`", "AppImageConfig.AppImageConfigName string `json:\"AppImageConfigName\"`", - "AppImageConfig.CodeEditorAppImageConfig json.RawMessage `json:\"CodeEditorAppImageConfig,omitempty\"`", + "AppImageConfig.CodeEditorAppImageConfig jsontext.Value `json:\"CodeEditorAppImageConfig,omitempty\"`", "AppImageConfig.CreationTime time.Time `json:\"CreationTime\"`", - "AppImageConfig.JupyterLabAppImageConfig json.RawMessage `json:\"JupyterLabAppImageConfig,omitempty\"`", - "AppImageConfig.KernelGatewayImageConfig json.RawMessage `json:\"KernelGatewayImageConfig,omitempty\"`", + "AppImageConfig.JupyterLabAppImageConfig jsontext.Value `json:\"JupyterLabAppImageConfig,omitempty\"`", + "AppImageConfig.KernelGatewayImageConfig jsontext.Value `json:\"KernelGatewayImageConfig,omitempty\"`", "AppImageConfig.LastModifiedTime time.Time `json:\"LastModifiedTime\"`", "AppImageConfig.Tags map[string]string `json:\"Tags,omitempty\"`", "AppImageConfig.{MarshalJSON}", @@ -31975,7 +31975,7 @@ "AutoMLJob.AutoMLJobObjective *AutoMLJobObjective `json:\"AutoMLJobObjective,omitempty\"`", "AutoMLJob.AutoMLJobSecondaryStatus string `json:\"AutoMLJobSecondaryStatus\"`", "AutoMLJob.AutoMLJobStatus string `json:\"AutoMLJobStatus\"`", - "AutoMLJob.AutoMLProblemTypeConfig json.RawMessage `json:\"AutoMLProblemTypeConfig,omitempty\"`", + "AutoMLJob.AutoMLProblemTypeConfig jsontext.Value `json:\"AutoMLProblemTypeConfig,omitempty\"`", "AutoMLJob.CreationTime time.Time `json:\"CreationTime\"`", "AutoMLJob.DataSplitConfig *AutoMLDataSplitConfig `json:\"DataSplitConfig,omitempty\"`", "AutoMLJob.EndTime *time.Time `json:\"EndTime,omitempty\"`", @@ -32001,8 +32001,8 @@ "AutoMLSecurityConfig.EnableInterContainerTrafficEncryption *bool `json:\"EnableInterContainerTrafficEncryption,omitempty\"`", "AutoMLSecurityConfig.VolumeKmsKeyID string `json:\"VolumeKmsKeyId,omitempty\"`", "AutoMLSecurityConfig.VpcConfig *VpcConfig `json:\"VpcConfig,omitempty\"`", - "AutoMLV1Options.CandidateGenerationConfig json.RawMessage `json:\"CandidateGenerationConfig,omitempty\"`", - "AutoMLV1Options.CompletionCriteria json.RawMessage `json:\"CompletionCriteria,omitempty\"`", + "AutoMLV1Options.CandidateGenerationConfig jsontext.Value `json:\"CandidateGenerationConfig,omitempty\"`", + "AutoMLV1Options.CompletionCriteria jsontext.Value `json:\"CompletionCriteria,omitempty\"`", "AutoMLV1Options.GenerateCandidateDefinitionsOnly bool `json:\"GenerateCandidateDefinitionsOnly,omitempty\"`", "AutoMLV1Options.Mode string `json:\"Mode,omitempty\"`", "AutoMLV1Options.ProblemType string `json:\"ProblemType,omitempty\"`", @@ -32128,7 +32128,7 @@ "CompilationJob.RoleArn string `json:\"RoleArn,omitempty\"`", "CompilationJob.StoppingCondition *StoppingCondition `json:\"StoppingCondition,omitempty\"`", "CompilationJob.Tags map[string]string `json:\"Tags,omitempty\"`", - "CompilationJob.VpcConfig json.RawMessage `json:\"VpcConfig,omitempty\"`", + "CompilationJob.VpcConfig jsontext.Value `json:\"VpcConfig,omitempty\"`", "CompilationJob.{MarshalJSON}", "CompilationJob.{UnmarshalJSON}", "CompilationOutputConfig.KmsKeyID string `json:\"KmsKeyId,omitempty\"`", @@ -32233,12 +32233,12 @@ "Domain.AppSecurityGroupManagement string `json:\"AppSecurityGroupManagement,omitempty\"`", "Domain.AuthMode string `json:\"AuthMode,omitempty\"`", "Domain.CreationTime time.Time `json:\"CreationTime\"`", - "Domain.DefaultSpaceSettings json.RawMessage `json:\"DefaultSpaceSettings,omitempty\"`", - "Domain.DefaultUserSettings json.RawMessage `json:\"DefaultUserSettings,omitempty\"`", + "Domain.DefaultSpaceSettings jsontext.Value `json:\"DefaultSpaceSettings,omitempty\"`", + "Domain.DefaultUserSettings jsontext.Value `json:\"DefaultUserSettings,omitempty\"`", "Domain.DomainArn string `json:\"DomainArn\"`", "Domain.DomainID string `json:\"DomainId\"`", "Domain.DomainName string `json:\"DomainName\"`", - "Domain.DomainSettings json.RawMessage `json:\"DomainSettings,omitempty\"`", + "Domain.DomainSettings jsontext.Value `json:\"DomainSettings,omitempty\"`", "Domain.HomeEfsFileSystemCreation string `json:\"HomeEfsFileSystemCreation,omitempty\"`", "Domain.HomeEfsFileSystemKmsKeyID string `json:\"HomeEfsFileSystemKmsKeyId,omitempty\"`", "Domain.KmsKeyID string `json:\"KmsKeyId,omitempty\"`", @@ -32259,9 +32259,9 @@ "EdgeDeploymentPlan.ModelConfigs []EdgeDeploymentModelConfig `json:\"ModelConfigs,omitempty\"`", "EdgeDeploymentPlan.Stages []*EdgeDeploymentStage `json:\"Stages,omitempty\"`", "EdgeDeploymentPlan.Tags map[string]string `json:\"Tags,omitempty\"`", - "EdgeDeploymentStage.DeploymentConfig json.RawMessage `json:\"DeploymentConfig,omitempty\"`", + "EdgeDeploymentStage.DeploymentConfig jsontext.Value `json:\"DeploymentConfig,omitempty\"`", "EdgeDeploymentStage.DeploymentStatus string `json:\"DeploymentStatus\"`", - "EdgeDeploymentStage.DeviceSelectionConfig json.RawMessage `json:\"DeviceSelectionConfig,omitempty\"`", + "EdgeDeploymentStage.DeviceSelectionConfig jsontext.Value `json:\"DeviceSelectionConfig,omitempty\"`", "EdgeDeploymentStage.StageName string `json:\"StageName\"`", "EdgeOutputConfig.KmsKeyID string `json:\"KmsKeyId,omitempty\"`", "EdgeOutputConfig.PresetDeploymentConfig string `json:\"PresetDeploymentConfig,omitempty\"`", @@ -32285,7 +32285,7 @@ "Endpoint.AsyncInferenceConfig *AsyncInferenceConfig `json:\"AsyncInferenceConfig,omitempty\"`", "Endpoint.CreationTime time.Time `json:\"CreationTime\"`", "Endpoint.DataCaptureConfig *DataCaptureConfig `json:\"DataCaptureConfig,omitempty\"`", - "Endpoint.DeploymentConfig json.RawMessage `json:\"DeploymentConfig,omitempty\"`", + "Endpoint.DeploymentConfig jsontext.Value `json:\"DeploymentConfig,omitempty\"`", "Endpoint.EndpointArn string `json:\"EndpointArn\"`", "Endpoint.EndpointConfigName string `json:\"EndpointConfigName\"`", "Endpoint.EndpointName string `json:\"EndpointName\"`", @@ -32302,9 +32302,9 @@ "EndpointConfig.EndpointConfigARN string `json:\"EndpointConfigARN\"`", "EndpointConfig.EndpointConfigName string `json:\"EndpointConfigName\"`", "EndpointConfig.ExecutionRoleArn string `json:\"ExecutionRoleArn,omitempty\"`", - "EndpointConfig.ExplainerConfig json.RawMessage `json:\"ExplainerConfig,omitempty\"`", + "EndpointConfig.ExplainerConfig jsontext.Value `json:\"ExplainerConfig,omitempty\"`", "EndpointConfig.KmsKeyID string `json:\"KmsKeyId,omitempty\"`", - "EndpointConfig.MetricsConfig json.RawMessage `json:\"MetricsConfig,omitempty\"`", + "EndpointConfig.MetricsConfig jsontext.Value `json:\"MetricsConfig,omitempty\"`", "EndpointConfig.ProductionVariants []ProductionVariant `json:\"ProductionVariants,omitempty\"`", "EndpointConfig.ShadowProductionVariants []ProductionVariant `json:\"ShadowProductionVariants,omitempty\"`", "EndpointConfig.Tags map[string]string `json:\"Tags,omitempty\"`", @@ -32433,11 +32433,11 @@ "HumanTaskUI.UITemplateContentSha256 string `json:\"-\"`", "HumanTaskUI.{MarshalJSON}", "HumanTaskUI.{UnmarshalJSON}", - "HyperParameterTuningJob.Autotune json.RawMessage `json:\"Autotune,omitempty\"`", + "HyperParameterTuningJob.Autotune jsontext.Value `json:\"Autotune,omitempty\"`", "HyperParameterTuningJob.CreationTime time.Time `json:\"CreationTime\"`", "HyperParameterTuningJob.HyperParameterTuningEndTime *time.Time `json:\"HyperParameterTuningEndTime,omitempty\"`", "HyperParameterTuningJob.HyperParameterTuningJobArn string `json:\"HyperParameterTuningJobArn\"`", - "HyperParameterTuningJob.HyperParameterTuningJobConfig json.RawMessage `json:\"HyperParameterTuningJobConfig,omitempty\"`", + "HyperParameterTuningJob.HyperParameterTuningJobConfig jsontext.Value `json:\"HyperParameterTuningJobConfig,omitempty\"`", "HyperParameterTuningJob.HyperParameterTuningJobName string `json:\"HyperParameterTuningJobName\"`", "HyperParameterTuningJob.HyperParameterTuningJobStatus string `json:\"HyperParameterTuningJobStatus\"`", "HyperParameterTuningJob.LastModifiedTime time.Time `json:\"LastModifiedTime\"`", @@ -32445,10 +32445,10 @@ "HyperParameterTuningJob.ResourceLimits HPResourceLimits `json:\"ResourceLimits\"`", "HyperParameterTuningJob.Strategy string `json:\"Strategy,omitempty\"`", "HyperParameterTuningJob.Tags map[string]string `json:\"Tags,omitempty\"`", - "HyperParameterTuningJob.TrainingJobDefinition json.RawMessage `json:\"TrainingJobDefinition,omitempty\"`", - "HyperParameterTuningJob.TrainingJobDefinitions json.RawMessage `json:\"TrainingJobDefinitions,omitempty\"`", + "HyperParameterTuningJob.TrainingJobDefinition jsontext.Value `json:\"TrainingJobDefinition,omitempty\"`", + "HyperParameterTuningJob.TrainingJobDefinitions jsontext.Value `json:\"TrainingJobDefinitions,omitempty\"`", "HyperParameterTuningJob.TrainingJobStatusCounters HPTrainingJobStatusCounters `json:\"TrainingJobStatusCounters\"`", - "HyperParameterTuningJob.WarmStartConfig json.RawMessage `json:\"WarmStartConfig,omitempty\"`", + "HyperParameterTuningJob.WarmStartConfig jsontext.Value `json:\"WarmStartConfig,omitempty\"`", "IamIdentity.Arn string `json:\"Arn,omitempty\"`", "IamIdentity.PrincipalID string `json:\"PrincipalId,omitempty\"`", "IamIdentity.SourceIdentity string `json:\"SourceIdentity,omitempty\"`", @@ -32476,7 +32476,7 @@ "InferenceComponent.CopyCount int32", "InferenceComponent.CreationTime time.Time", "InferenceComponent.CurrentCopyCount int32", - "InferenceComponent.DeploymentConfig json.RawMessage", + "InferenceComponent.DeploymentConfig jsontext.Value", "InferenceComponent.EndpointArn string", "InferenceComponent.EndpointName string", "InferenceComponent.FailureReason string", @@ -32489,17 +32489,17 @@ "InferenceComponent.Tags map[string]string", "InferenceComponent.VariantName string", "InferenceComponentContainerSpec.ArtifactURL string `json:\"ArtifactUrl,omitempty\"`", - "InferenceComponentContainerSpec.ContainerMetricsConfig json.RawMessage `json:\"ContainerMetricsConfig,omitempty\"`", + "InferenceComponentContainerSpec.ContainerMetricsConfig jsontext.Value `json:\"ContainerMetricsConfig,omitempty\"`", "InferenceComponentContainerSpec.Environment map[string]string `json:\"Environment,omitempty\"`", "InferenceComponentContainerSpec.Image string `json:\"Image,omitempty\"`", "InferenceComponentSpecification.BaseInferenceComponentName string `json:\"BaseInferenceComponentName,omitempty\"`", - "InferenceComponentSpecification.ComputeResourceRequirements json.RawMessage `json:\"ComputeResourceRequirements,omitempty\"`", + "InferenceComponentSpecification.ComputeResourceRequirements jsontext.Value `json:\"ComputeResourceRequirements,omitempty\"`", "InferenceComponentSpecification.Container *InferenceComponentContainerSpec `json:\"Container,omitempty\"`", - "InferenceComponentSpecification.DataCacheConfig json.RawMessage `json:\"DataCacheConfig,omitempty\"`", + "InferenceComponentSpecification.DataCacheConfig jsontext.Value `json:\"DataCacheConfig,omitempty\"`", "InferenceComponentSpecification.InstanceType string `json:\"InstanceType,omitempty\"`", "InferenceComponentSpecification.ModelName string `json:\"ModelName,omitempty\"`", - "InferenceComponentSpecification.SchedulingConfig json.RawMessage `json:\"SchedulingConfig,omitempty\"`", - "InferenceComponentSpecification.StartupParameters json.RawMessage `json:\"StartupParameters,omitempty\"`", + "InferenceComponentSpecification.SchedulingConfig jsontext.Value `json:\"SchedulingConfig,omitempty\"`", + "InferenceComponentSpecification.StartupParameters jsontext.Value `json:\"StartupParameters,omitempty\"`", "InferenceExecutionConfig.Mode string `json:\"Mode,omitempty\"`", "InferenceExperiment.Arn string `json:\"Arn\"`", "InferenceExperiment.CompletionTime *time.Time `json:\"CompletionTime,omitempty\"`", @@ -32531,16 +32531,16 @@ "InferenceExperimentSchedule.EndTime *float64 `json:\"EndTime,omitempty\"`", "InferenceExperimentSchedule.StartTime *float64 `json:\"StartTime,omitempty\"`", "InferenceRecommendationsJob.CreationTime time.Time `json:\"CreationTime\"`", - "InferenceRecommendationsJob.InputConfig json.RawMessage `json:\"InputConfig,omitempty\"`", + "InferenceRecommendationsJob.InputConfig jsontext.Value `json:\"InputConfig,omitempty\"`", "InferenceRecommendationsJob.JobArn string `json:\"JobArn\"`", "InferenceRecommendationsJob.JobDescription string `json:\"JobDescription,omitempty\"`", "InferenceRecommendationsJob.JobName string `json:\"JobName\"`", "InferenceRecommendationsJob.JobType string `json:\"JobType,omitempty\"`", "InferenceRecommendationsJob.LastModifiedTime time.Time `json:\"LastModifiedTime\"`", - "InferenceRecommendationsJob.OutputConfig json.RawMessage `json:\"OutputConfig,omitempty\"`", + "InferenceRecommendationsJob.OutputConfig jsontext.Value `json:\"OutputConfig,omitempty\"`", "InferenceRecommendationsJob.RoleArn string `json:\"RoleArn,omitempty\"`", "InferenceRecommendationsJob.Status string `json:\"Status\"`", - "InferenceRecommendationsJob.StoppingConditions json.RawMessage `json:\"StoppingConditions,omitempty\"`", + "InferenceRecommendationsJob.StoppingConditions jsontext.Value `json:\"StoppingConditions,omitempty\"`", "InferenceRecommendationsJob.Tags map[string]string `json:\"Tags,omitempty\"`", "InstanceGroup.InstanceCount int32 `json:\"InstanceCount\"`", "InstanceGroup.InstanceGroupName string `json:\"InstanceGroupName\"`", @@ -32566,7 +32566,7 @@ "Job.Tags map[string]string `json:\"Tags,omitempty\"`", "Job.{MarshalJSON}", "Job.{UnmarshalJSON}", - "JobDefinition.Config map[string]json.RawMessage `json:\"Config,omitempty\"`", + "JobDefinition.Config map[string]jsontext.Value `json:\"Config,omitempty\"`", "JobDefinition.CreationTime time.Time `json:\"CreationTime\"`", "JobDefinition.EndpointName string `json:\"EndpointName,omitempty\"`", "JobDefinition.JobDefinitionArn string `json:\"JobDefinitionArn\"`", @@ -32707,23 +32707,23 @@ "ModelDeployConfig.EndpointName string `json:\"EndpointName,omitempty\"`", "ModelInfrastructureConfig.InfrastructureType string `json:\"InfrastructureType,omitempty\"`", "ModelInfrastructureConfig.RealTimeInferenceConfig *RealTimeInferenceConfig `json:\"RealTimeInferenceConfig,omitempty\"`", - "ModelPackage.AdditionalInferenceSpecifications json.RawMessage `json:\"AdditionalInferenceSpecifications,omitempty\"`", + "ModelPackage.AdditionalInferenceSpecifications jsontext.Value `json:\"AdditionalInferenceSpecifications,omitempty\"`", "ModelPackage.ApprovalDescription string `json:\"ApprovalDescription,omitempty\"`", "ModelPackage.CertifyForMarketplace bool `json:\"CertifyForMarketplace,omitempty\"`", "ModelPackage.CreatedBy *UserContext `json:\"CreatedBy,omitempty\"`", "ModelPackage.CreationTime time.Time `json:\"CreationTime\"`", "ModelPackage.CustomerMetadataProperties map[string]string `json:\"CustomerMetadataProperties,omitempty\"`", "ModelPackage.Domain string `json:\"Domain,omitempty\"`", - "ModelPackage.DriftCheckBaselines json.RawMessage `json:\"DriftCheckBaselines,omitempty\"`", - "ModelPackage.InferenceSpecification json.RawMessage `json:\"InferenceSpecification,omitempty\"`", + "ModelPackage.DriftCheckBaselines jsontext.Value `json:\"DriftCheckBaselines,omitempty\"`", + "ModelPackage.InferenceSpecification jsontext.Value `json:\"InferenceSpecification,omitempty\"`", "ModelPackage.LastModifiedBy *UserContext `json:\"LastModifiedBy,omitempty\"`", "ModelPackage.LastModifiedTime time.Time `json:\"LastModifiedTime,omitzero\"`", "ModelPackage.ManagedStorageType string `json:\"ManagedStorageType,omitempty\"`", - "ModelPackage.MetadataProperties json.RawMessage `json:\"MetadataProperties,omitempty\"`", + "ModelPackage.MetadataProperties jsontext.Value `json:\"MetadataProperties,omitempty\"`", "ModelPackage.ModelApprovalStatus string `json:\"ModelApprovalStatus,omitempty\"`", - "ModelPackage.ModelCard json.RawMessage `json:\"ModelCard,omitempty\"`", - "ModelPackage.ModelLifeCycle json.RawMessage `json:\"ModelLifeCycle,omitempty\"`", - "ModelPackage.ModelMetrics json.RawMessage `json:\"ModelMetrics,omitempty\"`", + "ModelPackage.ModelCard jsontext.Value `json:\"ModelCard,omitempty\"`", + "ModelPackage.ModelLifeCycle jsontext.Value `json:\"ModelLifeCycle,omitempty\"`", + "ModelPackage.ModelMetrics jsontext.Value `json:\"ModelMetrics,omitempty\"`", "ModelPackage.ModelPackageArn string `json:\"ModelPackageArn\"`", "ModelPackage.ModelPackageDescription string `json:\"ModelPackageDescription,omitempty\"`", "ModelPackage.ModelPackageGroupName string `json:\"ModelPackageGroupName,omitempty\"`", @@ -32733,13 +32733,13 @@ "ModelPackage.ModelPackageStatusDetails ModelPackageStatusDetails `json:\"ModelPackageStatusDetails\"`", "ModelPackage.ModelPackageVersion int32 `json:\"ModelPackageVersion,omitempty\"`", "ModelPackage.SamplePayloadURL string `json:\"SamplePayloadUrl,omitempty\"`", - "ModelPackage.SecurityConfig json.RawMessage `json:\"SecurityConfig,omitempty\"`", + "ModelPackage.SecurityConfig jsontext.Value `json:\"SecurityConfig,omitempty\"`", "ModelPackage.SkipModelValidation string `json:\"SkipModelValidation,omitempty\"`", - "ModelPackage.SourceAlgorithmSpecification json.RawMessage `json:\"SourceAlgorithmSpecification,omitempty\"`", + "ModelPackage.SourceAlgorithmSpecification jsontext.Value `json:\"SourceAlgorithmSpecification,omitempty\"`", "ModelPackage.SourceURI string `json:\"SourceUri,omitempty\"`", "ModelPackage.Tags map[string]string `json:\"Tags,omitempty\"`", "ModelPackage.Task string `json:\"Task,omitempty\"`", - "ModelPackage.ValidationSpecification json.RawMessage `json:\"ValidationSpecification,omitempty\"`", + "ModelPackage.ValidationSpecification jsontext.Value `json:\"ValidationSpecification,omitempty\"`", "ModelPackage.{MarshalJSON}", "ModelPackage.{UnmarshalJSON}", "ModelPackageGroup.CreatedBy *UserContext `json:\"CreatedBy,omitempty\"`", @@ -32793,7 +32793,7 @@ "MonitoringSchedule.Tags map[string]string `json:\"Tags,omitempty\"`", "MonitoringSchedule.{MarshalJSON}", "MonitoringSchedule.{UnmarshalJSON}", - "MonitoringScheduleConfig.MonitoringJobDefinition json.RawMessage `json:\"MonitoringJobDefinition,omitempty\"`", + "MonitoringScheduleConfig.MonitoringJobDefinition jsontext.Value `json:\"MonitoringJobDefinition,omitempty\"`", "MonitoringScheduleConfig.MonitoringJobDefinitionName string `json:\"MonitoringJobDefinitionName,omitempty\"`", "MonitoringScheduleConfig.MonitoringType string `json:\"MonitoringType,omitempty\"`", "MonitoringScheduleConfig.ScheduleConfig *MonitoringScheduleSchedule `json:\"ScheduleConfig,omitempty\"`", @@ -32856,27 +32856,27 @@ "OptimizationJob.DeploymentInstanceType string `json:\"DeploymentInstanceType,omitempty\"`", "OptimizationJob.LastModifiedTime time.Time `json:\"LastModifiedTime\"`", "OptimizationJob.MaxInstanceCount int32 `json:\"MaxInstanceCount,omitempty\"`", - "OptimizationJob.ModelSource json.RawMessage `json:\"ModelSource,omitempty\"`", - "OptimizationJob.OptimizationConfigs json.RawMessage `json:\"OptimizationConfigs,omitempty\"`", + "OptimizationJob.ModelSource jsontext.Value `json:\"ModelSource,omitempty\"`", + "OptimizationJob.OptimizationConfigs jsontext.Value `json:\"OptimizationConfigs,omitempty\"`", "OptimizationJob.OptimizationEndTime *time.Time `json:\"OptimizationEndTime,omitempty\"`", "OptimizationJob.OptimizationEnvironment map[string]string `json:\"OptimizationEnvironment,omitempty\"`", "OptimizationJob.OptimizationJobArn string `json:\"OptimizationJobArn\"`", "OptimizationJob.OptimizationJobName string `json:\"OptimizationJobName\"`", "OptimizationJob.OptimizationJobStatus string `json:\"OptimizationJobStatus\"`", "OptimizationJob.OptimizationStartTime *time.Time `json:\"OptimizationStartTime,omitempty\"`", - "OptimizationJob.OutputConfig json.RawMessage `json:\"OutputConfig,omitempty\"`", + "OptimizationJob.OutputConfig jsontext.Value `json:\"OutputConfig,omitempty\"`", "OptimizationJob.RoleArn string `json:\"RoleArn,omitempty\"`", "OptimizationJob.StoppingCondition *StoppingCondition `json:\"StoppingCondition,omitempty\"`", "OptimizationJob.Tags map[string]string `json:\"Tags,omitempty\"`", "OptimizationJob.TrainingPlanArns []string `json:\"TrainingPlanArns,omitempty\"`", - "OptimizationJob.VpcConfig json.RawMessage `json:\"VpcConfig,omitempty\"`", + "OptimizationJob.VpcConfig jsontext.Value `json:\"VpcConfig,omitempty\"`", "OptimizationJob.{MarshalJSON}", "OptimizationJob.{UnmarshalJSON}", "OutputDataConfig.CompressionType string `json:\"CompressionType,omitempty\"`", "OutputDataConfig.KmsKeyID string `json:\"KmsKeyId,omitempty\"`", "OutputDataConfig.S3OutputPath string `json:\"S3OutputPath\"`", "ParallelismConfiguration.MaxParallelExecutionSteps int32 `json:\"MaxParallelExecutionSteps\"`", - "PartnerApp.ApplicationConfig json.RawMessage `json:\"ApplicationConfig,omitempty\"`", + "PartnerApp.ApplicationConfig jsontext.Value `json:\"ApplicationConfig,omitempty\"`", "PartnerApp.Arn string `json:\"Arn\"`", "PartnerApp.AuthType string `json:\"AuthType,omitempty\"`", "PartnerApp.BaseURL string `json:\"-\"`", @@ -33039,9 +33039,9 @@ "Project.ProjectName string `json:\"ProjectName\"`", "Project.ProjectStatus string `json:\"ProjectStatus\"`", "Project.ProvisionedProductID string `json:\"ProvisionedProductId,omitempty\"`", - "Project.ServiceCatalogProvisioningDetails json.RawMessage `json:\"ServiceCatalogProvisioningDetails,omitempty\"`", + "Project.ServiceCatalogProvisioningDetails jsontext.Value `json:\"ServiceCatalogProvisioningDetails,omitempty\"`", "Project.Tags map[string]string `json:\"Tags,omitempty\"`", - "Project.TemplateProviders json.RawMessage `json:\"TemplateProviders,omitempty\"`", + "Project.TemplateProviders jsontext.Value `json:\"TemplateProviders,omitempty\"`", "Project.{MarshalJSON}", "Project.{UnmarshalJSON}", "RealTimeInferenceConfig.InstanceCount int32 `json:\"InstanceCount,omitempty\"`", @@ -33138,12 +33138,12 @@ "Space.CreationTime time.Time `json:\"CreationTime\"`", "Space.DomainID string `json:\"DomainId\"`", "Space.LastModifiedTime time.Time `json:\"LastModifiedTime\"`", - "Space.OwnershipSettings json.RawMessage `json:\"OwnershipSettings,omitempty\"`", + "Space.OwnershipSettings jsontext.Value `json:\"OwnershipSettings,omitempty\"`", "Space.SpaceArn string `json:\"SpaceArn\"`", "Space.SpaceDisplayName string `json:\"SpaceDisplayName,omitempty\"`", "Space.SpaceName string `json:\"SpaceName\"`", - "Space.SpaceSettings json.RawMessage `json:\"SpaceSettings,omitempty\"`", - "Space.SpaceSharingSettings json.RawMessage `json:\"SpaceSharingSettings,omitempty\"`", + "Space.SpaceSettings jsontext.Value `json:\"SpaceSettings,omitempty\"`", + "Space.SpaceSharingSettings jsontext.Value `json:\"SpaceSharingSettings,omitempty\"`", "Space.SpaceStatus string `json:\"Status\"`", "Space.Tags map[string]string `json:\"Tags,omitempty\"`", "Space.{MarshalJSON}", @@ -33331,7 +33331,7 @@ "UserProfile.Tags map[string]string `json:\"Tags,omitempty\"`", "UserProfile.UserProfileArn string `json:\"UserProfileArn\"`", "UserProfile.UserProfileName string `json:\"UserProfileName\"`", - "UserProfile.UserSettings json.RawMessage `json:\"UserSettings,omitempty\"`", + "UserProfile.UserSettings jsontext.Value `json:\"UserSettings,omitempty\"`", "VariantRoutingConfig.RoutingStrategy string `json:\"RoutingStrategy\"`", "VpcConfig.SecurityGroupIDs []string `json:\"SecurityGroupIds\"`", "VpcConfig.Subnets []string `json:\"Subnets\"`", @@ -33370,7 +33370,7 @@ "backendSnapshot.PipelineVersions map[string]map[string][]*PipelineVersion `json:\"pipelineVersions,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SCPortfolioEnabled map[string]bool `json:\"servicecatalogPortfolioEnabled,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "pendingTrainingPlanExtension.CurrencyCode string `json:\"CurrencyCode\"`", "pendingTrainingPlanExtension.DurationHours int32 `json:\"DurationHours\"`", "pendingTrainingPlanExtension.EndDate time.Time `json:\"EndDate\"`", @@ -33400,7 +33400,7 @@ "Session.LastInvokedAt time.Time `json:\"lastInvokedAt\"`", "backendSnapshot.Invocations []*Invocation `json:\"invocations\"`", "backendSnapshot.NextID uint64 `json:\"nextId\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -33473,7 +33473,7 @@ "Target.SqsParameters *SqsParameters `json:\"sqsParameters,omitempty\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "persistedSchedule.ARN string `json:\"arn\"`", "persistedSchedule.AccountID string `json:\"accountID\"`", "persistedSchedule.ActionAfterCompletion string `json:\"actionAfterCompletion,omitempty\"`", @@ -33546,7 +33546,7 @@ "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ReplicationConfigs map[string]map[string][]ReplicationStatusType `json:\"replicationConfigs,omitempty\"`", "backendSnapshot.ResourcePolicies map[string]map[string]string `json:\"resourcePolicies,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "secretSnapshot.ARN string `json:\"arn\"`", "secretSnapshot.CreatedDate *float64 `json:\"createdDate,omitempty\"`", "secretSnapshot.CurrentVersionID string `json:\"currentVersionID\"`", @@ -33755,7 +33755,7 @@ "snapshot.OrgConfig *OrgConfig `json:\"orgConfig\"`", "snapshot.ProductSubscriptions map[string]string `json:\"productSubscriptions\"`", "snapshot.StandardsSeq int `json:\"standardsSeq\"`", - "snapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "snapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "snapshot.Tags map[string]map[string]string `json:\"tags\"`", "snapshot.TicketV2Seq int `json:\"ticketV2Seq\"`" ], @@ -33827,7 +33827,7 @@ "backendSnapshot.AppDependencies map[string]map[string][]*ApplicationDependency `json:\"appDependencies\"`", "backendSnapshot.AppPolicies map[string][]*ApplicationPolicyStatement `json:\"appPolicies\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "cfChangeSetSnapshot.AppName string `json:\"appName\"`", "cfChangeSetSnapshot.ChangeSet CloudFormationChangeSet `json:\"changeSet\"`", "cfTemplateSnapshot.AppName string `json:\"appName\"`", @@ -34014,7 +34014,7 @@ "TagOptionLink.Key string `json:\"key\"`", "TagOptionLink.ResourceID string `json:\"resourceID\"`", "TagOptionLink.TagOptionID string `json:\"tagOptionID\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -34076,7 +34076,7 @@ "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ServiceAttributes map[string]map[string]string `json:\"serviceAttributes\"`", "backendSnapshot.SvcCounter int `json:\"svcCounter\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -34140,7 +34140,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Seq int64 `json:\"seq\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -34244,7 +34244,7 @@ "backendSnapshot.Emails []Email `json:\"emails\"`", "backendSnapshot.Policies map[string]map[string]string `json:\"policies,omitempty\"`", "backendSnapshot.Region string `json:\"region,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "configSetSnapshot.Name string `json:\"name\"`", "configSetSnapshot.Set ConfigurationSet `json:\"set\"`", "eventDestinationSnapshot.ConfigSetName string `json:\"configSetName\"`", @@ -34417,7 +34417,7 @@ "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags\"`", "backendSnapshot.ResourceTenants map[string][]string `json:\"resourceTenants\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TenantResources map[string][]string `json:\"tenantResources\"`", "backendSnapshot.Tenants map[string]map[string]any `json:\"tenants\"`" ], @@ -34476,7 +34476,7 @@ "backendSnapshot.ProactiveEngagementStatus string `json:\"proactiveEngagementStatus,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.Subscription *Subscription `json:\"subscription,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -34534,7 +34534,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SigningKey string `json:\"signingKey\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -34594,7 +34594,7 @@ "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.SMSAttributes map[string]string `json:\"smsAttributes,omitempty\"`", "backendSnapshot.SMSSandboxEnabled *bool `json:\"smsSandboxEnabled,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TopicMessageArchive map[string][]*ArchivedMessage `json:\"topicMessageArchive,omitempty\"`", "backendSnapshot.TopicTags map[string]*svcTags.Tags `json:\"topicTags\"`" ], @@ -34657,7 +34657,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.RecentlyDeleted map[string]int64 `json:\"recentlyDeleted,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "encodedMessageAttribute.Bytes []byte", "encodedMessageAttribute.Name string", "fifoRateWindow.calls []time.Time", @@ -34889,7 +34889,7 @@ "backendSnapshot.PatchGroupToBaseline map[string]map[string]string `json:\"patch_group_to_baseline\"`", "backendSnapshot.ResourceIDToOpsMetadataArn map[string]map[string]string `json:\"resource_id_to_ops_metadata_arn\"`", "backendSnapshot.ResourcePolicies map[string]map[string][]*ResourcePolicy `json:\"resource_policies\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]*tags.Tags `json:\"tags\"`", "idempotencyRecord.CreatedAt time.Time `json:\"createdAt,omitzero\"`", "idempotencyRecord.Fingerprint string `json:\"fingerprint\"`", @@ -34985,8 +34985,8 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.ApplicationAssignConfig map[string]bool `json:\"applicationAssignConfig\"`", "backendSnapshot.ApplicationAssignments map[string][]*ApplicationAssignment `json:\"applicationAssignments\"`", - "backendSnapshot.ApplicationAuthMethods map[string]map[string]json.RawMessage `json:\"applicationAuthMethods\"`", - "backendSnapshot.ApplicationGrants map[string]map[string]json.RawMessage `json:\"applicationGrants\"`", + "backendSnapshot.ApplicationAuthMethods map[string]map[string]jsontext.Value `json:\"applicationAuthMethods\"`", + "backendSnapshot.ApplicationGrants map[string]map[string]jsontext.Value `json:\"applicationGrants\"`", "backendSnapshot.ApplicationScopes map[string]map[string][]string `json:\"applicationScopes\"`", "backendSnapshot.ApplicationSessions map[string]string `json:\"applicationSessions\"`", "backendSnapshot.AssignmentCreationIDs map[string]string `json:\"assignmentCreationIDs\"`", @@ -34995,7 +34995,7 @@ "backendSnapshot.InstanceRegions map[string][]RegionMetadata `json:\"instanceRegions\"`", "backendSnapshot.ProvisionedAt map[string]time.Time `json:\"provisionedAt\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "instanceACASnapshot.Config ABACConfig `json:\"config\"`", "instanceACASnapshot.InstanceArn string `json:\"instanceArn\"`", "permissionsBoundarySnapshot.Boundary PermissionsBoundary `json:\"boundary\"`", @@ -35041,7 +35041,7 @@ "Token.StartURL string `json:\"startUrl,omitempty\"`", "Token.Subject Subject `json:\"subject\"`", "Token.Token string `json:\"token\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -35208,7 +35208,7 @@ "TracingConfiguration.Enabled bool `json:\"enabled\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "executionSnapshot.Cause string `json:\"cause,omitempty\"`", "executionSnapshot.Error string `json:\"error,omitempty\"`", "executionSnapshot.ExecutionArn string `json:\"executionArn\"`", @@ -35227,7 +35227,7 @@ "executionSnapshot.Status string `json:\"status\"`", "executionSnapshot.StopDate *float64 `json:\"stopDate,omitempty\"`", "executionSnapshot.TraceHeader string `json:\"traceHeader,omitempty\"`", - "handlerSnapshot.Backend json.RawMessage `json:\"backend\"`", + "handlerSnapshot.Backend jsontext.Value `json:\"backend\"`", "handlerSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`", "integrationsSnapshot.ddbIntegration asl.DynamoDBIntegration", "integrationsSnapshot.ebIntegration asl.EventBridgeIntegration", @@ -35326,7 +35326,7 @@ "attachmentSetSnapshot.Set AttachmentSet `json:\"set\"`", "backendSnapshot.Communications map[string][]Communication `json:\"communications\"`", "backendSnapshot.NextDisplayID uint64 `json:\"nextDisplayId\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -35444,7 +35444,7 @@ "backendSnapshot.DecisionQueues map[string][]queuedDecisionTask `json:\"decisionQueues,omitempty\"`", "backendSnapshot.ExecutionOrder []string `json:\"executionOrder\"`", "backendSnapshot.History map[string][]HistoryEvent `json:\"history,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags,omitempty\"`", "queuedDecisionTask.RunID string `json:\"runId\"`", "queuedDecisionTask.ScheduledEventID int64 `json:\"scheduledEventId\"`", @@ -35627,7 +35627,7 @@ "backendSnapshot.ClientTokenToJobID map[string]map[string]string `json:\"clientTokenToJobId,omitempty\"`", "backendSnapshot.ExpenseClientTokenToJobID map[string]map[string]string `json:\"expenseClientTokenToJobId,omitempty\"`", "backendSnapshot.LendingClientTokenToJobID map[string]map[string]string `json:\"lendingClientTokenToJobId,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "expenseJobSnapshot.Job ExpenseJob `json:\"job\"`", "expenseJobSnapshot.Region string `json:\"region\"`", "jobSnapshot.Job DocumentJob `json:\"job\"`", @@ -35720,7 +35720,7 @@ "DBParameterGroup.Description string `json:\"description,omitempty\"`", "DBParameterGroup.ID string `json:\"id\"`", "DBParameterGroup.Name string `json:\"name\"`", - "DBParameterGroup.Parameters json.RawMessage `json:\"parameters,omitempty\"`", + "DBParameterGroup.Parameters jsontext.Value `json:\"parameters,omitempty\"`", "DBParameterGroup.Region string `json:\"region\"`", "DBParameterGroup.Tags map[string]string `json:\"tags,omitempty\"`", "LogDelivery.Bucket string `json:\"bucket\"`", @@ -35746,7 +35746,7 @@ "Placement.SubnetIDs []string `json:\"subnetIds\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 1 }, @@ -35830,7 +35830,7 @@ "accountSettingsSnapshot.QueryCompute *queryComputeSnapshot `json:\"query_compute,omitempty\"`", "accountSettingsSnapshot.QueryPricingModel string `json:\"query_pricing_model\"`", "backendSnapshot.AccountSettings map[string]accountSettingsSnapshot `json:\"account_settings\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "queryComputeSnapshot.ActiveQueryTCU *int32 `json:\"active_query_tcu,omitempty\"`", "queryComputeSnapshot.ComputeMode string `json:\"compute_mode,omitempty\"`", "queryComputeSnapshot.NotificationConfiguration *accountSettingsNotificationConfigSnapshot `json:\"notification_configuration,omitempty\"`" @@ -35930,7 +35930,7 @@ "Table.TableStatus string `json:\"table_status\"`", "backendSnapshot.NextTaskID int `json:\"next_task_id\"`", "backendSnapshot.Records map[string]map[string][]Record `json:\"records\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -36143,7 +36143,7 @@ "VocabularyFilter.VocabularyFilterName string `json:\"vocabularyFilterName\"`", "VocabularyFilter.Words []string `json:\"words,omitempty\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`" + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`" ], "version": 2 }, @@ -36382,7 +36382,7 @@ "WorkflowStep.DeleteStepDetails *DeleteStepDetails `json:\"delete_step_details,omitempty\"`", "WorkflowStep.TagStepDetails *TagStepDetails `json:\"tag_step_details,omitempty\"`", "WorkflowStep.Type string `json:\"type\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.TagsStore map[string]map[string]string `json:\"tags_store\"`" ], "version": 1 @@ -36451,7 +36451,7 @@ "TranslationJob.stopRequested bool", "backendSnapshot.AccountID string `json:\"accountId\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -36521,7 +36521,7 @@ "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourceTags map[string]map[string]string `json:\"resourceTags,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "policyStoreSchemaDTO.PolicyStoreID string `json:\"policyStoreId\"`", "policyStoreSchemaDTO.Value *PolicyStoreSchema `json:\"value\"`" ], @@ -36572,7 +36572,7 @@ "backendSnapshot.AuthPolicies map[string]string `json:\"authPolicies\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcePolicies map[string]string `json:\"resourcePolicies\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "backendSnapshot.Targets map[string][]*storedTarget `json:\"targets\"`", "storedALS.ARN string `json:\"arn\"`", @@ -36827,7 +36827,7 @@ "backendSnapshot.OutstandingChangeToken string `json:\"outstandingChangeToken,omitempty\"`", "backendSnapshot.PermissionPolicies map[string]string `json:\"permissionPolicies\"`", "backendSnapshot.RuleGroupRules map[string][]ActivatedRule `json:\"ruleGroupRules\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`" ], "version": 1 @@ -36873,43 +36873,43 @@ "RegexPatternSet.Tags map[string]string `json:\"tags,omitempty\"`", "RuleGroup.ARN string `json:\"arn,omitempty\"`", "RuleGroup.Capacity int64 `json:\"capacity\"`", - "RuleGroup.CustomResponseBodies json.RawMessage `json:\"customResponseBodies,omitempty\"`", + "RuleGroup.CustomResponseBodies jsontext.Value `json:\"customResponseBodies,omitempty\"`", "RuleGroup.Description string `json:\"description\"`", "RuleGroup.ID string `json:\"id\"`", "RuleGroup.LockToken string `json:\"lockToken\"`", - "RuleGroup.MonetizationConfig json.RawMessage `json:\"monetizationConfig,omitempty\"`", + "RuleGroup.MonetizationConfig jsontext.Value `json:\"monetizationConfig,omitempty\"`", "RuleGroup.Name string `json:\"name\"`", "RuleGroup.Rules []map[string]any `json:\"rules,omitempty\"`", "RuleGroup.Scope string `json:\"scope\"`", "RuleGroup.Tags map[string]string `json:\"tags,omitempty\"`", "RuleGroup.VisibilityConfig string `json:\"visibilityConfig\"`", "WebACL.ARN string `json:\"arn,omitempty\"`", - "WebACL.ApplicationConfig json.RawMessage `json:\"applicationConfig,omitempty\"`", - "WebACL.AssociationConfig json.RawMessage `json:\"associationConfig,omitempty\"`", - "WebACL.CaptchaConfig json.RawMessage `json:\"captchaConfig,omitempty\"`", - "WebACL.ChallengeConfig json.RawMessage `json:\"challengeConfig,omitempty\"`", - "WebACL.CustomResponseBodies json.RawMessage `json:\"customResponseBodies,omitempty\"`", - "WebACL.DataProtectionConfig json.RawMessage `json:\"dataProtectionConfig,omitempty\"`", - "WebACL.DefaultAction json.RawMessage `json:\"defaultAction,omitempty\"`", + "WebACL.ApplicationConfig jsontext.Value `json:\"applicationConfig,omitempty\"`", + "WebACL.AssociationConfig jsontext.Value `json:\"associationConfig,omitempty\"`", + "WebACL.CaptchaConfig jsontext.Value `json:\"captchaConfig,omitempty\"`", + "WebACL.ChallengeConfig jsontext.Value `json:\"challengeConfig,omitempty\"`", + "WebACL.CustomResponseBodies jsontext.Value `json:\"customResponseBodies,omitempty\"`", + "WebACL.DataProtectionConfig jsontext.Value `json:\"dataProtectionConfig,omitempty\"`", + "WebACL.DefaultAction jsontext.Value `json:\"defaultAction,omitempty\"`", "WebACL.Description string `json:\"description\"`", "WebACL.ID string `json:\"id\"`", "WebACL.LockToken string `json:\"lockToken\"`", - "WebACL.MonetizationConfig json.RawMessage `json:\"monetizationConfig,omitempty\"`", + "WebACL.MonetizationConfig jsontext.Value `json:\"monetizationConfig,omitempty\"`", "WebACL.Name string `json:\"name\"`", - "WebACL.OnSourceDDoSProtectionConfig json.RawMessage `json:\"onSourceDDoSProtectionConfig,omitempty\"`", + "WebACL.OnSourceDDoSProtectionConfig jsontext.Value `json:\"onSourceDDoSProtectionConfig,omitempty\"`", "WebACL.Rules []map[string]any `json:\"rules,omitempty\"`", "WebACL.Scope string `json:\"scope\"`", "WebACL.Tags map[string]string `json:\"tags,omitempty\"`", "WebACL.TokenDomains []string `json:\"tokenDomains,omitempty\"`", - "WebACL.VisibilityConfig json.RawMessage `json:\"visibilityConfig,omitempty\"`", + "WebACL.VisibilityConfig jsontext.Value `json:\"visibilityConfig,omitempty\"`", "apiKeySnapshot.Key APIKey `json:\"key\"`", "apiKeySnapshot.Region string `json:\"region\"`", "backendSnapshot.AccountID string `json:\"accountID\"`", "backendSnapshot.Associations map[string]map[string]string `json:\"associations,omitempty\"`", - "backendSnapshot.LoggingConfigs map[string]map[string]json.RawMessage `json:\"loggingConfigs,omitempty\"`", + "backendSnapshot.LoggingConfigs map[string]map[string]jsontext.Value `json:\"loggingConfigs,omitempty\"`", "backendSnapshot.PermissionPolicies map[string]map[string]string `json:\"permissionPolicies,omitempty\"`", "backendSnapshot.Region string `json:\"region\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "managedRuleSetSnapshot.Region string `json:\"region\"`", "managedRuleSetSnapshot.RuleSet ManagedRuleSet `json:\"ruleSet\"`" ], @@ -37110,7 +37110,7 @@ "backendSnapshot.OrgsByAlias map[string]string `json:\"orgsByAlias\"`", "backendSnapshot.Region string `json:\"region\"`", "backendSnapshot.ResourcesByEmail map[string]map[string]string `json:\"resourcesByEmail\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string][]Tag `json:\"tags\"`", "backendSnapshot.UsersByEmail map[string]map[string]string `json:\"usersByEmail\"`", "issuedImpersonationToken.ExpiresAt time.Time", @@ -37162,7 +37162,7 @@ "backendSnapshot.ClientProperties map[string]storedClientProps `json:\"clientProperties,omitempty\"`", "backendSnapshot.DirectoryIpGroups map[string]map[string]struct{} `json:\"directoryIpGroups\"`", "backendSnapshot.ImagePermissions map[string]map[string]bool `json:\"imagePermissions,omitempty\"`", - "backendSnapshot.Tables map[string]json.RawMessage `json:\"tables\"`", + "backendSnapshot.Tables map[string]jsontext.Value `json:\"tables\"`", "backendSnapshot.Tags map[string]map[string]string `json:\"tags\"`", "connAliasPermission.AccountID string `json:\"accountId\"`", "connAliasPermission.AllowAssociation bool `json:\"allowAssociation\"`", diff --git a/pkgs/pods/merge.go b/pkgs/pods/merge.go index 6cb37536c5..31417784a9 100644 --- a/pkgs/pods/merge.go +++ b/pkgs/pods/merge.go @@ -2,8 +2,11 @@ package pods import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type scopeKey struct{ account, region string } @@ -107,11 +110,10 @@ func mergeValue(cur, inc any, scopes map[scopeKey]bool) any { } func decodeJSON(raw []byte) (any, error) { - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fmt.Errorf("%w: %w", ErrCorrupt, err) } @@ -140,7 +142,7 @@ func MergeScoped(current, incoming []byte) ([]byte, bool, error) { return nil, false, err } - merged, err := json.Marshal(mergeValue(cur, inc, scopes)) + merged, err := json.Marshal(mergeValue(cur, inc, scopes), jsonx.V1()) if err != nil { return nil, false, fmt.Errorf("marshal merged snapshot: %w", err) } diff --git a/pkgs/pods/pods.go b/pkgs/pods/pods.go index 9fdcf4e60b..aed1579b57 100644 --- a/pkgs/pods/pods.go +++ b/pkgs/pods/pods.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -156,7 +157,7 @@ func (s *Service) Save(ctx context.Context, name, message string, snaps map[stri m.Size = int64(len(archive)) - meta, err := json.Marshal(m) + meta, err := json.Marshal(m, jsonx.V1()) if err != nil { return Manifest{}, fmt.Errorf("marshal manifest: %w", err) } @@ -179,7 +180,7 @@ func buildArchive(m *Manifest, snaps map[string][]byte) ([]byte, error) { zw := zip.NewWriter(&buf) - meta, err := json.Marshal(m) + meta, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal manifest: %w", err) } @@ -262,7 +263,7 @@ func (s *Service) readManifest(ctx context.Context, name string, v int) (Manifes } var m Manifest - if err = json.Unmarshal(raw, &m); err != nil { + if err = json.Unmarshal(raw, &m, jsonx.V1()); err != nil { return Manifest{}, fmt.Errorf("%w: manifest: %w", ErrCorrupt, err) } diff --git a/pkgs/regionpeers/regionpeers.go b/pkgs/regionpeers/regionpeers.go index 24bb98a5fd..aede357921 100644 --- a/pkgs/regionpeers/regionpeers.go +++ b/pkgs/regionpeers/regionpeers.go @@ -3,11 +3,14 @@ package regionpeers import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "regexp" "slices" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -113,7 +116,7 @@ func (s *Set[T]) Snapshot(base []byte, snap func(*T) []byte) []byte { } s.mu.Lock() - regions := make(map[string]json.RawMessage, len(s.peers)) + regions := make(map[string]jsontext.Value, len(s.peers)) for r, p := range s.peers { if data := snap(p); data != nil { @@ -126,19 +129,19 @@ func (s *Set[T]) Snapshot(base []byte, snap func(*T) []byte) []byte { return base } - doc := make(map[string]json.RawMessage) - if err := json.Unmarshal(base, &doc); err != nil { + doc := make(map[string]jsontext.Value) + if err := json.Unmarshal(base, &doc, jsonx.V1()); err != nil { return base } - enc, err := json.Marshal(regions) + enc, err := json.Marshal(regions, jsonx.V1()) if err != nil { return base } doc[snapshotKey] = enc - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return base } @@ -158,10 +161,10 @@ func (s *Set[T]) Restore(data []byte, restore func(*T, []byte) error, closeFn fu } var doc struct { - Regions map[string]json.RawMessage `json:"regions"` + Regions map[string]jsontext.Value `json:"regions"` } - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &doc, jsonx.V1()); err != nil { return err } diff --git a/pkgs/regionpeers/regionpeers_test.go b/pkgs/regionpeers/regionpeers_test.go index a367068151..42b3567184 100644 --- a/pkgs/regionpeers/regionpeers_test.go +++ b/pkgs/regionpeers/regionpeers_test.go @@ -1,13 +1,14 @@ package regionpeers_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" ) @@ -57,7 +58,7 @@ func TestSet_SnapshotRestore(t *testing.T) { t.Parallel() snap := func(p *fake) []byte { return []byte(`"` + p.state + `"`) } - restore := func(p *fake, d []byte) error { return json.Unmarshal(d, &p.state) } + restore := func(p *fake, d []byte) error { return json.Unmarshal(d, &p.state, jsonx.V1()) } closeFn := func(*fake) {} tests := []struct { diff --git a/pkgs/registryauth/dockerconfig.go b/pkgs/registryauth/dockerconfig.go index 75d94f0d50..60c491cdcc 100644 --- a/pkgs/registryauth/dockerconfig.go +++ b/pkgs/registryauth/dockerconfig.go @@ -4,13 +4,15 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "os" "os/exec" "path/filepath" "regexp" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const helperTimeout = 15 * time.Second @@ -63,7 +65,7 @@ func loadDockerConfig() *dockerConfig { } var cfg dockerConfig - if json.Unmarshal(data, &cfg) != nil { + if json.Unmarshal(data, &cfg, jsonx.V1()) != nil { return nil } @@ -157,7 +159,7 @@ func runHelper(ctx context.Context, name, host string) (authConfig, bool) { } var rep helperReply - if json.Unmarshal(out.Bytes(), &rep) != nil || rep.Secret == "" { + if json.Unmarshal(out.Bytes(), &rep, jsonx.V1()) != nil || rep.Secret == "" { return authConfig{}, false } diff --git a/pkgs/registryauth/resolver.go b/pkgs/registryauth/resolver.go index d2922193e7..9ef72126c4 100644 --- a/pkgs/registryauth/resolver.go +++ b/pkgs/registryauth/resolver.go @@ -3,10 +3,11 @@ package registryauth import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "log/slog" "sync" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -49,7 +50,7 @@ func (r *Resolver) RegisterEmulated(host, user, pass string) { } func encodeAuth(ac authConfig) string { - raw, err := json.Marshal(ac) //nolint:gosec // credentials are the payload of the X-Registry-Auth header + raw, err := json.Marshal(ac, jsonx.V1()) if err != nil { return "" } diff --git a/pkgs/registryauth/resolver_test.go b/pkgs/registryauth/resolver_test.go index 49ea6e39ba..dd5125eb2b 100644 --- a/pkgs/registryauth/resolver_test.go +++ b/pkgs/registryauth/resolver_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "log/slog" "os" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/registryauth" ) @@ -50,7 +51,7 @@ func decode(t *testing.T, enc string) map[string]string { require.NoError(t, err) m := map[string]string{} - require.NoError(t, json.Unmarshal(raw, &m)) + require.NoError(t, json.Unmarshal(raw, &m, jsonx.V1())) return m } diff --git a/pkgs/restroute/restroute_test.go b/pkgs/restroute/restroute_test.go index 64ebc486a2..a7d0bb18e4 100644 --- a/pkgs/restroute/restroute_test.go +++ b/pkgs/restroute/restroute_test.go @@ -2,7 +2,7 @@ package restroute_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -215,7 +216,7 @@ func TestServerDispatchAndErrors(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Header().Get("X-Amzn-Errortype")) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) if tt.wantCode != "" { assert.NotEmpty(t, body["message"]) @@ -242,7 +243,7 @@ func TestErrorMessageDropsRepeatedCode(t *testing.T) { ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "cell is busy", body["message"]) assert.Equal(t, "R", body["reason"]) } diff --git a/pkgs/restroute/serve.go b/pkgs/restroute/serve.go index 90530ecdde..910693fd37 100644 --- a/pkgs/restroute/serve.go +++ b/pkgs/restroute/serve.go @@ -2,7 +2,7 @@ package restroute import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Error is an AWS REST-JSON exception: HTTP status, error code, message and optional extra body members. @@ -61,7 +62,7 @@ func (r *Request) Decode(v any) error { return nil } - if err := json.Unmarshal(r.Body, v); err != nil { + if err := json.Unmarshal(r.Body, v, jsonx.V1()); err != nil { return Validation("request body is not valid JSON for this operation") } diff --git a/pkgs/service/cbor.go b/pkgs/service/cbor.go index 1726e3ec43..91e29e1b0f 100644 --- a/pkgs/service/cbor.go +++ b/pkgs/service/cbor.go @@ -2,7 +2,7 @@ package service import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "math" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/aws/smithy-go/encoding/cbor" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ContentTypeCBOR is the AWS CBOR wire protocol content type used by DynamoDB, Kinesis, and Timestream. @@ -39,7 +40,7 @@ func CBORToJSON(data []byte) ([]byte, error) { generic := cborToInterface(val) - out, err := json.Marshal(generic) + out, err := json.Marshal(generic, jsonx.V1()) if err != nil { return nil, fmt.Errorf("json marshal: %w", err) } @@ -58,7 +59,7 @@ func CBORToJSON(data []byte) ([]byte, error) { // produces a CBOR list of byte strings when "BS" is in binaryKeys. func JSONToCBOR(data []byte, binaryKeys map[string]bool) ([]byte, error) { var generic any - if err := json.Unmarshal(data, &generic); err != nil { + if err := json.Unmarshal(data, &generic, jsonx.V1()); err != nil { return nil, fmt.Errorf("json unmarshal: %w", err) } diff --git a/pkgs/service/cbor_test.go b/pkgs/service/cbor_test.go index 603e4bd699..6f9f9e9cee 100644 --- a/pkgs/service/cbor_test.go +++ b/pkgs/service/cbor_test.go @@ -2,7 +2,7 @@ package service_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/aws/smithy-go/encoding/cbor" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -109,7 +110,7 @@ func TestCBORToJSON(t *testing.T) { } var got map[string]any - if err := json.Unmarshal(jsonBytes, &got); err != nil { + if err := json.Unmarshal(jsonBytes, &got, jsonx.V1()); err != nil { t.Fatalf("unmarshal result: %v", err) } @@ -290,15 +291,15 @@ func TestCBORRoundTrip(t *testing.T) { } var orig, got map[string]any - if e := json.Unmarshal([]byte(tc.args.jsonInput), &orig); e != nil { + if e := json.Unmarshal([]byte(tc.args.jsonInput), &orig, jsonx.V1()); e != nil { t.Fatalf("unmarshal orig: %v", e) } - if e := json.Unmarshal(jsonOut, &got); e != nil { + if e := json.Unmarshal(jsonOut, &got, jsonx.V1()); e != nil { t.Fatalf("unmarshal got: %v", e) } - origJSON, _ := json.Marshal(orig) - gotJSON, _ := json.Marshal(got) + origJSON, _ := json.Marshal(orig, jsonx.V1()) + gotJSON, _ := json.Marshal(got, jsonx.V1()) if string(origJSON) != string(gotJSON) { t.Errorf("round-trip mismatch:\n orig: %s\n got: %s", origJSON, gotJSON) @@ -359,7 +360,7 @@ func TestCBORBinaryRoundTrip(t *testing.T) { } var jsonMap map[string]any - if err = json.Unmarshal(jsonBytes, &jsonMap); err != nil { + if err = json.Unmarshal(jsonBytes, &jsonMap, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } diff --git a/pkgs/service/cloudtrail_capture.go b/pkgs/service/cloudtrail_capture.go index adaacc7b3d..19973e0beb 100644 --- a/pkgs/service/cloudtrail_capture.go +++ b/pkgs/service/cloudtrail_capture.go @@ -2,7 +2,7 @@ package service import ( "bytes" - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // minCredentialParts is the minimum number of slash-delimited fields in an AWS @@ -275,7 +276,7 @@ func extractErrorInfo(status int, body []byte) (string, string) { func extractJSONErrorInfo(body []byte) (string, string) { var env jsonErrorEnvelope - if err := json.Unmarshal(body, &env); err != nil { + if err := json.Unmarshal(body, &env, jsonx.V1()); err != nil { return "", "" } diff --git a/pkgs/service/json_error_message.go b/pkgs/service/json_error_message.go index 72351eedfb..335affa963 100644 --- a/pkgs/service/json_error_message.go +++ b/pkgs/service/json_error_message.go @@ -2,7 +2,8 @@ package service import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "hash/crc32" "io" "net/http" @@ -10,6 +11,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSONErrorMessageMiddleware mirrors a lone "message" or "Message" key in JSON @@ -92,8 +95,8 @@ func (w *jsonErrorMirrorWriter) flush() { } func mirrorMessageKey(body []byte) []byte { - var obj map[string]json.RawMessage - if err := json.Unmarshal(body, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(body, &obj, jsonx.V1()); err != nil { return body } @@ -115,7 +118,7 @@ func mirrorMessageKey(body []byte) []byte { obj["message"] = upper } - out, err := json.Marshal(obj) + out, err := json.Marshal(obj, jsonx.V1()) if err != nil { return body } diff --git a/pkgs/service/jsondisp.go b/pkgs/service/jsondisp.go index 6dbb8916d1..e6b85b885a 100644 --- a/pkgs/service/jsondisp.go +++ b/pkgs/service/jsondisp.go @@ -2,7 +2,7 @@ package service import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSONErrorResponse is the standard JSON error envelope for AWS JSON-protocol services. @@ -34,7 +35,7 @@ func (r JSONErrorResponse) MarshalJSON() ([]byte, error) { Type string `json:"__type"` Message string `json:"message"` MessageCap string `json:"Message"` - }{r.Type, r.Message, r.Message}) + }{r.Type, r.Message, r.Message}, jsonx.V1()) } // DispatchFunc is the signature for service-specific action dispatch functions. @@ -57,7 +58,7 @@ func HandleJSON[In, Out any]( var input In if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } @@ -146,7 +147,7 @@ func HandleTarget( func writeDispatchError(c *echo.Context, contentType string, status int, errType, message string) error { c.Response().Header().Set("Content-Type", contentType) - payload, err := json.Marshal(JSONErrorResponse{Type: errType, Message: message}) + payload, err := json.Marshal(JSONErrorResponse{Type: errType, Message: message}, jsonx.V1()) if err != nil { return err } diff --git a/pkgs/service/jsondisp_golden_test.go b/pkgs/service/jsondisp_golden_test.go new file mode 100644 index 0000000000..6623e92abb --- /dev/null +++ b/pkgs/service/jsondisp_golden_test.go @@ -0,0 +1,80 @@ +package service_test + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/service" +) + +type goldenSendInput struct { + QueueURL string `json:"QueueUrl"` + MessageBody string `json:"MessageBody"` + DelaySeconds *int32 `json:"DelaySeconds,omitempty"` + Attributes map[string]string `json:"MessageAttributes"` + Ignored string `json:"-"` +} + +func TestJSONErrorResponseGolden(t *testing.T) { + t.Parallel() + + resp := service.JSONErrorResponse{Type: "com.amazonaws.sqs#QueueDoesNotExist", Message: "queue & y"} + b, err := resp.MarshalJSON() + require.NoError(t, err) + + esc := strings.NewReplacer("<", "\\u003c", ">", "\\u003e", "&", "\\u0026").Replace("queue & y") + want := `{"__type":"com.amazonaws.sqs#QueueDoesNotExist","message":"` + esc + `","Message":"` + esc + `"}` + + assert.Equal(t, want, string(b)) +} + +func TestHandleJSONGoldenDecode(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + body string + want goldenSendInput + wantErr string + }{ + { + name: "basic", + body: `{"QueueUrl":"u","MessageBody":"b","DelaySeconds":5,"MessageAttributes":{"a":"b"}}`, + want: goldenSendInput{ + QueueURL: "u", MessageBody: "b", DelaySeconds: new(int32(5)), + Attributes: map[string]string{"a": "b"}, + }, + }, + {name: "case insensitive and unknown", body: `{"queueurl":"u","MESSAGEBODY":"b","extra":1}`, + want: goldenSendInput{QueueURL: "u", MessageBody: "b"}}, + {name: "null members", body: `{"QueueUrl":null,"MessageAttributes":null}`, want: goldenSendInput{}}, + {name: "empty body", body: ``, want: goldenSendInput{}}, + { + name: "type mismatch", + body: `{"QueueUrl":5}`, + wantErr: "json: cannot unmarshal number into Go struct field goldenSendInput.QueueUrl of type string", + }, + {name: "truncated", body: `{"QueueUrl":`, wantErr: "unexpected end of JSON input"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := service.HandleJSON(context.Background(), []byte(tt.body), + func(_ context.Context, in *goldenSendInput) (*goldenSendInput, error) { return in, nil }) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + + return + } + + require.NoError(t, err) + assert.Equal(t, &tt.want, got) + }) + } +} diff --git a/pkgs/service/regions.go b/pkgs/service/regions.go index a131bb1d8d..dcbe94d3cf 100644 --- a/pkgs/service/regions.go +++ b/pkgs/service/regions.go @@ -1,12 +1,13 @@ package service import ( - "encoding/json" + "encoding/json/v2" "sort" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" ) @@ -94,7 +95,7 @@ func SnapshotKnownRegions() []byte { return nil } - data, err := json.Marshal(regions) + data, err := json.Marshal(regions, jsonx.V1()) if err != nil { return nil } @@ -107,7 +108,7 @@ func SnapshotKnownRegions() []byte { // backfill, not a correctness-critical load. func RestoreKnownRegions(data []byte) { var regions []string - if err := json.Unmarshal(data, ®ions); err != nil { + if err := json.Unmarshal(data, ®ions, jsonx.V1()); err != nil { return } diff --git a/pkgs/service/restdispatch.go b/pkgs/service/restdispatch.go index 97fa5dcbc7..0ef6d606f3 100644 --- a/pkgs/service/restdispatch.go +++ b/pkgs/service/restdispatch.go @@ -2,12 +2,13 @@ package service import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -95,7 +96,7 @@ func (r RESTRouter) handleREST(c *echo.Context) error { return c.JSON(statusCode, struct{}{}) } - data, jsonErr := json.Marshal(result) + data, jsonErr := json.Marshal(result, jsonx.V1()) if jsonErr != nil { return c.JSON(http.StatusInternalServerError, r.InternalErrorBody()) } diff --git a/pkgs/service/rpcv2cbor_bridge.go b/pkgs/service/rpcv2cbor_bridge.go index 614b300ccc..03688baf14 100644 --- a/pkgs/service/rpcv2cbor_bridge.go +++ b/pkgs/service/rpcv2cbor_bridge.go @@ -3,7 +3,8 @@ package service import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -122,11 +124,10 @@ func (b RPCv2CBORBridge) jsonToRPCv2CBOR(body []byte) ([]byte, error) { return cbor.Encode(cbor.Map{}), nil } - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, err } @@ -159,14 +160,14 @@ func (b RPCv2CBORBridge) anyToCBOR(v any, key string) cbor.Value { return cbor.String(t) case bool: return cbor.Bool(t) - case json.Number: + case jsonx.Number: return jsonNumberToCBOR(t, b.TimeKeys[key]) default: return &cbor.Nil{} } } -func jsonNumberToCBOR(n json.Number, timestamp bool) cbor.Value { +func jsonNumberToCBOR(n jsonx.Number, timestamp bool) cbor.Value { if !timestamp && !strings.ContainsAny(string(n), ".eE") { if u, err := strconv.ParseUint(string(n), 10, 64); err == nil { return cbor.Uint(u) diff --git a/pkgs/store/core.go b/pkgs/store/core.go index 614b3a6151..0b2da7df83 100644 --- a/pkgs/store/core.go +++ b/pkgs/store/core.go @@ -2,11 +2,14 @@ package store import ( "cmp" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "reflect" "slices" "unsafe" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // tableCore is the non-generic engine behind every Table[V]. Values are held @@ -171,7 +174,7 @@ func (t *tableCore) insertFresh(v unsafe.Pointer) { // slice is built with reflect so no per-V code (and no *Table[V] interface // conversion, which would keep every Table method alive in the binary) is // needed. -func (t *tableCore) snapshotJSON() (json.RawMessage, error) { +func (t *tableCore) snapshotJSON() (jsontext.Value, error) { items := t.snapshot() sliceType := reflect.SliceOf(reflect.PointerTo(t.elem)) s := reflect.Zero(sliceType) // nil marshals as null, as the typed original did @@ -184,7 +187,7 @@ func (t *tableCore) snapshotJSON() (json.RawMessage, error) { s.Index(i).Set(reflect.NewAt(t.elem, p)) } - data, err := json.Marshal(s.Interface()) + data, err := json.Marshal(s.Interface(), jsonx.V1()) if err != nil { return nil, fmt.Errorf("store: marshal table snapshot: %w", err) } @@ -193,10 +196,10 @@ func (t *tableCore) snapshotJSON() (json.RawMessage, error) { } // restoreJSON loads data produced by snapshotJSON. -func (t *tableCore) restoreJSON(data json.RawMessage) error { +func (t *tableCore) restoreJSON(data jsontext.Value) error { sp := reflect.New(reflect.SliceOf(reflect.PointerTo(t.elem))) - if err := json.Unmarshal(data, sp.Interface()); err != nil { + if err := json.Unmarshal(data, sp.Interface(), jsonx.V1()); err != nil { return fmt.Errorf("store: unmarshal table snapshot: %w", err) } @@ -215,8 +218,8 @@ func (t *tableCore) restoreJSON(data json.RawMessage) error { // tableSnapshotter is the type-erased contract a [Registry] holds per table. type tableSnapshotter interface { reset() - snapshotJSON() (json.RawMessage, error) - restoreJSON(data json.RawMessage) error + snapshotJSON() (jsontext.Value, error) + restoreJSON(data jsontext.Value) error } // indexCore is the non-generic engine behind every Index[V]. diff --git a/pkgs/store/registry.go b/pkgs/store/registry.go index e2f7e1a18e..1f18bdbb1b 100644 --- a/pkgs/store/registry.go +++ b/pkgs/store/registry.go @@ -1,7 +1,7 @@ package store import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sync" @@ -78,9 +78,9 @@ func (r *Registry) ResetAll() { // deterministic (see [Table.Snapshot]); marshaling a Go map with string keys // additionally sorts those keys, so the overall result — and any JSON built // from it — is byte-for-byte stable across calls for the same backend state. -func (r *Registry) SnapshotAll() (map[string]json.RawMessage, error) { +func (r *Registry) SnapshotAll() (map[string]jsontext.Value, error) { tables := r.all() - out := make(map[string]json.RawMessage, len(tables)) + out := make(map[string]jsontext.Value, len(tables)) for name, t := range tables { data, err := t.snapshotJSON() @@ -99,7 +99,7 @@ func (r *Registry) SnapshotAll() (map[string]json.RawMessage, error) { // from data is reset to empty rather than left untouched, so RestoreAll always // produces the same backend state a fresh [NewRegistry] plus SnapshotAll's // input would — never a stale mix of old and new state. -func (r *Registry) RestoreAll(data map[string]json.RawMessage) error { +func (r *Registry) RestoreAll(data map[string]jsontext.Value) error { for name, t := range r.all() { raw, ok := data[name] if !ok { diff --git a/pkgs/store/store_test.go b/pkgs/store/store_test.go index a5649cd8d1..8cd719c504 100644 --- a/pkgs/store/store_test.go +++ b/pkgs/store/store_test.go @@ -1,7 +1,8 @@ package store_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sync" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -336,11 +338,11 @@ func Test_TableSnapshotJSONRoundTrip(t *testing.T) { src := newWidgetTable(&widget{ID: "a", Group: "g", Count: 1}) - data, err := json.Marshal(src.Snapshot()) + data, err := json.Marshal(src.Snapshot(), jsonx.V1()) require.NoError(t, err) var items []*widget - require.NoError(t, json.Unmarshal(data, &items)) + require.NoError(t, json.Unmarshal(data, &items, jsonx.V1())) dst := store.New(widgetKey) dst.Restore(items) @@ -590,7 +592,7 @@ func Test_RegistryRestoreAllResetsTablesMissingFromData(t *testing.T) { widgets.Put(&widget{ID: "stale"}) // data intentionally omits "widgets" entirely. - require.NoError(t, reg.RestoreAll(map[string]json.RawMessage{})) + require.NoError(t, reg.RestoreAll(map[string]jsontext.Value{})) assert.Equal(t, 0, widgets.Len()) } diff --git a/pkgs/tags/tags.go b/pkgs/tags/tags.go index ef4020664e..2faffd45f2 100644 --- a/pkgs/tags/tags.go +++ b/pkgs/tags/tags.go @@ -5,9 +5,10 @@ package tags import ( - "encoding/json" + "encoding/json/v2" "sort" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/safemap" ) @@ -153,7 +154,7 @@ func MapToKV(m map[string]string) []KV { // MarshalJSON implements [json.Marshaler]. // Tags serialises as a plain JSON object (e.g. {"env":"prod","team":"platform"}). func (t *Tags) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Clone()) //nolint:wrapcheck // thin delegation + return json.Marshal(t.Clone(), jsonx.V1()) //nolint:wrapcheck // thin delegation } // UnmarshalJSON implements [json.Unmarshaler]. @@ -164,7 +165,7 @@ func (t *Tags) MarshalJSON() ([]byte, error) { // so the result matches the JSON payload exactly. func (t *Tags) UnmarshalJSON(data []byte) error { var m map[string]string - if err := json.Unmarshal(data, &m); err != nil { + if err := json.Unmarshal(data, &m, jsonx.V1()); err != nil { return err //nolint:wrapcheck // thin delegation } diff --git a/pkgs/tags/tags_test.go b/pkgs/tags/tags_test.go index bb49535e3b..cb16f248d9 100644 --- a/pkgs/tags/tags_test.go +++ b/pkgs/tags/tags_test.go @@ -1,11 +1,12 @@ package tags_test import ( - "encoding/json" + "encoding/json/v2" "sort" "sync" "testing" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/stretchr/testify/assert" @@ -332,7 +333,7 @@ func TestTags_MarshalJSON(t *testing.T) { tt.setup(tg) } - data, err := json.Marshal(tg) + data, err := json.Marshal(tg, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(data)) }) @@ -351,7 +352,7 @@ func TestTags_MarshalJSON_InStruct(t *testing.T) { tg.Set("team", "platform") r := resource{Name: "bucket", Tags: tg} - data, err := json.Marshal(r) + data, err := json.Marshal(r, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, `{"name":"bucket","tags":{"team":"platform"}}`, string(data)) } @@ -379,7 +380,7 @@ func TestTags_UnmarshalJSON(t *testing.T) { t.Parallel() tg := tags.New("test.unmarshal." + tt.name) - err := json.Unmarshal([]byte(tt.input), tg) + err := json.Unmarshal([]byte(tt.input), tg, jsonx.V1()) if tt.wantErr { require.Error(t, err) @@ -402,7 +403,7 @@ func TestTags_UnmarshalJSON_InStruct(t *testing.T) { } r := resource{Tags: tags.New("test.unmarshal.struct")} - err := json.Unmarshal([]byte(`{"name":"bucket","tags":{"env":"staging"}}`), &r) + err := json.Unmarshal([]byte(`{"name":"bucket","tags":{"env":"staging"}}`), &r, jsonx.V1()) require.NoError(t, err) assert.Equal(t, "bucket", r.Name) assert.Equal(t, map[string]string{"env": "staging"}, r.Tags.Clone()) @@ -415,11 +416,11 @@ func TestTags_RoundTrip(t *testing.T) { tg.Set("env", "prod") tg.Set("team", "platform") - data, err := json.Marshal(tg) + data, err := json.Marshal(tg, jsonx.V1()) require.NoError(t, err) tg2 := tags.New("test.roundtrip2") - require.NoError(t, json.Unmarshal(data, tg2)) + require.NoError(t, json.Unmarshal(data, tg2, jsonx.V1())) assert.Equal(t, tg.Clone(), tg2.Clone()) } @@ -431,7 +432,7 @@ func TestTags_UnmarshalJSON_ClearsStaleKeys(t *testing.T) { require.True(t, tg.HasTag("old-key")) // Second unmarshal with a different payload must not leave "old-key" behind. - require.NoError(t, json.Unmarshal([]byte(`{"new-key":"new-value"}`), tg)) + require.NoError(t, json.Unmarshal([]byte(`{"new-key":"new-value"}`), tg, jsonx.V1())) assert.False(t, tg.HasTag("old-key"), "stale key must be removed on unmarshal") v, ok := tg.Get("new-key") diff --git a/pkgs/wfinterp/flow_test.go b/pkgs/wfinterp/flow_test.go index 12d635a658..a57f6ea78d 100644 --- a/pkgs/wfinterp/flow_test.go +++ b/pkgs/wfinterp/flow_test.go @@ -402,7 +402,7 @@ main: require.NoError(t, err) ctx, cancel := context.WithCancel(t.Context()) - started := make(chan struct{}) + started := make(chan struct{}, 1) host := &wfinterp.Host{ Clock: &blockingClock{}, Progress: func(f []wfinterp.Frame) { diff --git a/pkgs/wfinterp/parsejson_test.go b/pkgs/wfinterp/parsejson_test.go new file mode 100644 index 0000000000..946b9adacd --- /dev/null +++ b/pkgs/wfinterp/parsejson_test.go @@ -0,0 +1,41 @@ +package wfinterp_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/wfinterp" +) + +func TestParseJSONTrailingData(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in string + wantErr bool + }{ + {name: "single", in: `{"a":1}`}, + {name: "trailing_space", in: "{\"a\":1} \n"}, + {name: "second_value", in: `{"a":1} {"b":2}`, wantErr: true}, + {name: "garbage", in: `{"a":1} x`, wantErr: true}, + {name: "invalid", in: `{"a":`, wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + _, err := wfinterp.ParseJSON([]byte(tt.in)) + if tt.wantErr { + require.Error(t, err) + + return + } + + assert.NoError(t, err) + }) + } +} diff --git a/pkgs/wfinterp/value.go b/pkgs/wfinterp/value.go index fca2b3317c..bfc661cff0 100644 --- a/pkgs/wfinterp/value.go +++ b/pkgs/wfinterp/value.go @@ -5,13 +5,16 @@ package wfinterp import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "math" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errTrailingJSON = errors.New("trailing data") @@ -165,15 +168,14 @@ func sortedKeys(m map[string]any) []string { // ParseJSON decodes JSON into workflow values: integers become int64, other numbers float64. func ParseJSON(raw []byte) (any, error) { - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fmt.Errorf("invalid JSON: %w", err) } - if dec.More() { + if rest := bytes.TrimLeft(raw[dec.InputOffset():], " \t\r\n"); len(rest) > 0 && rest[0] != ']' && rest[0] != '}' { return nil, fmt.Errorf("invalid JSON: %w", errTrailingJSON) } @@ -182,7 +184,7 @@ func ParseJSON(raw []byte) (any, error) { func normalizeJSON(v any) any { switch x := v.(type) { - case json.Number: + case jsonx.Number: if i, err := x.Int64(); err == nil && !strings.ContainsAny(string(x), ".eE") { return i } @@ -209,7 +211,7 @@ func normalizeJSON(v any) any { // ToJSON encodes a workflow value as JSON; bytes become base64 strings. func ToJSON(v any) ([]byte, error) { - raw, err := json.Marshal(jsonSafe(v)) + raw, err := json.Marshal(jsonSafe(v), jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode JSON: %w", err) } diff --git a/services/accessanalyzer/access_previews.go b/services/accessanalyzer/access_previews.go index 14fda3f418..3994e3e724 100644 --- a/services/accessanalyzer/access_previews.go +++ b/services/accessanalyzer/access_previews.go @@ -1,7 +1,7 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" "maps" "sort" "time" @@ -13,7 +13,7 @@ import ( // required and must contain exactly one element (api_op_CreateAccessPreview.go:39-43). func (b *InMemoryBackend) CreateAccessPreview( analyzerArn string, - configurations map[string]json.RawMessage, + configurations map[string]jsontext.Value, ) (*AccessPreview, error) { if len(configurations) != 1 { return nil, ErrValidation diff --git a/services/accessanalyzer/analyzer_validation.go b/services/accessanalyzer/analyzer_validation.go index 9ba2831846..c483d383a0 100644 --- a/services/accessanalyzer/analyzer_validation.go +++ b/services/accessanalyzer/analyzer_validation.go @@ -1,11 +1,14 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -28,7 +31,7 @@ func isValidAnalyzerType(t AnalyzerType) bool { // validateCreateAnalyzer enforces the CreateAnalyzer name pattern (1-255, // [A-Za-z][A-Za-z0-9_.-]*), the Type enum, and that the configuration union // member matches the analyzer type. -func validateCreateAnalyzer(name string, t AnalyzerType, config json.RawMessage) error { +func validateCreateAnalyzer(name string, t AnalyzerType, config jsontext.Value) error { if len(name) > maxAnalyzerNameLen || !analyzerNameRe.MatchString(name) { return fmt.Errorf("%w: analyzerName must match [A-Za-z][A-Za-z0-9_.-]* (1-255 characters)", ErrValidation) } @@ -36,7 +39,7 @@ func validateCreateAnalyzer(name string, t AnalyzerType, config json.RawMessage) return validateAnalyzerTypeAndConfig(t, config) } -func validateAnalyzerTypeAndConfig(t AnalyzerType, config json.RawMessage) error { +func validateAnalyzerTypeAndConfig(t AnalyzerType, config jsontext.Value) error { if !isValidAnalyzerType(t) { return fmt.Errorf("%w: unsupported analyzer type %q", ErrValidation, t) } @@ -45,8 +48,8 @@ func validateAnalyzerTypeAndConfig(t AnalyzerType, config json.RawMessage) error return nil } - var members map[string]json.RawMessage - if err := json.Unmarshal(config, &members); err != nil { + var members map[string]jsontext.Value + if err := json.Unmarshal(config, &members, jsonx.V1()); err != nil { return fmt.Errorf("%w: malformed configuration", ErrValidation) } @@ -65,12 +68,12 @@ func validateAnalyzerTypeAndConfig(t AnalyzerType, config json.RawMessage) error return nil } -func validateUnusedAccessAge(raw json.RawMessage) error { +func validateUnusedAccessAge(raw jsontext.Value) error { var cfg struct { UnusedAccessAge *int `json:"unusedAccessAge"` } - if err := json.Unmarshal(raw, &cfg); err != nil { + if err := json.Unmarshal(raw, &cfg, jsonx.V1()); err != nil { return fmt.Errorf("%w: malformed unusedAccess configuration", ErrValidation) } diff --git a/services/accessanalyzer/analyzers.go b/services/accessanalyzer/analyzers.go index ddd6e88374..ff40b8b8b8 100644 --- a/services/accessanalyzer/analyzers.go +++ b/services/accessanalyzer/analyzers.go @@ -1,7 +1,7 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" "slices" "sort" "time" @@ -15,7 +15,7 @@ import ( // UpdateAnalyzer all accept the AnalyzerConfiguration union as an optional // parameter this way so existing call sites (which never set it) do not // need to change. -func firstConfiguration(cs []json.RawMessage) json.RawMessage { +func firstConfiguration(cs []jsontext.Value) jsontext.Value { if len(cs) == 0 { return nil } @@ -34,7 +34,7 @@ func (b *InMemoryBackend) CreateAnalyzer( name string, analyzerType AnalyzerType, tags map[string]string, - configuration ...json.RawMessage, + configuration ...jsontext.Value, ) (*Analyzer, error) { if name == "" { return nil, ErrValidation @@ -154,7 +154,7 @@ func (b *InMemoryBackend) DeleteAnalyzer(name string) error { // AnalyzerConfiguration union body (see Analyzer.Configuration). func (b *InMemoryBackend) CreateServiceLinkedAnalyzer( analyzerType AnalyzerType, - configuration ...json.RawMessage, + configuration ...jsontext.Value, ) (*Analyzer, error) { name := "_AccessAnalyzerForInternalUse-" + uuid.NewString()[:8] @@ -166,7 +166,7 @@ func (b *InMemoryBackend) CreateServiceLinkedAnalyzer( // AnalyzerConfiguration; otherwise the existing configuration is left // untouched and simply echoed back, matching UpdateAnalyzerOutput's // contract of always returning the analyzer's current configuration. -func (b *InMemoryBackend) UpdateAnalyzer(name string, configuration ...json.RawMessage) (*Analyzer, error) { +func (b *InMemoryBackend) UpdateAnalyzer(name string, configuration ...jsontext.Value) (*Analyzer, error) { b.mu.Lock("UpdateAnalyzer") defer b.mu.Unlock() diff --git a/services/accessanalyzer/analyzers_test.go b/services/accessanalyzer/analyzers_test.go index 978f0c7d70..04c29d49f5 100644 --- a/services/accessanalyzer/analyzers_test.go +++ b/services/accessanalyzer/analyzers_test.go @@ -1,7 +1,7 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -101,8 +101,8 @@ func TestDeleteAnalyzer_CascadesGhostRows(t *testing.T) { _, err = b.AddAnalyzedResource(a.Arn, "arn:aws:s3:::analyzed-bucket", "AWS::S3::Bucket", false) require.NoError(t, err) - preview, err := b.CreateAccessPreview(a.Arn, map[string]json.RawMessage{ - "arn:aws:s3:::analyzed-bucket": json.RawMessage(`{"s3Bucket":{"bucketPolicy":"{}"}}`), + preview, err := b.CreateAccessPreview(a.Arn, map[string]jsontext.Value{ + "arn:aws:s3:::analyzed-bucket": jsontext.Value(`{"s3Bucket":{"bucketPolicy":"{}"}}`), }) require.NoError(t, err) diff --git a/services/accessanalyzer/handler.go b/services/accessanalyzer/handler.go index dece6fc389..0d1b151376 100644 --- a/services/accessanalyzer/handler.go +++ b/services/accessanalyzer/handler.go @@ -2,7 +2,7 @@ package accessanalyzer import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -232,7 +233,7 @@ func (h *Handler) handleREST(c *echo.Context) error { return c.JSON(statusCode, struct{}{}) } - data, err := json.Marshal(result) + data, err := json.Marshal(result, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorBody("InternalFailure", "failed to serialize response")) diff --git a/services/accessanalyzer/handler_access_previews.go b/services/accessanalyzer/handler_access_previews.go index fd5d819293..53fc5c39f3 100644 --- a/services/accessanalyzer/handler_access_previews.go +++ b/services/accessanalyzer/handler_access_previews.go @@ -1,9 +1,12 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -54,11 +57,11 @@ func (h *Handler) dispatchAccessPreviewOps( // AccessPreview.Configurations godoc). func (h *Handler) handleCreateAccessPreview(body []byte) (any, int, error) { var req struct { - Configurations map[string]json.RawMessage `json:"configurations"` - AnalyzerArn string `json:"analyzerArn"` + Configurations map[string]jsontext.Value `json:"configurations"` + AnalyzerArn string `json:"analyzerArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -135,7 +138,7 @@ func (h *Handler) handleListAccessPreviewFindings(path string, body []byte) (any MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_access_previews_test.go b/services/accessanalyzer/handler_access_previews_test.go index 7726238344..31a8ce279b 100644 --- a/services/accessanalyzer/handler_access_previews_test.go +++ b/services/accessanalyzer/handler_access_previews_test.go @@ -1,13 +1,14 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -43,7 +44,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) previewID := created["id"] require.NotEmpty(t, previewID) @@ -51,7 +52,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got, jsonx.V1())) ap := got["accessPreview"].(map[string]any) assert.Equal(t, previewID, ap["id"]) assert.Equal(t, "COMPLETED", ap["status"]) @@ -107,7 +108,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) previews, ok := resp["accessPreviews"].([]any) require.True(t, ok) require.Len(t, previews, 1) @@ -134,7 +135,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { "configurations": singleS3BucketConfig("arn:aws:s3:::preview-findings-bucket"), }) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) previewID := created["id"] rec2 := doRequest(t, h, http.MethodPost, "/access-preview/"+previewID, map[string]any{ @@ -143,7 +144,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) findings := resp["findings"].([]any) require.Len(t, findings, 1) @@ -170,7 +171,7 @@ func TestAccessPreviewLifecycle(t *testing.T) { "configurations": singleS3BucketConfig("arn:aws:s3:::preview-findings-no-arn-bucket"), }) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) previewID := created["id"] // ListAccessPreviewFindingsInput requires analyzerArn. diff --git a/services/accessanalyzer/handler_analyzed_resources.go b/services/accessanalyzer/handler_analyzed_resources.go index 50740fbf2a..66827a3a03 100644 --- a/services/accessanalyzer/handler_analyzed_resources.go +++ b/services/accessanalyzer/handler_analyzed_resources.go @@ -1,9 +1,11 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -69,7 +71,7 @@ func (h *Handler) handleListAnalyzedResources(body []byte) (any, int, error) { MaxResults int `json:"maxResults"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) resources, nextToken, err := h.Backend.ListAnalyzedResources( req.AnalyzerArn, req.ResourceType, req.MaxResults, req.NextToken, @@ -106,7 +108,7 @@ func (h *Handler) handleStartResourceScan(body []byte) (int, error) { ResourceArn string `json:"resourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return 0, ErrValidation } diff --git a/services/accessanalyzer/handler_analyzed_resources_test.go b/services/accessanalyzer/handler_analyzed_resources_test.go index 29e97ee765..ec90b6d558 100644 --- a/services/accessanalyzer/handler_analyzed_resources_test.go +++ b/services/accessanalyzer/handler_analyzed_resources_test.go @@ -1,13 +1,14 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -34,7 +35,7 @@ func TestAnalyzedResourceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resource := resp["resource"].(map[string]any) assert.Equal(t, resourceArn, resource["resourceArn"]) // resourceOwnerAccount is a required AnalyzedResource member. @@ -70,7 +71,7 @@ func TestAnalyzedResourceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources := resp["analyzedResources"].([]any) assert.Len(t, resources, 2) diff --git a/services/accessanalyzer/handler_analyzers.go b/services/accessanalyzer/handler_analyzers.go index 0e4f65fea6..1b8aba9033 100644 --- a/services/accessanalyzer/handler_analyzers.go +++ b/services/accessanalyzer/handler_analyzers.go @@ -1,10 +1,13 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -63,13 +66,13 @@ func (h *Handler) dispatchAnalyzerOps(op, path, query string, body []byte) (any, func (h *Handler) handleCreateAnalyzer(body []byte) (any, int, error) { var req struct { Tags map[string]string `json:"tags"` - Configuration json.RawMessage `json:"configuration"` + Configuration jsontext.Value `json:"configuration"` AnalyzerName string `json:"analyzerName"` Type string `json:"type"` ArchiveRules []inlineArchiveRule `json:"archiveRules"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -186,10 +189,10 @@ func (h *Handler) handleUpdateAnalyzer(path string, body []byte) (any, int, erro name := extractAnalyzerName(path) var req struct { - Configuration json.RawMessage `json:"configuration"` + Configuration jsontext.Value `json:"configuration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -208,12 +211,12 @@ func (h *Handler) handleUpdateAnalyzer(path string, body []byte) (any, int, erro func (h *Handler) handleCreateServiceLinkedAnalyzer(body []byte) (any, int, error) { var req struct { - Configuration json.RawMessage `json:"configuration"` + Configuration jsontext.Value `json:"configuration"` Type string `json:"type"` ArchiveRules []inlineArchiveRule `json:"archiveRules"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_analyzers_test.go b/services/accessanalyzer/handler_analyzers_test.go index 8ceb0167b5..8cfdffd9f8 100644 --- a/services/accessanalyzer/handler_analyzers_test.go +++ b/services/accessanalyzer/handler_analyzers_test.go @@ -1,7 +1,7 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -30,7 +31,7 @@ func TestServiceLinkedAnalyzerLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.True(t, strings.HasPrefix(resp["arn"], "arn:aws:access-analyzer:")) }, }, @@ -112,7 +113,7 @@ func TestUpdateAnalyzer(t *testing.T) { // UpdateAnalyzerOutput carries only "configuration" -- unlike // CreateAnalyzer/CreateServiceLinkedAnalyzer, it has no "arn" member. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasArn := resp["arn"] assert.False(t, hasArn, `UpdateAnalyzerOutput has no "arn" member`) }) @@ -145,7 +146,7 @@ func TestAnalyzerConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) analyzer, ok := getResp["analyzer"].(map[string]any) require.True(t, ok) @@ -160,7 +161,7 @@ func TestAnalyzerConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) analyzers, ok := listResp["analyzers"].([]any) require.True(t, ok) require.Len(t, analyzers, 1) @@ -185,7 +186,7 @@ func TestAnalyzerConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfg, ok := resp["configuration"].(map[string]any) require.True(t, ok) unused, ok := cfg["unusedAccess"].(map[string]any) diff --git a/services/accessanalyzer/handler_archive_rules.go b/services/accessanalyzer/handler_archive_rules.go index bac1b102b0..ca00d58268 100644 --- a/services/accessanalyzer/handler_archive_rules.go +++ b/services/accessanalyzer/handler_archive_rules.go @@ -1,11 +1,13 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -78,7 +80,7 @@ func (h *Handler) handleCreateArchiveRule(path string, body []byte) (int, error) RuleName string `json:"ruleName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return 0, ErrValidation } @@ -154,7 +156,7 @@ func (h *Handler) handleUpdateArchiveRule(path string, body []byte) (int, error) Filter map[string]FilterCriterion `json:"filter"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return 0, ErrValidation } @@ -171,7 +173,7 @@ func (h *Handler) handleApplyArchiveRule(body []byte) (int, error) { RuleName string `json:"ruleName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return 0, ErrValidation } diff --git a/services/accessanalyzer/handler_findings.go b/services/accessanalyzer/handler_findings.go index 79d73d814d..c27f68df71 100644 --- a/services/accessanalyzer/handler_findings.go +++ b/services/accessanalyzer/handler_findings.go @@ -1,10 +1,12 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -108,7 +110,7 @@ func (h *Handler) handleListFindings(body []byte) (any, int, error) { MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -151,7 +153,7 @@ func (h *Handler) handleUpdateFindings(body []byte) (int, error) { IDs []string `json:"ids"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return 0, ErrValidation } @@ -212,7 +214,7 @@ func (h *Handler) handleListFindingsV2(body []byte) (any, int, error) { MaxResults int `json:"maxResults"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) findings, nextToken, err := h.Backend.ListFindingsV2( req.AnalyzerArn, req.Status, req.Filter, req.Sort, req.MaxResults, req.NextToken, @@ -273,7 +275,7 @@ func (h *Handler) handleGetFindingsStatistics(body []byte) (any, int, error) { AnalyzerArn string `json:"analyzerArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_findings_test.go b/services/accessanalyzer/handler_findings_test.go index 228bd4a7a2..f6acf3eb38 100644 --- a/services/accessanalyzer/handler_findings_test.go +++ b/services/accessanalyzer/handler_findings_test.go @@ -1,7 +1,7 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -59,7 +60,7 @@ func TestGetFinding(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) finding, ok := resp["finding"].(map[string]any) require.True(t, ok) @@ -105,7 +106,7 @@ func TestListFindings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, ok := resp["findings"].([]any) require.True(t, ok) @@ -223,7 +224,7 @@ func TestFindingV2Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, findingID, resp["id"]) // findingType and the ExternalAccessDetails union member of // findingDetails are required GetFindingV2Output members. @@ -253,7 +254,7 @@ func TestFindingV2Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings := resp["findings"].([]any) assert.Len(t, findings, 2) first, ok := findings[0].(map[string]any) @@ -331,7 +332,7 @@ func TestGetFindingsStatistics(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantActive > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stats := resp["findingsStatistics"].([]any) require.Len(t, stats, 1) @@ -368,7 +369,7 @@ func TestFindingRecommendationLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["recommendationType"]) }, }, @@ -436,7 +437,7 @@ func TestGetFindingRecommendation_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "UnusedPermissionRecommendation", resp["recommendationType"]) assert.Equal(t, "arn:aws:s3:::bucket", resp["resourceArn"]) diff --git a/services/accessanalyzer/handler_generated_policies.go b/services/accessanalyzer/handler_generated_policies.go index 451fa61629..0f5cae1b2d 100644 --- a/services/accessanalyzer/handler_generated_policies.go +++ b/services/accessanalyzer/handler_generated_policies.go @@ -1,11 +1,13 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // errPolicyGenerationJobNotModeled translates ErrPolicyGenerationNotFound for @@ -82,7 +84,7 @@ func (h *Handler) handleStartPolicyGeneration(body []byte) (any, int, error) { } `json:"policyGenerationDetails"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_generated_policies_test.go b/services/accessanalyzer/handler_generated_policies_test.go index 040d669870..99cfc5a4e8 100644 --- a/services/accessanalyzer/handler_generated_policies_test.go +++ b/services/accessanalyzer/handler_generated_policies_test.go @@ -1,7 +1,7 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -36,7 +37,7 @@ func TestPolicyGenerationLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["jobId"]) }, }, @@ -55,7 +56,7 @@ func TestPolicyGenerationLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["policyGenerations"]) }, }, @@ -102,7 +103,7 @@ func TestGetGeneratedPolicy(t *testing.T) { "policyGenerationDetails": map[string]any{"principalArn": "arn:aws:iam::000000000000:role/R"}, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobID = resp["jobId"] } @@ -117,7 +118,7 @@ func TestGetGeneratedPolicy(t *testing.T) { // principalArn member -- that value only appears under // generatedPolicyResult.properties.principalArn for this operation. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobDetails, ok := resp["jobDetails"].(map[string]any) require.True(t, ok) _, hasPrincipalArn := jobDetails["principalArn"] @@ -163,7 +164,7 @@ func TestCancelPolicyGeneration(t *testing.T) { "policyGenerationDetails": map[string]any{"principalArn": "arn:aws:iam::000000000000:role/R"}, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobID = resp["jobId"] } @@ -198,13 +199,13 @@ func TestStartPolicyGeneration_CloudTrailDetails(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) getRec := doRequest(t, h, http.MethodGet, "/policy/generation/"+startResp["jobId"], nil) require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) result, ok := resp["generatedPolicyResult"].(map[string]any) require.True(t, ok) @@ -249,7 +250,7 @@ func TestListPolicyGenerations_MaxResultsAndNextToken(t *testing.T) { require.Equal(t, http.StatusOK, firstRec.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstResp, jsonx.V1())) firstPage, ok := firstResp["policyGenerations"].([]any) require.True(t, ok) @@ -262,7 +263,7 @@ func TestListPolicyGenerations_MaxResultsAndNextToken(t *testing.T) { require.Equal(t, http.StatusOK, secondRec.Code) var secondResp map[string]any - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondResp, jsonx.V1())) secondPage, ok := secondResp["policyGenerations"].([]any) require.True(t, ok) diff --git a/services/accessanalyzer/handler_policy_validation.go b/services/accessanalyzer/handler_policy_validation.go index b04510809b..a5a96bdda2 100644 --- a/services/accessanalyzer/handler_policy_validation.go +++ b/services/accessanalyzer/handler_policy_validation.go @@ -1,9 +1,11 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -57,7 +59,7 @@ func (h *Handler) handleCheckAccessNotGranted(body []byte) (any, int, error) { Access []AccessSpec `json:"access"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -86,7 +88,7 @@ func (h *Handler) handleCheckNoNewAccess(body []byte) (any, int, error) { PolicyType string `json:"policyType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -114,7 +116,7 @@ func (h *Handler) handleCheckNoPublicAccess(body []byte) (any, int, error) { ResourceType string `json:"resourceType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -143,7 +145,7 @@ func (h *Handler) handleValidatePolicy(body []byte, query string) (any, int, err ResourceType string `json:"validatePolicyResourceType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_policy_validation_test.go b/services/accessanalyzer/handler_policy_validation_test.go index f92c6e5790..7ece7c3b07 100644 --- a/services/accessanalyzer/handler_policy_validation_test.go +++ b/services/accessanalyzer/handler_policy_validation_test.go @@ -1,12 +1,14 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCheckPolicyOps verifies Check* operations return PASS. @@ -55,7 +57,7 @@ func TestCheckPolicyOps(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PASS", resp["result"]) }) } @@ -165,7 +167,7 @@ func TestCheckPolicyOps_MalformedPolicyDocument(t *testing.T) { assert.Equal(t, http.StatusUnprocessableEntity, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "UnprocessableEntityException", resp["__type"]) }) } @@ -183,7 +185,7 @@ func TestValidatePolicy(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["findings"]) } @@ -204,7 +206,7 @@ func TestValidatePolicy_FindingDetailsPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, ok := resp["findings"].([]any) require.True(t, ok) require.NotEmpty(t, findings) diff --git a/services/accessanalyzer/handler_tags.go b/services/accessanalyzer/handler_tags.go index f987e957bb..c40ae3e40a 100644 --- a/services/accessanalyzer/handler_tags.go +++ b/services/accessanalyzer/handler_tags.go @@ -1,10 +1,12 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -36,7 +38,7 @@ func (h *Handler) handleTagResource(path string, body []byte) (any, int, error) Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/accessanalyzer/handler_test.go b/services/accessanalyzer/handler_test.go index e90700ea3d..b9027d8be4 100644 --- a/services/accessanalyzer/handler_test.go +++ b/services/accessanalyzer/handler_test.go @@ -2,7 +2,7 @@ package accessanalyzer_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -33,7 +34,7 @@ func doRequest(t *testing.T, h *accessanalyzer.Handler, method, path string, bod if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/accessanalyzer/interfaces.go b/services/accessanalyzer/interfaces.go index b901307c4a..de86ab5985 100644 --- a/services/accessanalyzer/interfaces.go +++ b/services/accessanalyzer/interfaces.go @@ -2,7 +2,7 @@ package accessanalyzer import ( "context" - "encoding/json" + "encoding/json/jsontext" ) // StorageBackend defines the interface for Access Analyzer backend implementations. @@ -11,13 +11,13 @@ type StorageBackend interface { // Analyzer operations. configuration is the optional raw // AnalyzerConfiguration union wire body -- see Analyzer.Configuration. CreateAnalyzer( - name string, analyzerType AnalyzerType, tags map[string]string, configuration ...json.RawMessage, + name string, analyzerType AnalyzerType, tags map[string]string, configuration ...jsontext.Value, ) (*Analyzer, error) GetAnalyzer(name string) (*Analyzer, error) ListAnalyzers(analyzerType string) ([]*Analyzer, error) DeleteAnalyzer(name string) error - UpdateAnalyzer(name string, configuration ...json.RawMessage) (*Analyzer, error) - CreateServiceLinkedAnalyzer(analyzerType AnalyzerType, configuration ...json.RawMessage) (*Analyzer, error) + UpdateAnalyzer(name string, configuration ...jsontext.Value) (*Analyzer, error) + CreateServiceLinkedAnalyzer(analyzerType AnalyzerType, configuration ...jsontext.Value) (*Analyzer, error) // Archive rule operations CreateArchiveRule(analyzerName, ruleName string, filter map[string]FilterCriterion) (*ArchiveRule, error) @@ -82,7 +82,7 @@ type StorageBackend interface { ListPolicyGenerations(principalArn string, maxResults int, nextToken string) ([]*PolicyGeneration, string, error) // Access previews - CreateAccessPreview(analyzerArn string, configurations map[string]json.RawMessage) (*AccessPreview, error) + CreateAccessPreview(analyzerArn string, configurations map[string]jsontext.Value) (*AccessPreview, error) GetAccessPreview(accessPreviewID string) (*AccessPreview, error) ListAccessPreviews(analyzerArn string) ([]*AccessPreview, error) ListAccessPreviewFindings( diff --git a/services/accessanalyzer/models.go b/services/accessanalyzer/models.go index 79b937f5be..32470f04da 100644 --- a/services/accessanalyzer/models.go +++ b/services/accessanalyzer/models.go @@ -1,7 +1,7 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" "maps" "time" ) @@ -72,7 +72,7 @@ type Analyzer struct { Name string `json:"name"` Type AnalyzerType `json:"type"` Status AnalyzerStatus `json:"status"` - Configuration json.RawMessage `json:"configuration,omitempty"` + Configuration jsontext.Value `json:"configuration,omitempty"` } // ArchiveRule represents an archive rule for an analyzer. @@ -169,7 +169,7 @@ const ( // echoed back opaquely as submitted instead of decoded into the full union. type AccessPreview struct { CreatedAt time.Time - Configurations map[string]json.RawMessage + Configurations map[string]jsontext.Value ID string AnalyzerArn string Status AccessPreviewStatus @@ -230,7 +230,7 @@ func cloneFilter(f map[string]FilterCriterion) map[string]FilterCriterion { func copyAnalyzer(a *Analyzer) *Analyzer { cp := *a cp.Tags = cloneTags(a.Tags) - cp.Configuration = append(json.RawMessage(nil), a.Configuration...) + cp.Configuration = append(jsontext.Value(nil), a.Configuration...) return &cp } diff --git a/services/accessanalyzer/persistence.go b/services/accessanalyzer/persistence.go index 7b3d95b0de..355a474a75 100644 --- a/services/accessanalyzer/persistence.go +++ b/services/accessanalyzer/persistence.go @@ -2,7 +2,7 @@ package accessanalyzer import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -96,7 +96,7 @@ func buildPersistenceDTORegistry() (*store.Registry, *store.Table[archiveRuleSna // identity of its own, so it is persisted here directly, matching its // pre-refactor shape exactly. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -194,7 +194,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // store_setup.go's registerAllTables doc) from tables via the ephemeral DTO // registry, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreArchiveRules(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreArchiveRules(tables map[string]jsontext.Value) error { dtoReg, archiveRuleDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("accessanalyzer: restore snapshot DTO tables: %w", err) diff --git a/services/accessanalyzer/persistence_test.go b/services/accessanalyzer/persistence_test.go index 4a31cd1d16..6e0add3b36 100644 --- a/services/accessanalyzer/persistence_test.go +++ b/services/accessanalyzer/persistence_test.go @@ -1,7 +1,7 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "time" @@ -88,7 +88,7 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { analyzer, err := original.CreateAnalyzer( "analyzer-1", accessanalyzer.AnalyzerTypeAccount, map[string]string{"env": "test"}, - json.RawMessage(`{"unusedAccess":{"unusedAccessAge":90}}`), + jsontext.Value(`{"unusedAccess":{"unusedAccessAge":90}}`), ) require.NoError(t, err) @@ -119,8 +119,8 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { ) require.NoError(t, err) - ap, err := original.CreateAccessPreview(analyzer.Arn, map[string]json.RawMessage{ - "arn:aws:s3:::bucket-2": json.RawMessage(`{"s3Bucket":{"bucketPolicy":"{}"}}`), + ap, err := original.CreateAccessPreview(analyzer.Arn, map[string]jsontext.Value{ + "arn:aws:s3:::bucket-2": jsontext.Value(`{"s3Bucket":{"bucketPolicy":"{}"}}`), }) require.NoError(t, err) diff --git a/services/accessanalyzer/policy_analysis.go b/services/accessanalyzer/policy_analysis.go index c09b552aee..63dfb228db 100644 --- a/services/accessanalyzer/policy_analysis.go +++ b/services/accessanalyzer/policy_analysis.go @@ -1,11 +1,14 @@ package accessanalyzer import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "path/filepath" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -52,14 +55,14 @@ type iamStringOrSlice []string func (s *iamStringOrSlice) UnmarshalJSON(data []byte) error { var str string - if err := json.Unmarshal(data, &str); err == nil { + if err := json.Unmarshal(data, &str, jsonx.V1()); err == nil { *s = []string{str} return nil } var slice []string - if err := json.Unmarshal(data, &slice); err != nil { + if err := json.Unmarshal(data, &slice, jsonx.V1()); err != nil { return err } @@ -78,27 +81,27 @@ type iamPrincipal struct { //nolint:govet // field order chosen for readability func (p *iamPrincipal) UnmarshalJSON(data []byte) error { var str string - if err := json.Unmarshal(data, &str); err == nil { + if err := json.Unmarshal(data, &str, jsonx.V1()); err == nil { p.IsWildcard = str == "*" return nil } - var m map[string]json.RawMessage - if err := json.Unmarshal(data, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(data, &m, jsonx.V1()); err != nil { return err } if v, ok := m["AWS"]; ok { - _ = json.Unmarshal(v, &p.AWS) + _ = json.Unmarshal(v, &p.AWS, jsonx.V1()) } if v, ok := m["Service"]; ok { - _ = json.Unmarshal(v, &p.Service) + _ = json.Unmarshal(v, &p.Service, jsonx.V1()) } if v, ok := m["Federated"]; ok { - _ = json.Unmarshal(v, &p.Federated) + _ = json.Unmarshal(v, &p.Federated, jsonx.V1()) } return nil @@ -116,7 +119,7 @@ func parsePolicy(doc string) (iamPolicy, error) { } var p iamPolicy - if err := json.Unmarshal([]byte(doc), &p); err != nil { + if err := json.Unmarshal([]byte(doc), &p, jsonx.V1()); err != nil { return iamPolicy{}, ErrMalformedPolicy } @@ -439,7 +442,7 @@ func stmtFieldLoc(stmtPath []any, field string) []map[string]any { } // validateVersion checks the Version field of a policy document. -func validateVersion(raw map[string]json.RawMessage, p iamPolicy) []ValidatePolicyFinding { +func validateVersion(raw map[string]jsontext.Value, p iamPolicy) []ValidatePolicyFinding { var findings []ValidatePolicyFinding if _, ok := raw["Version"]; !ok { @@ -546,8 +549,8 @@ func validateStatement(i int, stmt iamStatement, policyType string) []ValidatePo // ValidatePolicy checks a policy document for structural and semantic errors. func ValidatePolicy(policyDoc, policyType string) []ValidatePolicyFinding { - var raw map[string]json.RawMessage - if err := json.Unmarshal([]byte(policyDoc), &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal([]byte(policyDoc), &raw, jsonx.V1()); err != nil { findings := []ValidatePolicyFinding{errFinding("INVALID_POLICY_SYNTAX", rootLoc())} attachSpans(policyDoc, findings) @@ -582,7 +585,7 @@ func ValidatePolicy(policyDoc, policyType string) []ValidatePolicyFinding { // the finding) falls back one step at a time toward the document root, // which always resolves -- so span is never dropped. func attachSpans(doc string, findings []ValidatePolicyFinding) { - root := json.RawMessage(doc) + root := jsontext.Value(doc) for i := range findings { locs := findings[i].Locations @@ -595,7 +598,7 @@ func attachSpans(doc string, findings []ValidatePolicyFinding) { // attachSpan resolves loc's "span" in place. loc is a map (a reference // type), so mutating it here mutates the same map attachSpans's caller // holds -- no index expression needs to survive past this call. -func attachSpan(doc string, root json.RawMessage, loc map[string]any) { +func attachSpan(doc string, root jsontext.Value, loc map[string]any) { path, _ := loc[keyPath].([]any) for { @@ -619,7 +622,7 @@ func attachSpan(doc string, root json.RawMessage, loc map[string]any) { // map[string]any{keyValue: field} / map[string]any{keyIndex: i} shapes // rootLoc/fieldLoc/stmtLoc/stmtFieldLoc build (never JSON-decoded, so the // map values are concrete Go string/int, not float64/etc). -func resolveRawAt(node json.RawMessage, nodeStart int, path []any) (json.RawMessage, int, bool) { +func resolveRawAt(node jsontext.Value, nodeStart int, path []any) (jsontext.Value, int, bool) { if len(path) == 0 { return node, nodeStart, true } @@ -641,9 +644,9 @@ func resolveRawAt(node json.RawMessage, nodeStart int, path []any) (json.RawMess // resolveKeyStep resolves a map[string]any{keyValue: key} path step: the // PathElementMemberValue shape rootLoc/fieldLoc/stmtFieldLoc build for "the // value at this object key". -func resolveKeyStep(node json.RawMessage, nodeStart int, key string, rest []any) (json.RawMessage, int, bool) { - var m map[string]json.RawMessage - if err := json.Unmarshal(node, &m); err != nil { +func resolveKeyStep(node jsontext.Value, nodeStart int, key string, rest []any) (jsontext.Value, int, bool) { + var m map[string]jsontext.Value + if err := json.Unmarshal(node, &m, jsonx.V1()); err != nil { return nil, 0, false } @@ -666,9 +669,9 @@ func resolveKeyStep(node json.RawMessage, nodeStart int, key string, rest []any) // against the whole node) so an earlier element byte-identical to arr[idx] // can't steal the match -- each element narrows the search window past // itself. -func resolveIndexStep(node json.RawMessage, nodeStart, idx int, rest []any) (json.RawMessage, int, bool) { - var arr []json.RawMessage - if err := json.Unmarshal(node, &arr); err != nil { +func resolveIndexStep(node jsontext.Value, nodeStart, idx int, rest []any) (jsontext.Value, int, bool) { + var arr []jsontext.Value + if err := json.Unmarshal(node, &arr, jsonx.V1()); err != nil { return nil, 0, false } diff --git a/services/accessanalyzer/policy_analysis_test.go b/services/accessanalyzer/policy_analysis_test.go index 23800ce87d..1ea550a0c7 100644 --- a/services/accessanalyzer/policy_analysis_test.go +++ b/services/accessanalyzer/policy_analysis_test.go @@ -1,13 +1,14 @@ package accessanalyzer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/accessanalyzer" ) @@ -24,7 +25,7 @@ func policyAllow(action, resource string) string { }, } - b, _ := json.Marshal(p) + b, _ := json.Marshal(p, jsonx.V1()) return string(b) } @@ -48,7 +49,7 @@ func policyPublic() string { }, } - b, _ := json.Marshal(p) + b, _ := json.Marshal(p, jsonx.V1()) return string(b) } @@ -541,7 +542,7 @@ func TestCheckAccessNotGrantedHTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantResult, resp["result"]) }) } @@ -588,7 +589,7 @@ func TestCheckNoNewAccessHTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantResult, resp["result"]) }) } @@ -633,7 +634,7 @@ func TestCheckNoPublicAccessHTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantResult, resp["result"]) assert.NotNil(t, resp["reasons"]) }) @@ -689,7 +690,7 @@ func TestValidatePolicyHTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["findings"]) findings := resp["findings"].([]any) diff --git a/services/account/handler.go b/services/account/handler.go index 0fdd91ed74..2bc93ec586 100644 --- a/services/account/handler.go +++ b/services/account/handler.go @@ -1,7 +1,7 @@ package account import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -219,7 +220,7 @@ func decodeJSON(body []byte, v any) error { return nil } - return json.Unmarshal(body, v) + return json.Unmarshal(body, v, jsonx.V1()) } func (h *Handler) handleGetContactInformation(c *echo.Context, body []byte) error { diff --git a/services/account/handler_account_info_test.go b/services/account/handler_account_info_test.go index cd3e46d239..f522bb7fc5 100644 --- a/services/account/handler_account_info_test.go +++ b/services/account/handler_account_info_test.go @@ -1,13 +1,15 @@ package account_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/account" ) @@ -21,14 +23,14 @@ func TestHandler_GetAccountInformation(t *testing.T) { respBody := rec.Body.Bytes() var out account.Info - require.NoError(t, json.Unmarshal(respBody, &out)) + require.NoError(t, json.Unmarshal(respBody, &out, jsonx.V1())) assert.NotEmpty(t, out.AccountID) assert.NotEmpty(t, out.AccountCreatedDate) assert.Equal(t, account.StateActive, out.AccountState) // Response is a flat object, not wrapped like most other operations. var raw map[string]any - require.NoError(t, json.Unmarshal(respBody, &raw)) + require.NoError(t, json.Unmarshal(respBody, &raw, jsonx.V1())) assert.NotContains(t, raw, "Account") } @@ -63,7 +65,7 @@ func TestHandler_PutAccountName(t *testing.T) { require.Equal(t, http.StatusOK, infoRec.Code) var out account.Info - require.NoError(t, json.NewDecoder(infoRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(infoRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.accountName, out.AccountName) } }) @@ -78,7 +80,7 @@ func TestHandler_PrimaryEmail_GetDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["PrimaryEmail"]) } @@ -114,7 +116,7 @@ func TestHandler_PrimaryEmail_UpdateFlow(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) assert.Equal(t, "PENDING", startOut["Status"]) acceptRec := doRequest(t, h, "/acceptPrimaryEmailUpdate", map[string]any{ @@ -125,14 +127,14 @@ func TestHandler_PrimaryEmail_UpdateFlow(t *testing.T) { require.Equal(t, http.StatusOK, acceptRec.Code) var acceptOut map[string]any - require.NoError(t, json.NewDecoder(acceptRec.Body).Decode(&acceptOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(acceptRec.Body), &acceptOut, jsonx.V1())) assert.Equal(t, "ACCEPTED", acceptOut["Status"]) getRec := doRequest(t, h, "/getPrimaryEmail", map[string]any{"AccountId": "000000000000"}) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, tc.newEmail, getOut["PrimaryEmail"]) }) } @@ -206,7 +208,7 @@ func TestHandler_PrimaryEmail_StartConflictsOnCurrentEmail(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) currentEmail, ok := getOut["PrimaryEmail"].(string) require.True(t, ok) @@ -217,7 +219,7 @@ func TestHandler_PrimaryEmail_StartConflictsOnCurrentEmail(t *testing.T) { require.Equal(t, http.StatusConflict, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "ConflictException", out["__type"]) assert.Equal(t, "ConflictException", rec.Header().Get("X-Amzn-Errortype")) } @@ -262,7 +264,7 @@ func TestHandler_GetPrimaryEmailUpdateStatus(t *testing.T) { } var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantBody, out["Status"]) assert.NotZero(t, out["UpdatedAt"]) }) @@ -277,7 +279,7 @@ func TestHandler_GetGovCloudAccountInformation_NotLinked(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", out["__type"]) assert.Equal(t, "ResourceNotFoundException", rec.Header().Get("X-Amzn-Errortype")) } diff --git a/services/account/handler_contacts_test.go b/services/account/handler_contacts_test.go index 40445f54f7..886d5a6f9c 100644 --- a/services/account/handler_contacts_test.go +++ b/services/account/handler_contacts_test.go @@ -1,13 +1,16 @@ package account_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ContactInformation_PutGet(t *testing.T) { @@ -42,7 +45,7 @@ func TestHandler_ContactInformation_PutGet(t *testing.T) { var out struct { ContactInformation map[string]any `json:"ContactInformation"` } - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &out, jsonx.V1())) // Every field set on Put round-trips through Get exactly. for k, v := range full { @@ -137,7 +140,7 @@ func TestHandler_AlternateContact_PutGetDelete(t *testing.T) { var out struct { AlternateContact map[string]any `json:"AlternateContact"` } - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &out, jsonx.V1())) assert.Equal(t, "Test Contact", out.AlternateContact["Name"]) assert.Equal(t, tt.contactType, out.AlternateContact["AlternateContactType"]) diff --git a/services/account/handler_regions_test.go b/services/account/handler_regions_test.go index 5dfc1f9e5c..689669af96 100644 --- a/services/account/handler_regions_test.go +++ b/services/account/handler_regions_test.go @@ -1,7 +1,8 @@ package account_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/account" ) @@ -44,7 +46,7 @@ func TestHandler_ListRegions(t *testing.T) { var out struct { Regions []account.Region `json:"Regions"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Regions, tt.wantCount) }) } @@ -60,7 +62,7 @@ func TestHandler_ListRegions_RegionFieldsPresent(t *testing.T) { var out struct { Regions []map[string]any `json:"Regions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Regions) for _, r := range out.Regions { @@ -79,7 +81,7 @@ func TestHandler_ListRegions_Alphabetical(t *testing.T) { var out struct { Regions []account.Region `json:"Regions"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.Regions) for i := 1; i < len(out.Regions); i++ { @@ -129,7 +131,7 @@ func TestHandler_ListRegions_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Regions []map[string]any `json:"Regions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Regions), tc.maxResults) for _, r := range resp.Regions { @@ -231,7 +233,7 @@ func TestHandler_GetRegionOptStatus(t *testing.T) { if tc.wantStatus != "" { var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tc.regionName, out["RegionName"]) assert.Equal(t, tc.wantStatus, out["RegionOptStatus"]) } @@ -263,7 +265,7 @@ func TestHandler_EnableDisableRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) return out["RegionOptStatus"].(string) } diff --git a/services/account/handler_test.go b/services/account/handler_test.go index 397bccd3e7..89c0f8df87 100644 --- a/services/account/handler_test.go +++ b/services/account/handler_test.go @@ -1,7 +1,8 @@ package account_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/internal/awstest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/account" ) @@ -247,6 +249,6 @@ func TestHandler_ErrorEnvelope(t *testing.T) { assert.Equal(t, "ResourceNotFoundException", rec.Header().Get("X-Amzn-Errortype")) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", out["__type"]) } diff --git a/services/account/persistence.go b/services/account/persistence.go index 199c24f9a8..7ee64bd71b 100644 --- a/services/account/persistence.go +++ b/services/account/persistence.go @@ -2,7 +2,7 @@ package account import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -58,21 +58,21 @@ const accountSnapshotVersion = 2 // key on) and Regions is a plain (non-map) slice, so both stay raw; the // rest are scalars. type backendSnapshot struct { - AccountCreatedDate time.Time `json:"accountCreatedDate"` - Tables map[string]json.RawMessage `json:"tables"` - ContactInfo *ContactInformation `json:"contactInfo,omitempty"` - AccountID string `json:"accountID"` - Region string `json:"region"` - AccountName string `json:"accountName"` - PrimaryEmail string `json:"primaryEmail"` - PendingEmail string `json:"pendingEmail"` - PendingOTP string `json:"pendingOTP"` - PhoneVerifyNumber string `json:"phoneVerifyNumber,omitempty"` - PhoneVerifyOTP string `json:"phoneVerifyOTP,omitempty"` - PrimaryEmailUpdateStatus PrimaryEmailUpdateStatus `json:"primaryEmailUpdateStatus,omitempty"` - PrimaryEmailUpdateAt time.Time `json:"primaryEmailUpdateAt"` - Regions []*Region `json:"regions"` - Version int `json:"version"` + AccountCreatedDate time.Time `json:"accountCreatedDate"` + Tables map[string]jsontext.Value `json:"tables"` + ContactInfo *ContactInformation `json:"contactInfo,omitempty"` + AccountID string `json:"accountID"` + Region string `json:"region"` + AccountName string `json:"accountName"` + PrimaryEmail string `json:"primaryEmail"` + PendingEmail string `json:"pendingEmail"` + PendingOTP string `json:"pendingOTP"` + PhoneVerifyNumber string `json:"phoneVerifyNumber,omitempty"` + PhoneVerifyOTP string `json:"phoneVerifyOTP,omitempty"` + PrimaryEmailUpdateStatus PrimaryEmailUpdateStatus `json:"primaryEmailUpdateStatus,omitempty"` + PrimaryEmailUpdateAt time.Time `json:"primaryEmailUpdateAt"` + Regions []*Region `json:"regions"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. It implements diff --git a/services/account/validation_test.go b/services/account/validation_test.go index d64640f719..a00e5fdda5 100644 --- a/services/account/validation_test.go +++ b/services/account/validation_test.go @@ -1,7 +1,7 @@ package account_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/account" ) @@ -165,7 +166,7 @@ func TestHandler_Validation(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantCode, out.Type) assert.Contains(t, out.Message, tt.wantMsg) assert.False(t, strings.HasPrefix(out.Message, tt.wantCode), "message repeats the code: %q", out.Message) diff --git a/services/acm/acme_domain_validation_status_test.go b/services/acm/acme_domain_validation_status_test.go index badf2e9663..008b279bb1 100644 --- a/services/acm/acme_domain_validation_status_test.go +++ b/services/acm/acme_domain_validation_status_test.go @@ -1,7 +1,7 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAcmeDomainValidation_SettlesToValid(t *testing.T) { @@ -39,7 +41,7 @@ func TestAcmeDomainValidation_SettlesToValid(t *testing.T) { "PrevalidationOptions": map[string]any{ "DnsPrevalidation": map[string]any{"HostedZoneId": "Z123456"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) @@ -48,7 +50,7 @@ func TestAcmeDomainValidation_SettlesToValid(t *testing.T) { var out struct { Arn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) time.Sleep(tt.wait) @@ -58,13 +60,13 @@ func TestAcmeDomainValidation_SettlesToValid(t *testing.T) { "PrevalidationOptions": map[string]any{ "DnsPrevalidation": map[string]any{"HostedZoneId": "Z999999"}, }, - }) + }, jsonx.V1()) require.NoError(t, mErr) require.Equal(t, http.StatusOK, postACMJSON(t, h, "UpdateAcmeDomainValidation", string(upd)).Code) } - descBody, err := json.Marshal(map[string]string{"AcmeDomainValidationArn": out.Arn}) + descBody, err := json.Marshal(map[string]string{"AcmeDomainValidationArn": out.Arn}, jsonx.V1()) require.NoError(t, err) desc := postACMJSON(t, h, "DescribeAcmeDomainValidation", string(descBody)) @@ -75,7 +77,7 @@ func TestAcmeDomainValidation_SettlesToValid(t *testing.T) { Status string `json:"Status"` } `json:"AcmeDomainValidation"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantStatus, got.V.Status) }) }) diff --git a/services/acm/certificate_detail_no_fabricated_keyid_test.go b/services/acm/certificate_detail_no_fabricated_keyid_test.go index 4a7367fb43..82b87f85c5 100644 --- a/services/acm/certificate_detail_no_fabricated_keyid_test.go +++ b/services/acm/certificate_detail_no_fabricated_keyid_test.go @@ -1,11 +1,14 @@ package acm_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestACMHandler_DescribeCertificate_NoFabricatedKeyId locks in the removal @@ -30,19 +33,19 @@ func TestACMHandler_DescribeCertificate_NoFabricatedKeyId(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) - var certRaw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(raw["Certificate"], &certRaw)) + var certRaw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(raw["Certificate"], &certRaw, jsonx.V1())) _, hasKeyID := certRaw["KeyId"] require.False(t, hasKeyID, "DescribeCertificate must not emit a fabricated KeyId member on Certificate") diff --git a/services/acm/certificate_validation_test.go b/services/acm/certificate_validation_test.go index 2817885695..570abcf89e 100644 --- a/services/acm/certificate_validation_test.go +++ b/services/acm/certificate_validation_test.go @@ -1,12 +1,14 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestACMHandler_MalformedArn_ReturnsInvalidArnException verifies that a @@ -102,7 +104,7 @@ func TestACMHandler_RequestCertificate_TooManySANs_ReturnsLimitExceeded(t *testi body, err := json.Marshal(map[string]any{ "DomainName": "toomany.example.com", "SubjectAlternativeNames": sans, - }) + }, jsonx.V1()) require.NoError(t, err) h := newACMHandler() @@ -124,7 +126,7 @@ func TestACMHandler_TooManyTags_ReturnsTooManyTagsException(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) tags := make([]map[string]string, 0, 51) for i := range 51 { @@ -134,7 +136,7 @@ func TestACMHandler_TooManyTags_ReturnsTooManyTagsException(t *testing.T) { body, err := json.Marshal(map[string]any{ "CertificateArn": reqOut.CertificateArn, "Tags": tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "AddTagsToCertificate", string(body)) @@ -155,12 +157,12 @@ func TestACMHandler_ReservedTagPrefix_ReturnsInvalidTagException(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) body, err := json.Marshal(map[string]any{ "CertificateArn": reqOut.CertificateArn, "Tags": []map[string]string{{"Key": "aws:reserved", "Value": "v"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "AddTagsToCertificate", string(body)) @@ -187,9 +189,9 @@ func TestACMHandler_RequestCertificate_DomainValidationOptions_Applied(t *testin var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) diff --git a/services/acm/handler.go b/services/acm/handler.go index 24f82f24ac..17aadabceb 100644 --- a/services/acm/handler.go +++ b/services/acm/handler.go @@ -2,7 +2,8 @@ package acm import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "regexp" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -182,8 +184,8 @@ func (h *Handler) ExtractResource(c *echo.Context) string { if err != nil { return "" } - var m map[string]json.RawMessage - if unmarshalErr := json.Unmarshal(body, &m); unmarshalErr != nil { + var m map[string]jsontext.Value + if unmarshalErr := json.Unmarshal(body, &m, jsonx.V1()); unmarshalErr != nil { return "" } @@ -194,7 +196,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var arn string - if unmarshalErr := json.Unmarshal(raw, &arn); unmarshalErr == nil && arn != "" { + if unmarshalErr := json.Unmarshal(raw, &arn, jsonx.V1()); unmarshalErr == nil && arn != "" { return arn } } @@ -227,7 +229,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // handleError writes a standardized error response back to the client. diff --git a/services/acm/handler_account.go b/services/acm/handler_account.go index 0f955ba393..84ac957a24 100644 --- a/services/acm/handler_account.go +++ b/services/acm/handler_account.go @@ -2,7 +2,9 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type expiryEventsConfiguration struct { @@ -32,7 +34,7 @@ func (h *Handler) jsonGetAccountConfiguration(ctx context.Context, _ []byte) (an func (h *Handler) jsonPutAccountConfiguration(ctx context.Context, body []byte) (any, error) { var input putAccountConfigurationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_acme_accounts.go b/services/acm/handler_acme_accounts.go index c11d7ef1d3..86194a6410 100644 --- a/services/acm/handler_acme_accounts.go +++ b/services/acm/handler_acme_accounts.go @@ -2,7 +2,9 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type acmeAccountWire struct { @@ -36,7 +38,7 @@ type describeAcmeAccountOutput struct { func (h *Handler) jsonDescribeAcmeAccount(ctx context.Context, body []byte) (any, error) { var input describeAcmeAccountInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -63,7 +65,7 @@ type listAcmeAccountsOutput struct { func (h *Handler) jsonListAcmeAccounts(ctx context.Context, body []byte) (any, error) { var input listAcmeAccountsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -89,7 +91,7 @@ type revokeAcmeAccountOutput struct{} func (h *Handler) jsonRevokeAcmeAccount(ctx context.Context, body []byte) (any, error) { var input revokeAcmeAccountInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_acme_domain_validations.go b/services/acm/handler_acme_domain_validations.go index ba9be4f718..95a6bf3747 100644 --- a/services/acm/handler_acme_domain_validations.go +++ b/services/acm/handler_acme_domain_validations.go @@ -2,9 +2,10 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -107,7 +108,7 @@ type createAcmeDomainValidationOutput struct { func (h *Handler) jsonCreateAcmeDomainValidation(ctx context.Context, body []byte) (any, error) { var input createAcmeDomainValidationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -146,7 +147,7 @@ type describeAcmeDomainValidationOutput struct { func (h *Handler) jsonDescribeAcmeDomainValidation(ctx context.Context, body []byte) (any, error) { var input describeAcmeDomainValidationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -176,7 +177,7 @@ type deleteAcmeDomainValidationOutput struct{} func (h *Handler) jsonDeleteAcmeDomainValidation(ctx context.Context, body []byte) (any, error) { var input deleteAcmeDomainValidationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -204,9 +205,10 @@ type listAcmeDomainValidationsOutput struct { AcmeDomainValidations []acmeDomainValidationWire `json:"AcmeDomainValidations"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) jsonListAcmeDomainValidations(ctx context.Context, body []byte) (any, error) { var input listAcmeDomainValidationsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -240,7 +242,7 @@ type updateAcmeDomainValidationOutput struct{} func (h *Handler) jsonUpdateAcmeDomainValidation(ctx context.Context, body []byte) (any, error) { var input updateAcmeDomainValidationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_acme_eab.go b/services/acm/handler_acme_eab.go index d7e527d11c..79fe1c47d4 100644 --- a/services/acm/handler_acme_eab.go +++ b/services/acm/handler_acme_eab.go @@ -2,9 +2,10 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -64,7 +65,7 @@ type createAcmeEABOutput struct { func (h *Handler) jsonCreateAcmeExternalAccountBinding(ctx context.Context, body []byte) (any, error) { var input createAcmeEABInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -110,7 +111,7 @@ type describeAcmeEABOutput struct { func (h *Handler) jsonDescribeAcmeExternalAccountBinding(ctx context.Context, body []byte) (any, error) { var input describeAcmeEABInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -140,7 +141,7 @@ type deleteAcmeEABOutput struct{} func (h *Handler) jsonDeleteAcmeExternalAccountBinding(ctx context.Context, body []byte) (any, error) { var input deleteAcmeEABInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -168,9 +169,10 @@ type listAcmeEABsOutput struct { ExternalAccountBindings []acmeEABWire `json:"ExternalAccountBindings"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) jsonListAcmeExternalAccountBindings(ctx context.Context, body []byte) (any, error) { var input listAcmeEABsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -208,7 +210,7 @@ type getAcmeEABCredentialsOutput struct { func (h *Handler) jsonGetAcmeExternalAccountBindingCredentials(ctx context.Context, body []byte) (any, error) { var input getAcmeEABCredentialsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -236,7 +238,7 @@ type revokeAcmeEABOutput struct{} func (h *Handler) jsonRevokeAcmeExternalAccountBinding(ctx context.Context, body []byte) (any, error) { var input revokeAcmeEABInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_acme_endpoints.go b/services/acm/handler_acme_endpoints.go index a1d47d8233..015f62e8b1 100644 --- a/services/acm/handler_acme_endpoints.go +++ b/services/acm/handler_acme_endpoints.go @@ -2,9 +2,10 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -65,7 +66,7 @@ type createAcmeEndpointOutput struct { func (h *Handler) jsonCreateAcmeEndpoint(ctx context.Context, body []byte) (any, error) { var input createAcmeEndpointInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -111,7 +112,7 @@ type describeAcmeEndpointOutput struct { func (h *Handler) jsonDescribeAcmeEndpoint(ctx context.Context, body []byte) (any, error) { var input describeAcmeEndpointInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -141,7 +142,7 @@ type deleteAcmeEndpointOutput struct{} func (h *Handler) jsonDeleteAcmeEndpoint(ctx context.Context, body []byte) (any, error) { var input deleteAcmeEndpointInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -170,7 +171,7 @@ type listAcmeEndpointsOutput struct { func (h *Handler) jsonListAcmeEndpoints(ctx context.Context, body []byte) (any, error) { var input listAcmeEndpointsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -198,7 +199,7 @@ type updateAcmeEndpointOutput struct{} func (h *Handler) jsonUpdateAcmeEndpoint(ctx context.Context, body []byte) (any, error) { var input updateAcmeEndpointInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_certificate_lifecycle_test.go b/services/acm/handler_certificate_lifecycle_test.go index a3a67fee04..79a183e88c 100644 --- a/services/acm/handler_certificate_lifecycle_test.go +++ b/services/acm/handler_certificate_lifecycle_test.go @@ -2,7 +2,7 @@ package acm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -29,11 +30,11 @@ func TestACMHandler_DNSValidationWorkflow(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) require.NotEmpty(t, reqOut.CertificateArn) // Describe should show PENDING_VALIDATION with CNAME records - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -48,7 +49,7 @@ func TestACMHandler_DNSValidationWorkflow(t *testing.T) { } `json:"DomainValidationOptions"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Equal(t, "PENDING_VALIDATION", descOut.Certificate.Status) require.NotEmpty(t, descOut.Certificate.DomainValidationOptions) assert.NotNil(t, descOut.Certificate.DomainValidationOptions[0].ResourceRecord) @@ -64,7 +65,7 @@ func TestACMHandler_DNSValidationWorkflow(t *testing.T) { Status string `json:"Status"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ISSUED", out.Certificate.Status, "cert should transition to ISSUED") }) } @@ -89,7 +90,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -98,7 +99,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { "CertificateArn": certARN, "Domain": "email-resend.example.com", "ValidationDomain": "email-resend.example.com", - }) + }, jsonx.V1()) return string(b) }, @@ -114,7 +115,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { "CertificateArn": certARN, "Domain": "example.com", "ValidationDomain": "example.com", - }) + }, jsonx.V1()) return string(b) }, @@ -131,7 +132,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -140,7 +141,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { "CertificateArn": certARN, "Domain": "issued.example.com", "ValidationDomain": "issued.example.com", - }) + }, jsonx.V1()) return string(b) }, @@ -157,7 +158,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -166,7 +167,7 @@ func TestACMHandler_ResendValidationEmail(t *testing.T) { "CertificateArn": certARN, "Domain": "dns.example.com", "ValidationDomain": "dns.example.com", - }) + }, jsonx.V1()) return string(b) }, @@ -222,7 +223,7 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -230,7 +231,7 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { b, _ := json.Marshal(map[string]string{ "CertificateArn": certARN, "RevocationReason": "UNSPECIFIED", - }) + }, jsonx.V1()) return string(b) }, @@ -245,7 +246,7 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -253,7 +254,7 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { b, _ := json.Marshal(map[string]string{ "CertificateArn": certARN, "RevocationReason": "KEY_COMPROMISE", - }) + }, jsonx.V1()) return string(b) }, @@ -286,12 +287,12 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Revoke once b, _ := json.Marshal(map[string]string{ "CertificateArn": out.CertificateArn, "RevocationReason": "UNSPECIFIED", - }) + }, jsonx.V1()) postACMJSON(t, h, "RevokeCertificate", string(b)) return out.CertificateArn @@ -300,7 +301,7 @@ func TestACMHandler_RevokeCertificate(t *testing.T) { b, _ := json.Marshal(map[string]string{ "CertificateArn": certARN, "RevocationReason": "UNSPECIFIED", - }) + }, jsonx.V1()) return string(b) }, @@ -349,18 +350,18 @@ func TestACMHandler_RevokeCertificate_DescribeShowsRevoked(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) // Revoke it revokeBody, _ := json.Marshal(map[string]string{ "CertificateArn": reqOut.CertificateArn, "RevocationReason": "KEY_COMPROMISE", - }) + }, jsonx.V1()) revokeRec := postACMJSON(t, h, "RevokeCertificate", string(revokeBody)) require.Equal(t, http.StatusOK, revokeRec.Code) // Describe should show REVOKED with RevocationReason - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -371,7 +372,7 @@ func TestACMHandler_RevokeCertificate_DescribeShowsRevoked(t *testing.T) { RevocationReason string `json:"RevocationReason"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "REVOKED", descOut.Certificate.Status) assert.Equal(t, "KEY_COMPROMISE", descOut.Certificate.RevocationReason) assert.NotNil(t, descOut.Certificate.RevokedAt) @@ -396,7 +397,7 @@ func TestACMHandler_UpdateCertificateOptions(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -406,7 +407,7 @@ func TestACMHandler_UpdateCertificateOptions(t *testing.T) { "Options": map[string]string{ "CertificateTransparencyLoggingPreference": "ENABLED", }, - }) + }, jsonx.V1()) return string(b) }, @@ -421,7 +422,7 @@ func TestACMHandler_UpdateCertificateOptions(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.CertificateArn }, @@ -431,7 +432,7 @@ func TestACMHandler_UpdateCertificateOptions(t *testing.T) { "Options": map[string]string{ "CertificateTransparencyLoggingPreference": "DISABLED", }, - }) + }, jsonx.V1()) return string(b) }, @@ -447,7 +448,7 @@ func TestACMHandler_UpdateCertificateOptions(t *testing.T) { "Options": map[string]string{ "CertificateTransparencyLoggingPreference": "ENABLED", }, - }) + }, jsonx.V1()) return string(b) }, @@ -506,7 +507,7 @@ func TestACMHandler_UpdateCertificateOptions_DescribeShowsOptions(t *testing.T) var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) // Update options optsBody, _ := json.Marshal(map[string]any{ @@ -514,12 +515,12 @@ func TestACMHandler_UpdateCertificateOptions_DescribeShowsOptions(t *testing.T) "Options": map[string]string{ "CertificateTransparencyLoggingPreference": "DISABLED", }, - }) + }, jsonx.V1()) optsRec := postACMJSON(t, h, "UpdateCertificateOptions", string(optsBody)) require.Equal(t, http.StatusOK, optsRec.Code) // Describe should reflect options - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -530,7 +531,7 @@ func TestACMHandler_UpdateCertificateOptions_DescribeShowsOptions(t *testing.T) } `json:"Options"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.Certificate.Options) assert.Equal(t, "DISABLED", descOut.Certificate.Options.CertificateTransparencyLoggingPreference) } @@ -559,9 +560,9 @@ func TestACMHandler_RevokeCertificate_PendingValidationRejected(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(body)) require.Equal(t, http.StatusOK, descRec.Code) @@ -570,14 +571,14 @@ func TestACMHandler_RevokeCertificate_PendingValidationRejected(t *testing.T) { Status string `json:"Status"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Equal(t, "PENDING_VALIDATION", descOut.Certificate.Status) // Try to revoke PENDING cert revokeBody, _ := json.Marshal(map[string]string{ "CertificateArn": reqOut.CertificateArn, "RevocationReason": "UNSPECIFIED", - }) + }, jsonx.V1()) revokeRec := postACMJSON(t, h, "RevokeCertificate", string(revokeBody)) assert.Equal(t, http.StatusBadRequest, revokeRec.Code) assert.Contains(t, revokeRec.Body.String(), "ConflictException") @@ -708,7 +709,7 @@ func TestACMHandler_StatusLifecycle_DescribeReflectsNewStatus(t *testing.T) { certARN := tt.setupCert(t, b) tt.transition(t, b, certARN) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": certARN}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": certARN}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -718,7 +719,7 @@ func TestACMHandler_StatusLifecycle_DescribeReflectsNewStatus(t *testing.T) { FailureReason string `json:"FailureReason,omitempty"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, tt.wantStatus, descOut.Certificate.Status) }) } diff --git a/services/acm/handler_certificate_status_errors_test.go b/services/acm/handler_certificate_status_errors_test.go index cce0dcd514..63e0ad1735 100644 --- a/services/acm/handler_certificate_status_errors_test.go +++ b/services/acm/handler_certificate_status_errors_test.go @@ -2,7 +2,7 @@ package acm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -49,7 +50,7 @@ func TestACMHandler_ExportCertificate_AmazonIssued(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) return reqOut.CertificateArn }, @@ -85,7 +86,7 @@ func TestACMHandler_ExportCertificate_AmazonIssued(t *testing.T) { body, _ := json.Marshal(map[string]string{ "CertificateArn": certARN, "Passphrase": "dGVzdA==", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "ExportCertificate", string(body)) if tt.wantErrType == "" { @@ -96,7 +97,7 @@ func TestACMHandler_ExportCertificate_AmazonIssued(t *testing.T) { CertificateChain string `json:"CertificateChain"` PrivateKey string `json:"PrivateKey"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out.Certificate, "BEGIN CERTIFICATE") assert.NotEmpty(t, out.PrivateKey) @@ -112,7 +113,7 @@ func TestACMHandler_ExportCertificate_AmazonIssued(t *testing.T) { Exported bool `json:"Exported"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) var found bool for _, s := range listOut.CertificateSummaryList { @@ -132,7 +133,7 @@ func TestACMHandler_ExportCertificate_AmazonIssued(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) }) } @@ -148,7 +149,7 @@ func requestAndAwaitIssued(t *testing.T, h *acm.Handler, domainName, exportOptio reqBody["Options"] = map[string]string{"Export": exportOption} } - reqJSON, err := json.Marshal(reqBody) + reqJSON, err := json.Marshal(reqBody, jsonx.V1()) require.NoError(t, err) reqRec := postACMJSON(t, h, "RequestCertificate", string(reqJSON)) @@ -157,7 +158,7 @@ func requestAndAwaitIssued(t *testing.T, h *acm.Handler, domainName, exportOptio var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) descRec := postACMJSON(t, h, "DescribeCertificate", `{"CertificateArn":"`+reqOut.CertificateArn+`"}`) @@ -166,7 +167,7 @@ func requestAndAwaitIssued(t *testing.T, h *acm.Handler, domainName, exportOptio Status string `json:"Status"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) require.Equal(t, "ISSUED", out.Certificate.Status) return reqOut.CertificateArn @@ -204,14 +205,14 @@ func TestACMHandler_RenewCertificate_Imported_Returns_RequestInProgressException require.NoError(t, err) h := acm.NewHandler(b) - body, _ := json.Marshal(map[string]string{"CertificateArn": imported.ARN}) + body, _ := json.Marshal(map[string]string{"CertificateArn": imported.ARN}, jsonx.V1()) rec := postACMJSON(t, h, "RenewCertificate", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "RequestInProgressException", errResp.Type, "RenewCertificate on IMPORTED cert must return RequestInProgressException, not RequestError") }) @@ -298,14 +299,14 @@ func TestACMHandler_GetCertificate_NonIssuedStates_Returns_RequestInProgressExce certARN := tt.setup(t, b) h := acm.NewHandler(b) - body, _ := json.Marshal(map[string]string{"CertificateArn": certARN}) + body, _ := json.Marshal(map[string]string{"CertificateArn": certARN}, jsonx.V1()) rec := postACMJSON(t, h, "GetCertificate", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal( t, "RequestInProgressException", @@ -348,16 +349,16 @@ func TestACMHandler_RevokeCertificate_PendingValidation_Returns_ConflictExceptio body, _ := json.Marshal(map[string]string{ "DomainName": "revoke-pending.example.com", "ValidationMethod": method, - }) + }, jsonx.V1()) reqRec := postACMJSON(t, h, "RequestCertificate", string(body)) require.Equal(t, http.StatusOK, reqRec.Code) var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -366,20 +367,20 @@ func TestACMHandler_RevokeCertificate_PendingValidation_Returns_ConflictExceptio Status string `json:"Status"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Equal(t, "PENDING_VALIDATION", descOut.Certificate.Status) revokeBody, _ := json.Marshal(map[string]string{ "CertificateArn": reqOut.CertificateArn, "RevocationReason": "UNSPECIFIED", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "RevokeCertificate", string(revokeBody)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal( t, "ConflictException", @@ -459,14 +460,14 @@ func TestACMHandler_UpdateCertificateOptions_NonIssued_Returns_InvalidStateExcep "Options": map[string]string{ "CertificateTransparencyLoggingPreference": "ENABLED", }, - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "UpdateCertificateOptions", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal( t, "InvalidStateException", @@ -499,7 +500,7 @@ func TestACMHandler_GetCertificate_Issued_Succeeds(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) // No ValidationMethod is set, so the cert issues synchronously. descRec := postACMJSON(t, h, "DescribeCertificate", @@ -509,17 +510,17 @@ func TestACMHandler_GetCertificate_Issued_Succeeds(t *testing.T) { Status string `json:"Status"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Equal(t, "ISSUED", descOut.Certificate.Status) - body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) rec := postACMJSON(t, h, "GetCertificate", string(body)) assert.Equal(t, http.StatusOK, rec.Code) var out struct { Certificate string `json:"Certificate"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out.Certificate, "BEGIN CERTIFICATE") }) } diff --git a/services/acm/handler_certificates.go b/services/acm/handler_certificates.go index f25f4054a2..f61894decf 100644 --- a/services/acm/handler_certificates.go +++ b/services/acm/handler_certificates.go @@ -3,9 +3,11 @@ package acm import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type requestCertificateInput struct { @@ -257,7 +259,7 @@ type updateCertificateOptionsOutput struct{} func (h *Handler) jsonRequestCertificate(ctx context.Context, body []byte) (any, error) { var input requestCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrRequestCertInvalidParameter } certType := "" @@ -400,7 +402,7 @@ func buildRenewalSummaryDetail(rs *RenewalSummary) *renewalSummaryDetail { func (h *Handler) jsonDescribeCertificate(ctx context.Context, body []byte) (any, error) { var input describeCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } if err := validateCertArn(input.CertificateArn); err != nil { @@ -459,7 +461,7 @@ func (h *Handler) jsonDescribeCertificate(ctx context.Context, body []byte) (any // jsonListCertificates handles the ListCertificates operation. func (h *Handler) jsonListCertificates(ctx context.Context, body []byte) (any, error) { var input listCertificatesInput - _ = json.Unmarshal(body, &input) + _ = json.Unmarshal(body, &input, jsonx.V1()) if input.MaxItems < 0 || input.MaxItems > maxListItems { return nil, fmt.Errorf("%w: MaxItems must be between 1 and %d", ErrInvalidArgs, maxListItems) @@ -639,7 +641,7 @@ type listCertificateDomainValidationsOutput struct { // jsonListCertificateDomainValidations handles the ListCertificateDomainValidations operation. func (h *Handler) jsonListCertificateDomainValidations(ctx context.Context, body []byte) (any, error) { var input listCertificateDomainValidationsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -668,7 +670,7 @@ func (h *Handler) jsonListCertificateDomainValidations(ctx context.Context, body func (h *Handler) jsonDeleteCertificate(ctx context.Context, body []byte) (any, error) { var input deleteCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } if err := validateCertArn(input.CertificateArn); err != nil { @@ -684,7 +686,7 @@ func (h *Handler) jsonDeleteCertificate(ctx context.Context, body []byte) (any, func (h *Handler) jsonImportCertificate(ctx context.Context, body []byte) (any, error) { var input importCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } if err := validateCertArn(input.CertificateArn); err != nil { @@ -719,7 +721,7 @@ func (h *Handler) jsonImportCertificate(ctx context.Context, body []byte) (any, func (h *Handler) jsonRenewCertificate(ctx context.Context, body []byte) (any, error) { var input renewCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } if err := validateCertArn(input.CertificateArn); err != nil { @@ -734,7 +736,7 @@ func (h *Handler) jsonRenewCertificate(ctx context.Context, body []byte) (any, e func (h *Handler) jsonExportCertificate(ctx context.Context, body []byte) (any, error) { var input exportCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -769,7 +771,7 @@ func (h *Handler) jsonExportCertificate(ctx context.Context, body []byte) (any, func (h *Handler) jsonGetCertificate(ctx context.Context, body []byte) (any, error) { var input getCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } if err := validateCertArn(input.CertificateArn); err != nil { @@ -788,7 +790,7 @@ func (h *Handler) jsonGetCertificate(ctx context.Context, body []byte) (any, err func (h *Handler) jsonResendValidationEmail(ctx context.Context, body []byte) (any, error) { var input resendValidationEmailInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -806,7 +808,7 @@ func (h *Handler) jsonResendValidationEmail(ctx context.Context, body []byte) (a func (h *Handler) jsonRevokeCertificate(ctx context.Context, body []byte) (any, error) { var input revokeCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -823,7 +825,7 @@ func (h *Handler) jsonRevokeCertificate(ctx context.Context, body []byte) (any, func (h *Handler) jsonUpdateCertificateOptions(ctx context.Context, body []byte) (any, error) { var input updateCertificateOptionsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_certificates_list_test.go b/services/acm/handler_certificates_list_test.go index f95d69acdb..fe6d322a0a 100644 --- a/services/acm/handler_certificates_list_test.go +++ b/services/acm/handler_certificates_list_test.go @@ -3,7 +3,7 @@ package acm_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -46,7 +47,7 @@ func TestACMHandler_ListCertificates_StatusFilter(t *testing.T) { Status string `json:"Status"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(filterRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(filterRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.CertificateSummaryList, 1, "only the immediately-issued cert should match") for _, s := range out.CertificateSummaryList { @@ -75,7 +76,7 @@ func TestACMHandler_ListCertificates_EnrichedSummary(t *testing.T) { DomainName string `json:"DomainName"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.CertificateSummaryList) summary := out.CertificateSummaryList[0] @@ -91,7 +92,7 @@ func TestACMHandler_ListCertificates_SortByCreatedAt(t *testing.T) { // Create three certs in sequence for _, domain := range []string{"sort-a.example.com", "sort-b.example.com", "sort-c.example.com"} { - body, _ := json.Marshal(map[string]string{"DomainName": domain}) + body, _ := json.Marshal(map[string]string{"DomainName": domain}, jsonx.V1()) rec := postACMJSON(t, h, "RequestCertificate", string(body)) require.Equal(t, http.StatusOK, rec.Code) } @@ -106,7 +107,7 @@ func TestACMHandler_ListCertificates_SortByCreatedAt(t *testing.T) { DomainName string `json:"DomainName"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.CertificateSummaryList, 3) // Descending means newest first: sort-c, sort-b, sort-a @@ -133,7 +134,7 @@ func TestACMHandler_ListCertificates_KeyTypesFilter(t *testing.T) { DomainName string `json:"DomainName"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(matchRec.Body.Bytes(), &matchOut)) + require.NoError(t, json.Unmarshal(matchRec.Body.Bytes(), &matchOut, jsonx.V1())) require.NotEmpty(t, matchOut.CertificateSummaryList, "EC_prime256v1 filter should return results") // Filter that does not match @@ -144,7 +145,7 @@ func TestACMHandler_ListCertificates_KeyTypesFilter(t *testing.T) { var noMatchOut struct { CertificateSummaryList []struct{} `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(noMatchRec.Body.Bytes(), &noMatchOut)) + require.NoError(t, json.Unmarshal(noMatchRec.Body.Bytes(), &noMatchOut, jsonx.V1())) assert.Empty(t, noMatchOut.CertificateSummaryList, "RSA_2048 filter should return no results") } @@ -167,7 +168,7 @@ func TestACMHandler_ListCertificates_SubjectAlternativeNameSummaries(t *testing. SubjectAlternativeNameSummaries []string `json:"SubjectAlternativeNameSummaries"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.CertificateSummaryList) assert.Contains(t, out.CertificateSummaryList[0].SubjectAlternativeNameSummaries, "www.san.example.com") assert.Contains(t, out.CertificateSummaryList[0].SubjectAlternativeNameSummaries, "api.san.example.com") @@ -231,7 +232,7 @@ func TestACMHandler_ListCertificates_IncludesFilters(t *testing.T) { var out struct { CertificateSummaryList []struct{} `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) if tt.wantNonEmpty { assert.NotEmpty(t, out.CertificateSummaryList) } else { @@ -357,7 +358,7 @@ func TestACMHandler_ListCertificates_StatusFilter_AllStatuses(t *testing.T) { filterBody, _ := json.Marshal(map[string][]string{ "CertificateStatuses": {tt.filterStatus}, - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "ListCertificates", string(filterBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -366,7 +367,7 @@ func TestACMHandler_ListCertificates_StatusFilter_AllStatuses(t *testing.T) { Status string `json:"Status"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.CertificateSummaryList, tt.wantCount) if tt.wantCount > 0 { @@ -398,7 +399,7 @@ func TestACMHandler_ListCertificates_SubjectAlternativeNameSummaries_IncludesPri SubjectAlternativeNameSummaries []string `json:"SubjectAlternativeNameSummaries"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.CertificateSummaryList) summaries := listOut.CertificateSummaryList[0].SubjectAlternativeNameSummaries @@ -480,13 +481,13 @@ func TestACMHandler_SearchCertificates(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out, jsonx.V1())) body, _ := json.Marshal(map[string]any{ "FilterStatement": map[string]any{ "Filter": map[string]any{"CertificateArn": out.CertificateArn}, }, - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "SearchCertificates", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "search-arn-1.example.com") @@ -566,7 +567,7 @@ func TestACMHandler_SearchCertificates(t *testing.T) { } `json:"X509Attributes"` } `json:"Results"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Results, 1) assert.Equal(t, "commonname.example.com", out.Results[0].X509Attributes.Subject.CommonName) }, @@ -587,7 +588,7 @@ func TestACMHandler_SearchCertificates(t *testing.T) { importBody, marshalErr := json.Marshal(map[string]any{ "Certificate": base64.StdEncoding.EncodeToString([]byte(certPEM)), "PrivateKey": base64.StdEncoding.EncodeToString([]byte(keyPEM)), - }) + }, jsonx.V1()) require.NoError(t, marshalErr) importRec := postACMJSON(t, h, "ImportCertificate", string(importBody)) @@ -604,7 +605,7 @@ func TestACMHandler_SearchCertificates(t *testing.T) { CertificateArn string `json:"CertificateArn"` } `json:"Results"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Results, 1) }, }, @@ -631,7 +632,7 @@ func TestACMHandler_SearchCertificates(t *testing.T) { } `json:"CertificateMetadata"` } `json:"Results"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Results, 2) assert.GreaterOrEqual(t, out.Results[0].CertificateMetadata.AcmCertificateMetadata.CreatedAt, diff --git a/services/acm/handler_certificates_test.go b/services/acm/handler_certificates_test.go index 68f843c970..3bf4b8aab8 100644 --- a/services/acm/handler_certificates_test.go +++ b/services/acm/handler_certificates_test.go @@ -3,7 +3,8 @@ package acm_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -42,7 +44,7 @@ func TestACMHandler_ImportCertificate(t *testing.T) { in, _ := json.Marshal(map[string]string{ "Certificate": b64(certPEM), "PrivateKey": b64(keyPEM), - }) + }, jsonx.V1()) return string(in) }(), @@ -57,7 +59,7 @@ func TestACMHandler_ImportCertificate(t *testing.T) { { name: "missing_key", body: func() string { - in, _ := json.Marshal(map[string]string{"Certificate": b64(certPEM)}) + in, _ := json.Marshal(map[string]string{"Certificate": b64(certPEM)}, jsonx.V1()) return string(in) }(), @@ -147,7 +149,7 @@ func TestACMHandler_ExportCertificate(t *testing.T) { body, _ := json.Marshal(map[string]string{ "CertificateArn": tt.certARN, "Passphrase": "dGVzdA==", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "ExportCertificate", string(body)) assert.Equal(t, tt.wantCode, rec.Code) for _, s := range tt.wantContains { @@ -183,9 +185,9 @@ func TestACMHandler_GetCertificate(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + body, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) rec := postACMJSON(t, h, "GetCertificate", string(body)) assert.Equal(t, tt.wantCode, rec.Code) for _, s := range tt.wantContains { @@ -208,9 +210,9 @@ func TestACMHandler_DescribeCertificate_RealistFields(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -224,7 +226,7 @@ func TestACMHandler_DescribeCertificate_RealistFields(t *testing.T) { SignatureAlgorithm string `json:"SignatureAlgorithm"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.NotEmpty(t, descOut.Certificate.Serial, "Serial should be set") assert.Contains(t, descOut.Certificate.Subject, "realism.example.com", "Subject should contain domain") assert.Contains(t, descOut.Certificate.Issuer, "realism.example.com", "Issuer should contain domain (self-signed)") @@ -247,16 +249,16 @@ func TestACMHandler_ImportCertificate_RealistFields(t *testing.T) { body, _ := json.Marshal(map[string]string{ "Certificate": b64(src.CertificateBody), "PrivateKey": b64(src.PrivateKey), - }) + }, jsonx.V1()) importRec := postACMJSON(t, h, "ImportCertificate", string(body)) require.Equal(t, http.StatusOK, importRec.Code) var importOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut, jsonx.V1())) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": importOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": importOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -266,7 +268,7 @@ func TestACMHandler_ImportCertificate_RealistFields(t *testing.T) { Type string `json:"Type"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "IMPORTED", descOut.Certificate.Type) assert.NotNil(t, descOut.Certificate.ImportedAt, "ImportedAt should be set for imported cert") } @@ -288,14 +290,14 @@ func TestACMHandler_ImportCertificate_ReImport(t *testing.T) { body1, _ := json.Marshal(map[string]string{ "Certificate": b64(src1.CertificateBody), "PrivateKey": b64(src1.PrivateKey), - }) + }, jsonx.V1()) importRec := postACMJSON(t, h, "ImportCertificate", string(body1)) require.Equal(t, http.StatusOK, importRec.Code) var importOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut, jsonx.V1())) originalARN := importOut.CertificateArn // Re-import using the same ARN with new cert material @@ -303,7 +305,7 @@ func TestACMHandler_ImportCertificate_ReImport(t *testing.T) { "CertificateArn": originalARN, "Certificate": b64(src2.CertificateBody), "PrivateKey": b64(src2.PrivateKey), - }) + }, jsonx.V1()) reImportRec := postACMJSON(t, h, "ImportCertificate", string(body2)) require.Equal(t, http.StatusOK, reImportRec.Code) @@ -311,7 +313,7 @@ func TestACMHandler_ImportCertificate_ReImport(t *testing.T) { var reImportOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reImportRec.Body.Bytes(), &reImportOut)) + require.NoError(t, json.Unmarshal(reImportRec.Body.Bytes(), &reImportOut, jsonx.V1())) assert.Equal(t, originalARN, reImportOut.CertificateArn, "re-import should return same ARN") } @@ -330,7 +332,7 @@ func TestACMHandler_RequestCertificate_IdempotencyToken(t *testing.T) { var out1 struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) // Second call with same token should return same ARN rec2 := postACMJSON(t, h, "RequestCertificate", body) @@ -339,7 +341,7 @@ func TestACMHandler_RequestCertificate_IdempotencyToken(t *testing.T) { var out2 struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1.CertificateArn, out2.CertificateArn, "repeated RequestCertificate with same IdempotencyToken must return same ARN") @@ -376,7 +378,7 @@ func TestACMHandler_RequestCertificate_DomainValidation(t *testing.T) { t.Parallel() h := newACMHandler() - body, _ := json.Marshal(map[string]string{"DomainName": tt.domain}) + body, _ := json.Marshal(map[string]string{"DomainName": tt.domain}, jsonx.V1()) rec := postACMJSON(t, h, "RequestCertificate", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) }) @@ -395,9 +397,9 @@ func TestACMHandler_DescribeCertificate_KeyUsageAndInUseBy(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -408,7 +410,7 @@ func TestACMHandler_DescribeCertificate_KeyUsageAndInUseBy(t *testing.T) { InUseBy []string `json:"InUseBy"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.NotEmpty(t, descOut.Certificate.KeyUsage, "KeyUsage should be set") assert.Equal(t, "DIGITAL_SIGNATURE", descOut.Certificate.KeyUsage[0]["Name"]) @@ -430,9 +432,9 @@ func TestACMHandler_RequestCertificate_EMAIL_ValidationEmails(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -444,7 +446,7 @@ func TestACMHandler_RequestCertificate_EMAIL_ValidationEmails(t *testing.T) { } `json:"DomainValidationOptions"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.NotEmpty(t, descOut.Certificate.DomainValidationOptions) dvo := descOut.Certificate.DomainValidationOptions[0] @@ -483,7 +485,7 @@ func TestACMHandler_ExportCertificate_PassphraseRequired(t *testing.T) { { name: "missing_passphrase_rejected", body: func() string { - b2, _ := json.Marshal(map[string]string{"CertificateArn": importedCert.ARN}) + b2, _ := json.Marshal(map[string]string{"CertificateArn": importedCert.ARN}, jsonx.V1()) return string(b2) }(), @@ -496,7 +498,7 @@ func TestACMHandler_ExportCertificate_PassphraseRequired(t *testing.T) { b2, _ := json.Marshal(map[string]string{ "CertificateArn": importedCert.ARN, "Passphrase": "dGVzdC1wYXNz", // base64("test-pass") - }) + }, jsonx.V1()) return string(b2) }(), @@ -509,7 +511,7 @@ func TestACMHandler_ExportCertificate_PassphraseRequired(t *testing.T) { b2, _ := json.Marshal(map[string]string{ "CertificateArn": importedCert.ARN, "Passphrase": "not-valid-base64!!!", - }) + }, jsonx.V1()) return string(b2) }(), @@ -548,7 +550,7 @@ func TestACMHandler_ExportCertificate_CertificateChainAlwaysPresent(t *testing.T exportBody, _ := json.Marshal(map[string]string{ "CertificateArn": imported.ARN, "Passphrase": "dGVzdA==", // base64("test") - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "ExportCertificate", string(exportBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -557,7 +559,7 @@ func TestACMHandler_ExportCertificate_CertificateChainAlwaysPresent(t *testing.T Certificate string `json:"Certificate"` PrivateKey string `json:"PrivateKey"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.CertificateChain, "CertificateChain should always be present on export") assert.NotEmpty(t, out.Certificate) assert.NotEmpty(t, out.PrivateKey) @@ -565,7 +567,7 @@ func TestACMHandler_ExportCertificate_CertificateChainAlwaysPresent(t *testing.T func mustMarshal(t *testing.T, v any) string { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) @@ -623,7 +625,7 @@ func TestACMHandler_SubjectAlternativeNames_IncludesPrimaryDomain(t *testing.T) var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) descBody := mustMarshal(t, map[string]string{"CertificateArn": reqOut.CertificateArn}) descRec := postACMJSON(t, h, "DescribeCertificate", descBody) @@ -634,7 +636,7 @@ func TestACMHandler_SubjectAlternativeNames_IncludesPrimaryDomain(t *testing.T) SubjectAlternativeNames []string `json:"SubjectAlternativeNames"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) sans := descOut.Certificate.SubjectAlternativeNames require.NotEmpty(t, sans, "SubjectAlternativeNames must not be empty") @@ -676,21 +678,21 @@ func TestACMHandler_DescribeCertificate_InUseByIsAlwaysArray(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) descBody := mustMarshal(t, map[string]string{"CertificateArn": reqOut.CertificateArn}) descRec := postACMJSON(t, h, "DescribeCertificate", descBody) require.Equal(t, http.StatusOK, descRec.Code) // Use raw JSON to verify InUseBy is an array, not null or missing. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) certRaw, ok := raw["Certificate"] require.True(t, ok, "response must have Certificate field") - var certMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(certRaw, &certMap)) + var certMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(certRaw, &certMap, jsonx.V1())) inUseByRaw, present := certMap["InUseBy"] require.True(t, present, @@ -716,7 +718,7 @@ func TestACMHandler_Serial_ColonSeparatedHexFormat(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) descBody := mustMarshal(t, map[string]string{"CertificateArn": reqOut.CertificateArn}) descRec := postACMJSON(t, h, "DescribeCertificate", descBody) @@ -727,7 +729,7 @@ func TestACMHandler_Serial_ColonSeparatedHexFormat(t *testing.T) { Serial string `json:"Serial"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) serial := descOut.Certificate.Serial require.NotEmpty(t, serial, "Serial must be set for an issued certificate") @@ -769,7 +771,7 @@ func TestACMHandler_ImportedCertificate_SerialColonFormat(t *testing.T) { var importOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut, jsonx.V1())) descBody := mustMarshal(t, map[string]string{"CertificateArn": importOut.CertificateArn}) descRec := postACMJSON(t, h, "DescribeCertificate", descBody) @@ -780,7 +782,7 @@ func TestACMHandler_ImportedCertificate_SerialColonFormat(t *testing.T) { Serial string `json:"Serial"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) serial := descOut.Certificate.Serial require.NotEmpty(t, serial, "imported cert Serial must be set") diff --git a/services/acm/handler_request_certificate_invalid_parameter_test.go b/services/acm/handler_request_certificate_invalid_parameter_test.go index 66d90c7a26..a77ec39916 100644 --- a/services/acm/handler_request_certificate_invalid_parameter_test.go +++ b/services/acm/handler_request_certificate_invalid_parameter_test.go @@ -1,13 +1,15 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestACMHandler_RequestCertificate_InvalidParameterException verifies that RequestCertificate's @@ -40,7 +42,7 @@ func TestACMHandler_RequestCertificate_InvalidParameterException(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) }) } @@ -65,7 +67,7 @@ func TestACMHandler_RequestCertificate_IdempotencyMismatch_InvalidParameterExcep var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) } @@ -83,7 +85,7 @@ func TestACMHandler_CreateAcmeDomainValidation_InvalidDomainName_StillValidation "AcmeEndpointArn": epARN, "DomainName": "foo..bad.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) @@ -92,7 +94,7 @@ func TestACMHandler_CreateAcmeDomainValidation_InvalidDomainName_StillValidation var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp.Type) } diff --git a/services/acm/handler_resource_tags.go b/services/acm/handler_resource_tags.go index 5b5ed84748..a243436e71 100644 --- a/services/acm/handler_resource_tags.go +++ b/services/acm/handler_resource_tags.go @@ -2,9 +2,10 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -66,7 +67,7 @@ type listTagsForResourceOutput struct { func (h *Handler) jsonListTagsForResource(ctx context.Context, body []byte) (any, error) { var input listTagsForResourceInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -86,7 +87,7 @@ type tagResourceOutput struct{} func (h *Handler) jsonTagResource(ctx context.Context, body []byte) (any, error) { var input tagResourceInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -119,7 +120,7 @@ type untagResourceOutput struct{} func (h *Handler) jsonUntagResource(ctx context.Context, body []byte) (any, error) { var input untagResourceInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_search_certificates.go b/services/acm/handler_search_certificates.go index 0c5967d71c..a184b848c9 100644 --- a/services/acm/handler_search_certificates.go +++ b/services/acm/handler_search_certificates.go @@ -2,8 +2,10 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type dnsNameFilterWire struct { @@ -291,7 +293,7 @@ type searchCertificatesOutput struct { func (h *Handler) jsonSearchCertificates(ctx context.Context, body []byte) (any, error) { var input searchCertificatesInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_tags.go b/services/acm/handler_tags.go index 837431c39e..9c9e0daa49 100644 --- a/services/acm/handler_tags.go +++ b/services/acm/handler_tags.go @@ -2,10 +2,11 @@ package acm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -146,7 +147,7 @@ func (h *Handler) getTags(resourceID string) []map[string]string { func (h *Handler) jsonListTagsForCertificate(ctx context.Context, body []byte) (any, error) { var input listTagsForCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -163,7 +164,7 @@ func (h *Handler) jsonListTagsForCertificate(ctx context.Context, body []byte) ( func (h *Handler) jsonAddTagsToCertificate(ctx context.Context, body []byte) (any, error) { var input addTagsToCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } @@ -188,7 +189,7 @@ func (h *Handler) jsonAddTagsToCertificate(ctx context.Context, body []byte) (an func (h *Handler) jsonRemoveTagsFromCertificate(ctx context.Context, body []byte) (any, error) { var input removeTagsFromCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidParameter } diff --git a/services/acm/handler_tags_test.go b/services/acm/handler_tags_test.go index b58a87004e..51bd2dc160 100644 --- a/services/acm/handler_tags_test.go +++ b/services/acm/handler_tags_test.go @@ -1,7 +1,7 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -66,17 +67,17 @@ func TestACMHandler_RequestCertificate_Tags(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Verify tags are stored - listBody, _ := json.Marshal(map[string]string{"CertificateArn": out.CertificateArn}) + listBody, _ := json.Marshal(map[string]string{"CertificateArn": out.CertificateArn}, jsonx.V1()) listRec := postACMJSON(t, h, "ListTagsForCertificate", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var tagsOut struct { Tags []map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) tagMap := make(map[string]string) for _, t2 := range tagsOut.Tags { @@ -113,18 +114,18 @@ func TestACMHandler_GenericResourceTags_RouteByArnType(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) // Tag via the certificate-specific op... addBody, _ := json.Marshal(map[string]any{ "CertificateArn": reqOut.CertificateArn, "Tags": []map[string]string{{"Key": "via", "Value": "AddTagsToCertificate"}}, - }) + }, jsonx.V1()) addRec := postACMJSON(t, h, "AddTagsToCertificate", string(addBody)) require.Equal(t, http.StatusOK, addRec.Code) // ...and read it back via the generic op. - listBody, _ := json.Marshal(map[string]string{"ResourceArn": reqOut.CertificateArn}) + listBody, _ := json.Marshal(map[string]string{"ResourceArn": reqOut.CertificateArn}, jsonx.V1()) listRec := postACMJSON(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) assert.Contains(t, listRec.Body.String(), "AddTagsToCertificate") @@ -133,12 +134,12 @@ func TestACMHandler_GenericResourceTags_RouteByArnType(t *testing.T) { tagBody, _ := json.Marshal(map[string]any{ "ResourceArn": reqOut.CertificateArn, "Tags": []map[string]string{{"Key": "via", "Value": "TagResource"}}, - }) + }, jsonx.V1()) tagRec := postACMJSON(t, h, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, tagRec.Code, tagRec.Body.String()) // ...and read it back via the certificate-specific op. - certListBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + certListBody, _ := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) certListRec := postACMJSON(t, h, "ListTagsForCertificate", string(certListBody)) require.Equal(t, http.StatusOK, certListRec.Code) assert.Contains(t, certListRec.Body.String(), "TagResource") @@ -154,16 +155,19 @@ func TestACMHandler_GenericResourceTags_RouteByArnType(t *testing.T) { tagBody, _ := json.Marshal(map[string]any{ "ResourceArn": epARN, "Tags": []map[string]string{{"Key": "env", "Value": "prod"}}, - }) + }, jsonx.V1()) tagRec := postACMJSON(t, h, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, tagRec.Code, tagRec.Body.String()) - listBody, _ := json.Marshal(map[string]string{"ResourceArn": epARN}) + listBody, _ := json.Marshal(map[string]string{"ResourceArn": epARN}, jsonx.V1()) listRec := postACMJSON(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) assert.Contains(t, listRec.Body.String(), "prod") - untagBody, _ := json.Marshal(map[string]any{"ResourceArn": epARN, "TagKeys": []string{"env"}}) + untagBody, _ := json.Marshal( + map[string]any{"ResourceArn": epARN, "TagKeys": []string{"env"}}, + jsonx.V1(), + ) untagRec := postACMJSON(t, h, "UntagResource", string(untagBody)) require.Equal(t, http.StatusOK, untagRec.Code) @@ -250,7 +254,7 @@ func TestACMHandler_NewStyleTagValidation_UsesValidationAndServiceQuota(t *testi "AuthorizationBehavior": "PRE_APPROVED", "CertificateAuthority": map[string]any{"PublicCertificateAuthority": map[string]any{}}, "Tags": tooManyTags(), - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "CreateAcmeEndpoint", string(body)) @@ -268,7 +272,7 @@ func TestACMHandler_NewStyleTagValidation_UsesValidationAndServiceQuota(t *testi body, err := json.Marshal(map[string]any{ "ResourceArn": epARN, "Tags": []map[string]string{{"Key": "aws:reserved", "Value": "v"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "TagResource", string(body)) @@ -276,7 +280,7 @@ func TestACMHandler_NewStyleTagValidation_UsesValidationAndServiceQuota(t *testi assert.Contains(t, rec.Body.String(), "ValidationException") assert.NotContains(t, rec.Body.String(), "InvalidTagException") - listBody, _ := json.Marshal(map[string]string{"ResourceArn": epARN}) + listBody, _ := json.Marshal(map[string]string{"ResourceArn": epARN}, jsonx.V1()) listRec := postACMJSON(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) assert.JSONEq(t, `{"Tags":[]}`, listRec.Body.String(), "rejected tag must not be stored") @@ -289,7 +293,7 @@ func TestACMHandler_NewStyleTagValidation_UsesValidationAndServiceQuota(t *testi epARN := createTestAcmeEndpoint(t, h) - body, err := json.Marshal(map[string]any{"ResourceArn": epARN, "Tags": tooManyTags()}) + body, err := json.Marshal(map[string]any{"ResourceArn": epARN, "Tags": tooManyTags()}, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "TagResource", string(body)) @@ -336,7 +340,7 @@ func TestACMHandler_LegacyTagOps_KeyOrValueTooLong_InvalidTagException(t *testin body, err := json.Marshal(map[string]any{ "DomainName": "toolong.example.com", "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := postACMJSON(t, h, "RequestCertificate", string(body)) diff --git a/services/acm/handler_test.go b/services/acm/handler_test.go index d33ca07179..07f0e83625 100644 --- a/services/acm/handler_test.go +++ b/services/acm/handler_test.go @@ -1,7 +1,7 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -205,10 +206,13 @@ func TestACMHandler(t *testing.T) { CertificateArn string `json:"CertificateArn"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.NotEmpty(t, listResp.CertificateSummaryList) b, _ := json.Marshal( - map[string]string{"CertificateArn": listResp.CertificateSummaryList[0].CertificateArn}, + map[string]string{ + "CertificateArn": listResp.CertificateSummaryList[0].CertificateArn, + }, + jsonx.V1(), ) body = string(b) } @@ -235,7 +239,7 @@ func createTestAcmeEndpoint(t *testing.T, h *acm.Handler) string { var out struct { AcmeEndpointArn string `json:"AcmeEndpointArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.AcmeEndpointArn) return out.AcmeEndpointArn @@ -291,7 +295,7 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { t.Helper() epARN := createTestAcmeEndpoint(t, h) - body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) rec := postACMJSON(t, h, "DescribeAcmeEndpoint", string(body)) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) assert.Contains(t, rec.Body.String(), `"Status":"ACTIVE"`) @@ -338,11 +342,14 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { t.Helper() epARN := createTestAcmeEndpoint(t, h) - updBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN, "Contact": "REQUIRED"}) + updBody, _ := json.Marshal( + map[string]string{"AcmeEndpointArn": epARN, "Contact": "REQUIRED"}, + jsonx.V1(), + ) updRec := postACMJSON(t, h, "UpdateAcmeEndpoint", string(updBody)) require.Equal(t, http.StatusOK, updRec.Code, updRec.Body.String()) - descBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + descBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeAcmeEndpoint", string(descBody)) assert.Contains(t, descRec.Body.String(), `"Contact":"REQUIRED"`) }, @@ -353,7 +360,7 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { t.Helper() epARN := createTestAcmeEndpoint(t, h) - delBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + delBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) delRec := postACMJSON(t, h, "DeleteAcmeEndpoint", string(delBody)) require.Equal(t, http.StatusOK, delRec.Code) @@ -377,7 +384,7 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { assert.Contains(t, delRec.Body.String(), "ValidationException") assert.NotContains(t, delRec.Body.String(), "ResourceNotFoundException") - descBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + descBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeAcmeEndpoint", string(descBody)) assert.Equal(t, http.StatusOK, descRec.Code, "unrelated endpoint must be untouched") }, @@ -395,7 +402,7 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { eabBody, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) eabRec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(eabBody)) require.Equal(t, http.StatusOK, eabRec.Code, eabRec.Body.String()) @@ -404,7 +411,7 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { AcmeExternalAccountBindingArn string `json:"AcmeExternalAccountBindingArn"` } `json:"ExternalAccountBinding"` } - require.NoError(t, json.Unmarshal(eabRec.Body.Bytes(), &eabOut)) + require.NoError(t, json.Unmarshal(eabRec.Body.Bytes(), &eabOut, jsonx.V1())) eabARN := eabOut.ExternalAccountBinding.AcmeExternalAccountBindingArn require.NotEmpty(t, eabARN) @@ -412,26 +419,26 @@ func TestACMHandler_AcmeEndpoints(t *testing.T) { "AcmeEndpointArn": epARN, "DomainName": "cascade.example.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) dvRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(dvBody)) require.Equal(t, http.StatusOK, dvRec.Code, dvRec.Body.String()) var dvOut struct { AcmeDomainValidationArn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(dvRec.Body.Bytes(), &dvOut)) + require.NoError(t, json.Unmarshal(dvRec.Body.Bytes(), &dvOut, jsonx.V1())) require.NotEmpty(t, dvOut.AcmeDomainValidationArn) - delBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + delBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) delRec := postACMJSON(t, h, "DeleteAcmeEndpoint", string(delBody)) require.Equal(t, http.StatusOK, delRec.Code) - descEABBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}) + descEABBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}, jsonx.V1()) descEABRec := postACMJSON(t, h, "DescribeAcmeExternalAccountBinding", string(descEABBody)) assert.Equal(t, http.StatusBadRequest, descEABRec.Code, "EAB must be cascade-deleted with its endpoint") descDVBody, _ := json.Marshal( - map[string]string{"AcmeDomainValidationArn": dvOut.AcmeDomainValidationArn}, + map[string]string{"AcmeDomainValidationArn": dvOut.AcmeDomainValidationArn}, jsonx.V1(), ) descDVRec := postACMJSON(t, h, "DescribeAcmeDomainValidation", string(descDVBody)) assert.Equal(t, http.StatusBadRequest, descDVRec.Code, @@ -473,7 +480,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", "Expiration": map[string]any{"Type": "DAYS", "Value": 7}, - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(body)) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) assert.Contains(t, rec.Body.String(), "acme-external-account-binding/") @@ -486,7 +493,10 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { t.Helper() epARN := createTestAcmeEndpoint(t, h) - body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN, "RoleArn": "not-a-role-arn"}) + body, _ := json.Marshal( + map[string]string{"AcmeEndpointArn": epARN, "RoleArn": "not-a-role-arn"}, + jsonx.V1(), + ) rec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Contains(t, rec.Body.String(), "ValidationException") @@ -500,7 +510,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { body, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": "arn:aws:acm:us-east-1:000000000000:acme-endpoint/nope", "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Contains(t, rec.Body.String(), "ResourceNotFoundException") @@ -515,7 +525,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { createBody, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(createBody)) require.Equal(t, http.StatusOK, createRec.Code) @@ -524,10 +534,10 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { AcmeExternalAccountBindingArn string `json:"AcmeExternalAccountBindingArn"` } `json:"ExternalAccountBinding"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) eabARN := createOut.ExternalAccountBinding.AcmeExternalAccountBindingArn - credBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}) + credBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}, jsonx.V1()) credRec := postACMJSON(t, h, "GetAcmeExternalAccountBindingCredentials", string(credBody)) require.Equal(t, http.StatusOK, credRec.Code) @@ -535,7 +545,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { KeyID string `json:"KeyId"` MacKey string `json:"MacKey"` } - require.NoError(t, json.Unmarshal(credRec.Body.Bytes(), &credOut)) + require.NoError(t, json.Unmarshal(credRec.Body.Bytes(), &credOut, jsonx.V1())) assert.NotEmpty(t, credOut.KeyID) assert.NotEmpty(t, credOut.MacKey) @@ -578,11 +588,11 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { createBody, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(createBody)) require.Equal(t, http.StatusOK, createRec.Code) - listBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + listBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) listRec := postACMJSON(t, h, "ListAcmeExternalAccountBindings", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) assert.Contains(t, listRec.Body.String(), "acme-external-account-binding/") @@ -597,7 +607,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { createBody, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(createBody)) require.Equal(t, http.StatusOK, createRec.Code) @@ -606,11 +616,11 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { AcmeExternalAccountBindingArn string `json:"AcmeExternalAccountBindingArn"` } `json:"ExternalAccountBinding"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) delBody, _ := json.Marshal(map[string]string{ "AcmeExternalAccountBindingArn": createOut.ExternalAccountBinding.AcmeExternalAccountBindingArn, - }) + }, jsonx.V1()) delRec := postACMJSON(t, h, "DeleteAcmeExternalAccountBinding", string(delBody)) assert.Equal(t, http.StatusOK, delRec.Code) }, @@ -627,7 +637,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { createBody, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "RoleArn": "arn:aws:iam::000000000000:role/acme-role", - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeExternalAccountBinding", string(createBody)) require.Equal(t, http.StatusOK, createRec.Code) @@ -636,7 +646,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { AcmeExternalAccountBindingArn string `json:"AcmeExternalAccountBindingArn"` } `json:"ExternalAccountBinding"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) eabARN := createOut.ExternalAccountBinding.AcmeExternalAccountBindingArn delBody := `{"AcmeExternalAccountBindingArn":"` + epARN + @@ -646,7 +656,7 @@ func TestACMHandler_AcmeExternalAccountBindings(t *testing.T) { assert.Contains(t, delRec.Body.String(), "ValidationException") assert.NotContains(t, delRec.Body.String(), "ResourceNotFoundException") - descBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}) + descBody, _ := json.Marshal(map[string]string{"AcmeExternalAccountBindingArn": eabARN}, jsonx.V1()) descRec := postACMJSON(t, h, "DescribeAcmeExternalAccountBinding", string(descBody)) assert.Equal(t, http.StatusOK, descRec.Code, "unrelated EAB must be untouched") }, @@ -688,17 +698,17 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { "PrevalidationOptions": map[string]any{ "DnsPrevalidation": map[string]any{"HostedZoneId": "Z123456"}, }, - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createOut struct { AcmeDomainValidationArn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) descBody, _ := json.Marshal( - map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, + map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, jsonx.V1(), ) descRec := postACMJSON(t, h, "DescribeAcmeDomainValidation", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) @@ -714,7 +724,10 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { t.Helper() epARN := createTestAcmeEndpoint(t, h) - body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN, "DomainName": "dv2.example.com"}) + body, _ := json.Marshal( + map[string]string{"AcmeEndpointArn": epARN, "DomainName": "dv2.example.com"}, + jsonx.V1(), + ) rec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Contains(t, rec.Body.String(), "ValidationException") @@ -730,11 +743,11 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { "AcmeEndpointArn": epARN, "DomainName": "dv3.example.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) require.Equal(t, http.StatusOK, createRec.Code) - listBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + listBody, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) listRec := postACMJSON(t, h, "ListAcmeDomainValidations", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) assert.Contains(t, listRec.Body.String(), "dv3.example.com") @@ -750,26 +763,26 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { "AcmeEndpointArn": epARN, "DomainName": "dv4.example.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) require.Equal(t, http.StatusOK, createRec.Code) var createOut struct { AcmeDomainValidationArn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) updBody, _ := json.Marshal(map[string]any{ "AcmeDomainValidationArn": createOut.AcmeDomainValidationArn, "PrevalidationOptions": map[string]any{ "DnsPrevalidation": map[string]any{"HostedZoneId": "Znew"}, }, - }) + }, jsonx.V1()) updRec := postACMJSON(t, h, "UpdateAcmeDomainValidation", string(updBody)) require.Equal(t, http.StatusOK, updRec.Code, updRec.Body.String()) descBody, _ := json.Marshal( - map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, + map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, jsonx.V1(), ) descRec := postACMJSON(t, h, "DescribeAcmeDomainValidation", string(descBody)) assert.Contains(t, descRec.Body.String(), `"HostedZoneId":"Znew"`) @@ -785,17 +798,17 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { "AcmeEndpointArn": epARN, "DomainName": "dv5.example.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) require.Equal(t, http.StatusOK, createRec.Code) var createOut struct { AcmeDomainValidationArn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) delBody, _ := json.Marshal( - map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, + map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, jsonx.V1(), ) delRec := postACMJSON(t, h, "DeleteAcmeDomainValidation", string(delBody)) assert.Equal(t, http.StatusOK, delRec.Code) @@ -814,14 +827,14 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { "AcmeEndpointArn": epARN, "DomainName": "dv6.example.com", "PrevalidationOptions": map[string]any{"DnsPrevalidation": map[string]any{}}, - }) + }, jsonx.V1()) createRec := postACMJSON(t, h, "CreateAcmeDomainValidation", string(body)) require.Equal(t, http.StatusOK, createRec.Code) var createOut struct { AcmeDomainValidationArn string `json:"AcmeDomainValidationArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) delBody := `{"AcmeDomainValidationArn":"` + epARN + `/acme-domain-validation/nope"}` delRec := postACMJSON(t, h, "DeleteAcmeDomainValidation", delBody) @@ -830,7 +843,7 @@ func TestACMHandler_AcmeDomainValidations(t *testing.T) { assert.NotContains(t, delRec.Body.String(), "ResourceNotFoundException") descBody, _ := json.Marshal( - map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, + map[string]string{"AcmeDomainValidationArn": createOut.AcmeDomainValidationArn}, jsonx.V1(), ) descRec := postACMJSON(t, h, "DescribeAcmeDomainValidation", string(descBody)) assert.Equal(t, http.StatusOK, descRec.Code, "unrelated domain validation must be untouched") @@ -869,7 +882,7 @@ func TestACMHandler_AcmeAccounts_HonestlyEmpty(t *testing.T) { body, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "AccountUrl": "https://example/acct/1", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "DescribeAcmeAccount", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Contains(t, rec.Body.String(), "ResourceNotFoundException") @@ -880,7 +893,7 @@ func TestACMHandler_AcmeAccounts_HonestlyEmpty(t *testing.T) { run: func(t *testing.T, h *acm.Handler, epARN string) { t.Helper() - body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}) + body, _ := json.Marshal(map[string]string{"AcmeEndpointArn": epARN}, jsonx.V1()) rec := postACMJSON(t, h, "ListAcmeAccounts", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), `"AcmeAccounts":[]`) @@ -893,7 +906,7 @@ func TestACMHandler_AcmeAccounts_HonestlyEmpty(t *testing.T) { body, _ := json.Marshal(map[string]string{ "AcmeEndpointArn": epARN, "AccountUrl": "https://example/acct/1", - }) + }, jsonx.V1()) rec := postACMJSON(t, h, "RevokeAcmeAccount", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Contains(t, rec.Body.String(), "ResourceNotFoundException") diff --git a/services/acm/persistence.go b/services/acm/persistence.go index 7befa5450b..c892e21063 100644 --- a/services/acm/persistence.go +++ b/services/acm/persistence.go @@ -2,11 +2,13 @@ package acm import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -60,7 +62,7 @@ func buildPersistenceDTORegistry() (*store.Registry, *store.Table[regionalDTO[Ce // directly, as before. Timers are runtime-only and were never persisted; // Restore rebuilds them from certificate state (see below). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` IdempotencyMap map[string]map[string]certIdempotencyEntry `json:"idempotencyMap,omitempty"` AccountIdempotency map[string]map[string]accountIdempotencyEntry `json:"accountIdempotency,omitempty"` AccountConfig map[string]AccountConfig `json:"accountConfig"` @@ -246,7 +248,7 @@ func (b *InMemoryBackend) applyRestoredMaps(snap *backendSnapshot) { // restoreCerts rebuilds the "dirty" certs store.Table from snap's DTO table, // factored out of Restore to keep Restore's own cognitive complexity low. // Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreCerts(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreCerts(tables map[string]jsontext.Value) error { dtoReg, certDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("acm: restore snapshot cert table: %w", err) @@ -279,7 +281,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { snap := handlerSnapshot{ Tags: tagsMap, } - if err := json.Unmarshal(h.Backend.Snapshot(ctx), &snap.Backend); err != nil { + if err := json.Unmarshal(h.Backend.Snapshot(ctx), &snap.Backend, jsonx.V1()); err != nil { return nil } @@ -291,12 +293,12 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { func (h *Handler) Restore(ctx context.Context, data []byte) error { var snap handlerSnapshot - if err := json.Unmarshal(data, &snap); err != nil { + if err := json.Unmarshal(data, &snap, jsonx.V1()); err != nil { // Fall back to backend-only snapshot for backward compatibility. return h.Backend.Restore(ctx, data) } - backendData, err := json.Marshal(snap.Backend) + backendData, err := json.Marshal(snap.Backend, jsonx.V1()) if err != nil { return err } diff --git a/services/acm/persistence_test.go b/services/acm/persistence_test.go index a55eabd99f..7bb9816a2a 100644 --- a/services/acm/persistence_test.go +++ b/services/acm/persistence_test.go @@ -2,7 +2,7 @@ package acm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -271,10 +272,10 @@ func TestInMemoryBackend_RestoreVersionMismatch(t *testing.T) { require.NoError(t, err) var snapMap map[string]any - require.NoError(t, json.Unmarshal(donor.Snapshot(t.Context()), &snapMap)) + require.NoError(t, json.Unmarshal(donor.Snapshot(t.Context()), &snapMap, jsonx.V1())) tt.mutate(snapMap) - mutatedSnap, err := json.Marshal(snapMap) + mutatedSnap, err := json.Marshal(snapMap, jsonx.V1()) require.NoError(t, err) // The restore target starts with its own, different state so we @@ -337,12 +338,12 @@ func TestACMHandler_Persistence_Tags(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) addBody, err := json.Marshal(map[string]any{ "CertificateArn": reqOut.CertificateArn, "Tags": []map[string]string{{"Key": "team", "Value": "platform"}}, - }) + }, jsonx.V1()) require.NoError(t, err) addRec := postACMJSON(t, h, "AddTagsToCertificate", string(addBody)) @@ -354,7 +355,7 @@ func TestACMHandler_Persistence_Tags(t *testing.T) { freshH := newACMHandler() require.NoError(t, freshH.Restore(t.Context(), snap)) - listBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + listBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) listRec := postACMJSON(t, freshH, "ListTagsForCertificate", string(listBody)) @@ -363,7 +364,7 @@ func TestACMHandler_Persistence_Tags(t *testing.T) { var tagsOut struct { Tags []map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) tagMap := make(map[string]string, len(tagsOut.Tags)) for _, kv := range tagsOut.Tags { diff --git a/services/acm/request_certificate_realism_test.go b/services/acm/request_certificate_realism_test.go index 68a9693901..7c453196ce 100644 --- a/services/acm/request_certificate_realism_test.go +++ b/services/acm/request_certificate_realism_test.go @@ -1,13 +1,15 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestACMHandler_RequestCertificate_InputRealism(t *testing.T) { @@ -66,7 +68,7 @@ func TestACMHandler_RequestCertificate_InputRealism(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp.Type) assert.NotContains(t, errResp.Message, "Exception") }) @@ -83,7 +85,7 @@ func TestACMHandler_WildcardValidationRecordAndNotFound(t *testing.T) { var created struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec = postACMJSON(t, h, "DescribeCertificate", `{"CertificateArn":"`+created.CertificateArn+`"}`) require.Equal(t, http.StatusOK, rec.Code) diff --git a/services/acm/wire_field_additions_test.go b/services/acm/wire_field_additions_test.go index 8b8745cf98..dc92b8b760 100644 --- a/services/acm/wire_field_additions_test.go +++ b/services/acm/wire_field_additions_test.go @@ -1,7 +1,7 @@ package acm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/acm" ) @@ -258,7 +259,7 @@ func TestACMHandler_ListCertificates_SummaryHasCreatedAtAndInUse(t *testing.T) { InUse *bool `json:"InUse"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.CertificateSummaryList) summary := out.CertificateSummaryList[0] @@ -296,7 +297,7 @@ func TestACMHandler_ListCertificates_SummaryKeyUsages(t *testing.T) { ExtendedKeyUsages []string `json:"ExtendedKeyUsages"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.CertificateSummaryList) summary := out.CertificateSummaryList[0] @@ -320,9 +321,9 @@ func TestACMHandler_RenewCertificate_RenewalSummaryHasUpdatedAt(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - renewBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + renewBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) renewRec := postACMJSON(t, h, "RenewCertificate", string(renewBody)) @@ -341,7 +342,7 @@ func TestACMHandler_RenewCertificate_RenewalSummaryHasUpdatedAt(t *testing.T) { RenewalSummary renewalSummary `json:"RenewalSummary"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.NotEmpty(t, descOut.Certificate.RenewalSummary.RenewalStatus) assert.Positive(t, descOut.Certificate.RenewalSummary.UpdatedAt, "RenewalSummary.UpdatedAt must always be present on the wire") @@ -362,9 +363,9 @@ func TestACMHandler_RequestCertificate_ExportOption_RoundTrips(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) @@ -377,7 +378,7 @@ func TestACMHandler_RequestCertificate_ExportOption_RoundTrips(t *testing.T) { } `json:"Options"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "ENABLED", descOut.Certificate.Options.Export) } @@ -429,7 +430,7 @@ func TestACMHandler_RequestCertificate_ManagedBy(t *testing.T) { body, err := json.Marshal(map[string]string{ "DomainName": tt.domain, "ManagedBy": tt.managedBy, - }) + }, jsonx.V1()) require.NoError(t, err) reqRec := postACMJSON(t, h, "RequestCertificate", string(body)) @@ -442,9 +443,9 @@ func TestACMHandler_RequestCertificate_ManagedBy(t *testing.T) { var reqOut struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut)) + require.NoError(t, json.Unmarshal(reqRec.Body.Bytes(), &reqOut, jsonx.V1())) - descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}) + descBody, err := json.Marshal(map[string]string{"CertificateArn": reqOut.CertificateArn}, jsonx.V1()) require.NoError(t, err) descRec := postACMJSON(t, h, "DescribeCertificate", string(descBody)) @@ -455,7 +456,7 @@ func TestACMHandler_RequestCertificate_ManagedBy(t *testing.T) { ManagedBy string `json:"ManagedBy"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, tt.wantManagedBy, descOut.Certificate.ManagedBy) listRec := postACMJSON(t, h, "ListCertificates", `{}`) @@ -466,7 +467,7 @@ func TestACMHandler_RequestCertificate_ManagedBy(t *testing.T) { ManagedBy string `json:"ManagedBy"` } `json:"CertificateSummaryList"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.CertificateSummaryList, 1) assert.Equal(t, tt.wantManagedBy, listOut.CertificateSummaryList[0].ManagedBy) }) diff --git a/services/acmpca/handler.go b/services/acmpca/handler.go index 17aef1f156..e51f0d611f 100644 --- a/services/acmpca/handler.go +++ b/services/acmpca/handler.go @@ -3,7 +3,8 @@ package acmpca import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -118,8 +120,8 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } - var m map[string]json.RawMessage - if unmarshalErr := json.Unmarshal(body, &m); unmarshalErr != nil { + var m map[string]jsontext.Value + if unmarshalErr := json.Unmarshal(body, &m, jsonx.V1()); unmarshalErr != nil { return "" } @@ -151,7 +153,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // handleError writes a standardized error response back to the client. @@ -308,11 +310,11 @@ func decodeBase64Field(encoded, fieldName string, sentinel error) (string, error } // extractFirstResourceByKeys returns the first string resource found for the provided JSON field names. -func extractFirstResourceByKeys(body map[string]json.RawMessage, keys ...string) string { +func extractFirstResourceByKeys(body map[string]jsontext.Value, keys ...string) string { for _, key := range keys { if raw, ok := body[key]; ok { var resourceID string - if err := json.Unmarshal(raw, &resourceID); err == nil { + if err := json.Unmarshal(raw, &resourceID, jsonx.V1()); err == nil { return resourceID } } diff --git a/services/acmpca/handler_audit_reports.go b/services/acmpca/handler_audit_reports.go index ea3a1f9696..aad7957c78 100644 --- a/services/acmpca/handler_audit_reports.go +++ b/services/acmpca/handler_audit_reports.go @@ -2,7 +2,9 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createCertificateAuthorityAuditReportInput struct { @@ -30,7 +32,7 @@ type describeCertificateAuthorityAuditReportOutput struct { func (h *Handler) jsonCreateAuditReport(ctx context.Context, body []byte) (any, error) { var input createCertificateAuthorityAuditReportInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArgs } @@ -52,7 +54,7 @@ func (h *Handler) jsonCreateAuditReport(ctx context.Context, body []byte) (any, func (h *Handler) jsonDescribeAuditReport(ctx context.Context, body []byte) (any, error) { var input describeCertificateAuthorityAuditReportInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArgs } diff --git a/services/acmpca/handler_ca_policy.go b/services/acmpca/handler_ca_policy.go index b9497bcded..484b83aaa1 100644 --- a/services/acmpca/handler_ca_policy.go +++ b/services/acmpca/handler_ca_policy.go @@ -2,7 +2,9 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getPolicyInput struct { @@ -28,7 +30,7 @@ type deletePolicyOutput struct{} func (h *Handler) jsonGetPolicy(ctx context.Context, body []byte) (any, error) { var input getPolicyInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -42,7 +44,7 @@ func (h *Handler) jsonGetPolicy(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonPutPolicy(ctx context.Context, body []byte) (any, error) { var input putPolicyInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -55,7 +57,7 @@ func (h *Handler) jsonPutPolicy(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonDeletePolicy(ctx context.Context, body []byte) (any, error) { var input deletePolicyInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } diff --git a/services/acmpca/handler_certificate_authorities.go b/services/acmpca/handler_certificate_authorities.go index 3de412ebe4..43d13e2ace 100644 --- a/services/acmpca/handler_certificate_authorities.go +++ b/services/acmpca/handler_certificate_authorities.go @@ -2,8 +2,10 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -211,14 +213,14 @@ func (in *updateCertificateAuthorityInput) UnmarshalJSON(data []byte) error { type alias updateCertificateAuthorityInput var a alias - if err := json.Unmarshal(data, &a); err != nil { + if err := json.Unmarshal(data, &a, jsonx.V1()); err != nil { return err } *in = updateCertificateAuthorityInput(a) - var raw map[string]json.RawMessage - if err := json.Unmarshal(data, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return err } @@ -262,7 +264,7 @@ type restoreCertificateAuthorityOutput struct{} func (h *Handler) jsonCreateCA(ctx context.Context, body []byte) (any, error) { var input createCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArgs } @@ -302,7 +304,7 @@ func (h *Handler) jsonCreateCA(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonDescribeCA(ctx context.Context, body []byte) (any, error) { var input describeCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -316,7 +318,7 @@ func (h *Handler) jsonDescribeCA(ctx context.Context, body []byte) (any, error) func (h *Handler) jsonListCAs(ctx context.Context, body []byte) (any, error) { var input listCertificateAuthoritiesInput - _ = json.Unmarshal(body, &input) + _ = json.Unmarshal(body, &input, jsonx.V1()) p, err := h.Backend.ListCertificateAuthorities(ctx, input.NextToken, input.MaxResults, input.ResourceOwner) if err != nil { @@ -337,7 +339,7 @@ func (h *Handler) jsonListCAs(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonDeleteCA(ctx context.Context, body []byte) (any, error) { var input deleteCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -356,7 +358,7 @@ func (h *Handler) jsonDeleteCA(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonUpdateCA(ctx context.Context, body []byte) (any, error) { var input updateCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -376,7 +378,7 @@ func (h *Handler) jsonUpdateCA(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonGetCsr(ctx context.Context, body []byte) (any, error) { var input getCertificateAuthorityCsrInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -390,7 +392,7 @@ func (h *Handler) jsonGetCsr(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonImportCACert(ctx context.Context, body []byte) (any, error) { var input importCertificateAuthorityCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -418,7 +420,7 @@ func (h *Handler) jsonImportCACert(ctx context.Context, body []byte) (any, error func (h *Handler) jsonGetCACert(ctx context.Context, body []byte) (any, error) { var input getCertificateAuthorityCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -432,7 +434,7 @@ func (h *Handler) jsonGetCACert(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonRestoreCA(ctx context.Context, body []byte) (any, error) { var input restoreCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } diff --git a/services/acmpca/handler_certificate_authorities_test.go b/services/acmpca/handler_certificate_authorities_test.go index 6e5bd34dbc..614c43001a 100644 --- a/services/acmpca/handler_certificate_authorities_test.go +++ b/services/acmpca/handler_certificate_authorities_test.go @@ -2,7 +2,7 @@ package acmpca_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acmpca" ) @@ -568,7 +569,7 @@ func TestACMPCA_DescribeCertificateAuthority_OwnerAccount(t *testing.T) { var createOut struct { CertificateAuthorityArn string `json:"CertificateAuthorityArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) descRec := doACMPCARequest(t, h, "DescribeCertificateAuthority", map[string]any{ "CertificateAuthorityArn": createOut.CertificateAuthorityArn, @@ -580,7 +581,7 @@ func TestACMPCA_DescribeCertificateAuthority_OwnerAccount(t *testing.T) { OwnerAccount string `json:"OwnerAccount"` } `json:"CertificateAuthority"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, testAccountID, descOut.CertificateAuthority.OwnerAccount, "OwnerAccount must match the backend account ID") @@ -666,7 +667,7 @@ func TestACMPCA_DescribeCertificateAuthority_Serial_AfterActivation(t *testing.T Status string `json:"Status"` } `json:"CertificateAuthority"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Equal(t, "ACTIVE", descOut.CertificateAuthority.Status) assert.NotEmpty(t, descOut.CertificateAuthority.Serial, diff --git a/services/acmpca/handler_certificates.go b/services/acmpca/handler_certificates.go index e323d2b781..cb770c01a7 100644 --- a/services/acmpca/handler_certificates.go +++ b/services/acmpca/handler_certificates.go @@ -2,11 +2,13 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -169,7 +171,7 @@ type revokeCertificateOutput struct{} func (h *Handler) jsonIssueCert(ctx context.Context, body []byte) (any, error) { var input issueCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArgs } @@ -486,7 +488,7 @@ func decodeGeneralName(gn generalNameWire) (APIPassthroughSAN, error) { func (h *Handler) jsonGetCert(ctx context.Context, body []byte) (any, error) { var input getCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -511,7 +513,7 @@ func (h *Handler) jsonGetCert(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonRevokeCert(ctx context.Context, body []byte) (any, error) { var input revokeCertificateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } diff --git a/services/acmpca/handler_certificates_test.go b/services/acmpca/handler_certificates_test.go index 0938770db9..5cc64d4969 100644 --- a/services/acmpca/handler_certificates_test.go +++ b/services/acmpca/handler_certificates_test.go @@ -2,7 +2,7 @@ package acmpca_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acmpca" ) @@ -265,7 +266,7 @@ func TestACMPCA_IssueCertificate_EndDateValidity(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.CertificateArn, "IssueCertificate with END_DATE must return a CertificateArn") } @@ -325,7 +326,7 @@ func TestACMPCA_IssueCertificate_ValidityTypeAliases(t *testing.T) { var out struct { CertificateArn string `json:"CertificateArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.CertificateArn, "validity type %q must return CertificateArn", tt.validityType) }) @@ -433,7 +434,7 @@ func TestACMPCA_GetCertificate_ChainIncludesCAChain(t *testing.T) { Certificate string `json:"Certificate"` CertificateChain string `json:"CertificateChain"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.Certificate, "Certificate must be present") if tt.wantChain { diff --git a/services/acmpca/handler_permissions.go b/services/acmpca/handler_permissions.go index e4896ed35b..5065acee0d 100644 --- a/services/acmpca/handler_permissions.go +++ b/services/acmpca/handler_permissions.go @@ -2,7 +2,9 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listPermissionsInput struct { @@ -44,7 +46,7 @@ type deletePermissionOutput struct{} func (h *Handler) jsonListPermissions(ctx context.Context, body []byte) (any, error) { var input listPermissionsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -76,7 +78,7 @@ func (h *Handler) jsonListPermissions(ctx context.Context, body []byte) (any, er func (h *Handler) jsonCreatePermission(ctx context.Context, body []byte) (any, error) { var input createPermissionInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -95,7 +97,7 @@ func (h *Handler) jsonCreatePermission(ctx context.Context, body []byte) (any, e func (h *Handler) jsonDeletePermission(ctx context.Context, body []byte) (any, error) { var input deletePermissionInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } diff --git a/services/acmpca/handler_tags.go b/services/acmpca/handler_tags.go index de14c7829d..6ee0a8a95b 100644 --- a/services/acmpca/handler_tags.go +++ b/services/acmpca/handler_tags.go @@ -2,11 +2,12 @@ package acmpca import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -126,7 +127,7 @@ func (h *Handler) GetTagsForTest(resourceID string) []map[string]string { func (h *Handler) jsonTagCA(ctx context.Context, body []byte) (any, error) { var input tagCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -151,7 +152,7 @@ func (h *Handler) jsonTagCA(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonUntagCA(ctx context.Context, body []byte) (any, error) { var input untagCertificateAuthorityInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } @@ -171,7 +172,7 @@ func (h *Handler) jsonUntagCA(ctx context.Context, body []byte) (any, error) { func (h *Handler) jsonListTags(ctx context.Context, body []byte) (any, error) { var input listTagsInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, ErrInvalidArn } diff --git a/services/acmpca/handler_test.go b/services/acmpca/handler_test.go index 0f909722a7..e45fe59cc0 100644 --- a/services/acmpca/handler_test.go +++ b/services/acmpca/handler_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/acmpca" ) @@ -49,7 +50,7 @@ func doACMPCARequest(t *testing.T, h *acmpca.Handler, op string, body map[string var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -71,7 +72,7 @@ func parseACMPCAResponse(t *testing.T, rec *httptest.ResponseRecorder) map[strin t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } @@ -135,7 +136,7 @@ func TestACMPCAHandler_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) diff --git a/services/acmpca/persistence.go b/services/acmpca/persistence.go index 1932972c45..6e608385e7 100644 --- a/services/acmpca/persistence.go +++ b/services/acmpca/persistence.go @@ -4,7 +4,7 @@ import ( "context" "crypto/ecdsa" "crypto/x509" - "encoding/json" + "encoding/json/jsontext" "encoding/pem" "errors" "fmt" @@ -108,7 +108,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Policies map[string]map[string]string `json:"policies"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -282,7 +282,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/amp/dataplane_status_http.go b/services/amp/dataplane_status_http.go index 8fce8f7509..3749cff544 100644 --- a/services/amp/dataplane_status_http.go +++ b/services/amp/dataplane_status_http.go @@ -2,7 +2,7 @@ package amp import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -173,7 +174,7 @@ func (h *Handler) postAlerts(c *echo.Context, wsID string) error { } var in []postableAlert - if jerr := json.Unmarshal(body, &in); jerr != nil || len(in) > maxPostedAlerts { + if jerr := json.Unmarshal(body, &in, jsonx.V1()); jerr != nil || len(in) > maxPostedAlerts { return promError(c, http.StatusBadRequest, "bad_data", "body must be a JSON array of alerts") } diff --git a/services/amp/dataplane_test.go b/services/amp/dataplane_test.go index 583ac4104c..b9d1e09350 100644 --- a/services/amp/dataplane_test.go +++ b/services/amp/dataplane_test.go @@ -1,7 +1,8 @@ package amp_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "math" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amp" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -69,8 +71,8 @@ type promResult struct { Status string `json:"status"` ErrorType string `json:"errorType"` Data struct { - ResultType string `json:"resultType"` - Result json.RawMessage `json:"result"` + ResultType string `json:"resultType"` + Result jsontext.Value `json:"result"` } `json:"data"` } @@ -80,7 +82,7 @@ func (f *fixture) query(t *testing.T, ws, path string, vals url.Values) (httpRes r := f.do(t, http.MethodGet, "/workspaces/"+ws+"/api/v1/"+path+"?"+vals.Encode(), nil, nil) var pr promResult - _ = json.Unmarshal(r.body, &pr) + _ = json.Unmarshal(r.body, &pr, jsonx.V1()) return r, pr } diff --git a/services/amp/persistence.go b/services/amp/persistence.go index 4eb7e69389..b6084f54ad 100644 --- a/services/amp/persistence.go +++ b/services/amp/persistence.go @@ -2,7 +2,7 @@ package amp import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state, samples included. diff --git a/services/amp/rules_eval_test.go b/services/amp/rules_eval_test.go index 677bdc0350..f87148d87a 100644 --- a/services/amp/rules_eval_test.go +++ b/services/amp/rules_eval_test.go @@ -2,7 +2,8 @@ package amp_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "strconv" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amp" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -86,7 +88,7 @@ func (f *fixture) vector(t *testing.T, ws, q string) []vecSample { require.Equal(t, http.StatusOK, r.status, string(r.body)) var out []vecSample - require.NoError(t, json.Unmarshal(pr.Data.Result, &out)) + require.NoError(t, json.Unmarshal(pr.Data.Result, &out, jsonx.V1())) return out } @@ -98,11 +100,11 @@ func (f *fixture) apiData(t *testing.T, ws, path string, into any) { require.Equal(t, http.StatusOK, r.status, string(r.body)) var env struct { - Data json.RawMessage `json:"data"` + Data jsontext.Value `json:"data"` } - require.NoError(t, json.Unmarshal(r.body, &env)) - require.NoError(t, json.Unmarshal(env.Data, into)) + require.NoError(t, json.Unmarshal(r.body, &env, jsonx.V1())) + require.NoError(t, json.Unmarshal(env.Data, into, jsonx.V1())) } func TestRecordingRule(t *testing.T) { @@ -416,7 +418,7 @@ inhibit_rules: func (f *fixture) postAlerts(t *testing.T, ws string, alerts []map[string]any) { t.Helper() - body, err := json.Marshal(alerts) + body, err := json.Marshal(alerts, jsonx.V1()) require.NoError(t, err) path := "/workspaces/" + ws + "/alertmanager/api/v2/alerts" diff --git a/services/amp/series_json.go b/services/amp/series_json.go index 3e08c20655..e3bcc9f3c5 100644 --- a/services/amp/series_json.go +++ b/services/amp/series_json.go @@ -1,9 +1,10 @@ package amp import ( - "encoding/json" + "encoding/json/v2" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -20,7 +21,7 @@ type seriesWithStale struct { // MarshalJSON writes stale markers as timestamps because JSON cannot carry NaN; other series are unchanged. func (r SeriesRecord) MarshalJSON() ([]byte, error) { if !slices.ContainsFunc(r.Samples, func(s promql.Sample) bool { return promql.IsStale(s.V) }) { - return json.Marshal(seriesPlain(r)) + return json.Marshal(seriesPlain(r), jsonx.V1()) } out := seriesWithStale{seriesPlain: seriesPlain(r)} @@ -33,13 +34,13 @@ func (r SeriesRecord) MarshalJSON() ([]byte, error) { } } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } // UnmarshalJSON restores stale markers in time order. func (r *SeriesRecord) UnmarshalJSON(data []byte) error { var in seriesWithStale - if err := json.Unmarshal(data, &in); err != nil { + if err := json.Unmarshal(data, &in, jsonx.V1()); err != nil { return err } diff --git a/services/amp/validate.go b/services/amp/validate.go index 46bac8264f..5880a02314 100644 --- a/services/amp/validate.go +++ b/services/amp/validate.go @@ -1,12 +1,13 @@ package amp import ( - "encoding/json" + "encoding/json/v2" "regexp" "strings" "gopkg.in/yaml.v3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -300,7 +301,7 @@ func validatePolicy(doc string) error { Statement any `json:"Statement"` } - if err := json.Unmarshal([]byte(doc), &p); err != nil || p.Statement == nil { + if err := json.Unmarshal([]byte(doc), &p, jsonx.V1()); err != nil || p.Statement == nil { return cannotParse("policyDocument must be a JSON policy with a Statement") } diff --git a/services/amplify/handler_apps.go b/services/amplify/handler_apps.go index 5de9a74782..a3900c5ad2 100644 --- a/services/amplify/handler_apps.go +++ b/services/amplify/handler_apps.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -112,7 +113,7 @@ func (h *Handler) createApp(ctx context.Context, c *echo.Context) error { var input createAppRequest - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } @@ -181,7 +182,7 @@ func (h *Handler) updateApp(ctx context.Context, c *echo.Context, appID string) var input createAppRequest - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_apps_test.go b/services/amplify/handler_apps_test.go index 9dde2c51e8..2c869cda0d 100644 --- a/services/amplify/handler_apps_test.go +++ b/services/amplify/handler_apps_test.go @@ -1,13 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amplify" ) @@ -50,7 +51,7 @@ func TestHandler_CreateApp(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) app := resp["app"].(map[string]any) assert.Equal(t, tt.wantName, app["name"]) } @@ -143,7 +144,7 @@ func TestHandler_ListApps(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps := resp["apps"].([]any) assert.Len(t, apps, tt.wantCount) }) @@ -197,7 +198,7 @@ func TestHandler_ListAppsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps := resp["apps"].([]any) assert.Len(t, apps, tt.wantCount) @@ -248,7 +249,7 @@ func TestHandler_DeleteApp(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) app, ok := resp["app"].(map[string]any) require.True(t, ok, "expected an \"app\" key wrapping the deleted App, got body %s", rec.Body.String()) assert.Equal(t, appID, app["appId"]) @@ -313,7 +314,7 @@ func TestHandler_UpdateApp(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) app := resp["app"].(map[string]any) assert.Equal(t, tt.wantName, app["name"]) } diff --git a/services/amplify/handler_artifacts.go b/services/amplify/handler_artifacts.go index 71aa71e384..cf61aeb074 100644 --- a/services/amplify/handler_artifacts.go +++ b/services/amplify/handler_artifacts.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // generateAccessLogs handles POST /apps/{appId}/accesslogs. @@ -27,7 +28,7 @@ func (h *Handler) generateAccessLogs(ctx context.Context, c *echo.Context, appID EndTime string `json:"endTime"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_artifacts_test.go b/services/amplify/handler_artifacts_test.go index fddfdb0913..e4f165d0c3 100644 --- a/services/amplify/handler_artifacts_test.go +++ b/services/amplify/handler_artifacts_test.go @@ -1,12 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_GenerateAccessLogs verifies POST /apps/{appId}/accesslogs returns a log URL. @@ -32,7 +34,7 @@ func TestHandler_GenerateAccessLogs(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["logUrl"]) }) } diff --git a/services/amplify/handler_branches.go b/services/amplify/handler_branches.go index 1680b89d97..348403ed41 100644 --- a/services/amplify/handler_branches.go +++ b/services/amplify/handler_branches.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -110,7 +111,7 @@ func (h *Handler) createBranch(ctx context.Context, c *echo.Context, appID strin var input createBranchRequest - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } @@ -184,7 +185,7 @@ func (h *Handler) updateBranch(ctx context.Context, c *echo.Context, appID, bran var input createBranchRequest - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_branches_test.go b/services/amplify/handler_branches_test.go index bc7e33debf..4bf1388b54 100644 --- a/services/amplify/handler_branches_test.go +++ b/services/amplify/handler_branches_test.go @@ -1,13 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amplify" ) @@ -223,7 +224,7 @@ func TestHandler_ListBranchesPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) branches := resp["branches"].([]any) assert.Len(t, branches, tt.wantCount) @@ -277,7 +278,7 @@ func TestHandler_DeleteBranch(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) branch, ok := resp["branch"].(map[string]any) require.True( t, ok, "expected a \"branch\" key wrapping the deleted Branch, got body %s", rec.Body.String(), @@ -347,7 +348,7 @@ func TestHandler_UpdateBranch(t *testing.T) { if tt.wantDesc != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) br := resp["branch"].(map[string]any) assert.Equal(t, tt.wantDesc, br["description"]) } diff --git a/services/amplify/handler_deployments.go b/services/amplify/handler_deployments.go index 48671a8dd3..4b9bae020e 100644 --- a/services/amplify/handler_deployments.go +++ b/services/amplify/handler_deployments.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createDeployment handles POST /apps/{appId}/branches/{branchName}/deployments. @@ -26,7 +27,7 @@ func (h *Handler) createDeployment(ctx context.Context, c *echo.Context, appID, } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } } @@ -60,7 +61,7 @@ func (h *Handler) startDeployment(ctx context.Context, c *echo.Context, appID, b SourceURLType string `json:"sourceUrlType"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_deployments_test.go b/services/amplify/handler_deployments_test.go index 81263254bf..d06eb1872c 100644 --- a/services/amplify/handler_deployments_test.go +++ b/services/amplify/handler_deployments_test.go @@ -1,12 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_DeploymentOps verifies CreateDeployment and StartDeployment via the HTTP handler. @@ -34,7 +36,7 @@ func TestHandler_DeploymentOps(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp["jobId"].(string) assert.NotEmpty(t, jobID) assert.NotNil(t, createResp["fileUploadUrls"]) @@ -45,7 +47,7 @@ func TestHandler_DeploymentOps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.NotNil(t, startResp["jobSummary"]) }) } diff --git a/services/amplify/handler_domains.go b/services/amplify/handler_domains.go index 9809103d60..984fdda9fb 100644 --- a/services/amplify/handler_domains.go +++ b/services/amplify/handler_domains.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSON response key used by the domain association handlers. @@ -79,7 +80,7 @@ func (h *Handler) createDomainAssociation(ctx context.Context, c *echo.Context, EnableAutoSubDomain bool `json:"enableAutoSubDomain"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } @@ -163,7 +164,7 @@ func (h *Handler) updateDomainAssociation( AutoSubDomainCreationPatterns []string `json:"autoSubDomainCreationPatterns"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_domains_test.go b/services/amplify/handler_domains_test.go index b5954ad67f..33f90a1c85 100644 --- a/services/amplify/handler_domains_test.go +++ b/services/amplify/handler_domains_test.go @@ -1,12 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_DomainAssociationCRUD verifies domain association lifecycle via the HTTP handler. @@ -34,7 +36,7 @@ func TestHandler_DomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) da := createResp["domainAssociation"].(map[string]any) assert.Equal(t, "example.com", da["domainName"]) @@ -47,7 +49,7 @@ func TestHandler_DomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["domainAssociations"].([]any), 1) // Update. @@ -56,7 +58,7 @@ func TestHandler_DomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) updated := updateResp["domainAssociation"].(map[string]any) assert.Equal(t, true, updated["enableAutoSubDomain"]) diff --git a/services/amplify/handler_environments.go b/services/amplify/handler_environments.go index 02550e8546..3336085415 100644 --- a/services/amplify/handler_environments.go +++ b/services/amplify/handler_environments.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSON response key used by the backend environment handlers. @@ -54,7 +55,7 @@ func (h *Handler) createBackendEnvironment(ctx context.Context, c *echo.Context, DeploymentArtifacts string `json:"deploymentArtifacts"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_environments_test.go b/services/amplify/handler_environments_test.go index f48278b017..ddbc497f22 100644 --- a/services/amplify/handler_environments_test.go +++ b/services/amplify/handler_environments_test.go @@ -1,12 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_BackendEnvironmentCRUD verifies backend environment lifecycle via the HTTP handler. @@ -32,7 +34,7 @@ func TestHandler_BackendEnvironmentCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) be := createResp["backendEnvironment"].(map[string]any) assert.Equal(t, "prod", be["environmentName"]) @@ -45,7 +47,7 @@ func TestHandler_BackendEnvironmentCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["backendEnvironments"].([]any), 1) // Delete. diff --git a/services/amplify/handler_jobs.go b/services/amplify/handler_jobs.go index 51f64b1ec1..21971ec33d 100644 --- a/services/amplify/handler_jobs.go +++ b/services/amplify/handler_jobs.go @@ -2,13 +2,14 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSON response key used by the job handlers (and by handler_deployments.go, @@ -58,7 +59,7 @@ func (h *Handler) startJob(ctx context.Context, c *echo.Context, appID, branchNa CommitTime float64 `json:"commitTime"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_jobs_test.go b/services/amplify/handler_jobs_test.go index 2052c0bf6a..73d8c7a247 100644 --- a/services/amplify/handler_jobs_test.go +++ b/services/amplify/handler_jobs_test.go @@ -1,7 +1,7 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amplify" ) @@ -38,7 +39,7 @@ func TestHandler_JobLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) summary := startResp["jobSummary"].(map[string]any) jobID := summary["jobId"].(string) assert.NotEmpty(t, jobID) @@ -48,7 +49,7 @@ func TestHandler_JobLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["jobSummaries"].([]any), 1) // Get job. @@ -56,7 +57,7 @@ func TestHandler_JobLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) job := getResp["job"].(map[string]any) assert.NotNil(t, job["summary"]) assert.NotNil(t, job["steps"]) @@ -109,7 +110,7 @@ func TestHandler_ListJobs_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["jobSummaries"].([]any), tt.wantCount) if tt.wantNextToken { @@ -149,7 +150,7 @@ func TestHandler_StartJob_ResponseShape(t *testing.T) { } `json:"jobSummary"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload, jsonx.V1())) assert.NotEmpty(t, payload.JobSummary.JobID) assert.NotEmpty(t, payload.JobSummary.JobARN) assert.Contains(t, payload.JobSummary.JobARN, "arn:aws:amplify:") @@ -174,7 +175,7 @@ func TestHandler_GetJob_Steps(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["jobSummary"].(map[string]any)["jobId"].(string) type step struct { @@ -195,7 +196,7 @@ func TestHandler_GetJob_Steps(t *testing.T) { Steps []step `json:"steps"` } `json:"job"` } - require.NoError(t, json.Unmarshal(stepsRec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(stepsRec.Body.Bytes(), &payload, jsonx.V1())) return payload.Job.Steps } diff --git a/services/amplify/handler_tags.go b/services/amplify/handler_tags.go index c1a7c4fba5..466eb7f546 100644 --- a/services/amplify/handler_tags.go +++ b/services/amplify/handler_tags.go @@ -2,7 +2,7 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleTags dispatches GET/POST/DELETE /tags/{resourceArn}. @@ -49,7 +50,7 @@ func (h *Handler) tagResource(ctx context.Context, c *echo.Context, resourceARN Tags map[string]string `json:"tags"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_test.go b/services/amplify/handler_test.go index cc3b3510ca..ff5faa9539 100644 --- a/services/amplify/handler_test.go +++ b/services/amplify/handler_test.go @@ -2,7 +2,7 @@ package amplify_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amplify" ) @@ -34,7 +35,7 @@ func doRequest( var buf *bytes.Buffer if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) buf = bytes.NewBuffer(b) } else { @@ -422,7 +423,7 @@ func TestHandler_ErrorResponseShape(t *testing.T) { var payload map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload, jsonx.V1())) assert.Equal(t, tt.wantType, payload["__type"]) assert.NotEmpty(t, payload["message"]) }) diff --git a/services/amplify/handler_webhooks.go b/services/amplify/handler_webhooks.go index cce179fef6..3cbbf7557a 100644 --- a/services/amplify/handler_webhooks.go +++ b/services/amplify/handler_webhooks.go @@ -2,12 +2,13 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // JSON response key used by the webhook handlers. @@ -51,7 +52,7 @@ func (h *Handler) createWebhook(ctx context.Context, c *echo.Context, appID stri Description string `json:"description"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } @@ -106,7 +107,7 @@ func (h *Handler) updateWebhook(ctx context.Context, c *echo.Context, webhookID BranchName string `json:"branchName"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return amplifyErrorJSON(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/amplify/handler_webhooks_test.go b/services/amplify/handler_webhooks_test.go index 738494a93a..33f0400f1c 100644 --- a/services/amplify/handler_webhooks_test.go +++ b/services/amplify/handler_webhooks_test.go @@ -1,12 +1,14 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_WebhookCRUD verifies create/get/list/update/delete webhook lifecycle. @@ -34,7 +36,7 @@ func TestHandler_WebhookCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) wh := createResp["webhook"].(map[string]any) webhookID := wh["webhookId"].(string) assert.NotEmpty(t, webhookID) @@ -48,7 +50,7 @@ func TestHandler_WebhookCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["webhooks"].([]any), 1) // Update. @@ -57,7 +59,7 @@ func TestHandler_WebhookCRUD(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) updated := updateResp["webhook"].(map[string]any) assert.Equal(t, "updated", updated["description"]) diff --git a/services/amplify/persistence.go b/services/amplify/persistence.go index 9d717ba583..46b4e1cb0a 100644 --- a/services/amplify/persistence.go +++ b/services/amplify/persistence.go @@ -2,7 +2,7 @@ package amplify import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -34,10 +34,10 @@ const amplifySnapshotVersion = 1 // store.Table.Restore from the webhooks table's AppID field. So Tables is // the entirety of the persisted state besides AccountID/Region. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. It implements diff --git a/services/amplify/sent_keys.go b/services/amplify/sent_keys.go index 2a9f6c6eec..993e4d36b3 100644 --- a/services/amplify/sent_keys.go +++ b/services/amplify/sent_keys.go @@ -1,12 +1,18 @@ package amplify -import "encoding/json" +import ( + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // sentKeys reports which top-level JSON members a request body carried, so an // update can tell an explicit empty value or false from an omitted member. func sentKeys(body []byte) map[string]bool { - var raw map[string]json.RawMessage - if err := json.Unmarshal(body, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(body, &raw, jsonx.V1()); err != nil { return nil } diff --git a/services/amplify/wire_shape_test.go b/services/amplify/wire_shape_test.go index 8eb49b055d..506f0774a9 100644 --- a/services/amplify/wire_shape_test.go +++ b/services/amplify/wire_shape_test.go @@ -1,7 +1,7 @@ package amplify_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amplify" ) @@ -163,7 +164,7 @@ func TestGetBackendEnvironment_NoFabricatedAppID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) be, ok := resp["backendEnvironment"].(map[string]any) require.True(t, ok) @@ -205,7 +206,7 @@ func TestGetDomainAssociation_NoFabricatedAppID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) da, ok := resp["domainAssociation"].(map[string]any) require.True(t, ok) diff --git a/services/apigateway/api_keys_test.go b/services/apigateway/api_keys_test.go index a6b6faa7b4..7b385bcd52 100644 --- a/services/apigateway/api_keys_test.go +++ b/services/apigateway/api_keys_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -23,7 +25,7 @@ func TestAPIGW_APIKeys(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "expected 2xx for CreateAPIKey") var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) keyID, _ := createResp["id"].(string) require.NotEmpty(t, keyID) @@ -91,7 +93,7 @@ func TestGetApiKey_ValueHiddenByDefault(t *testing.T) { require.True(t, createRec.Code >= 200 && createRec.Code < 300) var createResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) keyID, _ := createResp["id"].(string) require.NotEmpty(t, keyID) @@ -99,7 +101,7 @@ func TestGetApiKey_ValueHiddenByDefault(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) if tt.wantValue { assert.NotEmpty(t, resp["value"], "value must be present when includeValue=true") @@ -154,7 +156,7 @@ func TestGetApiKeys_ValueHiddenByDefault(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, ok := resp["item"].([]any) require.True(t, ok && len(items) > 0, "expected at least one key in response") @@ -207,7 +209,7 @@ func TestPatchOps_UpdateApiKey(t *testing.T) { require.True(t, createRec.Code >= 200 && createRec.Code < 300) var createResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) keyID, _ := createResp["id"].(string) require.NotEmpty(t, keyID) @@ -239,7 +241,7 @@ func TestApiKey_CustomerID(t *testing.T) { require.True(t, createRec.Code >= 200 && createRec.Code < 300) var createResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) keyID, _ := createResp["id"].(string) require.NotEmpty(t, keyID) assert.Equal(t, "mp-cust-123", createResp["customerId"], @@ -308,7 +310,7 @@ func TestUpdateApiKey(t *testing.T) { createRec := postWithHandler(t, handler, e, "CreateApiKey", `{"name":"orig-key","enabled":true}`) require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) keyID := createResp["id"].(string) lookupID := keyID diff --git a/services/apigateway/authorizers_test.go b/services/apigateway/authorizers_test.go index be539dd5b2..247eb73553 100644 --- a/services/apigateway/authorizers_test.go +++ b/services/apigateway/authorizers_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -76,7 +77,7 @@ func TestTestInvokeAuthorizer(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "user-1", resp["principalId"]) } }) diff --git a/services/apigateway/client_certificates_test.go b/services/apigateway/client_certificates_test.go index a7bef08235..9be47848f0 100644 --- a/services/apigateway/client_certificates_test.go +++ b/services/apigateway/client_certificates_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -26,7 +27,7 @@ func TestAPIGateway_UpdateClientCertificate_RESTRoute(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "updated desc", resp["description"]) } @@ -81,7 +82,7 @@ func TestClientCertificates(t *testing.T) { assert.Equal(t, tt.wantCreateCode, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["clientCertificateId"].(string) // Get @@ -98,7 +99,7 @@ func TestClientCertificates(t *testing.T) { listRec := postWithHandler(t, handler, e, "GetClientCertificates", `{}`) assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.GreaterOrEqual(t, len(listResp["item"].([]any)), tt.wantListMinLen) } diff --git a/services/apigateway/deployments_test.go b/services/apigateway/deployments_test.go index 40c7e7f0ea..229139fe43 100644 --- a/services/apigateway/deployments_test.go +++ b/services/apigateway/deployments_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -29,7 +31,7 @@ func TestDeleteDeployment_StageProtection(t *testing.T) { "create deployment: %d %s", rec.Code, rec.Body.String()) var depl map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &depl)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &depl, jsonx.V1())) deplID, _ := depl["id"].(string) require.NotEmpty(t, deplID) @@ -58,7 +60,7 @@ func TestDeleteDeployment_Unreferenced(t *testing.T) { "create deployment: %d %s", rec.Code, rec.Body.String()) var depl map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &depl)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &depl, jsonx.V1())) deplID, _ := depl["id"].(string) require.NotEmpty(t, deplID) @@ -104,7 +106,7 @@ func TestCreateDeployment_StageDescription(t *testing.T) { require.True(t, createAPIRec.Code >= 200 && createAPIRec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(createAPIRec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createAPIRec.Body), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -117,7 +119,7 @@ func TestCreateDeployment_StageDescription(t *testing.T) { require.True(t, stageRec.Code >= 200 && stageRec.Code < 300) var stage map[string]any - require.NoError(t, json.NewDecoder(stageRec.Body).Decode(&stage)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stageRec.Body), &stage, jsonx.V1())) // JSON omitempty means empty description is absent (nil), not "". got, _ := stage["description"].(string) assert.Equal(t, tt.wantDescription, got) @@ -163,13 +165,13 @@ func TestCreateDeployment_StageVariables(t *testing.T) { require.True(t, createAPIRec.Code >= 200 && createAPIRec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(createAPIRec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createAPIRec.Body), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) var deplBody string if tt.variables != nil { - varsJSON, _ := json.Marshal(tt.variables) + varsJSON, _ := json.Marshal(tt.variables, jsonx.V1()) deplBody = `{"stageName":"prod","variables":` + string(varsJSON) + `}` } else { deplBody = `{"stageName":"prod"}` @@ -181,7 +183,7 @@ func TestCreateDeployment_StageVariables(t *testing.T) { require.True(t, stageRec.Code >= 200 && stageRec.Code < 300) var stage map[string]any - require.NoError(t, json.NewDecoder(stageRec.Body).Decode(&stage)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stageRec.Body), &stage, jsonx.V1())) vars, _ := stage["variables"].(map[string]any) assert.Len(t, vars, tt.wantVarsLen) @@ -235,7 +237,7 @@ func TestUpdateDeployment(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.description, resp["description"]) } }) @@ -272,13 +274,13 @@ func TestHandler_GetAndDeleteDeployment(t *testing.T) { createRec := postWithHandler(t, h, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) deplRec := postWithHandler(t, h, e, "CreateDeployment", fmt.Sprintf(`{"restApiId":%q,"stageName":"prod","description":""}`, apiID)) var depl map[string]any - require.NoError(t, json.Unmarshal(deplRec.Body.Bytes(), &depl)) + require.NoError(t, json.Unmarshal(deplRec.Body.Bytes(), &depl, jsonx.V1())) deplID := depl["id"].(string) if tt.action == "DeleteDeployment" { diff --git a/services/apigateway/documentation_test.go b/services/apigateway/documentation_test.go index 0eb635f8e1..defacc04a5 100644 --- a/services/apigateway/documentation_test.go +++ b/services/apigateway/documentation_test.go @@ -1,13 +1,15 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDocumentationPart_CRUD tests GetDocumentationPart, GetDocumentationParts, DeleteDocumentationPart. @@ -61,7 +63,7 @@ func TestDocumentationPart_CRUD(t *testing.T) { fmt.Sprintf(`{"restApiId":%q}`, apiID)) assert.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["item"].([]any), tt.listWantLen) } @@ -130,7 +132,7 @@ func TestDocumentationVersion_CRUD(t *testing.T) { fmt.Sprintf(`{"restApiId":%q}`, apiID)) assert.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["item"].([]any), tt.listWantLen) } diff --git a/services/apigateway/domain_names_test.go b/services/apigateway/domain_names_test.go index b67aeb170f..5446fb5cd6 100644 --- a/services/apigateway/domain_names_test.go +++ b/services/apigateway/domain_names_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -26,7 +28,7 @@ func TestAPIGW_DomainNames(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "expected 2xx") var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -77,7 +79,7 @@ func TestAPIGateway_DomainNameAccessAssociation_RESTLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) arn, _ := created["domainNameAccessAssociationArn"].(string) require.NotEmpty(t, arn, "create must return a real association ARN") @@ -85,7 +87,7 @@ func TestAPIGateway_DomainNameAccessAssociation_RESTLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var list map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list, jsonx.V1())) items, _ := list["item"].([]any) require.Len(t, items, 1, "list must reflect the created association, not a hardcoded empty list") @@ -94,7 +96,7 @@ func TestAPIGateway_DomainNameAccessAssociation_RESTLifecycle(t *testing.T) { listAfterDeleteRec := restCall(t, h, http.MethodGet, "/domainnameaccessassociations", "", "") var listAfter map[string]any - require.NoError(t, json.Unmarshal(listAfterDeleteRec.Body.Bytes(), &listAfter)) + require.NoError(t, json.Unmarshal(listAfterDeleteRec.Body.Bytes(), &listAfter, jsonx.V1())) itemsAfter, _ := listAfter["item"].([]any) assert.Empty(t, itemsAfter) @@ -234,7 +236,7 @@ func TestHandlerDomainName_SecurityPolicy_RoundTrip(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "TLS_1_2", resp["securityPolicy"]) assert.NotEmpty(t, resp["regionalDomainName"]) assert.NotEmpty(t, resp["distributionDomainName"]) diff --git a/services/apigateway/error_envelope_test.go b/services/apigateway/error_envelope_test.go index 6c3ef72fe2..7343a120e1 100644 --- a/services/apigateway/error_envelope_test.go +++ b/services/apigateway/error_envelope_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/apigateway" @@ -86,7 +87,7 @@ func TestErrorEnvelope_GetRestApiNotFoundDecodesToTypedError(t *testing.T) { require.NoError(t, err) var envelope map[string]any - require.NoError(t, json.Unmarshal(raw, &envelope)) + require.NoError(t, json.Unmarshal(raw, &envelope, jsonx.V1())) require.Equal(t, "NotFoundException", envelope["__type"], "raw body must carry __type key restjson.GetErrorInfo's fallback reads: %s", raw) diff --git a/services/apigateway/export_test.go b/services/apigateway/export_test.go index 0df8b60323..ef349f3e89 100644 --- a/services/apigateway/export_test.go +++ b/services/apigateway/export_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -28,7 +29,7 @@ func TestAPIGateway_GetExport_RESTRoute(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var doc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1())) assert.Equal(t, "3.0.1", doc["openapi"]) } @@ -74,7 +75,7 @@ func TestAPIGateway_GetExport_HeadersNotBody_RealClient(t *testing.T) { require.NotEmpty(t, out.Body, "Body must round-trip as raw bytes, not a base64 JSON field") var doc map[string]any - require.NoError(t, json.Unmarshal(out.Body, &doc)) + require.NoError(t, json.Unmarshal(out.Body, &doc, jsonx.V1())) assert.Equal(t, "3.0.1", doc["openapi"]) } diff --git a/services/apigateway/gateway_responses_test.go b/services/apigateway/gateway_responses_test.go index 1a27a49987..1dbe7d18b2 100644 --- a/services/apigateway/gateway_responses_test.go +++ b/services/apigateway/gateway_responses_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -33,7 +34,7 @@ func TestAPIGateway_UpdateGatewayResponse_RESTRoute(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "404", resp["statusCode"]) } @@ -111,7 +112,7 @@ func TestGatewayResponses(t *testing.T) { if tt.wantDefaultResp { var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["defaultResponse"]) } } @@ -132,7 +133,7 @@ func TestGatewayResponses(t *testing.T) { fmt.Sprintf(`{"restApiId":%q,"responseType":%q}`, apiID, tt.responseType)) assert.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) // After delete, should revert to default assert.Equal(t, true, resp["defaultResponse"]) } @@ -166,7 +167,7 @@ func TestDeleteRestAPI_ClearsGatewayResponses(t *testing.T) { } `json:"gatewayResponses"` } `json:"tables"` } - require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded)) + require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded, jsonx.V1())) for _, gr := range decoded.Tables.GatewayResponses { assert.NotEqual(t, api.ID, gr.RestAPIID, "a deleted REST API's gateway responses must not survive in the persisted snapshot") diff --git a/services/apigateway/handler.go b/services/apigateway/handler.go index 9fee815216..5ca824ccc5 100644 --- a/services/apigateway/handler.go +++ b/services/apigateway/handler.go @@ -3,7 +3,8 @@ package apigateway import ( "context" "crypto/rsa" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -23,6 +24,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/executeapi" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -446,7 +448,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -515,7 +517,7 @@ func (h *Handler) Handler() echo.HandlerFunc { func writeJSONProtocolDispatchError(c *echo.Context, status int, errType, message string) error { c.Response().Header().Set(headerContentType, "application/x-amz-json-1.1") - payload, err := json.Marshal(ErrorResponse{Type: errType, Message: message}) + payload, err := json.Marshal(ErrorResponse{Type: errType, Message: message}, jsonx.V1()) if err != nil { return err } @@ -715,7 +717,7 @@ func detectImportRESTAPI( Body: body, FailOnWarnings: query.Get("failonwarnings") == litTrue, } - encoded, err := json.Marshal(in) + encoded, err := json.Marshal(in, jsonx.V1()) if err != nil { return "", nil, false } @@ -728,7 +730,7 @@ func detectImportRESTAPI( Body: body, Format: query.Get("format"), } - encoded, err := json.Marshal(in) + encoded, err := json.Marshal(in, jsonx.V1()) if err != nil { return "", nil, false } @@ -741,7 +743,7 @@ func detectImportRESTAPI( FailOnWarnings: query.Get("failonwarnings") == litTrue, Body: body, } - encoded, err := json.Marshal(in) + encoded, err := json.Marshal(in, jsonx.V1()) if err != nil { return "", nil, false } @@ -759,28 +761,28 @@ func detectImportRESTAPI( // must be injected as a bare JSON number, not a quoted string, or a real // client's Limit always 500s on json.Unmarshal. func injectJSONFieldAPIGW(body []byte, key, value string) []byte { - var m map[string]json.RawMessage + var m map[string]jsontext.Value if len(body) > 0 { - if err := json.Unmarshal(body, &m); err != nil { - m = make(map[string]json.RawMessage) + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { + m = make(map[string]jsontext.Value) } } else { - m = make(map[string]json.RawMessage) + m = make(map[string]jsontext.Value) } if key == keyLimit { if n, err := strconv.Atoi(value); err == nil { - m[key] = json.RawMessage(strconv.Itoa(n)) - result, _ := json.Marshal(m) + m[key] = jsontext.Value(strconv.Itoa(n)) + result, _ := json.Marshal(m, jsonx.V1()) return result } } - quoted, _ := json.Marshal(value) - m[key] = json.RawMessage(quoted) + quoted, _ := json.Marshal(value, jsonx.V1()) + m[key] = jsontext.Value(quoted) - result, _ := json.Marshal(m) + result, _ := json.Marshal(m, jsonx.V1()) return result } @@ -790,19 +792,19 @@ func injectJSONFieldAPIGW(body []byte, key, value string) []byte { // (encoder.AddQuery in a loop) rather than a single scalar -- e.g. tagKeys // (UntagResource, serializers.go:9284). func injectJSONArrayFieldAPIGW(body []byte, key string, values []string) []byte { - var m map[string]json.RawMessage + var m map[string]jsontext.Value if len(body) > 0 { - if err := json.Unmarshal(body, &m); err != nil { - m = make(map[string]json.RawMessage) + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { + m = make(map[string]jsontext.Value) } } else { - m = make(map[string]json.RawMessage) + m = make(map[string]jsontext.Value) } - encoded, _ := json.Marshal(values) + encoded, _ := json.Marshal(values, jsonx.V1()) m[key] = encoded - result, _ := json.Marshal(m) + result, _ := json.Marshal(m, jsonx.V1()) return result } @@ -984,7 +986,7 @@ func (h *Handler) dispatch( return statusCode, nil, raw, nil } - encoded, err := json.Marshal(response) + encoded, err := json.Marshal(response, jsonx.V1()) if err != nil { return 0, nil, nil, err } @@ -1047,7 +1049,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin Message: strings.TrimPrefix(reqErr.Error(), errType+": "), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/apigateway/handler_account.go b/services/apigateway/handler_account.go index e637447b6f..c43d1867fb 100644 --- a/services/apigateway/handler_account.go +++ b/services/apigateway/handler_account.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseAPIGWAccountPath handles /account paths. @@ -32,7 +34,7 @@ func (h *Handler) accountActions() map[string]actionFn { }, opUpdateAccount: func(b []byte) (int, any, error) { var input UpdateAccountInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateAccount(input) diff --git a/services/apigateway/handler_api_keys.go b/services/apigateway/handler_api_keys.go index eddb564e28..0eac95800d 100644 --- a/services/apigateway/handler_api_keys.go +++ b/services/apigateway/handler_api_keys.go @@ -1,10 +1,12 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getAPIKeyInput struct { @@ -56,7 +58,7 @@ func parseAPIGWAPIKeysPath(method string, segs []string, n int, query url.Values func (h *Handler) getAPIKeyAction(b []byte) (int, any, error) { var input getAPIKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } key, err := h.Backend.GetAPIKey(input.APIKeyID) @@ -72,7 +74,7 @@ func (h *Handler) getAPIKeyAction(b []byte) (int, any, error) { func (h *Handler) getAPIKeysAction(b []byte) (int, any, error) { var input getAPIKeysPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } keys, position, err := h.fetchAPIKeys(input) @@ -135,7 +137,7 @@ func filterAPIKeys(keys []APIKey, customerID, nameQuery string) []APIKey { func (h *Handler) deleteAPIKeyAction(b []byte) (int, any, error) { var input deleteAPIKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteAPIKey(input.APIKeyID); err != nil { @@ -150,7 +152,7 @@ func (h *Handler) apiKeyActions() map[string]actionFn { return map[string]actionFn{ opCreateAPIKey: func(b []byte) (int, any, error) { var input CreateAPIKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -166,7 +168,7 @@ func (h *Handler) apiKeyActions() map[string]actionFn { opDeleteAPIKey: h.deleteAPIKeyAction, opUpdateAPIKey: func(b []byte) (int, any, error) { var input updateAPIKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } key, err := h.Backend.UpdateAPIKey(input.APIKeyID, input.UpdateAPIKeyInput) diff --git a/services/apigateway/handler_authorizers.go b/services/apigateway/handler_authorizers.go index fcafe0c71b..3ed1a9ad02 100644 --- a/services/apigateway/handler_authorizers.go +++ b/services/apigateway/handler_authorizers.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createAuthorizerInput struct { @@ -67,7 +69,7 @@ func (h *Handler) authorizerActions() map[string]actionFn { func (h *Handler) createAuthorizerAction(b []byte) (int, any, error) { var input createAuthorizerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ttl := defaultAuthorizerTTLSeconds @@ -94,7 +96,7 @@ func (h *Handler) createAuthorizerAction(b []byte) (int, any, error) { func (h *Handler) getAuthorizerAction(b []byte) (int, any, error) { var input getAuthorizerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } auth, err := h.Backend.GetAuthorizer(input.RestAPIID, input.AuthorizerID) @@ -105,9 +107,10 @@ func (h *Handler) getAuthorizerAction(b []byte) (int, any, error) { return http.StatusOK, auth, nil } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) getAuthorizersAction(b []byte) (int, any, error) { var input getAuthorizersInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } auths, err := h.Backend.GetAuthorizers(input.RestAPIID) @@ -127,7 +130,7 @@ func (h *Handler) getAuthorizersAction(b []byte) (int, any, error) { func (h *Handler) updateAuthorizerAction(b []byte) (int, any, error) { var input updateAuthorizerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } auth, err := h.Backend.UpdateAuthorizer(input.RestAPIID, input.AuthorizerID, UpdateAuthorizerInput{ @@ -150,7 +153,7 @@ func (h *Handler) updateAuthorizerAction(b []byte) (int, any, error) { func (h *Handler) deleteAuthorizerAction(b []byte) (int, any, error) { var input deleteAuthorizerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteAuthorizer(input.RestAPIID, input.AuthorizerID); err != nil { @@ -162,7 +165,7 @@ func (h *Handler) deleteAuthorizerAction(b []byte) (int, any, error) { func (h *Handler) testInvokeAuthorizerAction(b []byte) (int, any, error) { var input TestInvokeAuthorizerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } auth, err := h.Backend.GetAuthorizer(input.RestAPIID, input.AuthorizerID) diff --git a/services/apigateway/handler_base_path_mappings.go b/services/apigateway/handler_base_path_mappings.go index 7fd37a2132..ca4997c1a9 100644 --- a/services/apigateway/handler_base_path_mappings.go +++ b/services/apigateway/handler_base_path_mappings.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getBasePathMappingInput struct { @@ -50,7 +52,7 @@ func (h *Handler) basePathMappingActions() map[string]actionFn { func (h *Handler) createBasePathMappingAction(b []byte) (int, any, error) { var input CreateBasePathMappingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -64,7 +66,7 @@ func (h *Handler) createBasePathMappingAction(b []byte) (int, any, error) { func (h *Handler) getBasePathMappingAction(b []byte) (int, any, error) { var input getBasePathMappingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } bpm, err := h.Backend.GetBasePathMapping(input.DomainName, input.BasePath) @@ -75,9 +77,10 @@ func (h *Handler) getBasePathMappingAction(b []byte) (int, any, error) { return http.StatusOK, bpm, nil } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) getBasePathMappingsAction(b []byte) (int, any, error) { var input getBasePathMappingsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } bpms, err := h.Backend.GetBasePathMappings(input.DomainName) @@ -98,7 +101,7 @@ func (h *Handler) getBasePathMappingsAction(b []byte) (int, any, error) { func (h *Handler) deleteBasePathMappingAction(b []byte) (int, any, error) { var input deleteBasePathMappingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteBasePathMapping(input.DomainName, input.BasePath); err != nil { @@ -110,7 +113,7 @@ func (h *Handler) deleteBasePathMappingAction(b []byte) (int, any, error) { func (h *Handler) updateBasePathMappingAction(b []byte) (int, any, error) { var input UpdateBasePathMappingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateBasePathMapping(input) diff --git a/services/apigateway/handler_client_certificates.go b/services/apigateway/handler_client_certificates.go index 13ad70dea6..6d9081e9ec 100644 --- a/services/apigateway/handler_client_certificates.go +++ b/services/apigateway/handler_client_certificates.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const opUpdateClientCertificate = "UpdateClientCertificate" @@ -48,7 +50,7 @@ func (h *Handler) clientCertificateActions() map[string]actionFn { func (h *Handler) generateClientCertificateAction(b []byte) (int, any, error) { var input GenerateClientCertificateInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GenerateClientCertificate(input) @@ -63,7 +65,7 @@ func (h *Handler) getClientCertificateAction(b []byte) (int, any, error) { var params struct { ClientCertificateID string `json:"clientCertificateId"` } - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetClientCertificate(params.ClientCertificateID) @@ -76,7 +78,7 @@ func (h *Handler) getClientCertificateAction(b []byte) (int, any, error) { func (h *Handler) getClientCertificatesAction(b []byte) (int, any, error) { var input getClientCertificatesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetClientCertificates() @@ -99,7 +101,7 @@ func (h *Handler) deleteClientCertificateAction(b []byte) (int, any, error) { var params struct { ClientCertificateID string `json:"clientCertificateId"` } - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteClientCertificate(params.ClientCertificateID); err != nil { @@ -111,7 +113,7 @@ func (h *Handler) deleteClientCertificateAction(b []byte) (int, any, error) { func (h *Handler) updateClientCertificateAction(b []byte) (int, any, error) { var input UpdateClientCertificateInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } diff --git a/services/apigateway/handler_deployments.go b/services/apigateway/handler_deployments.go index aee02b0ec8..b124bf768f 100644 --- a/services/apigateway/handler_deployments.go +++ b/services/apigateway/handler_deployments.go @@ -1,9 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createDeploymentInput struct { @@ -53,7 +55,7 @@ func (h *Handler) deploymentActions() map[string]actionFn { // createDeploymentAction handles CreateDeployment incl. inline stage update. func (h *Handler) createDeploymentAction(b []byte) (int, any, error) { var input createDeploymentInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } depl, err := h.Backend.CreateDeploymentWithCanary( @@ -104,7 +106,7 @@ func (h *Handler) deploymentCRUDActions() map[string]actionFn { func (h *Handler) getDeploymentAction(b []byte) (int, any, error) { var input getDeploymentInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } depl, err := h.Backend.GetDeployment(input.RestAPIID, input.DeploymentID) @@ -121,7 +123,7 @@ func (h *Handler) getDeploymentAction(b []byte) (int, any, error) { func (h *Handler) getDeploymentsAction(b []byte) (int, any, error) { var input getDeploymentsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } depls, err := h.Backend.GetDeployments(input.RestAPIID) @@ -146,7 +148,7 @@ func (h *Handler) getDeploymentsAction(b []byte) (int, any, error) { func (h *Handler) deleteDeploymentAction(b []byte) (int, any, error) { var input deleteDeploymentInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteDeployment(input.RestAPIID, input.DeploymentID); err != nil { @@ -163,7 +165,7 @@ func (h *Handler) deleteDeploymentAction(b []byte) (int, any, error) { func (h *Handler) updateDeploymentAction(b []byte) (int, any, error) { var input updateDeploymentHandlerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } depl, err := h.Backend.UpdateDeployment(input.RestAPIID, input.DeploymentID, input.UpdateDeploymentInput) diff --git a/services/apigateway/handler_documentation.go b/services/apigateway/handler_documentation.go index dbadca69bf..61dec97d11 100644 --- a/services/apigateway/handler_documentation.go +++ b/services/apigateway/handler_documentation.go @@ -1,9 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getDocumentationPartInput struct { @@ -120,7 +122,7 @@ func (h *Handler) documentationActions() map[string]actionFn { func (h *Handler) createDocumentationPartAction(b []byte) (int, any, error) { var input CreateDocumentationPartInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -134,7 +136,7 @@ func (h *Handler) createDocumentationPartAction(b []byte) (int, any, error) { func (h *Handler) createDocumentationVersionAction(b []byte) (int, any, error) { var input CreateDocumentationVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -148,7 +150,7 @@ func (h *Handler) createDocumentationVersionAction(b []byte) (int, any, error) { func (h *Handler) getDocumentationPartAction(b []byte) (int, any, error) { var input getDocumentationPartInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } p, err := h.Backend.GetDocumentationPart(input.RestAPIID, input.DocPartID) @@ -161,7 +163,7 @@ func (h *Handler) getDocumentationPartAction(b []byte) (int, any, error) { func (h *Handler) getDocumentationPartsAction(b []byte) (int, any, error) { var input getDocumentationPartsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ps, err := h.Backend.GetDocumentationParts(input.RestAPIID) @@ -231,7 +233,7 @@ func filterDocumentationPartsByStatus(parts []DocumentationPart, status string) func (h *Handler) updateDocumentationPartAction(b []byte) (int, any, error) { var input UpdateDocumentationPartInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateDocumentationPart(input) @@ -244,7 +246,7 @@ func (h *Handler) updateDocumentationPartAction(b []byte) (int, any, error) { func (h *Handler) updateDocumentationVersionAction(b []byte) (int, any, error) { var input UpdateDocumentationVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateDocumentationVersion(input) @@ -257,7 +259,7 @@ func (h *Handler) updateDocumentationVersionAction(b []byte) (int, any, error) { func (h *Handler) deleteDocumentationPartAction(b []byte) (int, any, error) { var input deleteDocumentationPartInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteDocumentationPart(input.RestAPIID, input.DocPartID); err != nil { @@ -269,7 +271,7 @@ func (h *Handler) deleteDocumentationPartAction(b []byte) (int, any, error) { func (h *Handler) getDocumentationVersionAction(b []byte) (int, any, error) { var input getDocumentationVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } v, err := h.Backend.GetDocumentationVersion(input.RestAPIID, input.DocVersion) @@ -280,9 +282,10 @@ func (h *Handler) getDocumentationVersionAction(b []byte) (int, any, error) { return http.StatusOK, v, nil } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) getDocumentationVersionsAction(b []byte) (int, any, error) { var input getDocumentationVersionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } vs, err := h.Backend.GetDocumentationVersions(input.RestAPIID) @@ -303,7 +306,7 @@ func (h *Handler) getDocumentationVersionsAction(b []byte) (int, any, error) { func (h *Handler) deleteDocumentationVersionAction(b []byte) (int, any, error) { var input deleteDocumentationVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteDocumentationVersion(input.RestAPIID, input.DocVersion); err != nil { diff --git a/services/apigateway/handler_domain_names.go b/services/apigateway/handler_domain_names.go index 661762e685..b1b04aa243 100644 --- a/services/apigateway/handler_domain_names.go +++ b/services/apigateway/handler_domain_names.go @@ -1,9 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -107,7 +109,7 @@ func (h *Handler) domainNameActions() map[string]actionFn { func (h *Handler) createDomainNameAction(b []byte) (int, any, error) { var input CreateDomainNameInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -121,7 +123,7 @@ func (h *Handler) createDomainNameAction(b []byte) (int, any, error) { func (h *Handler) getDomainNameAction(b []byte) (int, any, error) { var input getDomainNameInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } dn, err := h.Backend.GetDomainName(input.DomainName) @@ -134,7 +136,7 @@ func (h *Handler) getDomainNameAction(b []byte) (int, any, error) { func (h *Handler) getDomainNamesAction(b []byte) (int, any, error) { var input getDomainNamesPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if input.ResourceOwner == resourceOwnerOther { @@ -158,7 +160,7 @@ func (h *Handler) getDomainNamesAction(b []byte) (int, any, error) { func (h *Handler) deleteDomainNameAction(b []byte) (int, any, error) { var input deleteDomainNameInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteDomainName(input.DomainName); err != nil { @@ -170,7 +172,7 @@ func (h *Handler) deleteDomainNameAction(b []byte) (int, any, error) { func (h *Handler) updateDomainNameAction(b []byte) (int, any, error) { var input UpdateDomainNameInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateDomainName(input) @@ -187,7 +189,7 @@ func (h *Handler) domainNameAccessAssociationActions() map[string]actionFn { return map[string]actionFn{ opCreateDomainNameAccessAssociation: func(b []byte) (int, any, error) { var input CreateDomainNameAccessAssociationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -204,7 +206,7 @@ func (h *Handler) domainNameAccessAssociationActions() map[string]actionFn { Position string `json:"position,omitempty"` Limit int `json:"limit,omitempty"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -224,7 +226,7 @@ func (h *Handler) domainNameAccessAssociationActions() map[string]actionFn { var input struct { Arn string `json:"domainNameAccessAssociationArn"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -239,7 +241,7 @@ func (h *Handler) domainNameAccessAssociationActions() map[string]actionFn { Arn string `json:"domainNameAccessAssociationArn"` DomainNameArn string `json:"domainNameArn"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -265,7 +267,7 @@ func (h *Handler) withDomainNameID(fn actionFn) actionFn { DomainName string `json:"domainName"` DomainNameID string `json:"domainNameId"` } - if err := json.Unmarshal(b, &ref); err != nil { + if err := json.Unmarshal(b, &ref, jsonx.V1()); err != nil { return 0, nil, err } diff --git a/services/apigateway/handler_export.go b/services/apigateway/handler_export.go index a381cd3600..daf1382945 100644 --- a/services/apigateway/handler_export.go +++ b/services/apigateway/handler_export.go @@ -1,12 +1,14 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" "gopkg.in/yaml.v3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const opGetExport = "GetExport" @@ -39,7 +41,7 @@ func (h *Handler) exportActions() map[string]actionFn { return map[string]actionFn{ opGetExport: func(b []byte) (int, any, error) { var input getExportInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -82,5 +84,5 @@ func encodeExport(export map[string]any, contentType string) ([]byte, error) { return yaml.Marshal(export) } - return json.Marshal(export) + return json.Marshal(export, jsonx.V1()) } diff --git a/services/apigateway/handler_fixtures_test.go b/services/apigateway/handler_fixtures_test.go index b45dd31e63..c4f29b5c3f 100644 --- a/services/apigateway/handler_fixtures_test.go +++ b/services/apigateway/handler_fixtures_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -27,7 +28,7 @@ func boostAPI(t *testing.T, handler *apigateway.Handler, e *echo.Echo) string { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"boost-api"}`) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -38,7 +39,7 @@ func boostRootResource(t *testing.T, handler *apigateway.Handler, e *echo.Echo, rec := postWithHandler(t, handler, e, "GetResources", fmt.Sprintf(`{"restApiId":%q}`, apiID)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["item"].([]any) require.NotEmpty(t, items) @@ -52,7 +53,7 @@ func boostDeployment(t *testing.T, handler *apigateway.Handler, e *echo.Echo, ap fmt.Sprintf(`{"restApiId":%q,"stageName":%q}`, apiID, stageName)) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -72,7 +73,7 @@ func boostAuthorizer(t *testing.T, handler *apigateway.Handler, e *echo.Echo, ap ) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -84,7 +85,7 @@ func boostDocPart(t *testing.T, handler *apigateway.Handler, e *echo.Echo, apiID fmt.Sprintf(`{"restApiId":%q,"location":{"type":"API"},"properties":"{}"}`, apiID)) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } diff --git a/services/apigateway/handler_gateway_responses.go b/services/apigateway/handler_gateway_responses.go index c82b2bc0cb..738b0ea781 100644 --- a/services/apigateway/handler_gateway_responses.go +++ b/services/apigateway/handler_gateway_responses.go @@ -1,9 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const opUpdateGatewayResponse = "UpdateGatewayResponse" @@ -32,7 +34,7 @@ func (h *Handler) gatewayResponseActions() map[string]actionFn { func (h *Handler) getGatewayResponseAction(b []byte) (int, any, error) { var params getGatewayResponseInput - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetGatewayResponse(params.RestAPIID, params.ResponseType) @@ -45,7 +47,7 @@ func (h *Handler) getGatewayResponseAction(b []byte) (int, any, error) { func (h *Handler) getGatewayResponsesAction(b []byte) (int, any, error) { var params getGatewayResponsesInput - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetGatewayResponses(params.RestAPIID) @@ -67,7 +69,7 @@ func (h *Handler) getGatewayResponsesAction(b []byte) (int, any, error) { func (h *Handler) putGatewayResponseAction(b []byte) (int, any, error) { var input PutGatewayResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.PutGatewayResponse(input) @@ -80,7 +82,7 @@ func (h *Handler) putGatewayResponseAction(b []byte) (int, any, error) { func (h *Handler) updateGatewayResponseAction(b []byte) (int, any, error) { var input PutGatewayResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -94,7 +96,7 @@ func (h *Handler) updateGatewayResponseAction(b []byte) (int, any, error) { func (h *Handler) deleteGatewayResponseAction(b []byte) (int, any, error) { var params getGatewayResponseInput - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteGatewayResponse(params.RestAPIID, params.ResponseType); err != nil { diff --git a/services/apigateway/handler_import_export.go b/services/apigateway/handler_import_export.go index d1ae083393..9ed7d142c7 100644 --- a/services/apigateway/handler_import_export.go +++ b/services/apigateway/handler_import_export.go @@ -15,13 +15,14 @@ package apigateway // payload, optionally with "restApiId"/"mode" as extra sibling fields. import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -59,7 +60,7 @@ type restAPISpecEnvelope struct { // for the two payload shapes this handles. func decodeRestAPISpecPayload(b []byte) ([]byte, restAPISpecEnvelope) { var env restAPISpecEnvelope - if err := json.Unmarshal(b, &env); err == nil && env.Body != "" { + if err := json.Unmarshal(b, &env, jsonx.V1()); err == nil && env.Body != "" { if raw, decErr := base64.StdEncoding.DecodeString(env.Body); decErr == nil { return raw, env } @@ -75,7 +76,7 @@ func decodeRestAPISpecPayload(b []byte) ([]byte, restAPISpecEnvelope) { Format string `json:"format,omitempty"` } - _ = json.Unmarshal(b, &fallback) + _ = json.Unmarshal(b, &fallback, jsonx.V1()) return b, restAPISpecEnvelope{ RestAPIID: fallback.RestAPIID, @@ -206,7 +207,7 @@ func (h *Handler) dispatchRestAPISpec( env.Parameters[k] = v[0] } - envelope, err := json.Marshal(env) + envelope, err := json.Marshal(env, jsonx.V1()) if err != nil { return writeJSONProtocolDispatchError(c, http.StatusInternalServerError, "InternalFailure", "internal server error") diff --git a/services/apigateway/handler_import_export_test.go b/services/apigateway/handler_import_export_test.go index 72f3526c8f..cb1a6e8b46 100644 --- a/services/apigateway/handler_import_export_test.go +++ b/services/apigateway/handler_import_export_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -48,7 +49,7 @@ func TestAPIGateway_ImportRestApi_DirectDispatch(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, "Pet Store", api["name"]) assert.Equal(t, "A sample API", api["description"]) @@ -63,7 +64,7 @@ func TestAPIGateway_ImportRestApi_YAML(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, "YAML API", api["name"]) assert.Equal(t, "from yaml", api["description"]) } @@ -79,7 +80,7 @@ func TestAPIGateway_ImportRestApi_CreatesResourcesFromPaths(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) apiID, _ := api["id"].(string) require.NotEmpty(t, apiID) @@ -143,7 +144,7 @@ func TestAPIGateway_PutRestApi_DirectDispatch_Merge(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, "merged", api["name"]) resources, _, err := backend.GetResources(created.ID, "", 0) @@ -184,7 +185,7 @@ func TestAPIGateway_ImportRestApi_RESTRoute(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, "Pet Store", api["name"]) // A plain POST /restapis (no mode=import) must still route to CreateRestApi @@ -197,7 +198,7 @@ func TestAPIGateway_ImportRestApi_RESTRoute(t *testing.T) { require.Equal(t, http.StatusCreated, rec2.Code) var api2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &api2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &api2, jsonx.V1())) assert.Equal(t, "plain-create", api2["name"]) } @@ -228,7 +229,7 @@ func TestAPIGateway_PutRestApi_RESTRoute_Overwrite(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var api map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, "Pet Store", api["name"]) resources, _, err := backend.GetResources(created.ID, "", 0) @@ -300,7 +301,7 @@ func TestAPIGateway_ImportApiKeys_RESTRoute(t *testing.T) { if tt.wantCount > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, _ := resp["ids"].([]any) require.Len(t, ids, tt.wantCount) } @@ -329,7 +330,7 @@ func TestAPIGateway_ImportApiKeys_DuplicateWarns(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, _ := resp["ids"].([]any) warnings, _ := resp["warnings"].([]any) assert.Len(t, ids, 1, "only the non-duplicate row is imported") @@ -364,7 +365,7 @@ func TestAPIGateway_ImportDocumentationParts_RESTRoute(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, _ := resp["ids"].([]any) require.Len(t, ids, 2) diff --git a/services/apigateway/handler_integrations.go b/services/apigateway/handler_integrations.go index 9827335a17..d975d78fea 100644 --- a/services/apigateway/handler_integrations.go +++ b/services/apigateway/handler_integrations.go @@ -1,9 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putIntegrationInput struct { @@ -103,7 +105,7 @@ func (h *Handler) integrationResponseActions() map[string]actionFn { return map[string]actionFn{ opPutIntegrationResponse: func(b []byte) (int, any, error) { var input putIntegrationResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ir, err := h.Backend.PutIntegrationResponse( @@ -121,7 +123,7 @@ func (h *Handler) integrationResponseActions() map[string]actionFn { }, opGetIntegrationResponse: func(b []byte) (int, any, error) { var input getIntegrationResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ir, err := h.Backend.GetIntegrationResponse( @@ -138,7 +140,7 @@ func (h *Handler) integrationResponseActions() map[string]actionFn { }, opDeleteIntegrationResponse: func(b []byte) (int, any, error) { var input deleteIntegrationResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteIntegrationResponse( @@ -154,7 +156,7 @@ func (h *Handler) integrationResponseActions() map[string]actionFn { }, opUpdateIntegrationResponse: func(b []byte) (int, any, error) { var input UpdateIntegrationResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateIntegrationResponse(input) @@ -171,7 +173,7 @@ func (h *Handler) integrationActions() map[string]actionFn { return map[string]actionFn{ opPutIntegration: func(b []byte) (int, any, error) { var input putIntegrationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } switch input.Type { @@ -197,7 +199,7 @@ func (h *Handler) integrationActions() map[string]actionFn { }, opGetIntegration: func(b []byte) (int, any, error) { var input getIntegrationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } integ, err := h.Backend.GetIntegration(input.RestAPIID, input.ResourceID, input.HTTPMethod) @@ -209,7 +211,7 @@ func (h *Handler) integrationActions() map[string]actionFn { }, opDeleteIntegration: func(b []byte) (int, any, error) { var input deleteIntegrationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteIntegration(input.RestAPIID, input.ResourceID, input.HTTPMethod); err != nil { @@ -220,7 +222,7 @@ func (h *Handler) integrationActions() map[string]actionFn { }, opUpdateIntegration: func(b []byte) (int, any, error) { var input UpdateIntegrationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateIntegration(input) diff --git a/services/apigateway/handler_methods.go b/services/apigateway/handler_methods.go index 9e0304f5d8..424bd0037b 100644 --- a/services/apigateway/handler_methods.go +++ b/services/apigateway/handler_methods.go @@ -2,10 +2,12 @@ package apigateway import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type testInvokeMethodHandlerInput struct { @@ -295,7 +297,7 @@ func (h *Handler) invokeLambdaTestMethod( event.StageVariables = input.StageVariables - payload, _ := json.Marshal(event) + payload, _ := json.Marshal(event, jsonx.V1()) lambdaFn := ExtractLambdaFunctionName(interpolateStageVars(integration.URI, input.StageVariables)) respBytes, _, invokeErr := h.lambda.InvokeFunction(context.Background(), lambdaFn, "RequestResponse", payload) @@ -321,7 +323,7 @@ func lambdaTestOutput(respBytes []byte, invokeErr error) *TestInvokeMethodOutput } var lambdaResp LambdaProxyResponse - if json.Unmarshal(respBytes, &lambdaResp) == nil { + if json.Unmarshal(respBytes, &lambdaResp, jsonx.V1()) == nil { sc := lambdaResp.StatusCode if sc == 0 { sc = http.StatusOK @@ -366,7 +368,7 @@ func (h *Handler) methodActions() map[string]actionFn { func (h *Handler) putMethodAction(b []byte) (int, any, error) { var input putMethodInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } switch input.AuthorizationType { @@ -394,7 +396,7 @@ func (h *Handler) putMethodAction(b []byte) (int, any, error) { func (h *Handler) getMethodAction(b []byte) (int, any, error) { var input getMethodInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } m, err := h.Backend.GetMethod(input.RestAPIID, input.ResourceID, input.HTTPMethod) @@ -407,7 +409,7 @@ func (h *Handler) getMethodAction(b []byte) (int, any, error) { func (h *Handler) deleteMethodAction(b []byte) (int, any, error) { var input deleteMethodInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteMethod(input.RestAPIID, input.ResourceID, input.HTTPMethod); err != nil { @@ -419,7 +421,7 @@ func (h *Handler) deleteMethodAction(b []byte) (int, any, error) { func (h *Handler) updateMethodAction(b []byte) (int, any, error) { var input UpdateMethodInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateMethod(input) @@ -432,7 +434,7 @@ func (h *Handler) updateMethodAction(b []byte) (int, any, error) { func (h *Handler) testInvokeMethodAction(b []byte) (int, any, error) { var input testInvokeMethodHandlerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.testInvokeMethod(input.TestInvokeMethodInput) @@ -448,7 +450,7 @@ func (h *Handler) methodResponseActions() map[string]actionFn { return map[string]actionFn{ opPutMethodResponse: func(b []byte) (int, any, error) { var input putMethodResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } mr, err := h.Backend.PutMethodResponse( @@ -466,7 +468,7 @@ func (h *Handler) methodResponseActions() map[string]actionFn { }, opGetMethodResponse: func(b []byte) (int, any, error) { var input getMethodResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } mr, err := h.Backend.GetMethodResponse( @@ -483,7 +485,7 @@ func (h *Handler) methodResponseActions() map[string]actionFn { }, opDeleteMethodResponse: func(b []byte) (int, any, error) { var input deleteMethodResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteMethodResponse( @@ -499,7 +501,7 @@ func (h *Handler) methodResponseActions() map[string]actionFn { }, opUpdateMethodResponse: func(b []byte) (int, any, error) { var input UpdateMethodResponseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateMethodResponse(input) diff --git a/services/apigateway/handler_request_validators.go b/services/apigateway/handler_request_validators.go index fc161140c3..bd034fb8c3 100644 --- a/services/apigateway/handler_request_validators.go +++ b/services/apigateway/handler_request_validators.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createRequestValidatorInput struct { @@ -47,7 +49,7 @@ func (h *Handler) requestValidatorActions() map[string]actionFn { func (h *Handler) createRequestValidatorAction(b []byte) (int, any, error) { var input createRequestValidatorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } rv, err := h.Backend.CreateRequestValidator(input.RestAPIID, CreateRequestValidatorInput{ @@ -64,7 +66,7 @@ func (h *Handler) createRequestValidatorAction(b []byte) (int, any, error) { func (h *Handler) getRequestValidatorAction(b []byte) (int, any, error) { var input getRequestValidatorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } rv, err := h.Backend.GetRequestValidator(input.RestAPIID, input.ValidatorID) @@ -75,9 +77,10 @@ func (h *Handler) getRequestValidatorAction(b []byte) (int, any, error) { return http.StatusOK, rv, nil } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) getRequestValidatorsAction(b []byte) (int, any, error) { var input getRequestValidatorsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } rvs, err := h.Backend.GetRequestValidators(input.RestAPIID) @@ -98,7 +101,7 @@ func (h *Handler) getRequestValidatorsAction(b []byte) (int, any, error) { func (h *Handler) updateRequestValidatorAction(b []byte) (int, any, error) { var input updateRequestValidatorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } rv, err := h.Backend.UpdateRequestValidator( @@ -115,7 +118,7 @@ func (h *Handler) updateRequestValidatorAction(b []byte) (int, any, error) { func (h *Handler) deleteRequestValidatorAction(b []byte) (int, any, error) { var input deleteRequestValidatorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteRequestValidator(input.RestAPIID, input.ValidatorID); err != nil { diff --git a/services/apigateway/handler_resources.go b/services/apigateway/handler_resources.go index 460cc7f35c..b3f92333c7 100644 --- a/services/apigateway/handler_resources.go +++ b/services/apigateway/handler_resources.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getResourcesInput struct { @@ -47,7 +49,7 @@ func (h *Handler) resourceActions() map[string]actionFn { func (h *Handler) getResourcesAction(b []byte) (int, any, error) { var input getResourcesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } resources, position, err := h.Backend.GetResources(input.RestAPIID, input.Position, input.Limit) @@ -64,7 +66,7 @@ func (h *Handler) getResourcesAction(b []byte) (int, any, error) { func (h *Handler) getResourceAction(b []byte) (int, any, error) { var input getResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } r, err := h.Backend.GetResource(input.RestAPIID, input.ResourceID) @@ -77,7 +79,7 @@ func (h *Handler) getResourceAction(b []byte) (int, any, error) { func (h *Handler) createResourceAction(b []byte) (int, any, error) { var input createResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } r, err := h.Backend.CreateResource(input.RestAPIID, input.ParentID, input.PathPart) @@ -90,7 +92,7 @@ func (h *Handler) createResourceAction(b []byte) (int, any, error) { func (h *Handler) deleteResourceAction(b []byte) (int, any, error) { var input deleteResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteResource(input.RestAPIID, input.ResourceID); err != nil { @@ -102,7 +104,7 @@ func (h *Handler) deleteResourceAction(b []byte) (int, any, error) { func (h *Handler) updateResourceAction(b []byte) (int, any, error) { var input updateResourceHandlerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } res, err := h.Backend.UpdateResource(input.RestAPIID, input.ResourceID, input.UpdateResourceInput) diff --git a/services/apigateway/handler_rest_apis.go b/services/apigateway/handler_rest_apis.go index 013f062980..9fcfdb2ecf 100644 --- a/services/apigateway/handler_rest_apis.go +++ b/services/apigateway/handler_rest_apis.go @@ -1,9 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -39,7 +40,7 @@ func (h *Handler) restAPIActions() map[string]actionFn { func (h *Handler) createRestAPIAction(b []byte) (int, any, error) { var input createRestAPIInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if input.Tags != nil { @@ -61,7 +62,7 @@ func (h *Handler) createRestAPIAction(b []byte) (int, any, error) { func (h *Handler) deleteRestAPIAction(b []byte) (int, any, error) { var input deleteRestAPIInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } // Deployment IDs are freshly random and never reused, so the trie cache @@ -85,7 +86,7 @@ func (h *Handler) deleteRestAPIAction(b []byte) (int, any, error) { func (h *Handler) getRestAPIAction(b []byte) (int, any, error) { var input getRestAPIInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } api, err := h.Backend.GetRestAPI(input.RestAPIID) @@ -98,7 +99,7 @@ func (h *Handler) getRestAPIAction(b []byte) (int, any, error) { func (h *Handler) getRestAPIsAction(b []byte) (int, any, error) { var input getRestApisInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } apis, position, err := h.Backend.GetRestAPIs(input.Limit, input.Position) @@ -116,7 +117,7 @@ func (h *Handler) getRestAPIsAction(b []byte) (int, any, error) { func (h *Handler) updateRestAPIAction(b []byte) (int, any, error) { var input updateRestAPIHandlerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } api, err := h.Backend.UpdateRestAPI(input.RestAPIID, input.UpdateRestAPIInput) diff --git a/services/apigateway/handler_router_test.go b/services/apigateway/handler_router_test.go index c7a4afd8d8..d4ba5ecc14 100644 --- a/services/apigateway/handler_router_test.go +++ b/services/apigateway/handler_router_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "log/slog" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -31,7 +32,7 @@ func createParityAPI(t *testing.T, h *apigateway.Handler, name string) string { require.Equal(t, http.StatusCreated, rec.Code, "create api: %s", rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) id, _ := out["id"].(string) require.NotEmpty(t, id) @@ -49,7 +50,7 @@ func getRootResourceID(t *testing.T, h *apigateway.Handler, apiID string) string var out struct { Items []map[string]any `json:"item"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) for _, r := range out.Items { if r["path"] == "/" { @@ -575,7 +576,7 @@ func TestHandler_APIGateway_FullWorkflow(t *testing.T) { // parseJSON is a helper to decode JSON from a response recorder. func parseJSON(rec *httptest.ResponseRecorder, v any) error { - return json.Unmarshal(rec.Body.Bytes(), v) + return json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1()) } // errNoopNotImplemented is returned by noopBackend for methods that are not expected @@ -1357,7 +1358,7 @@ func TestHandler_GetSupportedOperations(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.Contains(t, ops, "CreateRestApi") }) } diff --git a/services/apigateway/handler_schema_models.go b/services/apigateway/handler_schema_models.go index 9dbce8598e..b653b09b29 100644 --- a/services/apigateway/handler_schema_models.go +++ b/services/apigateway/handler_schema_models.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getModelInput struct { @@ -47,7 +49,7 @@ func (h *Handler) schemaModelActions() map[string]actionFn { func (h *Handler) createModelAction(b []byte) (int, any, error) { var input CreateModelInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -61,7 +63,7 @@ func (h *Handler) createModelAction(b []byte) (int, any, error) { func (h *Handler) getModelAction(b []byte) (int, any, error) { var input getModelInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } m, err := h.Backend.GetModel(input.RestAPIID, input.ModelName, input.Flatten == litTrue) @@ -72,9 +74,10 @@ func (h *Handler) getModelAction(b []byte) (int, any, error) { return http.StatusOK, m, nil } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) getModelsAction(b []byte) (int, any, error) { var input getModelsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ms, err := h.Backend.GetModels(input.RestAPIID) @@ -94,7 +97,7 @@ func (h *Handler) getModelsAction(b []byte) (int, any, error) { func (h *Handler) deleteModelAction(b []byte) (int, any, error) { var input deleteModelInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteModel(input.RestAPIID, input.ModelName); err != nil { @@ -106,7 +109,7 @@ func (h *Handler) deleteModelAction(b []byte) (int, any, error) { func (h *Handler) updateModelAction(b []byte) (int, any, error) { var input updateModelInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } m, err := h.Backend.UpdateModel(input.RestAPIID, input.ModelName, input.UpdateModelInput) @@ -122,7 +125,7 @@ func (h *Handler) getModelTemplateAction(b []byte) (int, any, error) { RestAPIID string `json:"restApiId"` ModelName string `json:"modelName"` } - if err := json.Unmarshal(b, ¶ms); err != nil { + if err := json.Unmarshal(b, ¶ms, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetModelTemplate(params.RestAPIID, params.ModelName) diff --git a/services/apigateway/handler_sdk.go b/services/apigateway/handler_sdk.go index 6b10462fbd..ef757aa424 100644 --- a/services/apigateway/handler_sdk.go +++ b/services/apigateway/handler_sdk.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -48,12 +50,12 @@ func (h *Handler) sdkActions() map[string]actionFn { StageName string `json:"stageName"` SdkType string `json:"sdkType"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } var raw map[string]any - _ = json.Unmarshal(b, &raw) + _ = json.Unmarshal(b, &raw, jsonx.V1()) params := make(map[string]string) @@ -78,7 +80,7 @@ func (h *Handler) sdkActions() map[string]actionFn { var input struct { ID string `json:"id"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -93,7 +95,7 @@ func (h *Handler) sdkActions() map[string]actionFn { var input struct { Limit int `json:"limit,omitempty"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } diff --git a/services/apigateway/handler_stages.go b/services/apigateway/handler_stages.go index a74973de92..b72e7e810a 100644 --- a/services/apigateway/handler_stages.go +++ b/services/apigateway/handler_stages.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getStagesInput struct { @@ -46,7 +48,7 @@ func (h *Handler) stageActions() map[string]actionFn { func (h *Handler) createStageAction(b []byte) (int, any, error) { var input CreateStageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -60,7 +62,7 @@ func (h *Handler) createStageAction(b []byte) (int, any, error) { func (h *Handler) updateStageAction(b []byte) (int, any, error) { var input updateStageHandlerInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } stage, err := h.Backend.UpdateStage(input.RestAPIID, input.StageName, input.UpdateStageInput) @@ -77,7 +79,7 @@ func (h *Handler) updateStageAction(b []byte) (int, any, error) { func (h *Handler) flushStageCacheAction(b []byte) (int, any, error) { var input flushStageCacheInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if _, err := h.Backend.GetStage(input.RestAPIID, input.StageName); err != nil { @@ -91,7 +93,7 @@ func (h *Handler) flushStageCacheAction(b []byte) (int, any, error) { func (h *Handler) flushStageAuthorizersCacheAction(b []byte) (int, any, error) { var input flushStageCacheInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } h.authCache.flush() @@ -101,7 +103,7 @@ func (h *Handler) flushStageAuthorizersCacheAction(b []byte) (int, any, error) { func (h *Handler) getStagesAction(b []byte) (int, any, error) { var input getStagesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } stages, err := h.Backend.GetStages(input.RestAPIID) @@ -123,7 +125,7 @@ func (h *Handler) getStagesAction(b []byte) (int, any, error) { func (h *Handler) getStageAction(b []byte) (int, any, error) { var input getStageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } stage, err := h.Backend.GetStage(input.RestAPIID, input.StageName) @@ -136,7 +138,7 @@ func (h *Handler) getStageAction(b []byte) (int, any, error) { func (h *Handler) deleteStageAction(b []byte) (int, any, error) { var input deleteStageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteStage(input.RestAPIID, input.StageName); err != nil { diff --git a/services/apigateway/handler_tags.go b/services/apigateway/handler_tags.go index 58c12fbf38..53c4c78fbf 100644 --- a/services/apigateway/handler_tags.go +++ b/services/apigateway/handler_tags.go @@ -1,11 +1,12 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -51,7 +52,7 @@ func (h *Handler) tagActions() map[string]actionFn { return map[string]actionFn{ opGetTags: func(b []byte) (int, any, error) { var input getTagsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } t, err := h.Backend.GetResourceTags(input.ResourceARN) @@ -63,7 +64,7 @@ func (h *Handler) tagActions() map[string]actionFn { }, opTagResource: func(b []byte) (int, any, error) { var input tagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } for key := range input.Tags { @@ -83,7 +84,7 @@ func (h *Handler) tagActions() map[string]actionFn { }, opUntagResource: func(b []byte) (int, any, error) { var input untagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.UntagResource(input.ResourceARN, input.TagKeys); err != nil { diff --git a/services/apigateway/handler_test.go b/services/apigateway/handler_test.go index 2012383682..54e22b3d4c 100644 --- a/services/apigateway/handler_test.go +++ b/services/apigateway/handler_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -129,7 +130,7 @@ func TestHandler_RestAPI(t *testing.T) { assert.Equal(t, tt.wantCreateCode, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.NotEmpty(t, created["id"]) assert.Equal(t, tt.primaryAPIName, created["name"]) @@ -144,7 +145,7 @@ func TestHandler_RestAPI(t *testing.T) { assert.Equal(t, tt.wantGetCode, rec2.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, apiID, got["id"]) assert.Equal(t, tt.primaryAPIName, got["name"]) } @@ -154,7 +155,7 @@ func TestHandler_RestAPI(t *testing.T) { assert.Equal(t, http.StatusOK, rec3.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["item"].([]any), tt.wantListCount) } @@ -207,14 +208,14 @@ func TestHandler_Resources(t *testing.T) { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) rec2 := postWithHandler(t, handler, e, "GetResources", fmt.Sprintf(`{"restApiId":%q}`, apiID)) assert.Equal(t, http.StatusOK, rec2.Code) var res map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res, jsonx.V1())) items := res["item"].([]any) if tt.wantResourceCount > 0 { @@ -231,7 +232,7 @@ func TestHandler_Resources(t *testing.T) { assert.Equal(t, http.StatusCreated, rec3.Code) var child map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &child)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &child, jsonx.V1())) if tt.wantChildPath != "" { assert.Equal(t, tt.wantChildPath, child["path"]) @@ -284,12 +285,12 @@ func TestHandler_Method(t *testing.T) { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) rec2 := postWithHandler(t, handler, e, "GetResources", fmt.Sprintf(`{"restApiId":%q}`, apiID)) var res map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res, jsonx.V1())) rootID := res["item"].([]any)[0].(map[string]any)["id"].(string) rec3 := postWithHandler(t, handler, e, "PutMethod", @@ -298,7 +299,7 @@ func TestHandler_Method(t *testing.T) { assert.Equal(t, tt.wantPutCode, rec3.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &m, jsonx.V1())) assert.Equal(t, tt.httpMethod, m["httpMethod"]) rec4 := postWithHandler(t, handler, e, "GetMethod", @@ -347,12 +348,12 @@ func TestHandler_Integration(t *testing.T) { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) rec2 := postWithHandler(t, handler, e, "GetResources", fmt.Sprintf(`{"restApiId":%q}`, apiID)) var res map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &res, jsonx.V1())) rootID := res["item"].([]any)[0].(map[string]any)["id"].(string) postWithHandler(t, handler, e, "PutMethod", @@ -365,7 +366,7 @@ func TestHandler_Integration(t *testing.T) { assert.Equal(t, tt.wantPutCode, rec3.Code) var integ map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &integ, jsonx.V1())) assert.Equal(t, tt.integrationType, integ["type"]) rec4 := postWithHandler(t, handler, e, "GetIntegration", @@ -410,7 +411,7 @@ func TestHandler_Deployment(t *testing.T) { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) rec2 := postWithHandler(t, handler, e, "CreateDeployment", @@ -419,14 +420,14 @@ func TestHandler_Deployment(t *testing.T) { assert.Equal(t, tt.wantCreateCode, rec2.Code) var depl map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &depl)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &depl, jsonx.V1())) assert.NotEmpty(t, depl["id"]) rec3 := postWithHandler(t, handler, e, "GetDeployments", fmt.Sprintf(`{"restApiId":%q}`, apiID)) assert.Equal(t, http.StatusOK, rec3.Code) var depls map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &depls)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &depls, jsonx.V1())) assert.Len(t, depls["item"].([]any), tt.wantListCount) }) } @@ -463,7 +464,7 @@ func TestHandler_Stages(t *testing.T) { rec := postWithHandler(t, handler, e, "CreateRestApi", `{"name":"api"}`) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) postWithHandler(t, handler, e, "CreateDeployment", @@ -473,7 +474,7 @@ func TestHandler_Stages(t *testing.T) { assert.Equal(t, tt.wantListCode, rec2.Code) var stages map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &stages)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &stages, jsonx.V1())) assert.Len(t, stages["item"].([]any), tt.wantListCount) rec3 := postWithHandler(t, handler, e, "GetStage", @@ -540,7 +541,7 @@ func TestHandler_Errors(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -716,7 +717,7 @@ func TestHandler_CreateAPIKey(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["name"]) // value must be non-empty (either provided or auto-generated) assert.NotEmpty(t, resp["value"]) @@ -784,7 +785,7 @@ func TestHandler_CreateDocumentationPart(t *testing.T) { require.Equal(t, http.StatusCreated, apiRec.Code) var apiResp map[string]any - require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp)) + require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -833,7 +834,7 @@ func TestHandler_CreateDocumentationVersion(t *testing.T) { require.Equal(t, http.StatusCreated, apiRec.Code) var apiResp map[string]any - require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp)) + require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -979,7 +980,7 @@ func TestHandler_CreateModel(t *testing.T) { require.Equal(t, http.StatusCreated, apiRec.Code) var apiResp map[string]any - require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp)) + require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -1043,7 +1044,7 @@ func TestHandler_CreateStageStandalone(t *testing.T) { require.Equal(t, http.StatusCreated, apiRec.Code) var apiResp map[string]any - require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp)) + require.NoError(t, json.Unmarshal(apiRec.Body.Bytes(), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -1051,7 +1052,7 @@ func TestHandler_CreateStageStandalone(t *testing.T) { require.Equal(t, http.StatusCreated, deplRec.Code) var deplResp map[string]any - require.NoError(t, json.Unmarshal(deplRec.Body.Bytes(), &deplResp)) + require.NoError(t, json.Unmarshal(deplRec.Body.Bytes(), &deplResp, jsonx.V1())) deplID, _ := deplResp["id"].(string) require.NotEmpty(t, deplID) @@ -1170,7 +1171,7 @@ func TestHandler_CreateUsagePlan(t *testing.T) { if tt.checkResp != nil && rec.Code == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.checkResp(t, resp) } }) @@ -1187,7 +1188,7 @@ func TestHandler_CreateUsagePlanKey(t *testing.T) { require.Equal(t, http.StatusCreated, keyRec.Code) var keyResp map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &keyResp)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &keyResp, jsonx.V1())) keyID, _ := keyResp["id"].(string) require.NotEmpty(t, keyID) @@ -1195,7 +1196,7 @@ func TestHandler_CreateUsagePlanKey(t *testing.T) { require.Equal(t, http.StatusCreated, planRec.Code) var planResp map[string]any - require.NoError(t, json.Unmarshal(planRec.Body.Bytes(), &planResp)) + require.NoError(t, json.Unmarshal(planRec.Body.Bytes(), &planResp, jsonx.V1())) planID, _ := planResp["id"].(string) require.NotEmpty(t, planID) @@ -1204,7 +1205,7 @@ func TestHandler_CreateUsagePlanKey(t *testing.T) { require.Equal(t, http.StatusCreated, dupKeyRec.Code) var dupKeyResp map[string]any - require.NoError(t, json.Unmarshal(dupKeyRec.Body.Bytes(), &dupKeyResp)) + require.NoError(t, json.Unmarshal(dupKeyRec.Body.Bytes(), &dupKeyResp, jsonx.V1())) dupKeyID, _ := dupKeyResp["id"].(string) require.NotEmpty(t, dupKeyID) @@ -1260,7 +1261,7 @@ func TestHandler_CreateUsagePlanKey(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["type"]) } }) diff --git a/services/apigateway/handler_usage.go b/services/apigateway/handler_usage.go index 011b8e7ad8..8c01381843 100644 --- a/services/apigateway/handler_usage.go +++ b/services/apigateway/handler_usage.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const opUpdateUsage = "UpdateUsage" @@ -13,7 +15,7 @@ func (h *Handler) usageActions() map[string]actionFn { return map[string]actionFn{ opGetUsage: func(b []byte) (int, any, error) { var input GetUsageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.GetUsage(input) @@ -25,7 +27,7 @@ func (h *Handler) usageActions() map[string]actionFn { }, opUpdateUsage: func(b []byte) (int, any, error) { var raw map[string]string - if err := json.Unmarshal(b, &raw); err != nil { + if err := json.Unmarshal(b, &raw, jsonx.V1()); err != nil { return 0, nil, err } diff --git a/services/apigateway/handler_usage_plans.go b/services/apigateway/handler_usage_plans.go index 7c7726cd83..a1c090b864 100644 --- a/services/apigateway/handler_usage_plans.go +++ b/services/apigateway/handler_usage_plans.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getUsagePlansPageInput struct { @@ -139,7 +141,7 @@ func (h *Handler) usagePlanActions() map[string]actionFn { func (h *Handler) createUsagePlanAction(b []byte) (int, any, error) { var input CreateUsagePlanInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -153,7 +155,7 @@ func (h *Handler) createUsagePlanAction(b []byte) (int, any, error) { func (h *Handler) createUsagePlanKeyAction(b []byte) (int, any, error) { var input CreateUsagePlanKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -167,7 +169,7 @@ func (h *Handler) createUsagePlanKeyAction(b []byte) (int, any, error) { func (h *Handler) getUsagePlanAction(b []byte) (int, any, error) { var input getUsagePlanInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } p, err := h.Backend.GetUsagePlan(input.UsagePlanID) @@ -180,7 +182,7 @@ func (h *Handler) getUsagePlanAction(b []byte) (int, any, error) { func (h *Handler) getUsagePlansAction(b []byte) (int, any, error) { var input getUsagePlansPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -215,7 +217,7 @@ func (h *Handler) getUsagePlansAction(b []byte) (int, any, error) { func (h *Handler) updateUsagePlanAction(b []byte) (int, any, error) { var input UpdateUsagePlanInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } out, err := h.Backend.UpdateUsagePlan(input) @@ -228,7 +230,7 @@ func (h *Handler) updateUsagePlanAction(b []byte) (int, any, error) { func (h *Handler) deleteUsagePlanAction(b []byte) (int, any, error) { var input deleteUsagePlanInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteUsagePlan(input.UsagePlanID); err != nil { @@ -240,7 +242,7 @@ func (h *Handler) deleteUsagePlanAction(b []byte) (int, any, error) { func (h *Handler) getUsagePlanKeyAction(b []byte) (int, any, error) { var input getUsagePlanKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } k, err := h.Backend.GetUsagePlanKey(input.UsagePlanID, input.KeyID) @@ -253,7 +255,7 @@ func (h *Handler) getUsagePlanKeyAction(b []byte) (int, any, error) { func (h *Handler) getUsagePlanKeysAction(b []byte) (int, any, error) { var input getUsagePlanKeysInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } ks, err := h.Backend.GetUsagePlanKeys(input.UsagePlanID) @@ -282,7 +284,7 @@ func (h *Handler) getUsagePlanKeysAction(b []byte) (int, any, error) { func (h *Handler) deleteUsagePlanKeyAction(b []byte) (int, any, error) { var input deleteUsagePlanKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } if err := h.Backend.DeleteUsagePlanKey(input.UsagePlanID, input.KeyID); err != nil { diff --git a/services/apigateway/handler_vpc_links.go b/services/apigateway/handler_vpc_links.go index 762127269b..24d7d62f7c 100644 --- a/services/apigateway/handler_vpc_links.go +++ b/services/apigateway/handler_vpc_links.go @@ -1,8 +1,10 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -64,7 +66,7 @@ func (h *Handler) vpcLinkActions() map[string]actionFn { func (h *Handler) createVpcLinkAction(b []byte) (int, any, error) { var input CreateVpcLinkInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -78,7 +80,7 @@ func (h *Handler) createVpcLinkAction(b []byte) (int, any, error) { func (h *Handler) deleteVpcLinkAction(b []byte) (int, any, error) { var input deleteVpcLinkInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -91,7 +93,7 @@ func (h *Handler) deleteVpcLinkAction(b []byte) (int, any, error) { func (h *Handler) getVpcLinkAction(b []byte) (int, any, error) { var input getVpcLinkInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } @@ -105,7 +107,7 @@ func (h *Handler) getVpcLinkAction(b []byte) (int, any, error) { func (h *Handler) getVpcLinksAction(b []byte) (int, any, error) { var input getVpcLinksInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } links, err := h.Backend.GetVpcLinks() @@ -126,7 +128,7 @@ func (h *Handler) getVpcLinksAction(b []byte) (int, any, error) { func (h *Handler) updateVpcLinkAction(b []byte) (int, any, error) { var input UpdateVpcLinkInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return 0, nil, err } diff --git a/services/apigateway/import.go b/services/apigateway/import.go index 75d44d1a54..815ea95009 100644 --- a/services/apigateway/import.go +++ b/services/apigateway/import.go @@ -10,7 +10,8 @@ package apigateway import ( "bytes" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "strings" @@ -19,6 +20,7 @@ import ( "gopkg.in/yaml.v3" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -43,15 +45,15 @@ type PutRestAPIInput struct { // openAPIDoc is the subset of an OpenAPI/Swagger document we interpret. type openAPIDoc struct { - Info openAPIInfo `json:"info"` - Paths map[string]map[string]json.RawMessage `json:"paths"` - Definitions map[string]json.RawMessage `json:"definitions"` - Components *openAPIComponents `json:"components"` - SecurityDefinitions map[string]openAPISecurityScheme `json:"securityDefinitions"` - Swagger string `json:"swagger"` - OpenAPI string `json:"openapi"` - APIKeySourceExt string `json:"x-amazon-apigateway-api-key-source"` - BinaryMediaTypes []string `json:"x-amazon-apigateway-binary-media-types"` + Info openAPIInfo `json:"info"` + Paths map[string]map[string]jsontext.Value `json:"paths"` + Definitions map[string]jsontext.Value `json:"definitions"` + Components *openAPIComponents `json:"components"` + SecurityDefinitions map[string]openAPISecurityScheme `json:"securityDefinitions"` + Swagger string `json:"swagger"` + OpenAPI string `json:"openapi"` + APIKeySourceExt string `json:"x-amazon-apigateway-api-key-source"` + BinaryMediaTypes []string `json:"x-amazon-apigateway-binary-media-types"` } type openAPIInfo struct { @@ -61,7 +63,7 @@ type openAPIInfo struct { } type openAPIComponents struct { - Schemas map[string]json.RawMessage `json:"schemas"` + Schemas map[string]jsontext.Value `json:"schemas"` SecuritySchemes map[string]openAPISecurityScheme `json:"securitySchemes"` } @@ -76,20 +78,20 @@ type openAPIOperation struct { } type openAPIParameter struct { - Name string `json:"name"` - In string `json:"in"` - Schema json.RawMessage `json:"schema"` - Required bool `json:"required"` + Name string `json:"name"` + In string `json:"in"` + Schema jsontext.Value `json:"schema"` + Required bool `json:"required"` } type openAPIResponse struct { Content map[string]openAPIMediaTyp `json:"content"` Description string `json:"description"` - Schema json.RawMessage `json:"schema"` + Schema jsontext.Value `json:"schema"` } type openAPIMediaTyp struct { - Schema json.RawMessage `json:"schema"` + Schema jsontext.Value `json:"schema"` } type openAPIRequestBody struct { @@ -141,13 +143,13 @@ func parseOpenAPI(body []byte) (*openAPIDoc, error) { ) } - normalized, err := json.Marshal(raw) + normalized, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: invalid OpenAPI document: %w", ErrInvalidParameter, err) } var doc openAPIDoc - if jsonErr := json.Unmarshal(normalized, &doc); jsonErr != nil { + if jsonErr := json.Unmarshal(normalized, &doc, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: invalid OpenAPI document: %w", ErrInvalidParameter, jsonErr) } if doc.Swagger == "" && doc.OpenAPI == "" { @@ -244,7 +246,7 @@ func openAPIOperationWarnings(raw map[string]any, knownOperationExtensions map[s } // schemaDefinitions returns the named schemas regardless of spec version. -func (d *openAPIDoc) schemaDefinitions() map[string]json.RawMessage { +func (d *openAPIDoc) schemaDefinitions() map[string]jsontext.Value { if len(d.Definitions) > 0 { return d.Definitions } @@ -446,7 +448,7 @@ func importPaths(b *InMemoryBackend, api *RestAPI, doc *openAPIDoc) { continue } var op openAPIOperation - if err := json.Unmarshal(raw, &op); err != nil { + if err := json.Unmarshal(raw, &op, jsonx.V1()); err != nil { continue } importMethod(res, httpMethod, &op, auths) @@ -665,14 +667,14 @@ func applyImportedSecurity(method *Method, op *openAPIOperation) { // schemaRefName extracts the model name from a JSON schema reference such as // {"$ref":"#/definitions/Foo"} or {"$ref":"#/components/schemas/Foo"}. -func schemaRefName(raw json.RawMessage) string { +func schemaRefName(raw jsontext.Value) string { if len(raw) == 0 { return "" } var s struct { Ref string `json:"$ref"` } - if err := json.Unmarshal(raw, &s); err != nil || s.Ref == "" { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil || s.Ref == "" { return "" } idx := strings.LastIndex(s.Ref, "/") @@ -811,7 +813,7 @@ func (b *InMemoryBackend) ImportAPIKeys(body []byte, format string, failOnWarnin // "x-amazon-apigateway-documentation" documentationParts array. type importDocumentationPartEntry struct { Location DocumentationLocation `json:"location"` - Properties json.RawMessage `json:"properties"` + Properties jsontext.Value `json:"properties"` } // importDocumentationPartsPayload is the expected JSON shape for @@ -831,7 +833,7 @@ func (b *InMemoryBackend) ImportDocumentationParts( restAPIID string, body []byte, mode string, failOnWarnings bool, ) ([]string, []string, error) { var payload importDocumentationPartsPayload - if err := json.Unmarshal(body, &payload); err != nil { + if err := json.Unmarshal(body, &payload, jsonx.V1()); err != nil { return nil, nil, fmt.Errorf("%w: failed to parse documentation parts payload: %w", ErrInvalidParameter, err) } diff --git a/services/apigateway/import_test.go b/services/apigateway/import_test.go index 316aa698be..9377b50113 100644 --- a/services/apigateway/import_test.go +++ b/services/apigateway/import_test.go @@ -1,13 +1,15 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -282,7 +284,7 @@ func TestImportRestAPI_RESTRouting(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "PetStore", resp["name"]) apiID, _ := resp["id"].(string) require.NotEmpty(t, apiID) @@ -302,7 +304,7 @@ func TestImportRestAPI_RESTRouting(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "plain", resp["name"]) }) diff --git a/services/apigateway/integrations_test.go b/services/apigateway/integrations_test.go index ccfa55db32..81a815c872 100644 --- a/services/apigateway/integrations_test.go +++ b/services/apigateway/integrations_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -47,7 +49,7 @@ func TestPutIntegration_DefaultTimeout(t *testing.T) { require.True(t, createRec.Code >= 200 && createRec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -55,7 +57,7 @@ func TestPutIntegration_DefaultTimeout(t *testing.T) { require.True(t, resRec.Code >= 200 && resRec.Code < 300) var resResp map[string]any - require.NoError(t, json.NewDecoder(resRec.Body).Decode(&resResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resRec.Body), &resResp, jsonx.V1())) items := resResp["item"].([]any) rootID := items[0].(map[string]any)["id"].(string) @@ -70,7 +72,7 @@ func TestPutIntegration_DefaultTimeout(t *testing.T) { require.True(t, intRec.Code >= 200 && intRec.Code < 300) var intResp map[string]any - require.NoError(t, json.NewDecoder(intRec.Body).Decode(&intResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(intRec.Body), &intResp, jsonx.V1())) got, ok := intResp["timeoutInMillis"].(float64) require.True(t, ok, "timeoutInMillis must be present in PutIntegration response") @@ -318,14 +320,14 @@ func TestHandlerIntegration_ConnectionType_RoundTrip(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) rec = restRequest(t, h, http.MethodGet, "/restapis/"+apiID+"/resources", "") require.True(t, rec.Code >= 200 && rec.Code < 300) var resResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resResp, jsonx.V1())) items := resResp["item"].([]any) rootID := items[0].(map[string]any)["id"].(string) @@ -340,7 +342,7 @@ func TestHandlerIntegration_ConnectionType_RoundTrip(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var integResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&integResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &integResp, jsonx.V1())) assert.Equal(t, "INTERNET", integResp["connectionType"]) } diff --git a/services/apigateway/invoke_stage_test.go b/services/apigateway/invoke_stage_test.go index 5df23eb915..58ff716f74 100644 --- a/services/apigateway/invoke_stage_test.go +++ b/services/apigateway/invoke_stage_test.go @@ -1,13 +1,14 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -64,7 +65,7 @@ func TestInvokeStage(t *testing.T) { var ev apigateway.LambdaProxyEvent - require.NoError(t, json.Unmarshal(captured, &ev)) + require.NoError(t, json.Unmarshal(captured, &ev, jsonx.V1())) assert.Equal(t, "/items", ev.Resource) assert.Equal(t, `{"a":1}`, ev.Body) assert.Equal(t, "v", ev.QueryStringParameters["q"]) diff --git a/services/apigateway/methods_test.go b/services/apigateway/methods_test.go index 6e9ce9e89f..8b3371e777 100644 --- a/services/apigateway/methods_test.go +++ b/services/apigateway/methods_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -129,7 +130,7 @@ func TestPutMethod_RequestParametersRoundtrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, "body: %s", rec.Body.String()) var method map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &method)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &method, jsonx.V1())) params, _ := method["requestParameters"].(map[string]any) require.NotNil(t, params, "requestParameters must be present in response") @@ -234,7 +235,7 @@ func TestTestInvokeMethod(t *testing.T) { if tt.wantStatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.EqualValues(t, http.StatusOK, resp["status"]) } }) diff --git a/services/apigateway/models.go b/services/apigateway/models.go index f379b7ecb2..4d72f22fe5 100644 --- a/services/apigateway/models.go +++ b/services/apigateway/models.go @@ -1,10 +1,11 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -32,12 +33,12 @@ type unixEpochTime struct { } func (t unixEpochTime) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Unix()) + return json.Marshal(t.Unix(), jsonx.V1()) } func (t *unixEpochTime) UnmarshalJSON(b []byte) error { var epoch int64 - if err := json.Unmarshal(b, &epoch); err != nil { + if err := json.Unmarshal(b, &epoch, jsonx.V1()); err != nil { return err } @@ -105,7 +106,7 @@ func wirePolicyString(plain string) *string { return nil } - quoted, err := json.Marshal(plain) + quoted, err := json.Marshal(plain, jsonx.V1()) if err != nil || len(quoted) < 2 { return nil } diff --git a/services/apigateway/multi_region_test.go b/services/apigateway/multi_region_test.go index 6d509428cc..5f533a7793 100644 --- a/services/apigateway/multi_region_test.go +++ b/services/apigateway/multi_region_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -50,7 +51,7 @@ func apiNames(t *testing.T, h *apigateway.Handler, region string) []string { } `json:"item"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) names := make([]string, 0, len(out.Item)) for _, i := range out.Item { diff --git a/services/apigateway/patch.go b/services/apigateway/patch.go index ed2f5f59d0..10b384088b 100644 --- a/services/apigateway/patch.go +++ b/services/apigateway/patch.go @@ -1,12 +1,15 @@ package apigateway import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // patch.go implements AWS API Gateway's PATCH-operation semantics beyond what @@ -90,10 +93,10 @@ const ( // op used e.g. to promote a canary deployment, // {"op":"copy","from":"/canarySettings/deploymentId","path":"/deploymentId"}. type patchOp struct { - Op string `json:"op"` - Path string `json:"path"` - From string `json:"from,omitempty"` - Value json.RawMessage `json:"value"` + Op string `json:"op"` + Path string `json:"path"` + From string `json:"from,omitempty"` + Value jsontext.Value `json:"value"` } // parsePatchDocument detects and parses a PATCH request body: either a bare @@ -101,7 +104,7 @@ type patchOp struct { // wrapper (the real AWS wire shape), which may carry sibling fields alongside // "patchOperations" (rest). ok is false when body is neither shape, in which // case the caller should treat body as an already-flat field object. -func parsePatchDocument(body []byte) ([]patchOp, map[string]json.RawMessage, bool) { +func parsePatchDocument(body []byte) ([]patchOp, map[string]jsontext.Value, bool) { if len(body) == 0 { return nil, nil, false } @@ -109,7 +112,7 @@ func parsePatchDocument(body []byte) ([]patchOp, map[string]json.RawMessage, boo switch body[0] { case '[': var arr []patchOp - if err := json.Unmarshal(body, &arr); err != nil || len(arr) == 0 || arr[0].Op == "" { + if err := json.Unmarshal(body, &arr, jsonx.V1()); err != nil || len(arr) == 0 || arr[0].Op == "" { return nil, nil, false } @@ -118,13 +121,13 @@ func parsePatchDocument(body []byte) ([]patchOp, map[string]json.RawMessage, boo var wrapper struct { PatchOperations []patchOp `json:"patchOperations"` } - if err := json.Unmarshal(body, &wrapper); err != nil || len(wrapper.PatchOperations) == 0 { + if err := json.Unmarshal(body, &wrapper, jsonx.V1()); err != nil || len(wrapper.PatchOperations) == 0 { return nil, nil, false } - var siblings map[string]json.RawMessage + var siblings map[string]jsontext.Value - _ = json.Unmarshal(body, &siblings) + _ = json.Unmarshal(body, &siblings, jsonx.V1()) delete(siblings, "patchOperations") return wrapper.PatchOperations, siblings, true @@ -180,7 +183,7 @@ var removableTopLevelScalar = map[string]map[string]bool{ // top level. Fields absent from patchFieldKind are assumed to be strings (the // common case, and already correctly a JSON string on the wire) and passed // through unchanged. -func coerceTopLevelPatchValue(field string, raw json.RawMessage) json.RawMessage { +func coerceTopLevelPatchValue(field string, raw jsontext.Value) jsontext.Value { kind, known := patchFieldKind[field] if !known { return raw @@ -194,13 +197,13 @@ func coerceTopLevelPatchValue(field string, raw json.RawMessage) json.RawMessage switch kind { case patchKindBool: if b, parseErr := strconv.ParseBool(s); parseErr == nil { - if encoded, marshalErr := json.Marshal(b); marshalErr == nil { + if encoded, marshalErr := json.Marshal(b, jsonx.V1()); marshalErr == nil { return encoded } } case patchKindInt: if n, parseErr := strconv.Atoi(s); parseErr == nil { - if encoded, marshalErr := json.Marshal(n); marshalErr == nil { + if encoded, marshalErr := json.Marshal(n, jsonx.V1()); marshalErr == nil { return encoded } } @@ -228,13 +231,13 @@ func patchIgnorableErr(err error) bool { // numeric targets (see the package doc above), so every call site that needs // the underlying scalar goes through this and then parses it with strconv. // ok is false when Value is empty/absent or not a JSON string. -func patchValueString(raw json.RawMessage) (string, bool) { +func patchValueString(raw jsontext.Value) (string, bool) { if len(raw) == 0 { return "", false } var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return "", false } @@ -264,8 +267,8 @@ func patchPathSegments(path string) []string { // setJSONValue marshals value and stores it under field in out. Errors are // swallowed (defensive only): every call site below passes a well-formed // built-in Go value that always marshals successfully. -func setJSONValue(out map[string]json.RawMessage, field string, value any) { - raw, err := json.Marshal(value) +func setJSONValue(out map[string]jsontext.Value, field string, value any) { + raw, err := json.Marshal(value, jsonx.V1()) if err != nil { return } @@ -284,7 +287,7 @@ func setJSONValue(out map[string]json.RawMessage, field string, value any) { // discarding whatever an earlier op in the SAME request had already staged // there. ok is false when field isn't staged yet or fails to decode, in // which case the caller must fall back to reading current backend state. -func stagedValue[T any](out map[string]json.RawMessage, field string) (T, bool) { +func stagedValue[T any](out map[string]jsontext.Value, field string) (T, bool) { var v T raw, present := out[field] @@ -292,7 +295,7 @@ func stagedValue[T any](out map[string]json.RawMessage, field string) (T, bool) return v, false } - if err := json.Unmarshal(raw, &v); err != nil { + if err := json.Unmarshal(raw, &v, jsonx.V1()); err != nil { return v, false } @@ -334,7 +337,7 @@ func (h *Handler) applyStructuredPatch(action string, pathParams map[string]stri return body, nil } - out := make(map[string]json.RawMessage, len(rest)+len(ops)) + out := make(map[string]jsontext.Value, len(rest)+len(ops)) maps.Copy(out, rest) for _, op := range ops { @@ -350,7 +353,7 @@ func (h *Handler) applyStructuredPatch(action string, pathParams map[string]stri applyTopLevelPatchOp(action, op, out) } - raw, err := json.Marshal(out) + raw, err := json.Marshal(out, jsonx.V1()) if patchIgnorableErr(err) { return body, nil } @@ -385,7 +388,7 @@ func unmodeledPatchPath(action, path string) error { // "not provided" from "reset to zero value" — a much larger refactor across // every resource's Update backend method than this pass's PATCH-semantics // fix covers in full. Tracked as a follow-up (see PARITY.md gaps). -func applyTopLevelPatchOp(action string, op patchOp, out map[string]json.RawMessage) { +func applyTopLevelPatchOp(action string, op patchOp, out map[string]jsontext.Value) { field := strings.TrimPrefix(op.Path, "/") if field == "" || strings.Contains(field, "/") { return @@ -396,7 +399,7 @@ func applyTopLevelPatchOp(action string, op patchOp, out map[string]json.RawMess out[field] = coerceTopLevelPatchValue(field, op.Value) case patchOpRemove: if removableTopLevelScalar[action][field] { - out[field] = json.RawMessage(`""`) + out[field] = jsontext.Value(`""`) } } } @@ -408,7 +411,7 @@ func applyTopLevelPatchOp(action string, op patchOp, out map[string]json.RawMess // is one this action must reject, or (false, nil) to fall through to the // generic top-level fallback. type resourcePatchResolver func( - h *Handler, pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + h *Handler, pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) // resourcePatchResolvers maps each action with map/struct/list/rejectable @@ -440,7 +443,7 @@ var resourcePatchResolvers = map[string]resourcePatchResolver{ // single-field replace cannot express. See resourcePatchResolver's doc // comment for the return-value contract. func (h *Handler) applyResourcePatchOp( - action string, pathParams map[string]string, op patchOp, out map[string]json.RawMessage, + action string, pathParams map[string]string, op patchOp, out map[string]jsontext.Value, ) (bool, error) { segs := patchPathSegments(op.Path) if len(segs) == 0 { @@ -460,7 +463,7 @@ func (h *Handler) applyResourcePatchOp( // stage-variable edits, single canarySettings/accessLogSettings sub-fields, // and per-route method settings. func (h *Handler) applyStagePatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if op.Op == patchOpCopy && op.Path == "/deploymentId" && op.From == "/canarySettings/deploymentId" { stage, err := h.Backend.GetStage(pathParams[keyRestAPIID], pathParams[keyStageName]) @@ -499,7 +502,7 @@ func (h *Handler) applyStagePatchOp( // with whatever an earlier op in the SAME request already staged into // out["variables"] (see stagedValue's doc comment). func (h *Handler) applyStageVariablePatch( - pathParams map[string]string, op patchOp, rawName string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, rawName string, out map[string]jsontext.Value, ) bool { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return false @@ -534,7 +537,7 @@ func (h *Handler) applyStageVariablePatch( // with the stage's existing canary settings and whatever an earlier op in the // SAME request already staged into out["canarySettings"]. func (h *Handler) applyStageCanaryPatch( - pathParams map[string]string, op patchOp, prop string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, prop string, out map[string]jsontext.Value, ) bool { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return false @@ -595,7 +598,7 @@ func applyStageCanaryProp(cur *CanarySettings, prop, val string, remove bool) bo // contents are themselves a JSON-encoded {name: value} object. if !remove { var overrides map[string]string - if json.Unmarshal([]byte(val), &overrides) == nil { + if json.Unmarshal([]byte(val), &overrides, jsonx.V1()) == nil { cur.StageVariableOverrides = overrides } } @@ -611,7 +614,7 @@ func applyStageCanaryProp(cur *CanarySettings, prop, val string, remove bool) bo // existing access log settings and whatever an earlier op in the SAME // request already staged into out["accessLogSettings"]. func (h *Handler) applyStageAccessLogPatch( - pathParams map[string]string, op patchOp, prop string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, prop string, out map[string]jsontext.Value, ) bool { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return false @@ -737,7 +740,7 @@ func parseBoolLenient(v string) bool { // (e.g. "/~1pets/GET/throttling/burstLimit" or "/*/*/logging/loglevel") — // merged with the stage's existing per-route overrides. func (h *Handler) applyStageMethodSettingPatch( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) bool { apply, known := stageMethodSettingProperty[strings.Join(segs[2:], "/")] if !known || (op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove) { @@ -773,7 +776,7 @@ func (h *Handler) applyStageMethodSettingPatch( // silently drop every other configured type) and whatever an earlier op in // the SAME request already staged into out["binaryMediaTypes"]. func (h *Handler) applyRestAPIPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != patchPathSegs2 || segs[0] != "binaryMediaTypes" { return false, nil @@ -823,7 +826,7 @@ const accountFeatureUsagePlans = "UsagePlans" // membership. "/cloudwatchRoleArn" is a plain top-level string field and is // handled by the generic fallback (applyTopLevelPatchOp). func (h *Handler) applyAccountPatchOp( - _ map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + _ map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) == 1 && segs[0] == "features" { return h.applyAccountFeaturesPatch(op, out) @@ -892,7 +895,7 @@ func applyAccountThrottleProp(cur *ThrottleSettings, prop, val string, remove bo // removing accountFeatureUsagePlans, matching patch-operations.html's // documented exception, and any op other than add/remove (replace is "Not // supported"). -func (h *Handler) applyAccountFeaturesPatch(op patchOp, out map[string]json.RawMessage) (bool, error) { +func (h *Handler) applyAccountFeaturesPatch(op patchOp, out map[string]jsontext.Value) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpRemove { return true, unsupportedPatchOp("/features", op.Op) } @@ -941,7 +944,7 @@ func (h *Handler) applyAccountFeaturesPatch(op patchOp, out map[string]json.RawM // earlier op in this request has touched apiStages yet. ok is false only // when the plan itself can't be resolved. func (h *Handler) currentUsagePlanAPIStages( - pathParams map[string]string, out map[string]json.RawMessage, + pathParams map[string]string, out map[string]jsontext.Value, ) ([]APIStageAssociation, bool) { if staged, ok := stagedValue[[]APIStageAssociation](out, "apiStages"); ok { return staged, true @@ -962,7 +965,7 @@ func (h *Handler) currentUsagePlanAPIStages( // applyUsagePlanThrottlePatch) — see the AWS "UpdateUsagePlan" patch-operations // reference. func (h *Handler) applyUsagePlanPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) == 1 && segs[0] == "apiStages" { return h.applyUsagePlanAPIStageMembershipPatch(pathParams, op, out), nil @@ -978,7 +981,7 @@ func (h *Handler) applyUsagePlanPatchOp( // applyUsagePlanAPIStageMembershipPatch handles the whole-apiStage add/remove // case ("/apiStages", Value "{restApiId}:{stage}"). func (h *Handler) applyUsagePlanAPIStageMembershipPatch( - pathParams map[string]string, op patchOp, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, out map[string]jsontext.Value, ) bool { if op.Op != patchOpAdd && op.Op != patchOpRemove { return false @@ -1038,7 +1041,7 @@ const usagePlanThrottlePathMinSegs = 5 // APIStageAssociation.Throttle elsewhere in this service (see usage.go's // effectiveThrottle and CreateUsagePlan's test fixtures). func (h *Handler) applyUsagePlanThrottlePatch( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) bool { if len(segs) < usagePlanThrottlePathMinSegs || len(segs) > usagePlanThrottlePathMinSegs+1 { return false @@ -1139,7 +1142,7 @@ func (h *Handler) mergeUsagePlanThrottleEntry( // default response when none has been customized yet) and whatever an // earlier op in the SAME request already staged into out[segs[0]]. func (h *Handler) applyGatewayResponsePatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != patchPathSegs2 || (segs[0] != fieldResponseParameters && segs[0] != "responseTemplates") { return false, nil @@ -1185,7 +1188,7 @@ func (h *Handler) applyGatewayResponsePatchOp( // StageKeys (a wholesale replace would otherwise silently drop every other // associated stage). func (h *Handler) applyAPIKeyPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != 1 || segs[0] != "stages" { return false, nil @@ -1236,7 +1239,7 @@ func (h *Handler) applyAPIKeyPatchOp( // had no case for opUpdateDomainName at all, so every nested DomainName PATCH // path silently no-opped via applyTopLevelPatchOp's path-contains-"/" guard. func (h *Handler) applyDomainNamePatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != patchPathSegs2 { return false, nil @@ -1258,7 +1261,7 @@ func (h *Handler) applyDomainNamePatchOp( // and whatever an earlier op in the SAME request already staged into // out["endpointConfiguration"]. func (h *Handler) applyDomainNameEndpointConfigPatch( - pathParams map[string]string, op patchOp, prop string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, prop string, out map[string]jsontext.Value, ) bool { cur, ok := stagedValue[EndpointConfiguration](out, "endpointConfiguration") if !ok { @@ -1310,7 +1313,7 @@ func (h *Handler) applyDomainNameEndpointConfigPatch( // with the domain name's existing mTLS configuration and whatever an earlier // op in the SAME request already staged into out["mutualTlsAuthentication"]. func (h *Handler) applyDomainNameMTLSPatch( - pathParams map[string]string, op patchOp, prop string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, prop string, out map[string]jsontext.Value, ) bool { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return false @@ -1354,7 +1357,7 @@ func (h *Handler) applyDomainNameMTLSPatch( // patch-operations.html's UpdateResource table documents both paths as // replace-only. func (h *Handler) applyResourceEntityPatchOp( - _ map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + _ map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != 1 || segs[0] != "parentId" { return false, nil @@ -1398,7 +1401,7 @@ func (h *Handler) applyResourceEntityPatchOp( // decision). "/restApiId" and "/stage" are spelled identically in every AWS // source and already work via the generic fallback (applyTopLevelPatchOp). func (h *Handler) applyBasePathMappingPatchOp( - _ map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + _ map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != 1 { return false, nil @@ -1431,7 +1434,7 @@ func (h *Handler) applyBasePathMappingPatchOp( // json.Unmarshal then failed the WHOLE PATCH request with a 500 (same bug // class as UpdateIntegration's /cacheKeyParameters). func (h *Handler) applyAuthorizerPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != 1 || segs[0] != "providerARNs" { return false, nil @@ -1484,7 +1487,7 @@ func (h *Handler) applyAuthorizerPatchOp( // requestValidatorId) are plain replace-only fields left to the generic // fallback. func (h *Handler) applyMethodPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) == 1 && segs[0] == "authorizationScopes" { return true, unmodeledPatchPath(opUpdateMethod, op.Path) @@ -1510,7 +1513,7 @@ func (h *Handler) applyMethodPatchOp( // silently drop every other parameter) and whatever an earlier op in the SAME // request already staged into out["requestParameters"]. func (h *Handler) applyMethodRequestParameterPatch( - pathParams map[string]string, op patchOp, rawName string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, rawName string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/requestParameters", op.Op) @@ -1546,7 +1549,7 @@ func (h *Handler) applyMethodRequestParameterPatch( // existing RequestModels and whatever an earlier op in the SAME request // already staged into out["requestModels"]. func (h *Handler) applyMethodRequestModelPatch( - pathParams map[string]string, op patchOp, rawContentType string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, rawContentType string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/requestModels", op.Op) @@ -1587,7 +1590,7 @@ func (h *Handler) applyMethodRequestModelPatch( // connectionId, connectionType, contentHandling, httpMethod, passthroughBehavior, // uri, timeoutInMillis) are plain replace-only fields left to the generic fallback. func (h *Handler) applyIntegrationPatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) == 1 { return h.applyIntegrationTopLevelPatchOp(pathParams, op, segs[0], out) @@ -1613,7 +1616,7 @@ func (h *Handler) applyIntegrationPatchOp( // PATCH paths that need explicit rejection or list-membership merging rather // than the generic scalar fallback. func (h *Handler) applyIntegrationTopLevelPatchOp( - pathParams map[string]string, op patchOp, field string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, field string, out map[string]jsontext.Value, ) (bool, error) { switch field { case "type": @@ -1636,7 +1639,7 @@ func (h *Handler) applyIntegrationTopLevelPatchOp( // matching this file's other single-segment list-membership paths // (UpdateApiKey's "/stages", UpdateRestApi's "/binaryMediaTypes"). func (h *Handler) applyIntegrationCacheKeyParametersPatch( - pathParams map[string]string, op patchOp, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/cacheKeyParameters", op.Op) @@ -1681,7 +1684,7 @@ func (h *Handler) applyIntegrationCacheKeyParametersPatch( // with the integration's existing map and whatever an earlier op in the SAME // request already staged into out[field]. func (h *Handler) applyIntegrationMapPatch( - pathParams map[string]string, op patchOp, field, rawKey string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, field, rawKey string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/"+field, op.Op) @@ -1722,7 +1725,7 @@ func (h *Handler) applyIntegrationMapPatch( // edits. "/contentHandling" and "/selectionPattern" are plain top-level // replace-only fields left to the generic fallback. func (h *Handler) applyIntegrationResponsePatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != patchPathSegs2 { return false, nil @@ -1743,7 +1746,7 @@ func (h *Handler) applyIntegrationResponsePatchOp( // merging with the integration response's existing map and whatever an // earlier op in the SAME request already staged into out[field]. func (h *Handler) applyIntegrationResponseMapPatch( - pathParams map[string]string, op patchOp, field, rawKey string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, field, rawKey string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/"+field, op.Op) @@ -1782,7 +1785,7 @@ func (h *Handler) applyIntegrationResponseMapPatch( // applyMethodResponsePatchOp handles UpdateMethodResponse's per-key // "/responseModels/{content-type}" and "/responseParameters/{name}" map edits. func (h *Handler) applyMethodResponsePatchOp( - pathParams map[string]string, op patchOp, segs []string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, segs []string, out map[string]jsontext.Value, ) (bool, error) { if len(segs) != patchPathSegs2 { return false, nil @@ -1803,7 +1806,7 @@ func (h *Handler) applyMethodResponsePatchOp( // method response's existing ResponseModels and whatever an earlier op in the // SAME request already staged into out["responseModels"]. func (h *Handler) applyMethodResponseModelPatch( - pathParams map[string]string, op patchOp, rawContentType string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, rawContentType string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/responseModels", op.Op) @@ -1840,7 +1843,7 @@ func (h *Handler) applyMethodResponseModelPatch( // method response's existing ResponseParameters and whatever an earlier op in // the SAME request already staged into out["responseParameters"]. func (h *Handler) applyMethodResponseParameterPatch( - pathParams map[string]string, op patchOp, rawName string, out map[string]json.RawMessage, + pathParams map[string]string, op patchOp, rawName string, out map[string]jsontext.Value, ) (bool, error) { if op.Op != patchOpAdd && op.Op != patchOpReplace && op.Op != patchOpRemove { return true, unsupportedPatchOp("/responseParameters", op.Op) diff --git a/services/apigateway/persistence.go b/services/apigateway/persistence.go index 638d739a53..bd9e89b343 100644 --- a/services/apigateway/persistence.go +++ b/services/apigateway/persistence.go @@ -2,7 +2,7 @@ package apigateway import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -414,7 +414,7 @@ var dirtyTableNames = struct { // registerAllTables doc for the clean/dirty split), merged into one map so a // single Tables blob round-trips the whole backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Account *Account `json:"account,omitempty"` UsageOverrides map[string]map[string]int64 `json:"usageOverrides,omitempty"` Version int `json:"version"` diff --git a/services/apigateway/proxy.go b/services/apigateway/proxy.go index bcce84fde1..bb7210f8ed 100644 --- a/services/apigateway/proxy.go +++ b/services/apigateway/proxy.go @@ -3,7 +3,8 @@ package apigateway import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "github.com/golang-jwt/jwt/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -410,7 +412,7 @@ func writeThrottleResponse(w http.ResponseWriter, errorType, message string) { w.Header().Set(headerContentType, "application/json") w.Header().Set("X-Amzn-Errortype", errorType) w.WriteHeader(http.StatusTooManyRequests) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: message}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{jsonMessageKey: message}) } // writeMissingAuthenticationTokenResponse writes AWS API Gateway's real response for a @@ -423,7 +425,10 @@ func writeMissingAuthenticationTokenResponse(w http.ResponseWriter) { w.Header().Set(headerContentType, "application/json") w.Header().Set("X-Amzn-Errortype", "MissingAuthenticationTokenException") w.WriteHeader(http.StatusForbidden) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: "Missing Authentication Token"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{jsonMessageKey: "Missing Authentication Token"}, + ) } // dispatchIntegration routes the request to the appropriate integration handler. diff --git a/services/apigateway/proxy_authorizer.go b/services/apigateway/proxy_authorizer.go index 0c96694f72..28470245db 100644 --- a/services/apigateway/proxy_authorizer.go +++ b/services/apigateway/proxy_authorizer.go @@ -3,7 +3,8 @@ package apigateway import ( "container/list" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -162,7 +164,7 @@ func authorizerTTL(auth *Authorizer) time.Duration { func writeAuthorizerError(w http.ResponseWriter, status int, message string) { w.Header().Set(headerContentType, "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: message}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{jsonMessageKey: message}) } // runAuthorizer enforces the method's authorizer and returns true if the request @@ -219,7 +221,7 @@ func (h *Handler) runAuthorizer( event := h.buildAuthorizerEvent(ctx, r, auth, apiID, stageName) event.MethodArn = methodArn - payload, _ := json.Marshal(event) + payload, _ := json.Marshal(event, jsonx.V1()) funcName := ExtractLambdaFunctionName(auth.AuthorizerURI) respBytes, _, invokeErr := h.lambda.InvokeFunction(ctx, funcName, "RequestResponse", payload) @@ -232,7 +234,7 @@ func (h *Handler) runAuthorizer( } var authResp AuthorizerResponse - if parseErr := json.Unmarshal(respBytes, &authResp); parseErr != nil { + if parseErr := json.Unmarshal(respBytes, &authResp, jsonx.V1()); parseErr != nil { logger.Load(ctx).WarnContext(ctx, "APIGateway proxy: failed to parse authorizer response", "error", parseErr) writeAuthorizerError(w, http.StatusUnauthorized, msgUnauthorized) diff --git a/services/apigateway/proxy_authorizer_cache_internal_test.go b/services/apigateway/proxy_authorizer_cache_internal_test.go index b793aa2be4..0b5deb1697 100644 --- a/services/apigateway/proxy_authorizer_cache_internal_test.go +++ b/services/apigateway/proxy_authorizer_cache_internal_test.go @@ -2,7 +2,7 @@ package apigateway import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "sync/atomic" @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // policyInvoker allows only callers whose "auth" header (or token) is in allowed, for allowedARN. @@ -25,7 +27,7 @@ func (p *policyInvoker) InvokeFunction(_ context.Context, _, _ string, payload [ p.calls.Add(1) var ev AuthorizerEvent - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, http.StatusInternalServerError, err } @@ -49,7 +51,7 @@ func (p *policyInvoker) InvokeFunction(_ context.Context, _, _ string, payload [ PolicyDocument: &PolicyDocument{Statement: []PolicyStatement{ {Effect: effect, Action: "execute-api:Invoke", Resource: res}, }}, - }) + }, jsonx.V1()) return body, http.StatusOK, nil } diff --git a/services/apigateway/proxy_authorizer_test.go b/services/apigateway/proxy_authorizer_test.go index 0a90c96f6f..b47bd5fa19 100644 --- a/services/apigateway/proxy_authorizer_test.go +++ b/services/apigateway/proxy_authorizer_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -53,13 +54,13 @@ func setupAuthorizerAPI( require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) // Get root resource. listRec := postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rootID := listResp["item"].([]any)[0].(map[string]any)["id"].(string) // Create /secure resource. @@ -68,7 +69,7 @@ func setupAuthorizerAPI( require.Equal(t, http.StatusCreated, childRec.Code) var childResp map[string]any - require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp)) + require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) // Create authorizer. @@ -83,7 +84,7 @@ func setupAuthorizerAPI( require.Equal(t, http.StatusCreated, authRec.Code) var authResp map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) authID := authResp["id"].(string) // PutMethod with authorizerId. @@ -280,7 +281,7 @@ func TestRunAuthorizer_MethodArn_ResourcePath(t *testing.T) { // Verify the methodArn in the captured event uses the stripped resource path ("/secure"), // not the full proxy path ("/restapis/.../prod/_user_request_/secure"). var event map[string]any - require.NoError(t, json.Unmarshal(capturedPayload, &event)) + require.NoError(t, json.Unmarshal(capturedPayload, &event, jsonx.V1())) methodArn, _ := event["methodArn"].(string) assert.Contains(t, methodArn, "/secure", "methodArn should contain the stripped path") assert.NotContains(t, methodArn, "_user_request_", "methodArn must not contain internal proxy prefix") @@ -856,12 +857,12 @@ func TestRunCognitoAuthorizer_ValidToken(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) listRec := postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rootID := listResp["item"].([]any)[0].(map[string]any)["id"].(string) childRec := postWithHandler(t, h, e, "CreateResource", @@ -869,7 +870,7 @@ func TestRunCognitoAuthorizer_ValidToken(t *testing.T) { require.Equal(t, http.StatusCreated, childRec.Code) var childResp map[string]any - require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp)) + require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) authBody := `{ @@ -882,7 +883,7 @@ func TestRunCognitoAuthorizer_ValidToken(t *testing.T) { require.Equal(t, http.StatusCreated, authRec.Code) var authResp map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) authID := authResp["id"].(string) putMethodBody := `{"restApiId":"` + apiID + `","resourceId":"` + childID + diff --git a/services/apigateway/proxy_integrations.go b/services/apigateway/proxy_integrations.go index 9ccf56b2c7..6883f14023 100644 --- a/services/apigateway/proxy_integrations.go +++ b/services/apigateway/proxy_integrations.go @@ -4,7 +4,8 @@ import ( "container/list" "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "mime" @@ -16,6 +17,7 @@ import ( "sync" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -43,7 +45,7 @@ func (h *Handler) handleAWSProxy( return } - payload, _ := json.Marshal(event) + payload, _ := json.Marshal(event, jsonx.V1()) respBytes, _, invokeErr := h.lambda.InvokeFunction( ctx, @@ -315,7 +317,10 @@ func writeAWSIntegrationReadError(ctx context.Context, w http.ResponseWriter, re w.Header().Set(headerContentType, contentTypeJSON) w.Header().Set("X-Amzn-Errortype", "UnsupportedMediaTypeException") w.WriteHeader(http.StatusUnsupportedMediaType) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: "Unsupported Media Type"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{jsonMessageKey: "Unsupported Media Type"}, + ) return } @@ -983,7 +988,10 @@ func writeInternalServerError(w http.ResponseWriter, status int) { w.Header().Set(headerContentType, "application/json") w.Header().Set("X-Amzn-Errortype", "InternalServerErrorException") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: "Internal server error"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{jsonMessageKey: "Internal server error"}, + ) } var errMalformedProxyResponse = errors.New("malformed Lambda proxy response") @@ -997,8 +1005,8 @@ const ( // way execute-api does: a JSON object with an integer statusCode, and // string-valued headers and body. func parseLambdaProxyResponse(raw []byte) (*LambdaProxyResponse, error) { - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil || obj == nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil || obj == nil { return nil, errMalformedProxyResponse } @@ -1009,14 +1017,14 @@ func parseLambdaProxyResponse(raw []byte) (*LambdaProxyResponse, error) { return nil, errMalformedProxyResponse } - var n json.Number - if err := json.Unmarshal(code, &n); err != nil { + var n jsonx.Number + if err := json.Unmarshal(code, &n, jsonx.V1()); err != nil { var str string - if json.Unmarshal(code, &str) != nil { + if json.Unmarshal(code, &str, jsonx.V1()) != nil { return nil, errMalformedProxyResponse } - n = json.Number(str) + n = jsonx.Number(str) } status, err := strconv.Atoi(n.String()) @@ -1045,13 +1053,13 @@ func parseLambdaProxyResponse(raw []byte) (*LambdaProxyResponse, error) { return &resp, nil } -func decodeProxyField(obj map[string]json.RawMessage, key string, dst any) error { +func decodeProxyField(obj map[string]jsontext.Value, key string, dst any) error { raw, ok := obj[key] if !ok || string(raw) == jsonNull { return nil } - if err := json.Unmarshal(raw, dst); err != nil { + if err := json.Unmarshal(raw, dst, jsonx.V1()); err != nil { return errMalformedProxyResponse } diff --git a/services/apigateway/proxy_mapping_test.go b/services/apigateway/proxy_mapping_test.go index 419b1564d6..ede2c06342 100644 --- a/services/apigateway/proxy_mapping_test.go +++ b/services/apigateway/proxy_mapping_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -45,14 +46,14 @@ func setupMappingAPI( rec := postWithHandler(t, h, e, "CreateRestApi", `{"name":"map-api"}`) require.Equal(t, http.StatusCreated, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) apiID := created["id"].(string) rec = postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) var list map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &list, jsonx.V1())) rootID := list["item"].([]any)[0].(map[string]any)["id"].(string) rec = postWithHandler(t, h, e, "CreateResource", @@ -61,7 +62,7 @@ func setupMappingAPI( var child map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &child)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &child, jsonx.V1())) resID := child["id"].(string) base := `"restApiId":"` + apiID + `","resourceId":"` + resID + `","httpMethod":"POST"` diff --git a/services/apigateway/proxy_test.go b/services/apigateway/proxy_test.go index 86d2a91b89..c6bdf3755f 100644 --- a/services/apigateway/proxy_test.go +++ b/services/apigateway/proxy_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -70,7 +71,7 @@ func setupProxyAPIViaHandler( require.Equal(tb, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(tb, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(tb, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) // Get root resource. @@ -78,7 +79,7 @@ func setupProxyAPIViaHandler( require.Equal(tb, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(tb, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(tb, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rootID := listResp["item"].([]any)[0].(map[string]any)["id"].(string) // Create child resource. @@ -87,7 +88,7 @@ func setupProxyAPIViaHandler( require.Equal(tb, http.StatusCreated, childRec.Code) var childResp map[string]any - require.NoError(tb, json.Unmarshal(childRec.Body.Bytes(), &childResp)) + require.NoError(tb, json.Unmarshal(childRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) // PutMethod. @@ -270,18 +271,18 @@ func setupVTLAPI(t *testing.T) (*apigateway.Handler, *echo.Echo, string) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) listRec := postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rootID := listResp["item"].([]any)[0].(map[string]any)["id"].(string) childRec := postWithHandler(t, h, e, "CreateResource", `{"restApiId":"`+apiID+`","parentId":"`+rootID+`","pathPart":"transform"}`) var childResp map[string]any - require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp)) + require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) postWithHandler(t, h, e, "PutMethod", @@ -332,7 +333,7 @@ func TestHandleAWSIntegration(t *testing.T) { checkCapture: func(t *testing.T, captured []byte) { t.Helper() var got map[string]any - require.NoError(t, json.Unmarshal(captured, &got)) + require.NoError(t, json.Unmarshal(captured, &got, jsonx.V1())) assert.Equal(t, "alice", got["name"]) }, }, @@ -484,14 +485,14 @@ func setupProxyAPIWithResource( createRec := postWithHandler(t, h, e, "CreateRestApi", `{"name":"path-api","description":"test"}`) require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) // Get root resource ID by finding the resource with path "/". listRec := postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["item"].([]any) var parentID string @@ -519,7 +520,7 @@ func setupProxyAPIWithResource( `{"restApiId":"`+apiID+`","parentId":"`+parentID+`","pathPart":"`+part+`"}`) require.Equal(t, http.StatusCreated, r.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1())) parentID = resp["id"].(string) } } @@ -635,7 +636,7 @@ func TestPathVariableMatching(t *testing.T) { checkEvent: func(t *testing.T, payload []byte) { t.Helper() var event map[string]any - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) params, _ := event["pathParameters"].(map[string]any) assert.Equal(t, "42", params["id"]) }, @@ -648,7 +649,7 @@ func TestPathVariableMatching(t *testing.T) { checkEvent: func(t *testing.T, payload []byte) { t.Helper() var event map[string]any - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) params, _ := event["pathParameters"].(map[string]any) assert.Equal(t, "/a/b/c", params["proxy"]) }, @@ -891,20 +892,20 @@ func TestHandleProxyRequest_RequiresDeployedStage(t *testing.T) { createRec := postWithHandler(t, h, e, "CreateRestApi", `{"name":"gate-api"}`) require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiID := createResp["id"].(string) listRec := postWithHandler(t, h, e, "GetResources", `{"restApiId":"`+apiID+`"}`) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rootID := listResp["item"].([]any)[0].(map[string]any)["id"].(string) childRec := postWithHandler(t, h, e, "CreateResource", `{"restApiId":"`+apiID+`","parentId":"`+rootID+`","pathPart":"items"}`) require.Equal(t, http.StatusCreated, childRec.Code) var childResp map[string]any - require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp)) + require.NoError(t, json.Unmarshal(childRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) methodRec := postWithHandler(t, h, e, "PutMethod", diff --git a/services/apigateway/proxy_validation.go b/services/apigateway/proxy_validation.go index a1040aaaf9..900d019f8d 100644 --- a/services/apigateway/proxy_validation.go +++ b/services/apigateway/proxy_validation.go @@ -2,13 +2,15 @@ package apigateway import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/url" "sort" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -72,7 +74,7 @@ func (h *Handler) validateRequestBody( // Replace body so downstream handlers can still read it. r.Body = io.NopCloser(strings.NewReader(string(bodyBytes))) - if len(bodyBytes) == 0 || !json.Valid(bodyBytes) { + if len(bodyBytes) == 0 || !jsontext.Value(bodyBytes).IsValid(jsonx.V1()) { writeValidationError(w, "Invalid request body") return true @@ -130,7 +132,7 @@ func writeValidationError(w http.ResponseWriter, message string) { w.Header().Set(headerContentType, "application/json") w.Header().Set("X-Amzn-Errortype", "BadRequestException") w.WriteHeader(http.StatusBadRequest) - _ = json.NewEncoder(w).Encode(map[string]string{jsonMessageKey: message}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{jsonMessageKey: message}) } // missingRequiredParameters returns the names of required request parameters (as diff --git a/services/apigateway/proxy_validation_test.go b/services/apigateway/proxy_validation_test.go index 7e548fb38a..c6427c69af 100644 --- a/services/apigateway/proxy_validation_test.go +++ b/services/apigateway/proxy_validation_test.go @@ -4,7 +4,7 @@ import ( "compress/gzip" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -616,7 +617,7 @@ func TestProxy_StageVariables_InterpolatedInURI(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) // The invoker receives the payload; check the captured function name via the event. var event map[string]any - require.NoError(t, json.Unmarshal(capturedPayload, &event)) + require.NoError(t, json.Unmarshal(capturedPayload, &event, jsonx.V1())) // Response comes from captureInvoker which echoes the payload back, so status is 200. // The important check is that the handler resolved the stage variable without panicking. } @@ -902,7 +903,7 @@ func TestProxy_AuthorizerCacheKey_IdentityValidationExpression(t *testing.T) { authRec := postWithHandler(t, h, e, "CreateAuthorizer", authBody) require.Equal(t, http.StatusCreated, authRec.Code) var auth map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &auth)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &auth, jsonx.V1())) authID := auth["id"].(string) _, _ = backend.PutMethod(apigateway.PutMethodInput{ diff --git a/services/apigateway/request_validators_test.go b/services/apigateway/request_validators_test.go index 1eac7c110d..2f750b2761 100644 --- a/services/apigateway/request_validators_test.go +++ b/services/apigateway/request_validators_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -46,7 +47,7 @@ func TestUpdateRequestValidator(t *testing.T) { fmt.Sprintf(`{"restApiId":%q,"name":"orig-validator"}`, apiID)) require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) validatorID := createResp["id"].(string) lookupID := validatorID diff --git a/services/apigateway/resources_test.go b/services/apigateway/resources_test.go index 269a48cbe4..20e5cd6053 100644 --- a/services/apigateway/resources_test.go +++ b/services/apigateway/resources_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -46,7 +48,7 @@ func TestGetResources_NoPositionOnLastPage(t *testing.T) { require.True(t, apiRec.Code >= 200 && apiRec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(apiRec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(apiRec.Body), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -55,7 +57,7 @@ func TestGetResources_NoPositionOnLastPage(t *testing.T) { require.True(t, resRec.Code >= 200 && resRec.Code < 300) var resResp map[string]any - require.NoError(t, json.NewDecoder(resRec.Body).Decode(&resResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resRec.Body), &resResp, jsonx.V1())) items := resResp["item"].([]any) rootID := items[0].(map[string]any)["id"].(string) @@ -69,7 +71,7 @@ func TestGetResources_NoPositionOnLastPage(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) _, hasPosition := resp["position"] assert.False(t, hasPosition, @@ -162,7 +164,7 @@ func TestUpdateResource(t *testing.T) { fmt.Sprintf(`{"restApiId":%q,"parentId":%q,"pathPart":"original"}`, apiID, rootID)) require.Equal(t, http.StatusCreated, createRec.Code) var childResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &childResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &childResp, jsonx.V1())) childID := childResp["id"].(string) lookupID := childID diff --git a/services/apigateway/rest_api_policy_wire_test.go b/services/apigateway/rest_api_policy_wire_test.go index 421b35a722..d2b2ea92be 100644 --- a/services/apigateway/rest_api_policy_wire_test.go +++ b/services/apigateway/rest_api_policy_wire_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -55,9 +56,9 @@ func TestRestAPIPolicy_WireReEscaping(t *testing.T) { var created struct { ID string `json:"id"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) - patchValue, err := json.Marshal(tt.plainPolicy) + patchValue, err := json.Marshal(tt.plainPolicy, jsonx.V1()) require.NoError(t, err) patchBody := `{"patchOperations":[{"op":"replace","path":"/policy","value":` + string(patchValue) + `}]}` @@ -68,7 +69,7 @@ func TestRestAPIPolicy_WireReEscaping(t *testing.T) { var resp struct { Policy string `json:"policy"` } - require.NoError(t, json.Unmarshal(patchRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(patchRec.Body.Bytes(), &resp, jsonx.V1())) // Mirror terraform-provider-aws's flattenAPIPolicy: wrap in // literal quotes, then unquote -- must recover the plain policy. diff --git a/services/apigateway/rest_apis_test.go b/services/apigateway/rest_apis_test.go index 497a16a2a8..40e666f7c1 100644 --- a/services/apigateway/rest_apis_test.go +++ b/services/apigateway/rest_apis_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -54,7 +56,7 @@ func TestGetRestApis_NoPositionOnLastPage(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) _, hasPosition := resp["position"] assert.False(t, hasPosition, @@ -212,7 +214,7 @@ func TestUpdateRestAPI(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.newName, resp["name"]) } }) @@ -365,7 +367,7 @@ func TestGetRestAPIs_RESTPath_WithLimit(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["item"]) }) } diff --git a/services/apigateway/route_matcher_proxy_test.go b/services/apigateway/route_matcher_proxy_test.go index a140686e48..5d305df476 100644 --- a/services/apigateway/route_matcher_proxy_test.go +++ b/services/apigateway/route_matcher_proxy_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/apigateway" "github.com/blackbirdworks/gopherstack/services/s3" @@ -66,7 +67,7 @@ func jsonAction(t *testing.T, client *http.Client, baseURL, action, body string, require.NoError(t, err) if out != nil && len(respBody) > 0 { - require.NoError(t, json.Unmarshal(respBody, out)) + require.NoError(t, json.Unmarshal(respBody, out, jsonx.V1())) } return resp.StatusCode diff --git a/services/apigateway/schema_models.go b/services/apigateway/schema_models.go index fdd130f49b..993dcda0ff 100644 --- a/services/apigateway/schema_models.go +++ b/services/apigateway/schema_models.go @@ -1,10 +1,13 @@ package apigateway import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // CreateModel creates a data model for a REST API. @@ -25,7 +28,7 @@ func (b *InMemoryBackend) CreateModel(input CreateModelInput) (*Model, error) { return nil, fmt.Errorf("%w: Model name must be alphanumeric", ErrInvalidParameter) } - if input.Schema != "" && !json.Valid([]byte(input.Schema)) { + if input.Schema != "" && !jsontext.Value([]byte(input.Schema)).IsValid(jsonx.V1()) { return nil, fmt.Errorf("%w: Invalid model specified: schema is not valid JSON", ErrInvalidParameter) } @@ -95,13 +98,13 @@ func (b *InMemoryBackend) GetModel(restAPIID, modelName string, flatten bool) (* // JSON, or a $ref naming no model in this REST API, is left untouched. func flattenModelSchema(b *InMemoryBackend, restAPIID, schema string, visiting map[string]bool) string { var node any - if err := json.Unmarshal([]byte(schema), &node); err != nil { + if err := json.Unmarshal([]byte(schema), &node, jsonx.V1()); err != nil { return schema } resolved := resolveModelRefNode(b, restAPIID, node, visiting) - out, err := json.Marshal(resolved) + out, err := json.Marshal(resolved, jsonx.V1()) if err != nil { return schema } @@ -150,7 +153,7 @@ func resolveModelRefNode(b *InMemoryBackend, restAPIID string, node any, visitin func resolveModelRef(b *InMemoryBackend, restAPIID, ref string, visiting map[string]bool) (any, bool) { refJSON, err := json.Marshal(struct { Ref string `json:"$ref"` - }{Ref: ref}) + }{Ref: ref}, jsonx.V1()) if err != nil { return nil, false } @@ -172,7 +175,7 @@ func resolveModelRef(b *InMemoryBackend, restAPIID, ref string, visiting map[str nested[name] = true var decoded any - if unmarshalErr := json.Unmarshal([]byte(m.Schema), &decoded); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal([]byte(m.Schema), &decoded, jsonx.V1()); unmarshalErr != nil { return nil, false } diff --git a/services/apigateway/schema_models_test.go b/services/apigateway/schema_models_test.go index 723371ee1a..924161cbf8 100644 --- a/services/apigateway/schema_models_test.go +++ b/services/apigateway/schema_models_test.go @@ -1,13 +1,16 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAPIGW_Models covers GetModel, GetModels, CreateModel, DeleteModel. @@ -21,7 +24,7 @@ func TestAPIGW_Models(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "expected 2xx") var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &apiResp, jsonx.V1())) apiID, _ := apiResp["id"].(string) require.NotEmpty(t, apiID) @@ -90,7 +93,7 @@ func TestGetModel_Flatten(t *testing.T) { var got struct { Schema string `json:"schema"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &got, jsonx.V1())) assert.JSONEq(t, tt.wantSchema, got.Schema) }) } @@ -117,7 +120,7 @@ func TestGetModel_FlattenSelfCycle(t *testing.T) { var got struct { Schema string `json:"schema"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &got, jsonx.V1())) assert.JSONEq(t, `{"$ref":"https://apigateway.amazonaws.com/restapis/{restApiId}/models/Node"}`, got.Schema) } diff --git a/services/apigateway/sdk.go b/services/apigateway/sdk.go index 7b531c0fab..57fa5491d5 100644 --- a/services/apigateway/sdk.go +++ b/services/apigateway/sdk.go @@ -3,8 +3,11 @@ package apigateway import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // SdkType describes a code-generation target supported by GetSdk. AWS @@ -87,7 +90,7 @@ func (b *InMemoryBackend) GetSdk( ctx := exportContext{b: b, restAPIID: restAPIID, apiName: api.Name, opts: ExportOptions{Integrations: true}} spec := buildOAS30Export(ctx, stageName) - specJSON, err := json.MarshalIndent(spec, "", " ") + specJSON, err := json.Marshal(spec, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return nil, fmt.Errorf("failed to marshal OpenAPI spec: %w", err) } diff --git a/services/apigateway/sdk_test.go b/services/apigateway/sdk_test.go index 5109f261b5..f2422791b4 100644 --- a/services/apigateway/sdk_test.go +++ b/services/apigateway/sdk_test.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -28,7 +29,7 @@ func TestAPIGateway_GetSdkTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["item"].([]any) require.NotEmpty(t, items) @@ -52,7 +53,7 @@ func TestAPIGateway_GetSdkType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "java", resp["id"]) assert.Equal(t, "Java", resp["friendlyName"]) diff --git a/services/apigateway/stages_test.go b/services/apigateway/stages_test.go index bbab176182..1cc70e1e04 100644 --- a/services/apigateway/stages_test.go +++ b/services/apigateway/stages_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -164,7 +166,7 @@ func TestHandlerStage_ClientCertificateId(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var certResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&certResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &certResp, jsonx.V1())) certID := certResp["clientCertificateId"].(string) require.NotEmpty(t, certID) @@ -172,7 +174,7 @@ func TestHandlerStage_ClientCertificateId(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) rec = restRequest(t, h, http.MethodPost, "/restapis/"+apiID+"/deployments", @@ -184,7 +186,7 @@ func TestHandlerStage_ClientCertificateId(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var stageResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&stageResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &stageResp, jsonx.V1())) assert.Equal(t, certID, stageResp["clientCertificateId"]) } diff --git a/services/apigateway/store_test.go b/services/apigateway/store_test.go index 0bacbed38d..e280294d2b 100644 --- a/services/apigateway/store_test.go +++ b/services/apigateway/store_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -112,7 +113,7 @@ func TestPaginatedListing(t *testing.T) { rec2 := postWithHandler(t, handler, e, tt.action, `{}`) assert.Equal(t, tt.wantCode, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["item"].([]any) assert.GreaterOrEqual(t, len(items), tt.wantMinItems) }) diff --git a/services/apigateway/tags_test.go b/services/apigateway/tags_test.go index 417ad888b8..b206c57baa 100644 --- a/services/apigateway/tags_test.go +++ b/services/apigateway/tags_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -45,7 +46,7 @@ func TestGetTags_DomainNameAccessAssociation(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tagsResp, jsonx.V1())) gotTags, _ := tagsResp["tags"].(map[string]any) assert.Empty(t, gotTags) } @@ -91,13 +92,13 @@ func TestResourceTags(t *testing.T) { fmt.Sprintf(`{"name":"tagged-api","tags":%s}`, tt.initialTags)) require.Equal(t, http.StatusCreated, rec.Code) var apiResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) arn := fmt.Sprintf("arn:aws:apigateway:us-east-1::/restapis/%s", apiID) // TagResource - tagsJSON, err := json.Marshal(tt.addTags) + tagsJSON, err := json.Marshal(tt.addTags, jsonx.V1()) require.NoError(t, err) tagRec := postWithHandler(t, handler, e, "TagResource", fmt.Sprintf(`{"resourceArn":%q,"tags":%s}`, arn, tagsJSON)) @@ -105,7 +106,7 @@ func TestResourceTags(t *testing.T) { // UntagResource (if applicable) if len(tt.removeTags) > 0 { - keysJSON, err2 := json.Marshal(tt.removeTags) + keysJSON, err2 := json.Marshal(tt.removeTags, jsonx.V1()) require.NoError(t, err2) untagRec := postWithHandler(t, handler, e, "UntagResource", fmt.Sprintf(`{"resourceArn":%q,"tagKeys":%s}`, arn, keysJSON)) @@ -117,7 +118,7 @@ func TestResourceTags(t *testing.T) { fmt.Sprintf(`{"resourceArn":%q}`, arn)) assert.Equal(t, http.StatusOK, getRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tagsResp, jsonx.V1())) tagsMap, _ := tagsResp["tags"].(map[string]any) for k, v := range tt.wantTagsAfter { assert.Equal(t, v, tagsMap[k]) diff --git a/services/apigateway/test_invoke.go b/services/apigateway/test_invoke.go index c3f976490d..43722e72e4 100644 --- a/services/apigateway/test_invoke.go +++ b/services/apigateway/test_invoke.go @@ -2,7 +2,7 @@ package apigateway import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const testInvokeStage = "test-invoke-stage" @@ -100,7 +101,7 @@ func claimString(v any) string { return s } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Sprint(v) } @@ -131,7 +132,7 @@ func (h *Handler) testInvokeLambdaAuthorizer( methodArn := authorizerMethodArn(ctx, r, apiID, testInvokeStage, r.URL.Path) event.MethodArn = methodArn - payload, _ := json.Marshal(event) + payload, _ := json.Marshal(event, jsonx.V1()) funcName := ExtractLambdaFunctionName(auth.AuthorizerURI) resp, failure := h.invokeAuthorizerFunction(ctx, funcName, payload) @@ -139,7 +140,7 @@ func (h *Handler) testInvokeLambdaAuthorizer( return &TestInvokeAuthorizerOutput{ClientStatus: http.StatusUnauthorized, Log: failure}, nil } - policy, _ := json.Marshal(resp.PolicyDocument) + policy, _ := json.Marshal(resp.PolicyDocument, jsonx.V1()) out := &TestInvokeAuthorizerOutput{ PrincipalID: resp.PrincipalID, PolicyDocument: string(policy), @@ -213,7 +214,7 @@ func (h *Handler) invokeAuthorizerFunction( return resp, "Authorizer function invocation failed: " + invokeErr.Error() } - if err := json.Unmarshal(respBytes, &resp); err != nil { + if err := json.Unmarshal(respBytes, &resp, jsonx.V1()); err != nil { return resp, "Authorizer function returned an unparseable response: " + err.Error() } diff --git a/services/apigateway/test_invoke_test.go b/services/apigateway/test_invoke_test.go index 2cb66c1ab7..a5b51edc2f 100644 --- a/services/apigateway/test_invoke_test.go +++ b/services/apigateway/test_invoke_test.go @@ -2,7 +2,7 @@ package apigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -188,7 +189,7 @@ func TestTestInvokeMethod_StageVariablesAndPathParameters(t *testing.T) { assert.Equal(t, "real-fn", inv.fn, "stage variable substituted into the integration URI") var event map[string]any - require.NoError(t, json.Unmarshal(inv.payload, &event)) + require.NoError(t, json.Unmarshal(inv.payload, &event, jsonx.V1())) assert.Equal(t, map[string]any{"id": "42"}, event["pathParameters"]) assert.Equal(t, map[string]any{"fn": "real-fn"}, event["stageVariables"]) } diff --git a/services/apigateway/usage_plans_test.go b/services/apigateway/usage_plans_test.go index 93cb6febd4..d440e6e0cb 100644 --- a/services/apigateway/usage_plans_test.go +++ b/services/apigateway/usage_plans_test.go @@ -1,7 +1,8 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -27,7 +29,7 @@ func TestAPIGW_UsagePlans(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "expected 2xx") var keyResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&keyResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &keyResp, jsonx.V1())) keyID, _ := keyResp["id"].(string) require.NotEmpty(t, keyID) @@ -42,7 +44,7 @@ func TestAPIGW_UsagePlans(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "expected 2xx") var planResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&planResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &planResp, jsonx.V1())) planID, _ := planResp["id"].(string) require.NotEmpty(t, planID) @@ -262,7 +264,7 @@ func TestHandlerUsagePlan_ApiStages(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &apiResp, jsonx.V1())) apiID := apiResp["id"].(string) rec = restRequest(t, h, http.MethodPost, "/restapis/"+apiID+"/deployments", @@ -276,7 +278,7 @@ func TestHandlerUsagePlan_ApiStages(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var planResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&planResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &planResp, jsonx.V1())) apiStages, ok := planResp["apiStages"].([]any) require.True(t, ok) require.Len(t, apiStages, 1) @@ -317,7 +319,7 @@ func TestUpdateUsagePlan(t *testing.T) { createRec := postWithHandler(t, handler, e, "CreateUsagePlan", `{"name":"orig-plan"}`) require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) planID := createResp["id"].(string) lookupID := planID diff --git a/services/apigateway/usage_test.go b/services/apigateway/usage_test.go index bf32857e27..a67cf25b6b 100644 --- a/services/apigateway/usage_test.go +++ b/services/apigateway/usage_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -41,7 +42,7 @@ func TestUpdateUsage_ValidatesKeyExists(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300, "create plan: %s", rec.Body.String()) var plan map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &plan)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &plan, jsonx.V1())) planID, _ := plan["id"].(string) require.NotEmpty(t, planID) @@ -81,7 +82,7 @@ func TestAPIGateway_UpdateUsage_RESTRoute(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) values, _ := resp["values"].(map[string]any) require.Contains(t, values, key.ID, "real wire key is \"values\", not \"items\"") @@ -174,7 +175,7 @@ func TestGetUsage(t *testing.T) { planRec := postWithHandler(t, handler, e, "CreateUsagePlan", `{"name":"my-plan"}`) require.Equal(t, http.StatusCreated, planRec.Code) var planResp map[string]any - require.NoError(t, json.Unmarshal(planRec.Body.Bytes(), &planResp)) + require.NoError(t, json.Unmarshal(planRec.Body.Bytes(), &planResp, jsonx.V1())) planID := planResp["id"].(string) lookupID := planID @@ -250,7 +251,7 @@ func TestDeleteUsagePlan_ClearsUsageOverrides(t *testing.T) { var decoded struct { UsageOverrides map[string]map[string]int64 `json:"usageOverrides"` } - require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded)) + require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded, jsonx.V1())) assert.NotContains(t, decoded.UsageOverrides, plan.ID, "a deleted usage plan's override entry must not survive in the persisted snapshot") } @@ -278,7 +279,7 @@ func TestDeleteAPIKey_ClearsUsageOverrides(t *testing.T) { var decoded struct { UsageOverrides map[string]map[string]int64 `json:"usageOverrides"` } - require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded)) + require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &decoded, jsonx.V1())) assert.NotContains(t, decoded.UsageOverrides[plan.ID], key.ID, "a deleted API key's usage override must not survive in the persisted snapshot") } diff --git a/services/apigateway/validation.go b/services/apigateway/validation.go index 1cf6b79cbb..0a95677c16 100644 --- a/services/apigateway/validation.go +++ b/services/apigateway/validation.go @@ -1,11 +1,13 @@ package apigateway import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "math" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // errSchemaViolation is the sentinel returned when a request body fails JSON Schema @@ -31,7 +33,7 @@ func validateJSONAgainstSchema(body []byte, schema string) error { } var doc any - if err := json.Unmarshal(body, &doc); err != nil { + if err := json.Unmarshal(body, &doc, jsonx.V1()); err != nil { return errSchemaViolation } @@ -44,7 +46,7 @@ func validateJSONAgainstSchema(body []byte, schema string) error { // when the schema is not a parseable JSON object. func parseSchemaObject(schema string) (map[string]any, bool) { var root map[string]any - if err := json.Unmarshal([]byte(schema), &root); err != nil { + if err := json.Unmarshal([]byte(schema), &root, jsonx.V1()); err != nil { return nil, false } @@ -316,11 +318,11 @@ func toFloat(raw any) (float64, bool) { // jsonValuesEqual compares two decoded JSON values for equality (used by enum checks). func jsonValuesEqual(a, b any) bool { - ab, err := json.Marshal(a) + ab, err := json.Marshal(a, jsonx.V1()) if err != nil { return false } - bb, err := json.Marshal(b) + bb, err := json.Marshal(b, jsonx.V1()) if err != nil { return false } diff --git a/services/apigateway/vpc_links_test.go b/services/apigateway/vpc_links_test.go index eb9d2606cc..4e773dff51 100644 --- a/services/apigateway/vpc_links_test.go +++ b/services/apigateway/vpc_links_test.go @@ -1,7 +1,7 @@ package apigateway_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigateway" ) @@ -29,7 +30,7 @@ func TestAPIGateway_VpcLink_RESTLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) id, _ := created["id"].(string) require.NotEmpty(t, id) assert.Equal(t, "my-link", created["name"]) @@ -41,7 +42,7 @@ func TestAPIGateway_VpcLink_RESTLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var list map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list, jsonx.V1())) items, _ := list["item"].([]any) assert.Len(t, items, 1) @@ -50,7 +51,7 @@ func TestAPIGateway_VpcLink_RESTLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, patchRec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(patchRec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(patchRec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "renamed-link", updated["name"]) deleteRec := restCall(t, h, http.MethodDelete, "/vpclinks/"+id, "", "") @@ -117,7 +118,7 @@ func TestVpcLinks(t *testing.T) { if tt.wantCreateCode == http.StatusCreated { var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) linkID = createResp["id"].(string) // Get @@ -133,7 +134,7 @@ func TestVpcLinks(t *testing.T) { listRec := postWithHandler(t, handler, e, "GetVpcLinks", `{}`) assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.GreaterOrEqual(t, len(listResp["item"].([]any)), 1) // Update @@ -142,7 +143,7 @@ func TestVpcLinks(t *testing.T) { fmt.Sprintf(`{"vpcLinkId":%q,"name":%q}`, linkID, tt.updateName)) assert.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, tt.updateName, updateResp["name"]) } diff --git a/services/apigateway/vtl_values.go b/services/apigateway/vtl_values.go index 54ace57f14..dc04046e48 100644 --- a/services/apigateway/vtl_values.go +++ b/services/apigateway/vtl_values.go @@ -2,7 +2,8 @@ package apigateway import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -10,6 +11,8 @@ import ( "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // vtlMap is an insertion-ordered map, matching the LinkedHashMap API Gateway @@ -67,55 +70,59 @@ var errVTLJSON = errors.New("invalid JSON") // parseOrderedJSON decodes JSON into vtl values (ordered maps, lists, int64/float64, string, bool, nil). func parseOrderedJSON(s string) (any, error) { - dec := json.NewDecoder(strings.NewReader(s)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(s), jsonx.V1()) v, err := decodeVTLValue(dec) if err != nil { return nil, err } - if _, err = dec.Token(); !errors.Is(err, io.EOF) { + if _, err = dec.ReadToken(); !errors.Is(err, io.EOF) { return nil, errVTLJSON } return v, nil } -func decodeVTLValue(dec *json.Decoder) (any, error) { - tok, err := dec.Token() +func decodeVTLValue(dec *jsontext.Decoder) (any, error) { + tok, err := dec.ReadToken() if err != nil { return nil, err } - switch t := tok.(type) { - case json.Delim: - if t == '{' { - return decodeVTLObject(dec) - } - + switch tok.Kind() { + case '{': + return decodeVTLObject(dec) + case '[': return decodeVTLArray(dec) - case json.Number: - return numberFromJSON(t), nil + case '0': + return numberFromJSON(jsonx.Number(tok.String())), nil + case '"': + return tok.String(), nil + case 't', 'f': + return tok.Bool(), nil default: - return t, nil + var null any + + return null, nil } } -func decodeVTLObject(dec *json.Decoder) (any, error) { +func decodeVTLObject(dec *jsontext.Decoder) (any, error) { m := newVTLMap() - for dec.More() { - keyTok, err := dec.Token() + for dec.PeekKind() != '}' { + keyTok, err := dec.ReadToken() if err != nil { return nil, err } - key, ok := keyTok.(string) - if !ok { + if keyTok.Kind() != '"' { return nil, errVTLJSON } + key := keyTok.String() + v, err := decodeVTLValue(dec) if err != nil { return nil, err @@ -124,17 +131,17 @@ func decodeVTLObject(dec *json.Decoder) (any, error) { m.put(key, v) } - if _, err := dec.Token(); err != nil { + if _, err := dec.ReadToken(); err != nil { return nil, err } return m, nil } -func decodeVTLArray(dec *json.Decoder) (any, error) { +func decodeVTLArray(dec *jsontext.Decoder) (any, error) { l := &vtlList{items: []any{}} - for dec.More() { + for dec.PeekKind() != ']' { v, err := decodeVTLValue(dec) if err != nil { return nil, err @@ -143,14 +150,14 @@ func decodeVTLArray(dec *json.Decoder) (any, error) { l.items = append(l.items, v) } - if _, err := dec.Token(); err != nil { + if _, err := dec.ReadToken(); err != nil { return nil, err } return l, nil } -func numberFromJSON(n json.Number) any { +func numberFromJSON(n jsonx.Number) any { if i, err := strconv.ParseInt(n.String(), 10, 64); err == nil { return i } @@ -213,9 +220,8 @@ func writeVTLJSON(buf *bytes.Buffer, v any) { func writeJSONString(buf *bytes.Buffer, s string) { var tmp bytes.Buffer - enc := json.NewEncoder(&tmp) - enc.SetEscapeHTML(false) - _ = enc.Encode(s) + enc := jsontext.NewEncoder(&tmp, jsonx.V1(), jsontext.EscapeForHTML(false)) + _ = json.MarshalEncode(enc, s) buf.Write(bytes.TrimRight(tmp.Bytes(), "\n")) } diff --git a/services/apigatewaymanagementapi/admin.go b/services/apigatewaymanagementapi/admin.go index 470bc720b1..9c28498dd5 100644 --- a/services/apigatewaymanagementapi/admin.go +++ b/services/apigatewaymanagementapi/admin.go @@ -1,7 +1,8 @@ package apigatewaymanagementapi import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -244,10 +246,9 @@ func (h *Handler) connectionExists(connectionID string) bool { } func decodeJSON(r io.Reader, dst any) error { - dec := json.NewDecoder(r) - dec.DisallowUnknownFields() + dec := jsontext.NewDecoder(r) - if err := dec.Decode(dst); err != nil { + if err := json.UnmarshalDecode(dec, dst, jsonx.V1(), json.RejectUnknownMembers(true)); err != nil { return err } diff --git a/services/apigatewaymanagementapi/admin_test.go b/services/apigatewaymanagementapi/admin_test.go index 28d243a496..f62d2b51a6 100644 --- a/services/apigatewaymanagementapi/admin_test.go +++ b/services/apigatewaymanagementapi/admin_test.go @@ -2,7 +2,7 @@ package apigatewaymanagementapi_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewaymanagementapi" ) @@ -34,7 +35,7 @@ func TestAdmin_ListConnections(t *testing.T) { Connections []apigatewaymanagementapi.Connection `json:"connections"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Connections, 2) } @@ -56,7 +57,7 @@ func TestAdmin_ListConnections_QueryFilter(t *testing.T) { Connections []apigatewaymanagementapi.Connection `json:"connections"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Connections, 1) assert.Equal(t, "beta-2", resp.Connections[0].ConnectionID) } @@ -70,7 +71,7 @@ func TestAdmin_SimulateConnection(t *testing.T) { "connectionId": "sim-1", "sourceIp": "1.2.3.4", "userAgent": "ua", - }) + }, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/_gopherstack/apigwmgmt/connections", body) assert.Equal(t, http.StatusCreated, rec.Code) @@ -88,7 +89,7 @@ func TestAdmin_SimulateDuplicate(t *testing.T) { _, err := h.Backend.CreateConnection("dupe", "1.1.1.1", "ua", nil) require.NoError(t, err) - body, _ := json.Marshal(map[string]string{"connectionId": "dupe"}) + body, _ := json.Marshal(map[string]string{"connectionId": "dupe"}, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/_gopherstack/apigwmgmt/connections", body) assert.Equal(t, http.StatusConflict, rec.Code) } @@ -98,7 +99,7 @@ func TestAdmin_SimulateEmptyConnectionID(t *testing.T) { h := newTestHandler(t) - body, _ := json.Marshal(map[string]string{"connectionId": ""}) + body, _ := json.Marshal(map[string]string{"connectionId": ""}, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/_gopherstack/apigwmgmt/connections", body) assert.Equal(t, http.StatusBadRequest, rec.Code) } @@ -123,7 +124,7 @@ func TestAdmin_GetMessages(t *testing.T) { } `json:"messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Messages, 2) assert.Equal(t, "hello", resp.Messages[0].Data) assert.Equal(t, 5, resp.Messages[0].Bytes) @@ -170,7 +171,7 @@ func TestAdmin_GetTimeline(t *testing.T) { Events []apigatewaymanagementapi.LifecycleEvent `json:"events"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Events, 3) assert.Equal(t, apigatewaymanagementapi.EventConnected, resp.Events[0].Type) assert.Equal(t, apigatewaymanagementapi.EventMessage, resp.Events[1].Type) @@ -208,7 +209,7 @@ func TestAdmin_Broadcast(t *testing.T) { require.NoError(t, err) } - body, _ := json.Marshal(map[string]string{"data": "broadcast-payload"}) + body, _ := json.Marshal(map[string]string{"data": "broadcast-payload"}, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/_gopherstack/apigwmgmt/broadcast", body) assert.Equal(t, http.StatusOK, rec.Code) @@ -217,7 +218,7 @@ func TestAdmin_Broadcast(t *testing.T) { Delivered int `json:"delivered"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 3, resp.Delivered) for _, id := range []string{"a", "b", "c"} { @@ -236,7 +237,7 @@ func TestAdmin_BroadcastTooLarge(t *testing.T) { require.NoError(t, err) big := bytes.Repeat([]byte("x"), 130*1024) - body, _ := json.Marshal(map[string]string{"data": string(big)}) + body, _ := json.Marshal(map[string]string{"data": string(big)}, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/_gopherstack/apigwmgmt/broadcast", body) assert.Equal(t, http.StatusRequestEntityTooLarge, rec.Code) @@ -255,7 +256,7 @@ func TestAdmin_Stats(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var stats apigatewaymanagementapi.Stats - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stats)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stats, jsonx.V1())) assert.Equal(t, 1, stats.ActiveConnections) assert.Equal(t, int64(1), stats.TotalConnections) assert.Equal(t, int64(1), stats.TotalMessages) diff --git a/services/apigatewaymanagementapi/error_messages_test.go b/services/apigatewaymanagementapi/error_messages_test.go index 2ac98e8888..fb2beda9d0 100644 --- a/services/apigatewaymanagementapi/error_messages_test.go +++ b/services/apigatewaymanagementapi/error_messages_test.go @@ -1,7 +1,7 @@ package apigatewaymanagementapi_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewaymanagementapi" ) @@ -53,7 +54,7 @@ func TestModeledErrorBodies(t *testing.T) { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantMsg, body["message"]) assert.NotContains(t, body, "connectionId") }) diff --git a/services/apigatewaymanagementapi/handler_test.go b/services/apigatewaymanagementapi/handler_test.go index 2b1fec35ff..1001144ea5 100644 --- a/services/apigatewaymanagementapi/handler_test.go +++ b/services/apigatewaymanagementapi/handler_test.go @@ -2,7 +2,8 @@ package apigatewaymanagementapi_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "log/slog" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/apigatewaymanagementapi" ) @@ -325,8 +327,8 @@ func TestHandler_GetConnection(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) if tt.wantStatus == http.StatusOK { - var body map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + var body map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Contains(t, body, "connectedAt") assert.Contains(t, body, "identity") assert.Contains(t, body, "lastActiveAt") @@ -362,8 +364,8 @@ func TestHandler_GetConnection_NoConnectionIDField(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/@connections/"+tt.connectionID, nil) require.Equal(t, http.StatusOK, rec.Code) - var body map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + var body map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotContains(t, body, "connectionId", "GetConnectionOutput has no ConnectionId member on the real API") }) @@ -398,15 +400,15 @@ func TestHandler_GetConnection_IdentityShape(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/@connections/conn-identity", nil) require.Equal(t, http.StatusOK, rec.Code) - var body map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + var body map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) // AWS shape: identity must be a nested object. identityRaw, ok := body["identity"] require.True(t, ok, "response must include 'identity' field") var identity map[string]string - require.NoError(t, json.Unmarshal(identityRaw, &identity)) + require.NoError(t, json.Unmarshal(identityRaw, &identity, jsonx.V1())) assert.Equal(t, tt.sourceIP, identity["sourceIp"], "identity.sourceIp must match") assert.Equal(t, tt.userAgent, identity["userAgent"], "identity.userAgent must match") @@ -440,7 +442,7 @@ func TestHandler_GetConnection_TimestampFields(t *testing.T) { ConnectedAt string `json:"connectedAt"` LastActiveAt string `json:"lastActiveAt"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body.ConnectedAt, "connectedAt must be non-empty") assert.NotEmpty(t, body.LastActiveAt, "lastActiveAt must be non-empty") } @@ -533,7 +535,7 @@ func TestHandler_GoneExceptionWireShape(t *testing.T) { assert.Equal(t, "GoneException", rec.Header().Get("X-Amzn-Errortype")) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "GoneException", body["__type"]) assert.NotEqual(t, "GoneException", body["message"], "message must be a human-readable string, not the error type") @@ -563,7 +565,7 @@ func TestHandler_PostToConnection_LimitExceeded(t *testing.T) { assert.Equal(t, "LimitExceededException", rec.Header().Get("X-Amzn-Errortype")) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "LimitExceededException", body["__type"]) assert.NotEmpty(t, body["message"]) @@ -588,7 +590,7 @@ func TestHandler_PostToConnection_PayloadTooLarge_WireShape(t *testing.T) { assert.Equal(t, "PayloadTooLargeException", rec.Header().Get("X-Amzn-Errortype")) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "PayloadTooLargeException", body["__type"]) assert.NotEmpty(t, body["message"]) } diff --git a/services/apigatewaymanagementapi/ringbuffer_test.go b/services/apigatewaymanagementapi/ringbuffer_test.go index 60c2514745..b9958eb638 100644 --- a/services/apigatewaymanagementapi/ringbuffer_test.go +++ b/services/apigatewaymanagementapi/ringbuffer_test.go @@ -1,13 +1,14 @@ package apigatewaymanagementapi_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewaymanagementapi" ) @@ -211,7 +212,7 @@ func TestBackend_SnapshotPreservesEverything(t *testing.T) { // Verify well-formed JSON var raw map[string]any - require.NoError(t, json.Unmarshal(data, &raw)) + require.NoError(t, json.Unmarshal(data, &raw, jsonx.V1())) fresh := apigatewaymanagementapi.NewInMemoryBackend() require.NoError(t, fresh.Restore(t.Context(), data)) diff --git a/services/apigatewayv2/authorizer_cache_internal_test.go b/services/apigatewayv2/authorizer_cache_internal_test.go index 53d5f2390d..17330fbde6 100644 --- a/services/apigatewayv2/authorizer_cache_internal_test.go +++ b/services/apigatewayv2/authorizer_cache_internal_test.go @@ -3,7 +3,7 @@ package apigatewayv2 import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -15,6 +15,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // doInternalRequest performs an HTTP request against h and returns the @@ -27,7 +29,7 @@ func doInternalRequest(t *testing.T, h *Handler, method, path string, body any) var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) } else { diff --git a/services/apigatewayv2/authorizer_context.go b/services/apigatewayv2/authorizer_context.go index c2bbed876d..86cec940fe 100644 --- a/services/apigatewayv2/authorizer_context.go +++ b/services/apigatewayv2/authorizer_context.go @@ -1,13 +1,15 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/v2" "slices" "strconv" "strings" "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const authorizerCtxKey = "apigwv2.authorizer" @@ -51,7 +53,7 @@ func stringifyClaim(v any) string { return "[" + strings.Join(parts, " ") + "]" default: - b, _ := json.Marshal(t) + b, _ := json.Marshal(t, jsonx.V1()) return string(b) } @@ -97,7 +99,7 @@ func lambdaAuthorizerContext(respBytes []byte) map[string]any { var resp struct { Context map[string]any `json:"context"` } - if err := json.Unmarshal(respBytes, &resp); err != nil || len(resp.Context) == 0 { + if err := json.Unmarshal(respBytes, &resp, jsonx.V1()); err != nil || len(resp.Context) == 0 { return nil } diff --git a/services/apigatewayv2/authorizers.go b/services/apigatewayv2/authorizers.go index 3bec274b22..39395e14ae 100644 --- a/services/apigatewayv2/authorizers.go +++ b/services/apigatewayv2/authorizers.go @@ -1,7 +1,7 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "sort" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -211,7 +212,7 @@ func (f *flexibleStrings) UnmarshalJSON(data []byte) error { if strings.HasPrefix(trimmed, "[") { var arr []string - if err := json.Unmarshal(data, &arr); err != nil { + if err := json.Unmarshal(data, &arr, jsonx.V1()); err != nil { return err } @@ -221,7 +222,7 @@ func (f *flexibleStrings) UnmarshalJSON(data []byte) error { } var s string - if err := json.Unmarshal(data, &s); err != nil { + if err := json.Unmarshal(data, &s, jsonx.V1()); err != nil { return err } @@ -375,7 +376,7 @@ func buildAuthorizerPayload( }, } - return json.Marshal(event) + return json.Marshal(event, jsonx.V1()) } event := requestAuthorizerEventV2{ @@ -403,7 +404,7 @@ func buildAuthorizerPayload( }, } - return json.Marshal(event) + return json.Marshal(event, jsonx.V1()) } // authorizerUsesV1Payload reports whether the authorizer expects the 1.0 event @@ -418,7 +419,7 @@ func evaluateAuthorizerResponse(respBytes []byte, auth *Authorizer, routeArn str // Simple response: only valid with payload 2.0 + EnableSimpleResponses. if auth.EnableSimpleResponses && !authorizerUsesV1Payload(auth) { var simple simpleAuthorizerResponse - if err := json.Unmarshal(respBytes, &simple); err == nil { + if err := json.Unmarshal(respBytes, &simple, jsonx.V1()); err == nil { return simple.IsAuthorized, false } @@ -427,7 +428,7 @@ func evaluateAuthorizerResponse(respBytes []byte, auth *Authorizer, routeArn str // IAM policy response. var policy iamPolicyResponse - if err := json.Unmarshal(respBytes, &policy); err != nil { + if err := json.Unmarshal(respBytes, &policy, jsonx.V1()); err != nil { return false, false } diff --git a/services/apigatewayv2/custom_domain_test.go b/services/apigatewayv2/custom_domain_test.go index 888908c79c..a9ab330f1a 100644 --- a/services/apigatewayv2/custom_domain_test.go +++ b/services/apigatewayv2/custom_domain_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -32,11 +33,11 @@ func TestCustomDomainRouting(t *testing.T) { } `json:"requestContext"` RawPath string `json:"rawPath"` } - _ = json.Unmarshal(payload, &ev) + _ = json.Unmarshal(payload, &ev, jsonx.V1()) b, _ := json.Marshal(map[string]any{ "statusCode": 200, "body": name + "|" + ev.RequestContext.Stage + "|" + ev.RawPath, - }) + }, jsonx.V1()) return b, 200, nil }, diff --git a/services/apigatewayv2/custom_id_test.go b/services/apigatewayv2/custom_id_test.go index 53b1711682..5bc7bf6d7e 100644 --- a/services/apigatewayv2/custom_id_test.go +++ b/services/apigatewayv2/custom_id_test.go @@ -1,13 +1,14 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -54,7 +55,7 @@ func TestCreateAPICustomIDTag(t *testing.T) { } var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, tt.wantID, api.APIID) assert.NotContains(t, api.Tags, "_custom_id_") assert.NotContains(t, api.Tags, "gopherstack:id") diff --git a/services/apigatewayv2/handler.go b/services/apigatewayv2/handler.go index 24d6f15c5f..874ebcde7a 100644 --- a/services/apigatewayv2/handler.go +++ b/services/apigatewayv2/handler.go @@ -3,7 +3,8 @@ package apigatewayv2 import ( "context" "crypto/rsa" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "slices" @@ -19,6 +20,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" "github.com/blackbirdworks/gopherstack/pkgs/executeapi" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -643,7 +645,7 @@ func handleCreateMulti[I, O any]( log := logger.Load(c.Request().Context()) var input I - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -682,7 +684,7 @@ func handleUpdate[I, O any]( log := logger.Load(c.Request().Context()) var input I - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -785,7 +787,7 @@ func handleCreateNoParent[I, O any]( log := logger.Load(c.Request().Context()) var input I - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } diff --git a/services/apigatewayv2/handler_api_mappings_test.go b/services/apigatewayv2/handler_api_mappings_test.go index 63223f459a..ed27246ff4 100644 --- a/services/apigatewayv2/handler_api_mappings_test.go +++ b/services/apigatewayv2/handler_api_mappings_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -119,7 +120,7 @@ func TestHandler_CreateApiMapping(t *testing.T) { if tt.wantAPIIDSet { var mapping apigatewayv2.APIMapping - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping, jsonx.V1())) assert.Equal(t, apiID, mapping.APIID) assert.NotEmpty(t, mapping.APIMappingID) } @@ -174,7 +175,7 @@ func TestHandler_GetAPIMappings(t *testing.T) { var out struct { Items []apigatewayv2.APIMapping `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.mappingsCnt) } }) @@ -230,7 +231,7 @@ func TestHandler_GetAPIMapping(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var mapping apigatewayv2.APIMapping - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping, jsonx.V1())) domainName := "example.com" mappingID := mapping.APIMappingID @@ -299,7 +300,7 @@ func TestHandler_DeleteAPIMapping(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var mapping apigatewayv2.APIMapping - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &mapping, jsonx.V1())) domainName := "example.com" mappingID := mapping.APIMappingID @@ -340,7 +341,7 @@ func TestHandler_UpdateAPIMapping(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) var m apigatewayv2.APIMapping - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m, jsonx.V1())) return "api.example.com", m.APIMappingID }, diff --git a/services/apigatewayv2/handler_api_models_test.go b/services/apigatewayv2/handler_api_models_test.go index 5fe44c52a2..8e2dd18c52 100644 --- a/services/apigatewayv2/handler_api_models_test.go +++ b/services/apigatewayv2/handler_api_models_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -73,7 +74,7 @@ func TestHandler_CreateModel(t *testing.T) { if tt.wantName != "" { var model apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model, jsonx.V1())) assert.Equal(t, tt.wantName, model.Name) assert.NotEmpty(t, model.ModelID) } @@ -121,7 +122,7 @@ func TestHandler_GetModelTemplate(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var model apigatewayv2.Model - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &model)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &model, jsonx.V1())) rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/v2/apis/%s/models/%s/template", apiID, model.ModelID), nil) @@ -130,7 +131,7 @@ func TestHandler_GetModelTemplate(t *testing.T) { var out struct { Value string `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantValue, out.Value) }) } @@ -224,7 +225,7 @@ func TestHandler_GetModels(t *testing.T) { var out struct { Items []apigatewayv2.Model `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.modelCnt) } }) @@ -265,7 +266,7 @@ func TestHandler_GetModel(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var model apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model, jsonx.V1())) modelID := model.ModelID if tt.useWrongID { @@ -277,7 +278,7 @@ func TestHandler_GetModel(t *testing.T) { if tt.wantStatus == http.StatusOK { var got apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "MyModel", got.Name) } }) @@ -318,7 +319,7 @@ func TestHandler_DeleteModel(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var model apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &model, jsonx.V1())) modelID := model.ModelID if tt.useWrongID { @@ -350,7 +351,7 @@ func TestHandler_UpdateModel(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) var m apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m, jsonx.V1())) return apiID, m.ModelID }, @@ -390,7 +391,7 @@ func TestHandler_UpdateModel(t *testing.T) { if tt.wantName != "" { var m apigatewayv2.Model - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &m, jsonx.V1())) assert.Equal(t, tt.wantName, m.Name) } }) @@ -432,7 +433,7 @@ func TestGetModels_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), 2) for _, m := range resp.Items { diff --git a/services/apigatewayv2/handler_apis.go b/services/apigatewayv2/handler_apis.go index 40c08e7fd9..d27ee0af22 100644 --- a/services/apigatewayv2/handler_apis.go +++ b/services/apigatewayv2/handler_apis.go @@ -1,7 +1,8 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "gopkg.in/yaml.v3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -103,7 +105,7 @@ func (h *Handler) handleCreateAPI(c *echo.Context) error { log := logger.Load(c.Request().Context()) var input CreateAPIInput - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -196,7 +198,7 @@ func (h *Handler) handleUpdateAPI(c *echo.Context, apiID string) error { log := logger.Load(c.Request().Context()) var input UpdateAPIInput - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -364,7 +366,7 @@ func parseOpenAPISpec(body string) (*openAPISpec, error) { return &spec, nil } - if err := json.Unmarshal([]byte(body), &spec); err != nil { + if err := json.Unmarshal([]byte(body), &spec, jsonx.V1()); err != nil { return nil, err } @@ -466,7 +468,7 @@ func (h *Handler) handleImportAPI(c *echo.Context) error { var input struct { Body string `json:"body"` } - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -510,7 +512,7 @@ func (h *Handler) handleReimportAPI(c *echo.Context, apiID string) error { var input struct { Body string `json:"body"` } - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -677,7 +679,7 @@ func (h *Handler) handleExportAPI(c *echo.Context, apiID, specification string) return c.Blob(http.StatusOK, "application/x-yaml", blob) } - blob, mErr := json.Marshal(spec) + blob, mErr := json.Marshal(spec, jsonx.V1()) if mErr != nil { return writeErr(c, http.StatusInternalServerError, mErr.Error()) } diff --git a/services/apigatewayv2/handler_apis_test.go b/services/apigatewayv2/handler_apis_test.go index c9644fef71..fd1aa6e5ac 100644 --- a/services/apigatewayv2/handler_apis_test.go +++ b/services/apigatewayv2/handler_apis_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -59,7 +60,7 @@ func TestHandler_CreateAPI(t *testing.T) { if tt.wantName != "" { var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, tt.wantName, api.Name) assert.NotEmpty(t, api.APIID) } @@ -106,7 +107,7 @@ func TestHandler_GetAPI(t *testing.T) { if tt.wantStatus == http.StatusOK { var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, apiID, api.APIID) } }) @@ -151,7 +152,7 @@ func TestHandler_GetAPIs(t *testing.T) { } var resp listResp - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, tt.apiCount) }) } @@ -238,7 +239,7 @@ func TestHandler_UpdateAPI(t *testing.T) { if tt.wantName != "" { var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) assert.Equal(t, tt.wantName, api.Name) } }) @@ -301,7 +302,7 @@ func TestExportAPI_ReturnsRawSpec(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var spec map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec, jsonx.V1())) // Raw OpenAPI doc — not the old {"body":...,"specification":...} wrapper. assert.Equal(t, "3.0.1", spec["openapi"]) @@ -355,7 +356,7 @@ func TestExportAPI_OutputType(t *testing.T) { var asJSON map[string]any require.Error( t, - json.Unmarshal(rr.Body.Bytes(), &asJSON), + json.Unmarshal(rr.Body.Bytes(), &asJSON, jsonx.V1()), "YAML output must not also be valid strict JSON object syntax expected by this assertion", ) assert.Contains(t, rr.Body.String(), "openapi:") @@ -364,7 +365,7 @@ func TestExportAPI_OutputType(t *testing.T) { } var spec map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec, jsonx.V1())) assert.Equal(t, "3.0.1", spec["openapi"]) }) } @@ -400,7 +401,7 @@ func TestExportAPI_JWTSecurityScheme(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var auth apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+apiID+"/routes", map[string]any{ "routeKey": "GET /jwt", @@ -413,7 +414,7 @@ func TestExportAPI_JWTSecurityScheme(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var spec map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &spec, jsonx.V1())) components, ok := spec["components"].(map[string]any) require.True(t, ok, "expected components block") @@ -494,7 +495,7 @@ func TestGetAPIs_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.API `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), tc.maxResults, "page must not exceed maxResults") for _, api := range resp.Items { @@ -540,7 +541,7 @@ func TestGetAPIs_NoMaxResults_ReturnsAll(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.API `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, 5) assert.Empty(t, resp.NextToken) } @@ -563,7 +564,7 @@ func TestGetAPIs_EmptyLastPage(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.API `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, 2) assert.Empty(t, resp.NextToken, "no nextToken when all items fit") } @@ -583,7 +584,7 @@ func TestCreateAPI_HTTP_DefaultAPIKeySelectionExpression(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "$request.header.x-api-key", resp["apiKeySelectionExpression"]) } @@ -602,7 +603,7 @@ func TestCreateAPI_WebSocket_DefaultAPIKeySelectionExpression(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "$context.authorizer.usageIdentifierKey", resp["apiKeySelectionExpression"]) } @@ -623,7 +624,7 @@ func TestCreateAPI_ExplicitAPIKeySelectionExpressionPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, custom, resp["apiKeySelectionExpression"]) } @@ -675,7 +676,7 @@ func TestImportAPI_Basepath_Prepend(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodGet, "/v2/apis/"+api.APIID+"/routes", nil) require.Equal(t, http.StatusOK, rr.Code) @@ -683,7 +684,7 @@ func TestImportAPI_Basepath_Prepend(t *testing.T) { var routes struct { Items []apigatewayv2.Route `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes, jsonx.V1())) require.Len(t, routes.Items, 1) assert.Equal(t, "GET /v1/items", routes.Items[0].RouteKey) } @@ -707,7 +708,7 @@ func TestImportAPI_Basepath_DefaultIgnoresSpecBasePath(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodGet, "/v2/apis/"+api.APIID+"/routes", nil) require.Equal(t, http.StatusOK, rr.Code) @@ -715,7 +716,7 @@ func TestImportAPI_Basepath_DefaultIgnoresSpecBasePath(t *testing.T) { var routes struct { Items []apigatewayv2.Route `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes, jsonx.V1())) require.Len(t, routes.Items, 1) assert.Equal(t, "GET /items", routes.Items[0].RouteKey) } @@ -791,7 +792,7 @@ func TestImportAPI_Basepath_RouteKeyTransforms(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodGet, "/v2/apis/"+api.APIID+"/routes", nil) require.Equal(t, http.StatusOK, rr.Code) @@ -799,7 +800,7 @@ func TestImportAPI_Basepath_RouteKeyTransforms(t *testing.T) { var routes struct { Items []apigatewayv2.Route `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes, jsonx.V1())) require.Len(t, routes.Items, 1) assert.Equal(t, tt.wantRouteKey, routes.Items[0].RouteKey) }) @@ -846,7 +847,7 @@ func TestReimportAPI_Basepath_RouteKeyTransforms(t *testing.T) { var routes struct { Items []apigatewayv2.Route `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &routes, jsonx.V1())) require.Len(t, routes.Items, 1) assert.Equal(t, tt.wantRouteKey, routes.Items[0].RouteKey) }) @@ -899,7 +900,7 @@ func TestImportAPI_FailOnWarnings(t *testing.T) { var apis struct { Items []apigatewayv2.API `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &apis)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &apis, jsonx.V1())) assert.Len(t, apis.Items, tt.wantAPIs) }) } diff --git a/services/apigatewayv2/handler_authorizers_test.go b/services/apigatewayv2/handler_authorizers_test.go index 94a9d92240..a5197582d7 100644 --- a/services/apigatewayv2/handler_authorizers_test.go +++ b/services/apigatewayv2/handler_authorizers_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -48,7 +49,7 @@ func TestHandler_Authorizers(t *testing.T) { require.Equal(t, tt.wantCreateCode, rr.Code) var auth apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth, jsonx.V1())) assert.Equal(t, tt.authorizerName, auth.Name) // GetAuthorizers @@ -245,7 +246,7 @@ func TestGetAuthorizers_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), tc.maxResults) for _, a := range resp.Items { @@ -311,7 +312,7 @@ func setupRequestAuthAPI( authCalls := &atomic.Int64{} integrationCalls := &atomic.Int64{} - respBytes, _ := json.Marshal(sc.authResponse) + respBytes, _ := json.Marshal(sc.authResponse, jsonx.V1()) h.SetLambdaInvoker(&mockLambdaInvoker{ fn: func(_ context.Context, name, _ string, _ []byte) ([]byte, int, error) { @@ -324,7 +325,7 @@ func setupRequestAuthAPI( // Integration backend function. integrationCalls.Add(1) - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, @@ -341,7 +342,7 @@ func setupRequestAuthAPI( require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) // Authorizer. identity := sc.identitySource @@ -365,7 +366,7 @@ func setupRequestAuthAPI( require.Equal(t, http.StatusCreated, rr.Code) var auth apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &auth, jsonx.V1())) authorizerID := auth.AuthorizerID if sc.authorizerMissing { @@ -619,7 +620,7 @@ func TestRouteAuth_AWSIAM(t *testing.T) { fn: func(_ context.Context, _, _ string, _ []byte) ([]byte, int, error) { integrationCalls.Add(1) - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, @@ -635,7 +636,7 @@ func TestRouteAuth_AWSIAM(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+apiID+"/routes", map[string]any{ "routeKey": "GET /iam", diff --git a/services/apigatewayv2/handler_deployments_test.go b/services/apigatewayv2/handler_deployments_test.go index 1ff59a659c..a28a4016e5 100644 --- a/services/apigatewayv2/handler_deployments_test.go +++ b/services/apigatewayv2/handler_deployments_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -54,7 +55,7 @@ func TestHandler_CreateDeployment(t *testing.T) { if tt.wantStatus == http.StatusCreated { var deployment apigatewayv2.Deployment - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment, jsonx.V1())) assert.NotEmpty(t, deployment.DeploymentID) assert.Equal(t, "DEPLOYED", deployment.DeploymentStatus) } @@ -95,7 +96,7 @@ func TestHandler_GetDeployment(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var deployment apigatewayv2.Deployment - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment, jsonx.V1())) deploymentID = deployment.DeploymentID } @@ -138,7 +139,7 @@ func TestHandler_DeleteDeployment(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var deployment apigatewayv2.Deployment - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &deployment, jsonx.V1())) deploymentID = deployment.DeploymentID } @@ -213,7 +214,7 @@ func TestHandler_GetDeployments(t *testing.T) { } var resp listResp - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, tt.deployCount) } }) @@ -239,7 +240,7 @@ func TestHandler_UpdateDeployment(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) var dep apigatewayv2.Deployment - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep, jsonx.V1())) return apiID, dep.DeploymentID }, @@ -279,7 +280,7 @@ func TestHandler_UpdateDeployment(t *testing.T) { if tt.wantDesc != "" { var dep apigatewayv2.Deployment - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, tt.wantDesc, dep.Description) } }) @@ -304,7 +305,7 @@ func getStage(t *testing.T, h *apigatewayv2.Handler, apiID, stageName string) ap require.Equal(t, http.StatusOK, rr.Code) var s apigatewayv2.Stage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &s, jsonx.V1())) return s } @@ -320,7 +321,7 @@ func createIntegrationHelper(t *testing.T, h *apigatewayv2.Handler, apiID string require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) return integ.IntegrationID } @@ -369,7 +370,7 @@ func TestAutoDeploy_RouteAndIntegrationChangesDeploy(t *testing.T) { var out struct { Items []apigatewayv2.Deployment `json:"items"` } - require.NoError(t, json.Unmarshal(drr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(drr.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Items) var foundAuto bool @@ -423,7 +424,7 @@ func TestGetDeployments_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), 2) for _, d := range resp.Items { @@ -484,6 +485,6 @@ func TestCreateDeployment_ValidStageName201(t *testing.T) { assert.Equal(t, http.StatusCreated, rr.Code) var dep map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, "DEPLOYED", dep["deploymentStatus"]) } diff --git a/services/apigatewayv2/handler_domain_names_test.go b/services/apigatewayv2/handler_domain_names_test.go index de26e60fda..799717c9ea 100644 --- a/services/apigatewayv2/handler_domain_names_test.go +++ b/services/apigatewayv2/handler_domain_names_test.go @@ -1,7 +1,8 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -59,7 +61,7 @@ func TestHandler_CreateDomainName(t *testing.T) { if tt.wantDomain != "" { var dn apigatewayv2.DomainName - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn, jsonx.V1())) assert.Equal(t, tt.wantDomain, dn.DomainNameValue) } }) @@ -138,7 +140,7 @@ func TestHandler_GetDomainNames(t *testing.T) { var out struct { Items []apigatewayv2.DomainName `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.domainCnt) } }) @@ -181,7 +183,7 @@ func TestHandler_GetDomainName(t *testing.T) { if tt.wantStatus == http.StatusOK { var dn apigatewayv2.DomainName - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn, jsonx.V1())) assert.Equal(t, "example.com", dn.DomainNameValue) } }) @@ -310,7 +312,7 @@ func TestHandler_CreateDomainName_WithConfigurations(t *testing.T) { if tt.wantConfig { var dn apigatewayv2.DomainName - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn, jsonx.V1())) require.Len(t, dn.DomainNameConfigurations, 1) assert.Equal(t, "AVAILABLE", dn.DomainNameConfigurations[0].DomainNameStatus) assert.Equal(t, "REGIONAL", dn.DomainNameConfigurations[0].EndpointType) @@ -358,14 +360,14 @@ func TestDomainNameConfigurationsNullBug(t *testing.T) { rr := doRequest(t, h, http.MethodPost, "/v2/domainnames", tt.body) require.Equal(t, http.StatusCreated, rr.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw, jsonx.V1())) _, hasKey := raw["domainNameConfigurations"] assert.True(t, hasKey, "domainNameConfigurations key must always be present in DomainName response") var dn apigatewayv2.DomainName - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &dn, jsonx.V1())) if tt.wantConfigsEmpty { assert.Empty(t, dn.DomainNameConfigurations, diff --git a/services/apigatewayv2/handler_export_api_sdk_test.go b/services/apigatewayv2/handler_export_api_sdk_test.go index 932743ff66..bb51b58d42 100644 --- a/services/apigatewayv2/handler_export_api_sdk_test.go +++ b/services/apigatewayv2/handler_export_api_sdk_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -36,7 +37,7 @@ func TestExportApi_OutputType_RoundTrip(t *testing.T) { require.NoError(t, err) var asJSON map[string]any - require.NoError(t, json.Unmarshal(jsonOut.Body, &asJSON), "OutputType=JSON must return valid JSON") + require.NoError(t, json.Unmarshal(jsonOut.Body, &asJSON, jsonx.V1()), "OutputType=JSON must return valid JSON") assert.Equal(t, "3.0.1", asJSON["openapi"]) yamlOut, err := client.ExportApi(t.Context(), &apigatewayv2sdk.ExportApiInput{ @@ -47,7 +48,7 @@ func TestExportApi_OutputType_RoundTrip(t *testing.T) { require.NoError(t, err) require.Error( - t, json.Unmarshal(yamlOut.Body, &asJSON), + t, json.Unmarshal(yamlOut.Body, &asJSON, jsonx.V1()), "OutputType=YAML must not return the same JSON syntax as OutputType=JSON", ) assert.Contains(t, string(yamlOut.Body), "openapi:") diff --git a/services/apigatewayv2/handler_integration_responses_test.go b/services/apigatewayv2/handler_integration_responses_test.go index 2e9987ff49..0f5aa058e9 100644 --- a/services/apigatewayv2/handler_integration_responses_test.go +++ b/services/apigatewayv2/handler_integration_responses_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -69,7 +70,7 @@ func TestHandler_CreateIntegrationResponse(t *testing.T) { require.Equal(t, http.StatusCreated, integrationRR.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(integrationRR.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(integrationRR.Body.Bytes(), &integ, jsonx.V1())) integrationID = integ.IntegrationID } @@ -91,7 +92,7 @@ func TestHandler_CreateIntegrationResponse(t *testing.T) { if tt.wantRespKey != "" { var ir apigatewayv2.IntegrationResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir, jsonx.V1())) assert.Equal(t, tt.wantRespKey, ir.IntegrationResponseKey) assert.NotEmpty(t, ir.IntegrationResponseID) } @@ -125,7 +126,7 @@ func TestHandler_DuplicateIntegrationResponseKey(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/integrations/%s/integrationresponses", apiID, integration.IntegrationID) @@ -195,7 +196,7 @@ func TestHandler_GetIntegrationResponses(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) integrationID = integration.IntegrationID } @@ -215,7 +216,7 @@ func TestHandler_GetIntegrationResponses(t *testing.T) { var out struct { Items []apigatewayv2.IntegrationResponse `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.responseCnt) } }) @@ -255,7 +256,7 @@ func TestHandler_GetIntegrationResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/integrations/%s/integrationresponses", apiID, integration.IntegrationID) rr = doRequest(t, h, http.MethodPost, path, map[string]any{ @@ -264,7 +265,7 @@ func TestHandler_GetIntegrationResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var ir apigatewayv2.IntegrationResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir, jsonx.V1())) responseID := ir.IntegrationResponseID if tt.useWrongID { @@ -312,7 +313,7 @@ func TestHandler_DeleteIntegrationResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/integrations/%s/integrationresponses", apiID, integration.IntegrationID) rr = doRequest(t, h, http.MethodPost, path, map[string]any{ @@ -321,7 +322,7 @@ func TestHandler_DeleteIntegrationResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var ir apigatewayv2.IntegrationResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir, jsonx.V1())) responseID := ir.IntegrationResponseID if tt.useWrongID { @@ -354,14 +355,14 @@ func TestHandler_UpdateIntegrationResponse(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) var i apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &i)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &i, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, fmt.Sprintf("/v2/apis/%s/integrations/%s/integrationresponses", apiID, i.IntegrationID), map[string]any{"integrationResponseKey": "200"}) require.Equal(t, http.StatusCreated, rr.Code) var ir apigatewayv2.IntegrationResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ir, jsonx.V1())) return apiID, i.IntegrationID, ir.IntegrationResponseID }, diff --git a/services/apigatewayv2/handler_integrations_test.go b/services/apigatewayv2/handler_integrations_test.go index 7a5e9ca429..5f1fcceda9 100644 --- a/services/apigatewayv2/handler_integrations_test.go +++ b/services/apigatewayv2/handler_integrations_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -54,7 +55,7 @@ func TestHandler_CreateIntegration(t *testing.T) { if tt.wantStatus == http.StatusCreated { var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) assert.Equal(t, tt.integrationType, integration.IntegrationType) assert.NotEmpty(t, integration.IntegrationID) } @@ -123,7 +124,7 @@ func TestHandler_GetIntegrations(t *testing.T) { } var resp listResp - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, len(tt.integrations)) } }) @@ -165,7 +166,7 @@ func TestHandler_GetIntegration(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) integrationID = integration.IntegrationID } @@ -210,7 +211,7 @@ func TestHandler_DeleteIntegration(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) integrationID = integration.IntegrationID } @@ -261,7 +262,7 @@ func TestHandler_UpdateIntegration(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integration apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integration, jsonx.V1())) integrationID = integration.IntegrationID } @@ -350,7 +351,7 @@ func TestHandler_CreateIntegration_InvalidType(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, fmt.Sprintf("/v2/apis/%s/integrations", api.APIID), tt.body) assert.Equal(t, tt.wantStatus, rr.Code) @@ -405,7 +406,7 @@ func TestGetIntegrations_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), tc.maxResults) for _, s := range resp.Items { diff --git a/services/apigatewayv2/handler_portals.go b/services/apigatewayv2/handler_portals.go index 9f90fa6f98..a7a250367d 100644 --- a/services/apigatewayv2/handler_portals.go +++ b/services/apigatewayv2/handler_portals.go @@ -1,7 +1,8 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -9,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -449,7 +451,12 @@ func (h *Handler) handleUpdatePortal(c *echo.Context, portalID string) error { func (h *Handler) handlePublishPortal(c *echo.Context, portalID string) error { var input PublishPortalInput - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil && !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &input, + jsonx.V1(), + ); err != nil && + !errors.Is(err, io.EOF) { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } @@ -521,7 +528,7 @@ func (h *Handler) handleGetPortalProductSharingPolicy(c *echo.Context, portalPro func (h *Handler) handlePutPortalProductSharingPolicy(c *echo.Context, portalProductID string) error { var input PortalProductSharingPolicy - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } diff --git a/services/apigatewayv2/handler_portals_test.go b/services/apigatewayv2/handler_portals_test.go index 397f1886db..e53bbcf98e 100644 --- a/services/apigatewayv2/handler_portals_test.go +++ b/services/apigatewayv2/handler_portals_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -123,7 +124,7 @@ func TestHandler_CreatePortal(t *testing.T) { if tt.wantPortal { var portal apigatewayv2.Portal - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &portal)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &portal, jsonx.V1())) assert.NotEmpty(t, portal.PortalID) assert.Empty(t, portal.PublishStatus) require.NotNil(t, portal.LastModified) @@ -184,7 +185,7 @@ func TestHandler_CreatePortalProduct(t *testing.T) { if tt.wantName != "" { var product apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product, jsonx.V1())) assert.Equal(t, tt.wantName, product.DisplayName) assert.NotEmpty(t, product.PortalProductID) } @@ -234,7 +235,7 @@ func TestHandler_CreateProductPage(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var product apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product, jsonx.V1())) portalProductID = product.PortalProductID } @@ -252,7 +253,7 @@ func TestHandler_CreateProductPage(t *testing.T) { if tt.wantPage { var page apigatewayv2.ProductPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page, jsonx.V1())) assert.NotEmpty(t, page.ProductPageID) } }) @@ -333,7 +334,7 @@ func TestHandler_CreateProductRestEndpointPage(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var product apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product, jsonx.V1())) portalProductID = product.PortalProductID } @@ -351,7 +352,7 @@ func TestHandler_CreateProductRestEndpointPage(t *testing.T) { if tt.wantPage { var page apigatewayv2.ProductRestEndpointPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page, jsonx.V1())) assert.NotEmpty(t, page.ProductRestEndpointPageID) require.NotNil(t, page.RestEndpointIdentifier) require.NotNil(t, page.RestEndpointIdentifier.IdentifierParts) @@ -399,7 +400,7 @@ func TestHandler_ListPortals(t *testing.T) { var out struct { Items []apigatewayv2.Portal `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.portalCnt) }) } @@ -434,7 +435,7 @@ func TestHandler_GetPortal(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var portal apigatewayv2.Portal - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &portal)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &portal, jsonx.V1())) portalID := portal.PortalID if tt.useWrongID { @@ -486,7 +487,7 @@ func TestHandler_ListPortalProducts(t *testing.T) { var out struct { Items []apigatewayv2.PortalProduct `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.productCnt) }) } @@ -523,7 +524,7 @@ func TestHandler_GetPortalProduct(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var pp apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp, jsonx.V1())) ppID := pp.PortalProductID if tt.useWrongID { @@ -578,7 +579,7 @@ func TestHandler_ListProductPages(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var pp apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp, jsonx.V1())) ppID = pp.PortalProductID } @@ -596,7 +597,7 @@ func TestHandler_ListProductPages(t *testing.T) { var out struct { Items []apigatewayv2.ProductPage `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.pageCnt) } }) @@ -645,7 +646,7 @@ func TestHandler_ListProductRestEndpointPages(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var pp apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp, jsonx.V1())) ppID = pp.PortalProductID } @@ -671,7 +672,7 @@ func TestHandler_ListProductRestEndpointPages(t *testing.T) { var out struct { Items []apigatewayv2.ProductRestEndpointPage `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.pageCnt) } }) @@ -683,7 +684,7 @@ func createPortal(t *testing.T, h *apigatewayv2.Handler) string { rr := doRequest(t, h, http.MethodPost, "/v2/portals", validCreatePortalBody()) require.Equal(t, http.StatusCreated, rr.Code) var p apigatewayv2.Portal - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p, jsonx.V1())) return p.PortalID } @@ -695,7 +696,7 @@ func createPortalProduct(t *testing.T, h *apigatewayv2.Handler) string { }) require.Equal(t, http.StatusCreated, rr.Code) var pp apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &pp, jsonx.V1())) return pp.PortalProductID } @@ -879,7 +880,7 @@ func TestHandler_GetProductPage(t *testing.T) { fmt.Sprintf("/v2/portalproducts/%s/productpages", ppID), map[string]any{}) require.Equal(t, http.StatusCreated, rr.Code) var p apigatewayv2.ProductPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p, jsonx.V1())) return ppID, p.ProductPageID }, @@ -932,7 +933,7 @@ func TestHandler_DeleteProductPage(t *testing.T) { fmt.Sprintf("/v2/portalproducts/%s/productpages", ppID), map[string]any{}) require.Equal(t, http.StatusCreated, rr.Code) var p apigatewayv2.ProductPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p, jsonx.V1())) return ppID, p.ProductPageID }, @@ -986,7 +987,7 @@ func TestHandler_GetProductRestEndpointPage(t *testing.T) { ) require.Equal(t, http.StatusCreated, rr.Code) var p apigatewayv2.ProductRestEndpointPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p, jsonx.V1())) return ppID, p.ProductRestEndpointPageID }, @@ -1040,7 +1041,7 @@ func TestHandler_DeleteProductRestEndpointPage(t *testing.T) { ) require.Equal(t, http.StatusCreated, rr.Code) var p apigatewayv2.ProductRestEndpointPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &p, jsonx.V1())) return ppID, p.ProductRestEndpointPageID }, @@ -1094,7 +1095,7 @@ func TestHandler_ListPortals_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.Portal `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Items, 1, "maxResults=1 must limit the page to 1 item") require.NotEmpty(t, page1.NextToken, "a partial page must return a nextToken") @@ -1106,7 +1107,7 @@ func TestHandler_ListPortals_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.Portal `json:"items"` } - require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.Items, portalCount-1, "second page must return the remainder") } @@ -1130,7 +1131,7 @@ func TestHandler_ListPortalProducts_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.PortalProduct `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Items, 1, "maxResults=1 must limit the page to 1 item") require.NotEmpty(t, page1.NextToken, "a partial page must return a nextToken") @@ -1142,6 +1143,6 @@ func TestHandler_ListPortalProducts_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.PortalProduct `json:"items"` } - require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.Items, productCount-1, "second page must return the remainder") } diff --git a/services/apigatewayv2/handler_route_responses_test.go b/services/apigatewayv2/handler_route_responses_test.go index e99cd4209e..3c276fb51a 100644 --- a/services/apigatewayv2/handler_route_responses_test.go +++ b/services/apigatewayv2/handler_route_responses_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -69,7 +70,7 @@ func TestHandler_CreateRouteResponse(t *testing.T) { require.Equal(t, http.StatusCreated, routeRR.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &route, jsonx.V1())) routeID = route.RouteID } @@ -91,7 +92,7 @@ func TestHandler_CreateRouteResponse(t *testing.T) { if tt.wantRespKey != "" { var rresp apigatewayv2.RouteResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rresp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rresp, jsonx.V1())) assert.Equal(t, tt.wantRespKey, rresp.RouteResponseKey) assert.NotEmpty(t, rresp.RouteResponseID) } @@ -125,7 +126,7 @@ func TestHandler_DuplicateRouteResponseKey(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/routes/%s/routeresponses", apiID, route.RouteID) @@ -187,7 +188,7 @@ func TestHandler_GetRouteResponses(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) routeID = route.RouteID } @@ -199,7 +200,7 @@ func TestHandler_GetRouteResponses(t *testing.T) { var out struct { Items []apigatewayv2.RouteResponse `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Items, tt.responseCnt) } }) @@ -238,7 +239,7 @@ func TestHandler_GetRouteResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/routes/%s/routeresponses", apiID, route.RouteID) rr = doRequest(t, h, http.MethodPost, path, map[string]any{ @@ -247,7 +248,7 @@ func TestHandler_GetRouteResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var rr2 apigatewayv2.RouteResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2, jsonx.V1())) responseID := rr2.RouteResponseID if tt.useWrongID { @@ -293,7 +294,7 @@ func TestHandler_DeleteRouteResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) path := fmt.Sprintf("/v2/apis/%s/routes/%s/routeresponses", apiID, route.RouteID) rr = doRequest(t, h, http.MethodPost, path, map[string]any{ @@ -302,7 +303,7 @@ func TestHandler_DeleteRouteResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var rr2 apigatewayv2.RouteResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2, jsonx.V1())) responseID := rr2.RouteResponseID if tt.useWrongID { @@ -334,14 +335,14 @@ func TestHandler_UpdateRouteResponse(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) var r apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &r, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, fmt.Sprintf("/v2/apis/%s/routes/%s/routeresponses", apiID, r.RouteID), map[string]any{"routeResponseKey": "$default"}) require.Equal(t, http.StatusCreated, rr.Code) var rr2 apigatewayv2.RouteResponse - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &rr2, jsonx.V1())) return apiID, r.RouteID, rr2.RouteResponseID }, diff --git a/services/apigatewayv2/handler_routes_test.go b/services/apigatewayv2/handler_routes_test.go index 6d535e431e..88edf4b4fc 100644 --- a/services/apigatewayv2/handler_routes_test.go +++ b/services/apigatewayv2/handler_routes_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -54,7 +55,7 @@ func TestHandler_CreateRoute(t *testing.T) { if tt.wantStatus == http.StatusCreated { var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) assert.Equal(t, tt.routeKey, route.RouteKey) assert.NotEmpty(t, route.RouteID) } @@ -117,7 +118,7 @@ func TestHandler_GetRoutes(t *testing.T) { } var resp listResp - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, len(tt.routes)) } }) @@ -159,7 +160,7 @@ func TestHandler_GetRoute(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) routeID = route.RouteID } @@ -204,7 +205,7 @@ func TestHandler_DeleteRoute(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) routeID = route.RouteID } @@ -249,7 +250,7 @@ func TestHandler_UpdateRoute(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var route apigatewayv2.Route - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &route, jsonx.V1())) routeID = route.RouteID } @@ -355,7 +356,7 @@ func TestUpdateRouteClearsAuthorizerIDOnNone(t *testing.T) { require.Equal(t, http.StatusCreated, authRR.Code) var authResult map[string]any - require.NoError(t, json.Unmarshal(authRR.Body.Bytes(), &authResult)) + require.NoError(t, json.Unmarshal(authRR.Body.Bytes(), &authResult, jsonx.V1())) authID := authResult["authorizerId"].(string) // Create a route with JWT authorization. @@ -367,7 +368,7 @@ func TestUpdateRouteClearsAuthorizerIDOnNone(t *testing.T) { require.Equal(t, http.StatusCreated, routeRR.Code) var routeResult map[string]any - require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &routeResult)) + require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &routeResult, jsonx.V1())) routeID := routeResult["routeId"].(string) // Patch the route with the test body. @@ -381,7 +382,7 @@ func TestUpdateRouteClearsAuthorizerIDOnNone(t *testing.T) { require.Equal(t, http.StatusOK, patchRR.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(patchRR.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(patchRR.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, tt.wantAuthType, updated["authorizationType"]) @@ -417,7 +418,7 @@ func TestUpdateRouteClearsAuthorizerIDRoundtrip(t *testing.T) { require.Equal(t, http.StatusCreated, authRR.Code) var authResult map[string]any - require.NoError(t, json.Unmarshal(authRR.Body.Bytes(), &authResult)) + require.NoError(t, json.Unmarshal(authRR.Body.Bytes(), &authResult, jsonx.V1())) authID := authResult["authorizerId"].(string) // Create route with JWT auth. @@ -429,7 +430,7 @@ func TestUpdateRouteClearsAuthorizerIDRoundtrip(t *testing.T) { require.Equal(t, http.StatusCreated, routeRR.Code) var routeResult map[string]any - require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &routeResult)) + require.NoError(t, json.Unmarshal(routeRR.Body.Bytes(), &routeResult, jsonx.V1())) routeID := routeResult["routeId"].(string) // Verify initial state: JWT auth with authorizer ID set. @@ -446,7 +447,7 @@ func TestUpdateRouteClearsAuthorizerIDRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, getRR.Code) var getResult map[string]any - require.NoError(t, json.Unmarshal(getRR.Body.Bytes(), &getResult)) + require.NoError(t, json.Unmarshal(getRR.Body.Bytes(), &getResult, jsonx.V1())) assert.Equal(t, "NONE", getResult["authorizationType"]) assert.Empty(t, getResult["authorizerId"], "authorizerId should be empty after UpdateRoute to NONE") @@ -498,7 +499,7 @@ func TestGetRoutes_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []apigatewayv2.Route `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), tc.maxResults) for _, r := range resp.Items { @@ -540,7 +541,7 @@ func TestCreateRoute_APIKeyRequiredPresentAndFalse(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) v, hasKey := resp["apiKeyRequired"] assert.True(t, hasKey, "apiKeyRequired should be present in response") @@ -562,7 +563,7 @@ func TestCreateRoute_AuthorizationScopesIsEmptyArray(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) raw, hasKey := resp["authorizationScopes"] assert.True(t, hasKey, "authorizationScopes should be present in response") @@ -586,7 +587,7 @@ func TestGetRoute_AuthorizationScopesIsEmptyArray(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created, jsonx.V1())) routeID, _ := created["routeId"].(string) require.NotEmpty(t, routeID) @@ -595,7 +596,7 @@ func TestGetRoute_AuthorizationScopesIsEmptyArray(t *testing.T) { require.Equal(t, http.StatusOK, rr2.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rr2.Body.Bytes(), &got, jsonx.V1())) raw, hasKey := got["authorizationScopes"] assert.True(t, hasKey, "authorizationScopes should be present in GetRoute response") diff --git a/services/apigatewayv2/handler_stages_test.go b/services/apigatewayv2/handler_stages_test.go index 9759e2bbce..dac4678ee6 100644 --- a/services/apigatewayv2/handler_stages_test.go +++ b/services/apigatewayv2/handler_stages_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -54,7 +55,7 @@ func TestHandler_CreateStage(t *testing.T) { if tt.wantStatus == http.StatusCreated { var stage apigatewayv2.Stage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &stage)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &stage, jsonx.V1())) assert.Equal(t, tt.stageName, stage.StageName) } }) @@ -116,7 +117,7 @@ func TestHandler_GetStages(t *testing.T) { } var resp listResp - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, len(tt.stages)) } }) @@ -306,7 +307,7 @@ func TestGetStages_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.LessOrEqual(t, len(resp.Items), tc.maxResults) for _, s := range resp.Items { diff --git a/services/apigatewayv2/handler_tags.go b/services/apigatewayv2/handler_tags.go index cb84fee7c4..8e2ee0a080 100644 --- a/services/apigatewayv2/handler_tags.go +++ b/services/apigatewayv2/handler_tags.go @@ -1,7 +1,8 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -75,7 +77,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string) error { log := logger.Load(c.Request().Context()) var input tagResourceInput - if err := json.NewDecoder(c.Request().Body).Decode(&input); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &input, jsonx.V1()); err != nil { return writeErr(c, http.StatusBadRequest, msgInvalidBody) } diff --git a/services/apigatewayv2/handler_tags_test.go b/services/apigatewayv2/handler_tags_test.go index d8ba9fd64f..6e44eea8d3 100644 --- a/services/apigatewayv2/handler_tags_test.go +++ b/services/apigatewayv2/handler_tags_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Tags(t *testing.T) { @@ -75,7 +77,7 @@ func TestHandler_Tags(t *testing.T) { var out struct { Tags map[string]string `json:"tags"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantTags, out.Tags) } }) diff --git a/services/apigatewayv2/handler_test.go b/services/apigatewayv2/handler_test.go index eb896410a1..ec86211a08 100644 --- a/services/apigatewayv2/handler_test.go +++ b/services/apigatewayv2/handler_test.go @@ -3,7 +3,7 @@ package apigatewayv2_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *apigatewayv2.Handler, method, path string, body var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) } else { @@ -77,7 +78,7 @@ func createAPI(t *testing.T, h *apigatewayv2.Handler, name string) string { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) return api.APIID } diff --git a/services/apigatewayv2/handler_vpc_links_test.go b/services/apigatewayv2/handler_vpc_links_test.go index 43e9895a18..41cd5fffa5 100644 --- a/services/apigatewayv2/handler_vpc_links_test.go +++ b/services/apigatewayv2/handler_vpc_links_test.go @@ -1,13 +1,15 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -46,14 +48,14 @@ func TestVpcLinkSecurityGroupIDsNullBug(t *testing.T) { rr := doRequest(t, h, http.MethodPost, "/v2/vpclinks", tt.body) require.Equal(t, http.StatusCreated, rr.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw, jsonx.V1())) _, ok := raw["securityGroupIds"] assert.True(t, ok, "securityGroupIds key must always be present in VpcLink response") var link apigatewayv2.VpcLink - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &link)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &link, jsonx.V1())) assert.Equal(t, tt.wantSecGroups, link.SecurityGroupIDs) }) } @@ -71,8 +73,8 @@ func TestVpcLinkSubnetIDsNullBug(t *testing.T) { }) require.Equal(t, http.StatusCreated, rr.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &raw, jsonx.V1())) _, ok := raw["subnetIds"] assert.True(t, ok, "subnetIds key must always be present in VpcLink response") @@ -92,13 +94,13 @@ func TestVpcLinkGetReturnsArrayFields(t *testing.T) { require.Equal(t, http.StatusCreated, createRR.Code) var created apigatewayv2.VpcLink - require.NoError(t, json.Unmarshal(createRR.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRR.Body.Bytes(), &created, jsonx.V1())) getRR := doRequest(t, h, http.MethodGet, "/v2/vpclinks/"+created.VpcLinkID, nil) require.Equal(t, http.StatusOK, getRR.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(getRR.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(getRR.Body.Bytes(), &raw, jsonx.V1())) _, hasSec := raw["securityGroupIds"] assert.True(t, hasSec, "securityGroupIds key must be present in GetVpcLink response") diff --git a/services/apigatewayv2/http_proxy.go b/services/apigatewayv2/http_proxy.go index bcf614317b..5a81bbbae4 100644 --- a/services/apigatewayv2/http_proxy.go +++ b/services/apigatewayv2/http_proxy.go @@ -2,7 +2,7 @@ package apigatewayv2 import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -19,6 +19,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -373,7 +374,7 @@ func buildV1Payload( }, } - p, _ := json.Marshal(event) + p, _ := json.Marshal(event, jsonx.V1()) return p } @@ -451,7 +452,7 @@ func buildV2Payload( }, } - p, _ := json.Marshal(event) + p, _ := json.Marshal(event, jsonx.V1()) return p } diff --git a/services/apigatewayv2/http_proxy_flows_test.go b/services/apigatewayv2/http_proxy_flows_test.go index c29cee4d90..05d211089c 100644 --- a/services/apigatewayv2/http_proxy_flows_test.go +++ b/services/apigatewayv2/http_proxy_flows_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "maps" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -39,7 +40,7 @@ func newFlowAPI(t *testing.T, cors map[string]any) *flowAPI { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) return &flowAPI{h: h, apiID: api.APIID} } @@ -51,7 +52,7 @@ func (f *flowAPI) integration(t *testing.T, body map[string]any) string { require.Equal(t, http.StatusCreated, rr.Code, rr.Body.String()) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) return integ.IntegrationID } @@ -95,7 +96,7 @@ func (r *recordedEvents) last(t *testing.T) map[string]any { func recordingInvoker(rec *recordedEvents, response string) *mockLambdaInvoker { return &mockLambdaInvoker{fn: func(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { var ev map[string]any - if err := json.Unmarshal(payload, &ev); err == nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err == nil { rec.mu.Lock() rec.events = append(rec.events, ev) rec.mu.Unlock() @@ -359,7 +360,7 @@ func TestHTTPAPIProxy_JWTAuthorizerCognitoFlow(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var authz apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authz)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authz, jsonx.V1())) extra := map[string]any{"authorizationType": "JWT", "authorizerId": authz.AuthorizerID} if tt.scopes != nil { @@ -418,7 +419,7 @@ func TestHTTPAPIProxy_LambdaAuthorizerContext(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var authz apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authz)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authz, jsonx.V1())) f.lambdaRoute(t, "GET /secure", "2.0", map[string]any{"authorizationType": "CUSTOM", "authorizerId": authz.AuthorizerID}) diff --git a/services/apigatewayv2/http_proxy_test.go b/services/apigatewayv2/http_proxy_test.go index b4dd6947d7..3f32f6f887 100644 --- a/services/apigatewayv2/http_proxy_test.go +++ b/services/apigatewayv2/http_proxy_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -36,7 +37,7 @@ func (m *mockLambdaInvoker) InvokeFunction( } resp := map[string]any{"statusCode": 200, "body": "ok"} - b, _ := json.Marshal(resp) + b, _ := json.Marshal(resp, jsonx.V1()) return b, 200, nil } @@ -119,7 +120,7 @@ func buildHTTPAPIWithLambda( require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) theAPIID := api.APIID @@ -132,7 +133,7 @@ func buildHTTPAPIWithLambda( require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) theIntegrationID := integ.IntegrationID @@ -222,12 +223,12 @@ func TestHTTPAPIProxy_RouteMatching(t *testing.T) { lambda := &mockLambdaInvoker{ fn: func(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { var ev map[string]any - if err := json.Unmarshal(payload, &ev); err == nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err == nil { capturedRouteKey, _ = ev["routeKey"].(string) } resp := map[string]any{"statusCode": 200, "body": "routed"} - b, _ := json.Marshal(resp) + b, _ := json.Marshal(resp, jsonx.V1()) return b, 200, nil }, @@ -247,7 +248,7 @@ func TestHTTPAPIProxy_RouteMatching(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+apiID+"/routes", map[string]any{ "routeKey": rk, @@ -337,10 +338,10 @@ func TestHTTPAPIProxy_PayloadFormat(t *testing.T) { var capturedPayload map[string]any lambda := &mockLambdaInvoker{ fn: func(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { - _ = json.Unmarshal(payload, &capturedPayload) + _ = json.Unmarshal(payload, &capturedPayload, jsonx.V1()) resp := map[string]any{"statusCode": 200, "body": "ok"} - b, _ := json.Marshal(resp) + b, _ := json.Marshal(resp, jsonx.V1()) return b, 200, nil }, @@ -354,7 +355,7 @@ func TestHTTPAPIProxy_PayloadFormat(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/integrations", map[string]any{ "integrationType": "AWS_PROXY", @@ -364,7 +365,7 @@ func TestHTTPAPIProxy_PayloadFormat(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/routes", map[string]any{ "routeKey": "GET /test", @@ -414,7 +415,7 @@ func TestHTTPAPIProxy_CORSPreflight(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) // Create a route (even though OPTIONS won't use it). rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/integrations", map[string]any{ @@ -424,7 +425,7 @@ func TestHTTPAPIProxy_CORSPreflight(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/routes", map[string]any{ "routeKey": "GET /items", @@ -459,7 +460,7 @@ func TestHTTPAPIProxy_JWTAuthorizer(t *testing.T) { fn: func(_ context.Context, _, _ string, _ []byte) ([]byte, int, error) { integrationCalls.Add(1) - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, @@ -472,7 +473,7 @@ func TestHTTPAPIProxy_JWTAuthorizer(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) // Create JWT authorizer. rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/authorizers", map[string]any{ @@ -487,7 +488,7 @@ func TestHTTPAPIProxy_JWTAuthorizer(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var authorizer apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authorizer)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authorizer, jsonx.V1())) // Create integration + route with JWT auth. rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/integrations", map[string]any{ @@ -497,7 +498,7 @@ func TestHTTPAPIProxy_JWTAuthorizer(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/routes", map[string]any{ "routeKey": "GET /secure", @@ -535,7 +536,7 @@ func TestHTTPAPIProxy_JWTAuthorizerMissing_DoesNotInvokeIntegration(t *testing.T fn: func(_ context.Context, _, _ string, _ []byte) ([]byte, int, error) { integrationCalls.Add(1) - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, @@ -547,7 +548,7 @@ func TestHTTPAPIProxy_JWTAuthorizerMissing_DoesNotInvokeIntegration(t *testing.T require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/integrations", map[string]any{ "integrationType": "AWS_PROXY", @@ -556,7 +557,7 @@ func TestHTTPAPIProxy_JWTAuthorizerMissing_DoesNotInvokeIntegration(t *testing.T require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) // authorizerId names an authorizer that was never created. rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/routes", map[string]any{ @@ -612,7 +613,7 @@ func TestHTTPAPIProxy_JWTAuthorizer_ValidCognitoToken(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var api apigatewayv2.API - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &api, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/authorizers", map[string]any{ "name": "cognito-jwt", @@ -626,7 +627,7 @@ func TestHTTPAPIProxy_JWTAuthorizer_ValidCognitoToken(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var authorizer apigatewayv2.Authorizer - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authorizer)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &authorizer, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/integrations", map[string]any{ "integrationType": "AWS_PROXY", @@ -635,7 +636,7 @@ func TestHTTPAPIProxy_JWTAuthorizer_ValidCognitoToken(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+api.APIID+"/routes", map[string]any{ "routeKey": "GET /secure", @@ -707,7 +708,7 @@ func TestHTTPAPIProxy_LambdaResponse(t *testing.T) { lambda := &mockLambdaInvoker{ fn: func(_ context.Context, _, _ string, _ []byte) ([]byte, int, error) { - b, _ := json.Marshal(tt.lambdaBody) + b, _ := json.Marshal(tt.lambdaBody, jsonx.V1()) return b, 200, nil }, @@ -744,13 +745,13 @@ func TestHTTPAPIProxy_PathParameters(t *testing.T) { h.SetLambdaInvoker(&mockLambdaInvoker{ fn: func(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { var ev map[string]any - if err := json.Unmarshal(payload, &ev); err == nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err == nil { if pp, ok := ev["pathParameters"].(map[string]any); ok { capturedPathParams = pp } } - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, @@ -795,7 +796,7 @@ func TestHTTPAPIProxy_DeploymentSnapshot(t *testing.T) { body = "from-b" } - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": body}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": body}, jsonx.V1()) return b, 200, nil }, @@ -812,7 +813,7 @@ func TestHTTPAPIProxy_DeploymentSnapshot(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var integ apigatewayv2.Integration - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &integ, jsonx.V1())) rr = doRequest(t, h, http.MethodPost, "/v2/apis/"+apiID+"/routes", map[string]any{ "routeKey": "GET /a", diff --git a/services/apigatewayv2/http_proxy_throttle_test.go b/services/apigatewayv2/http_proxy_throttle_test.go index ec9b1a26dd..4ec7a6a633 100644 --- a/services/apigatewayv2/http_proxy_throttle_test.go +++ b/services/apigatewayv2/http_proxy_throttle_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "sync/atomic" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -38,7 +39,7 @@ func TestHTTPAPIProxy_RouteThrottle_TooManyRequests(t *testing.T) { fn: func(_ context.Context, _, _ string, _ []byte) ([]byte, int, error) { integrationCalls.Add(1) - b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}) + b, _ := json.Marshal(map[string]any{"statusCode": 200, "body": "ok"}, jsonx.V1()) return b, 200, nil }, diff --git a/services/apigatewayv2/lambda_response.go b/services/apigatewayv2/lambda_response.go index 6d560285f1..f7a547293d 100644 --- a/services/apigatewayv2/lambda_response.go +++ b/services/apigatewayv2/lambda_response.go @@ -3,11 +3,14 @@ package apigatewayv2 import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -22,16 +25,16 @@ const ( func writeHTTPAPILambdaResponse(c *echo.Context, respBytes []byte, payloadVersion string) error { trimmed := bytes.TrimSpace(respBytes) - var envelope map[string]json.RawMessage + var envelope map[string]jsontext.Value - isObject := json.Unmarshal(trimmed, &envelope) == nil && envelope != nil + isObject := json.Unmarshal(trimmed, &envelope, jsonx.V1()) == nil && envelope != nil if isObject && isFunctionErrorEnvelope(envelope) { return writeJSONMessage(c, http.StatusInternalServerError, msgInternalError) } _, hasStatus := envelope["statusCode"] if !isObject || !hasStatus { - if payloadVersion == payloadFormatV1 || len(trimmed) == 0 || !json.Valid(trimmed) { + if payloadVersion == payloadFormatV1 || len(trimmed) == 0 || !jsontext.Value(trimmed).IsValid(jsonx.V1()) { return writeJSONMessage(c, http.StatusInternalServerError, msgInternalError) } @@ -43,14 +46,14 @@ func writeHTTPAPILambdaResponse(c *echo.Context, respBytes []byte, payloadVersio } var lambdaResp httpAPILambdaResponse - if err := json.Unmarshal(trimmed, &lambdaResp); err != nil { + if err := json.Unmarshal(trimmed, &lambdaResp, jsonx.V1()); err != nil { return writeJSONMessage(c, http.StatusInternalServerError, msgInternalError) } return writeLambdaEnvelope(c, &lambdaResp) } -func isFunctionErrorEnvelope(envelope map[string]json.RawMessage) bool { +func isFunctionErrorEnvelope(envelope map[string]jsontext.Value) bool { _, hasMsg := envelope["errorMessage"] _, hasType := envelope["errorType"] _, hasStatus := envelope["statusCode"] @@ -111,7 +114,7 @@ func writeJSONMessage(c *echo.Context, status int, body string) error { // httpAPIMessage renders the `{"message":"..."}` body execute-api returns for gateway errors. func httpAPIMessage(msg string) string { - b, _ := json.Marshal(map[string]string{jsonKeyMessage: msg}) + b, _ := json.Marshal(map[string]string{jsonKeyMessage: msg}, jsonx.V1()) return strings.TrimSpace(string(b)) } diff --git a/services/apigatewayv2/models.go b/services/apigatewayv2/models.go index 9bfc20e50c..45f18033b7 100644 --- a/services/apigatewayv2/models.go +++ b/services/apigatewayv2/models.go @@ -1,8 +1,10 @@ package apigatewayv2 import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // isoTime wraps [time.Time] and marshals to/from a JSON string in RFC3339 format, @@ -12,12 +14,12 @@ type isoTime struct { } func (t isoTime) MarshalJSON() ([]byte, error) { - return json.Marshal(t.UTC().Format(time.RFC3339)) + return json.Marshal(t.UTC().Format(time.RFC3339), jsonx.V1()) } func (t *isoTime) UnmarshalJSON(b []byte) error { var s string - if err := json.Unmarshal(b, &s); err != nil { + if err := json.Unmarshal(b, &s, jsonx.V1()); err != nil { return err } diff --git a/services/apigatewayv2/multi_region_test.go b/services/apigatewayv2/multi_region_test.go index c6ed1738dc..87abad8c80 100644 --- a/services/apigatewayv2/multi_region_test.go +++ b/services/apigatewayv2/multi_region_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -37,7 +38,7 @@ func regionCall(t *testing.T, h *apigatewayv2.Handler, region, method, path, bod require.Less(t, rec.Code, http.StatusMultipleChoices, rec.Body.String()) out := map[string]any{} - if rec.Body.Len() > 0 && json.Unmarshal(rec.Body.Bytes(), &out) != nil { + if rec.Body.Len() > 0 && json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) != nil { out["raw"] = rec.Body.String() } diff --git a/services/apigatewayv2/persistence.go b/services/apigatewayv2/persistence.go index 80c00f5490..24a5ec5aca 100644 --- a/services/apigatewayv2/persistence.go +++ b/services/apigatewayv2/persistence.go @@ -2,7 +2,7 @@ package apigatewayv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -619,9 +619,9 @@ var dirtyTableNames = struct { // registerAllTables doc for the clean/dirty split), merged into one map so a // single Tables blob round-trips the whole backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - PortalProductSharingPolicies map[string]string `json:"portalProductSharingPolicies,omitempty"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + PortalProductSharingPolicies map[string]string `json:"portalProductSharingPolicies,omitempty"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/apigatewayv2/rse.go b/services/apigatewayv2/rse.go index 56b1fdbecb..1a9c190373 100644 --- a/services/apigatewayv2/rse.go +++ b/services/apigatewayv2/rse.go @@ -2,11 +2,13 @@ package apigatewayv2 import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // EvalRouteSelectionExpression evaluates an AWS API Gateway v2 route-selection @@ -95,7 +97,7 @@ func parseJSONBody(r *http.Request) map[string]any { } var obj map[string]any - if jsonErr := json.Unmarshal(raw, &obj); jsonErr != nil { + if jsonErr := json.Unmarshal(raw, &obj, jsonx.V1()); jsonErr != nil { return nil } diff --git a/services/apigatewayv2/wire_field_fixes_respsweep_test.go b/services/apigatewayv2/wire_field_fixes_respsweep_test.go index 5b4ad9a947..b14aceede6 100644 --- a/services/apigatewayv2/wire_field_fixes_respsweep_test.go +++ b/services/apigatewayv2/wire_field_fixes_respsweep_test.go @@ -1,13 +1,14 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -74,7 +75,7 @@ func testPortalIncludedPortalProductArnsPresent(t *testing.T, h *apigatewayv2.Ha require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created, jsonx.V1())) arns, ok := created["includedPortalProductArns"] assert.True(t, ok, "includedPortalProductArns key must be present on Create") assert.Equal(t, []any{}, arns) @@ -85,7 +86,7 @@ func testPortalIncludedPortalProductArnsPresent(t *testing.T, h *apigatewayv2.Ha var listed struct { Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed, jsonx.V1())) require.Len(t, listed.Items, 1) arns, ok = listed.Items[0]["includedPortalProductArns"] @@ -100,7 +101,7 @@ func testPortalProductDescriptionPresent(t *testing.T, h *apigatewayv2.Handler) require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created, jsonx.V1())) desc, ok := created["description"] assert.True(t, ok, "description key must be present on Create") assert.Empty(t, desc) @@ -111,7 +112,7 @@ func testPortalProductDescriptionPresent(t *testing.T, h *apigatewayv2.Handler) var listed struct { Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed, jsonx.V1())) require.Len(t, listed.Items, 1) desc, ok = listed.Items[0]["description"] @@ -126,7 +127,7 @@ func testProductPageArnAndTitlePresent(t *testing.T, h *apigatewayv2.Handler) { require.Equal(t, http.StatusCreated, rr.Code) var product map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product, jsonx.V1())) portalProductID, _ := product["portalProductId"].(string) require.NotEmpty(t, portalProductID) @@ -137,7 +138,7 @@ func testProductPageArnAndTitlePresent(t *testing.T, h *apigatewayv2.Handler) { require.Equal(t, http.StatusCreated, rr.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created, jsonx.V1())) assert.NotEmpty(t, created["productPageArn"]) assert.Equal(t, "My Page", created["pageTitle"]) @@ -147,7 +148,7 @@ func testProductPageArnAndTitlePresent(t *testing.T, h *apigatewayv2.Handler) { var listed struct { Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &listed, jsonx.V1())) require.Len(t, listed.Items, 1) assert.NotEmpty(t, listed.Items[0]["productPageArn"]) assert.Equal(t, "My Page", listed.Items[0]["pageTitle"]) diff --git a/services/apigatewayv2/wire_field_fixes_test.go b/services/apigatewayv2/wire_field_fixes_test.go index 4a91cb8575..3b2e674e22 100644 --- a/services/apigatewayv2/wire_field_fixes_test.go +++ b/services/apigatewayv2/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package apigatewayv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -550,7 +551,7 @@ func TestCreateProductRestEndpointPage_DisplayContent(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var product apigatewayv2.PortalProduct - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &product, jsonx.V1())) body := map[string]any{ "restEndpointIdentifier": map[string]any{ @@ -571,7 +572,7 @@ func TestCreateProductRestEndpointPage_DisplayContent(t *testing.T) { require.Equal(t, http.StatusCreated, rr.Code) var created apigatewayv2.ProductRestEndpointPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, wantEndpoint, created.DisplayContent["endpoint"]) assert.Equal(t, "custom docs", created.DisplayContent["body"]) assert.Equal(t, wantEndpoint, created.Endpoint) @@ -584,7 +585,7 @@ func TestCreateProductRestEndpointPage_DisplayContent(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var got apigatewayv2.ProductRestEndpointPage - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, wantEndpoint, got.DisplayContent["endpoint"]) assert.Equal(t, "custom docs", got.DisplayContent["body"]) assert.Equal(t, wantEndpoint, got.Endpoint) diff --git a/services/apigatewayv2/ws_proxy.go b/services/apigatewayv2/ws_proxy.go index c3d1455490..485e9318ff 100644 --- a/services/apigatewayv2/ws_proxy.go +++ b/services/apigatewayv2/ws_proxy.go @@ -3,7 +3,7 @@ package apigatewayv2 import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -19,6 +19,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -164,7 +165,7 @@ func (s *wsSession) sendForbidden() { jsonKeyMessage: wsForbiddenMessage, "connectionId": s.connID, "requestId": uuid.NewString(), - }) + }, jsonx.V1()) s.send(b) } @@ -223,7 +224,7 @@ func (s *wsSession) invoke( return nil, fmt.Errorf("%w: %s", ErrInvalidLambdaArn, integration.IntegrationURI) } - payload, marshalErr := json.Marshal(event) + payload, marshalErr := json.Marshal(event, jsonx.V1()) if marshalErr != nil { return nil, fmt.Errorf("failed to marshal WebSocket event: %w", marshalErr) } @@ -364,7 +365,7 @@ func proxyResponseStatus(out []byte) int { var resp struct { StatusCode *int `json:"statusCode"` } - if json.Unmarshal(out, &resp) != nil || resp.StatusCode == nil { + if json.Unmarshal(out, &resp, jsonx.V1()) != nil || resp.StatusCode == nil { return 0 } @@ -441,7 +442,7 @@ func (h *Handler) enforceWSRequestAuthorizer(c *echo.Context, s *wsSession, auth "multiValueQueryStringParameters": s.query, "stageVariables": map[string]string{}, evKeyRequestContext: s.requestContext(routeKeyConnect, wsEventConnect), - }) + }, jsonx.V1()) if err != nil { return errRouteAuthConfigInvalid } @@ -458,7 +459,7 @@ func (h *Handler) enforceWSRequestAuthorizer(c *echo.Context, s *wsSession, auth var policy iamPolicyResponse allow, explicit := false, false - if json.Unmarshal(out, &policy) == nil { + if json.Unmarshal(out, &policy, jsonx.V1()) == nil { allow, explicit = evaluateIAMPolicy(policy.PolicyDocument.Statement, methodArn) } @@ -497,7 +498,7 @@ func evalRouteSelection(expr string, body []byte) string { expr = strings.ReplaceAll(expr, `\$`, escaped) var doc any - _ = json.Unmarshal(body, &doc) + _ = json.Unmarshal(body, &doc, jsonx.V1()) failed := false out := wsBodyVarRE.ReplaceAllStringFunc(expr, func(m string) string { diff --git a/services/apigatewayv2/ws_realclient_test.go b/services/apigatewayv2/ws_realclient_test.go index 9f8cf81ee3..d1b9013f1a 100644 --- a/services/apigatewayv2/ws_realclient_test.go +++ b/services/apigatewayv2/ws_realclient_test.go @@ -2,7 +2,7 @@ package apigatewayv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/apigatewaymanagementapi" "github.com/blackbirdworks/gopherstack/services/apigatewayv2" @@ -40,7 +41,7 @@ type wsLambda struct { func (l *wsLambda) InvokeFunction(_ context.Context, fn, _ string, payload []byte) ([]byte, int, error) { var ev map[string]any - _ = json.Unmarshal(payload, &ev) + _ = json.Unmarshal(payload, &ev, jsonx.V1()) l.calls <- wsLambdaCall{fn: fn, event: ev} if h := l.handlers[fn]; h != nil { @@ -459,7 +460,7 @@ func TestWebSocket_NoMatchingRouteSendsForbidden(t *testing.T) { require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(`{"action":"x"}`))) var frame map[string]string - require.NoError(t, json.Unmarshal([]byte(readText(t, conn)), &frame)) + require.NoError(t, json.Unmarshal([]byte(readText(t, conn)), &frame, jsonx.V1())) assert.Equal(t, "Forbidden", frame["message"]) assert.NotEmpty(t, frame["connectionId"]) } diff --git a/services/appconfig/deletion_protection_test.go b/services/appconfig/deletion_protection_test.go index 4f61628b16..9c0389ffed 100644 --- a/services/appconfig/deletion_protection_test.go +++ b/services/appconfig/deletion_protection_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/appconfig" "github.com/blackbirdworks/gopherstack/services/appconfigdata" @@ -58,20 +59,20 @@ func newDeletionProtectionFixture(t *testing.T) *deletionProtectionFixture { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"name":"dp-env"}`)) require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"name":"dp-profile","locationUri":"hosted","type":"AWS.Freeform"}`)) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) return &deletionProtectionFixture{h: h, acd: acd, appID: app.ID, envID: env.ID, profileID: profile.ID} } diff --git a/services/appconfig/errsweep_wire_shape_test.go b/services/appconfig/errsweep_wire_shape_test.go index adea34d7dc..4a2d4e01ee 100644 --- a/services/appconfig/errsweep_wire_shape_test.go +++ b/services/appconfig/errsweep_wire_shape_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/appconfig" @@ -55,6 +56,6 @@ func TestHandler_UnmatchedRouteCarriesTypedErrorCode(t *testing.T) { require.NoError(t, err) var decoded map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) + require.NoError(t, json.Unmarshal(raw, &decoded, jsonx.V1())) require.NotEmpty(t, decoded["message"]) } diff --git a/services/appconfig/extension_actions.go b/services/appconfig/extension_actions.go index dee040d9ba..102a32ee18 100644 --- a/services/appconfig/extension_actions.go +++ b/services/appconfig/extension_actions.go @@ -5,7 +5,8 @@ import ( "crypto/rand" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +14,7 @@ import ( "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -185,7 +187,7 @@ func (b *InMemoryBackend) runPreActions( ev["Parameters"] = params ev["Content"] = base64.StdEncoding.EncodeToString(content) - payload, err := json.Marshal(ev) + payload, err := json.Marshal(ev, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: encoding extension event: %w", ErrBadRequest, err) } @@ -209,12 +211,12 @@ func applyActionResponse(uri string, raw, content []byte) ([]byte, error) { return content, nil } - if !json.Valid(raw) { + if !jsontext.Value(raw).IsValid(jsonx.V1()) { return content, nil } var resp actionResponse - if err := json.Unmarshal(raw, &resp); err != nil { + if err := json.Unmarshal(raw, &resp, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: extension action %s returned an unreadable response: %w", ErrBadRequest, uri, err) } diff --git a/services/appconfig/extension_actions_test.go b/services/appconfig/extension_actions_test.go index c246ad0033..5e350f3b29 100644 --- a/services/appconfig/extension_actions_test.go +++ b/services/appconfig/extension_actions_test.go @@ -3,7 +3,7 @@ package appconfig_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "sync" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -50,7 +51,7 @@ func (r *recordingInvoker) lastEvent(t *testing.T) map[string]any { require.NotEmpty(t, r.payloads) var ev map[string]any - require.NoError(t, json.Unmarshal(r.payloads[len(r.payloads)-1], &ev)) + require.NoError(t, json.Unmarshal(r.payloads[len(r.payloads)-1], &ev, jsonx.V1())) return ev } diff --git a/services/appconfig/extension_events.go b/services/appconfig/extension_events.go index f8e69e7ba6..45a1db3f6a 100644 --- a/services/appconfig/extension_events.go +++ b/services/appconfig/extension_events.go @@ -2,7 +2,8 @@ package appconfig import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "regexp" @@ -11,6 +12,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ebbackend "github.com/blackbirdworks/gopherstack/services/eventbridge" @@ -276,7 +278,7 @@ func (b *InMemoryBackend) deliverAction(act preAction, event map[string]any) ([] return nil, nil } - payload, err := json.Marshal(ev) + payload, err := json.Marshal(ev, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: encoding extension event: %w", ErrBadRequest, err) } @@ -303,7 +305,7 @@ func stringifyDeploymentNumber(ev map[string]any) map[string]any { } func (b *InMemoryBackend) deliverSNS(act preAction, ev map[string]any) error { - msg, err := json.Marshal(stringifyDeploymentNumber(ev)) + msg, err := json.Marshal(stringifyDeploymentNumber(ev), jsonx.V1()) if err != nil { return err } @@ -315,7 +317,7 @@ func (b *InMemoryBackend) deliverSNS(act preAction, ev map[string]any) error { } func (b *InMemoryBackend) deliverSQS(act preAction, ev map[string]any) error { - body, err := json.Marshal(stringifyDeploymentNumber(ev)) + body, err := json.Marshal(stringifyDeploymentNumber(ev), jsonx.V1()) if err != nil { return err } @@ -324,7 +326,7 @@ func (b *InMemoryBackend) deliverSQS(act preAction, ev map[string]any) error { } func (b *InMemoryBackend) deliverEvents(act preAction, ev map[string]any) error { - detail, err := json.Marshal(ev) + detail, err := json.Marshal(ev, jsonx.V1()) if err != nil { return err } @@ -404,12 +406,12 @@ func (b *InMemoryBackend) tickRollbackReason(job tickJob) string { return fmt.Sprintf("Extension action %s failed: %v", act.uri, err) } - if len(raw) == 0 || !json.Valid(raw) { + if len(raw) == 0 || !jsontext.Value(raw).IsValid(jsonx.V1()) { continue } var resp actionResponse - if json.Unmarshal(raw, &resp) != nil { + if json.Unmarshal(raw, &resp, jsonx.V1()) != nil { continue } diff --git a/services/appconfig/extension_events_test.go b/services/appconfig/extension_events_test.go index 6086b0c6cc..c1aa7bf588 100644 --- a/services/appconfig/extension_events_test.go +++ b/services/appconfig/extension_events_test.go @@ -2,7 +2,7 @@ package appconfig_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "slices" "sync" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -104,7 +105,7 @@ type lifecycleInvoker struct { func (l *lifecycleInvoker) Invoke(_ context.Context, _ string, payload []byte) ([]byte, error) { var ev map[string]any - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, err } @@ -270,7 +271,7 @@ func TestExtensionActions_OnDeploymentTargets(t *testing.T) { assert.Equal(t, "OnDeploymentComplete", snsMsgs[1].attrs["MessageType"]) var m map[string]any - require.NoError(t, json.Unmarshal([]byte(snsMsgs[0].message), &m)) + require.NoError(t, json.Unmarshal([]byte(snsMsgs[0].message), &m, jsonx.V1())) assert.Equal(t, "1", m["DeploymentNumber"]) assert.Equal(t, "1", m["ConfigurationVersion"]) assert.Equal(t, "my deploy", m["Description"]) @@ -282,7 +283,7 @@ func TestExtensionActions_OnDeploymentTargets(t *testing.T) { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(sqsMsgs[1]), &m)) + require.NoError(t, json.Unmarshal([]byte(sqsMsgs[1]), &m, jsonx.V1())) assert.Equal(t, "OnDeploymentComplete", m["Type"]) assert.Equal(t, "1", m["DeploymentNumber"]) }}, @@ -297,7 +298,7 @@ func TestExtensionActions_OnDeploymentTargets(t *testing.T) { assert.Contains(t, busMsgs[0].resources[0], ":extensionassociation/") var m map[string]any - require.NoError(t, json.Unmarshal([]byte(busMsgs[0].detail), &m)) + require.NoError(t, json.Unmarshal([]byte(busMsgs[0].detail), &m, jsonx.V1())) assert.InDelta(t, 1, m["DeploymentNumber"], 0) }}, } diff --git a/services/appconfig/extension_sibling_test.go b/services/appconfig/extension_sibling_test.go index b4e068bd7d..92a8de639b 100644 --- a/services/appconfig/extension_sibling_test.go +++ b/services/appconfig/extension_sibling_test.go @@ -1,12 +1,13 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" sqsbackend "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -61,7 +62,7 @@ func TestExtensionActions_SiblingSQSDelivery(t *testing.T) { } var m map[string]any - require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &m)) + require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &m, jsonx.V1())) return m["Type"] == "OnDeploymentComplete" }, eventuallyWait, eventuallyTick) diff --git a/services/appconfig/feature_flags.go b/services/appconfig/feature_flags.go index 49bd0dbcdf..9b78e86cd3 100644 --- a/services/appconfig/feature_flags.go +++ b/services/appconfig/feature_flags.go @@ -1,6 +1,12 @@ package appconfig -import "encoding/json" +import ( + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // featureFlagDocument mirrors the parts of the real AWS.AppConfig.FeatureFlags // JSON schema this backend needs in order to validate CreateExperimentDefinition's @@ -19,7 +25,7 @@ import "encoding/json" // feature flags), so it is documented and field-diffed against AWS's public // user-guide schema page directly rather than SDK-generated code. type featureFlagDocument struct { - Flags map[string]json.RawMessage `json:"flags"` + Flags map[string]jsontext.Value `json:"flags"` } // parseFeatureFlagKeys attempts to parse content as a real @@ -39,7 +45,7 @@ func parseFeatureFlagKeys(content []byte) (map[string]struct{}, bool) { } var doc featureFlagDocument - if err := json.Unmarshal(content, &doc); err != nil || doc.Flags == nil { + if err := json.Unmarshal(content, &doc, jsonx.V1()); err != nil || doc.Flags == nil { return nil, false } diff --git a/services/appconfig/handler_account_settings_test.go b/services/appconfig/handler_account_settings_test.go index 532d856f18..efeb4fc6a6 100644 --- a/services/appconfig/handler_account_settings_test.go +++ b/services/appconfig/handler_account_settings_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -47,7 +48,7 @@ func TestHandler_GetAccountSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var settings appconfig.AccountSettings - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings, jsonx.V1())) } // TestHandler_UpdateAccountSettings_DeletionProtectionFieldsSurviveIndependentUpdates @@ -73,7 +74,7 @@ func TestHandler_UpdateAccountSettings_DeletionProtectionFieldsSurviveIndependen require.Equal(t, http.StatusOK, rec.Code) var settings appconfig.AccountSettings - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings, jsonx.V1())) require.NotNil(t, settings.DeletionProtection) require.NotNil(t, settings.DeletionProtection.Enabled) assert.False(t, *settings.DeletionProtection.Enabled, "B's own field must apply") @@ -84,7 +85,7 @@ func TestHandler_UpdateAccountSettings_DeletionProtectionFieldsSurviveIndependen // GetAccountSettings must reflect the same merged state. rec = doRequest(t, h, http.MethodGet, "/settings", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings, jsonx.V1())) require.NotNil(t, settings.DeletionProtection.ProtectionPeriodInMinutes) assert.EqualValues(t, 120, *settings.DeletionProtection.ProtectionPeriodInMinutes) } @@ -100,7 +101,7 @@ func TestHandler_UpdateAccountSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var settings appconfig.AccountSettings - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings, jsonx.V1())) require.NotNil(t, settings.DeletionProtection) require.NotNil(t, settings.DeletionProtection.Enabled) assert.True(t, *settings.DeletionProtection.Enabled) @@ -109,7 +110,7 @@ func TestHandler_UpdateAccountSettings(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/settings", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settings, jsonx.V1())) require.NotNil(t, settings.DeletionProtection) assert.True(t, *settings.DeletionProtection.Enabled) } diff --git a/services/appconfig/handler_applications_test.go b/services/appconfig/handler_applications_test.go index 9f0ce0c5df..c4db0720e1 100644 --- a/services/appconfig/handler_applications_test.go +++ b/services/appconfig/handler_applications_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -51,7 +52,7 @@ func TestHandler_Application_CRUD(t *testing.T) { if tt.wantName != "" { var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) assert.Equal(t, tt.wantName, app.Name) } }) @@ -76,7 +77,7 @@ func TestHandler_DeleteApplication(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Delete it. rec = doRequest(t, h, http.MethodDelete, "/applications/"+app.ID, nil) @@ -96,7 +97,7 @@ func TestHandler_UpdateApplication(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest( t, @@ -108,7 +109,7 @@ func TestHandler_UpdateApplication(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "updated", updated.Name) assert.Equal(t, "new desc", updated.Description) } @@ -128,7 +129,7 @@ func TestHandler_UpdateApplication_OmittedDescriptionPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Update only Name; Description is omitted from the request body. rec = doRequest(t, h, http.MethodPatch, "/applications/"+app.ID, @@ -136,7 +137,7 @@ func TestHandler_UpdateApplication_OmittedDescriptionPreserved(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "renamed", updated.Name) assert.Equal(t, "keep-me", updated.Description, "omitted Description must not clobber the existing value") @@ -182,7 +183,7 @@ func TestHandler_ListApplicationsPagination(t *testing.T) { NextToken string `json:"NextToken"` Items []any `json:"Items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, 4) assert.Empty(t, resp.NextToken) }) @@ -200,7 +201,7 @@ func TestHandler_ListApplicationsPagination(t *testing.T) { NextToken string `json:"NextToken"` Items []any `json:"Items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, 2) assert.NotEmpty(t, resp.NextToken) }) @@ -219,7 +220,7 @@ func TestHandler_ListApplicationsPagination(t *testing.T) { NextToken string `json:"NextToken"` Items []any `json:"Items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first, jsonx.V1())) require.NotEmpty(t, first.NextToken) rec2 := doRequest( @@ -234,7 +235,7 @@ func TestHandler_ListApplicationsPagination(t *testing.T) { NextToken string `json:"NextToken"` Items []any `json:"Items"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Items, 2) assert.Empty(t, resp.NextToken) }) @@ -260,7 +261,7 @@ func TestHandler_ListApplicationsPagination(t *testing.T) { NextToken string `json:"NextToken"` Items []any `json:"Items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.Items) assert.Empty(t, resp.NextToken) }) @@ -297,14 +298,14 @@ func TestHandler_CreateApplication_TagsAppliedInline(t *testing.T) { body, err := json.Marshal(map[string]any{ "Name": "tagged-app-" + tt.name, "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/applications", body) require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID tagsRec := doRequest(t, h, http.MethodGet, "/tags/"+resourceArn, nil) @@ -313,7 +314,7 @@ func TestHandler_CreateApplication_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) diff --git a/services/appconfig/handler_configuration_profiles_test.go b/services/appconfig/handler_configuration_profiles_test.go index acd049a8b5..0b580a8601 100644 --- a/services/appconfig/handler_configuration_profiles_test.go +++ b/services/appconfig/handler_configuration_profiles_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -23,7 +24,7 @@ func TestHandler_ConfigurationProfile_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Create profile. profileBody := []byte(`{"name":"my-config","locationUri":"hosted","type":"AWS.Freeform"}`) @@ -37,7 +38,7 @@ func TestHandler_ConfigurationProfile_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) assert.Equal(t, "my-config", profile.Name) // Get profile. @@ -70,7 +71,7 @@ func TestHandler_ListConfigurationProfiles_HTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Create two profiles. for _, name := range []string{"prof-1", "prof-2"} { @@ -99,7 +100,7 @@ func TestHandler_UpdateConfigurationProfile_HTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) profileBody := []byte(`{"name":"old-name","locationUri":"hosted","type":"AWS.Freeform"}`) rec = doRequest( @@ -112,7 +113,7 @@ func TestHandler_UpdateConfigurationProfile_HTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) // Update. rec = doRequest( @@ -125,7 +126,7 @@ func TestHandler_UpdateConfigurationProfile_HTTP(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "new-name", updated.Name) } @@ -143,7 +144,7 @@ func TestHandler_UpdateConfigurationProfile_RetrievalRoleArnAndValidators(t *tes require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) profileBody := []byte( `{"Name":"old-name","Description":"keep-me","LocationUri":"hosted","Type":"AWS.Freeform"}`, @@ -152,7 +153,7 @@ func TestHandler_UpdateConfigurationProfile_RetrievalRoleArnAndValidators(t *tes require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) rec = doRequest(t, h, http.MethodPatch, "/applications/"+app.ID+"/configurationprofiles/"+profile.ID, @@ -161,7 +162,7 @@ func TestHandler_UpdateConfigurationProfile_RetrievalRoleArnAndValidators(t *tes assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "arn:aws:iam::123456789012:role/retrieval", updated.RetrievalRoleArn, "RetrievalRoleArn must be applied, not silently dropped") require.Len(t, updated.Validators, 1, "Validators must be applied, not silently dropped") @@ -245,21 +246,21 @@ func TestHandler_CreateConfigurationProfile_TagsAppliedInline(t *testing.T) { require.Equal(t, http.StatusCreated, appRec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) body, err := json.Marshal(map[string]any{ "Name": "tagged-prof-" + tt.name, "LocationUri": "hosted", "Type": "AWS.Freeform", "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", body) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID + "/configurationprofile/" + profile.ID @@ -269,7 +270,7 @@ func TestHandler_CreateConfigurationProfile_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) @@ -334,7 +335,7 @@ func seedExperimentDefinitionHTTP(t *testing.T, h *appconfig.Handler) (string, s var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"exp-http-env"}`)) @@ -343,7 +344,7 @@ func seedExperimentDefinitionHTTP(t *testing.T, h *appconfig.Handler) (string, s var env struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"exp-http-profile","LocationUri":"hosted","Type":"AWS.AppConfig.FeatureFlags"}`)) @@ -352,7 +353,7 @@ func seedExperimentDefinitionHTTP(t *testing.T, h *appconfig.Handler) (string, s var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) return app.ID, env.ID, prof.ID } @@ -382,7 +383,7 @@ func TestHandler_ExperimentDefinition_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created appconfig.ExperimentDefinition - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) assert.NotEmpty(t, created.ID) assert.Equal(t, appID, created.ApplicationID) assert.Equal(t, envID, created.EnvironmentID) @@ -396,7 +397,7 @@ func TestHandler_ExperimentDefinition_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got appconfig.ExperimentDefinition - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, created.ID, got.ID) listRec := doRequest(t, h, http.MethodGet, "/experimentdefinitions?application_identifier="+appID, nil) @@ -405,7 +406,7 @@ func TestHandler_ExperimentDefinition_CRUD(t *testing.T) { var listResp struct { Items []appconfig.ExperimentDefinition `json:"Items"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Items, 1) assert.Equal(t, created.ID, listResp.Items[0].ID) @@ -414,7 +415,7 @@ func TestHandler_ExperimentDefinition_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updated appconfig.ExperimentDefinition - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "country == 'US'", updated.AudienceRule) deleteRec := doRequest(t, h, http.MethodDelete, "/applications/"+appID+"/experimentdefinitions/"+created.ID, nil) @@ -424,7 +425,7 @@ func TestHandler_ExperimentDefinition_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, afterDeleteRec.Code, "default delete_type archives rather than destroying") var afterDelete appconfig.ExperimentDefinition - require.NoError(t, json.Unmarshal(afterDeleteRec.Body.Bytes(), &afterDelete)) + require.NoError(t, json.Unmarshal(afterDeleteRec.Body.Bytes(), &afterDelete, jsonx.V1())) assert.Equal(t, "ARCHIVED", afterDelete.Status) // FlagKey is validated against the profile's actual feature flag @@ -551,14 +552,14 @@ func TestHandler_DeleteConfigurationProfile_DeletionProtectionCheck(t *testing.T require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"name":"dpc-profile","locationUri":"hosted","type":"AWS.Freeform"}`)) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) path := "/applications/" + app.ID + "/configurationprofiles/" + profile.ID diff --git a/services/appconfig/handler_configuration_test.go b/services/appconfig/handler_configuration_test.go index 9bc3377ceb..7eb2ade566 100644 --- a/services/appconfig/handler_configuration_test.go +++ b/services/appconfig/handler_configuration_test.go @@ -2,7 +2,7 @@ package appconfig_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -26,7 +27,7 @@ func TestHandler_GetConfiguration(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest( t, @@ -38,14 +39,14 @@ func TestHandler_GetConfiguration(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"conf-profile","LocationUri":"hosted","Type":"AWS.Freeform"}`)) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) tests := []struct { name string @@ -94,7 +95,7 @@ func TestHandler_GetConfiguration_WithContent(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest( t, @@ -106,14 +107,14 @@ func TestHandler_GetConfiguration_WithContent(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"content-profile","LocationUri":"hosted","Type":"AWS.Freeform"}`)) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) // Create a hosted configuration version. e := echo.New() @@ -143,7 +144,7 @@ func TestHandler_GetConfiguration_WithContent(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var strat appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat, jsonx.V1())) depBody := []byte( `{"ConfigurationProfileId":"` + profile.ID + @@ -171,14 +172,14 @@ func TestHandler_ValidateConfiguration(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"validate-profile","LocationUri":"hosted","Type":"AWS.Freeform"}`)) require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) validatorBase := "/applications/" + app.ID + "/configurationprofiles/" + profile.ID + "/validators" diff --git a/services/appconfig/handler_deployment_strategies_test.go b/services/appconfig/handler_deployment_strategies_test.go index b35c1d47b1..42c505b07f 100644 --- a/services/appconfig/handler_deployment_strategies_test.go +++ b/services/appconfig/handler_deployment_strategies_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -24,7 +25,7 @@ func TestHandler_DeploymentStrategy_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var strategy appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strategy)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strategy, jsonx.V1())) assert.Equal(t, "my-strategy", strategy.Name) // Get. @@ -88,14 +89,14 @@ func TestHandler_UpdateDeploymentStrategy_OmittedDescriptionPreserved(t *testing require.Equal(t, http.StatusCreated, rec.Code) var strat appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat, jsonx.V1())) rec = doRequest(t, h, http.MethodPatch, "/deploymentstrategies/"+strat.ID, []byte(`{"GrowthFactor":50}`)) assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.InDelta(t, float32(50), updated.GrowthFactor, 0.001) assert.Equal(t, "keep-me", updated.Description, "omitted Description must be preserved") } @@ -137,14 +138,14 @@ func TestHandler_CreateDeploymentStrategy_TagsAppliedInline(t *testing.T) { "GrowthType": "LINEAR", "ReplicateTo": "NONE", "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/deploymentstrategies", body) require.Equal(t, http.StatusCreated, rec.Code) var strategy appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strategy)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strategy, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:deploymentstrategy/" + strategy.ID tagsRec := doRequest(t, h, http.MethodGet, "/tags/"+resourceArn, nil) @@ -153,7 +154,7 @@ func TestHandler_CreateDeploymentStrategy_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) diff --git a/services/appconfig/handler_deployments_test.go b/services/appconfig/handler_deployments_test.go index 8933058c53..0457f67a0c 100644 --- a/services/appconfig/handler_deployments_test.go +++ b/services/appconfig/handler_deployments_test.go @@ -2,7 +2,7 @@ package appconfig_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -138,7 +139,7 @@ func seedExperimentRunHTTP(t *testing.T, h *appconfig.Handler) (string, appconfi var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"run-http-env"}`)) @@ -147,7 +148,7 @@ func seedExperimentRunHTTP(t *testing.T, h *appconfig.Handler) (string, appconfi var env struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"run-http-profile","LocationUri":"hosted","Type":"AWS.AppConfig.FeatureFlags"}`)) @@ -156,7 +157,7 @@ func seedExperimentRunHTTP(t *testing.T, h *appconfig.Handler) (string, appconfi var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) defBody := `{ "Name": "run-http-def", @@ -171,7 +172,7 @@ func seedExperimentRunHTTP(t *testing.T, h *appconfig.Handler) (string, appconfi require.Equal(t, http.StatusCreated, defRec.Code) var def appconfig.ExperimentDefinition - require.NoError(t, json.Unmarshal(defRec.Body.Bytes(), &def)) + require.NoError(t, json.Unmarshal(defRec.Body.Bytes(), &def, jsonx.V1())) return app.ID, def } @@ -194,7 +195,7 @@ func TestHandler_ExperimentRun_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var run appconfig.ExperimentRun - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &run, jsonx.V1())) assert.Equal(t, int32(1), run.Run) assert.Equal(t, "RUNNING", run.Status) assert.InDelta(t, float32(10), run.ExposurePercentage, 0.001) @@ -208,14 +209,14 @@ func TestHandler_ExperimentRun_Lifecycle(t *testing.T) { var listResp struct { Items []appconfig.ExperimentRun `json:"Items"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Items, 1) updateRec := doRequest(t, h, http.MethodPatch, base+"/1/update", []byte(`{"ExposurePercentage":25}`)) require.Equal(t, http.StatusOK, updateRec.Code) var updated appconfig.ExperimentRun - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated, jsonx.V1())) assert.InDelta(t, float32(25), updated.ExposurePercentage, 0.001) stopRec := doRequest(t, h, http.MethodPatch, base+"/1/stop", @@ -223,7 +224,7 @@ func TestHandler_ExperimentRun_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopped appconfig.ExperimentRun - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopped)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopped, jsonx.V1())) assert.Equal(t, "DONE", stopped.Status) require.NotNil(t, stopped.Result) assert.Equal(t, "treatment won", stopped.Result.ExecutiveSummary) @@ -234,7 +235,7 @@ func TestHandler_ExperimentRun_Lifecycle(t *testing.T) { var eventsResp struct { Items []appconfig.ExperimentRunEvent `json:"Items"` } - require.NoError(t, json.Unmarshal(eventsRec.Body.Bytes(), &eventsResp)) + require.NoError(t, json.Unmarshal(eventsRec.Body.Bytes(), &eventsResp, jsonx.V1())) require.Len(t, eventsResp.Items, 3, "RUN_STARTED, EXPOSURE_UPDATED, RUN_STOPPED") assert.Equal(t, "RUN_STOPPED", eventsResp.Items[0].EventType, "most-recent-first ordering") } @@ -295,7 +296,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Create env. rec = doRequest( @@ -308,7 +309,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) // Create configuration profile (required by StartDeployment validation). profBody := []byte(`{"name":"my-profile","locationUri":"hosted"}`) @@ -322,7 +323,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var prof appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &prof, jsonx.V1())) // Create a hosted configuration version (required for StartDeployment // to validate ConfigurationVersion against, for a "hosted" profile). @@ -343,7 +344,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var strat appconfig.DeploymentStrategy - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &strat, jsonx.V1())) // Start deployment. depBodyStr := `{"configurationProfileId":"` + prof.ID + @@ -358,7 +359,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var dep appconfig.Deployment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, int32(1), dep.DeploymentNumber) assert.Equal(t, "DEPLOYING", dep.State, "a non-zero-duration strategy must not complete synchronously") assert.NotEmpty(t, dep.EventLog, "StartDeployment must record a DEPLOYMENT_STARTED event") @@ -377,7 +378,7 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var final appconfig.Deployment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &final)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &final, jsonx.V1())) assert.Equal(t, "COMPLETE", final.State) assert.InDelta(t, float32(100.0), final.PercentageComplete, 0.001) @@ -412,13 +413,13 @@ func TestHandler_Deployment_Lifecycle(t *testing.T) { "Allow-Revert", "true", nil, ) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, "REVERTED", dep.State, "StopDeploymentOutput itself must reflect the new state") rec = doRequest(t, h, http.MethodGet, "/applications/"+app.ID+"/environments/"+env.ID+"/deployments/1", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, "REVERTED", dep.State) }) } @@ -493,7 +494,7 @@ func TestHandler_ListDeployments_HTTP(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appOut)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appOut, jsonx.V1())) envRec := doRequest( t, h, http.MethodPost, @@ -506,7 +507,7 @@ func TestHandler_ListDeployments_HTTP(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &envOut)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &envOut, jsonx.V1())) listRec := doRequest( t, h, http.MethodGet, @@ -531,7 +532,7 @@ func TestHandler_DeploymentFieldsPresent(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"dep-env"}`)) @@ -541,7 +542,7 @@ func TestHandler_DeploymentFieldsPresent(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", @@ -552,7 +553,7 @@ func TestHandler_DeploymentFieldsPresent(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) hcvRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles/"+prof.ID+"/hostedconfigurationversions", @@ -567,13 +568,13 @@ func TestHandler_DeploymentFieldsPresent(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat, jsonx.V1())) depBody, _ := json.Marshal(map[string]string{ "ConfigurationProfileId": prof.ID, "DeploymentStrategyId": strat.ID, "ConfigurationVersion": "1", - }) + }, jsonx.V1()) depRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments/"+env.ID+"/deployments", depBody) require.Equal(t, http.StatusCreated, depRec.Code) @@ -587,7 +588,7 @@ func TestHandler_DeploymentFieldsPresent(t *testing.T) { PercentageComplete float32 `json:"PercentageComplete"` DeploymentNumber int32 `json:"DeploymentNumber"` } - require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, app.ID, dep.ApplicationID) assert.Equal(t, env.ID, dep.EnvironmentID) @@ -612,7 +613,7 @@ func TestHandler_DeploymentNumberIncrements(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"seq-env"}`)) @@ -622,7 +623,7 @@ func TestHandler_DeploymentNumberIncrements(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", @@ -633,7 +634,7 @@ func TestHandler_DeploymentNumberIncrements(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) hcvRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles/"+prof.ID+"/hostedconfigurationversions", @@ -648,13 +649,13 @@ func TestHandler_DeploymentNumberIncrements(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat, jsonx.V1())) depBody, _ := json.Marshal(map[string]string{ "ConfigurationProfileId": prof.ID, "DeploymentStrategyId": strat.ID, "ConfigurationVersion": "1", - }) + }, jsonx.V1()) for wantNum := int32(1); wantNum <= 3; wantNum++ { rec := doRequest(t, h, http.MethodPost, @@ -664,7 +665,7 @@ func TestHandler_DeploymentNumberIncrements(t *testing.T) { var dep struct { DeploymentNumber int32 `json:"DeploymentNumber"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dep, jsonx.V1())) assert.Equal(t, wantNum, dep.DeploymentNumber, "deployment must have sequential DeploymentNumber") } diff --git a/services/appconfig/handler_environments_test.go b/services/appconfig/handler_environments_test.go index dfbde232ac..65ec83ed68 100644 --- a/services/appconfig/handler_environments_test.go +++ b/services/appconfig/handler_environments_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -22,7 +23,7 @@ func TestHandler_Environment_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // Create env. rec = doRequest( @@ -35,7 +36,7 @@ func TestHandler_Environment_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) assert.Equal(t, "production", env.Name) assert.Equal(t, app.ID, env.ApplicationID) @@ -62,7 +63,7 @@ func TestHandler_UpdateEnvironment_HTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest( t, @@ -74,7 +75,7 @@ func TestHandler_UpdateEnvironment_HTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) // Update env. rec = doRequest( @@ -87,7 +88,7 @@ func TestHandler_UpdateEnvironment_HTTP(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "production", updated.Name) } @@ -104,14 +105,14 @@ func TestHandler_UpdateEnvironment_OmittedFieldsPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"staging","Description":"keep-me","Monitors":[{"AlarmArn":"arn:aws:cloudwatch:alarm1"}]}`)) require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) require.Len(t, env.Monitors, 1) // Update only Name; Description and Monitors are omitted. @@ -120,7 +121,7 @@ func TestHandler_UpdateEnvironment_OmittedFieldsPreserved(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "production", updated.Name) assert.Equal(t, "keep-me", updated.Description, "omitted Description must be preserved") require.Len(t, updated.Monitors, 1, "omitted Monitors must be preserved") @@ -132,7 +133,7 @@ func TestHandler_UpdateEnvironment_OmittedFieldsPreserved(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var cleared appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cleared)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cleared, jsonx.V1())) assert.Empty(t, cleared.Monitors, "an explicit empty Monitors list must replace the existing one") } @@ -209,19 +210,19 @@ func TestHandler_CreateEnvironment_TagsAppliedInline(t *testing.T) { require.Equal(t, http.StatusCreated, appRec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) body, err := json.Marshal(map[string]any{ "Name": "tagged-env-" + tt.name, "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", body) require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID + "/environment/" + env.ID @@ -231,7 +232,7 @@ func TestHandler_CreateEnvironment_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) @@ -255,7 +256,7 @@ func TestHandler_EnvironmentStateIsReadyForDeployment(t *testing.T) { var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) tests := []struct { name string @@ -285,7 +286,7 @@ func TestHandler_EnvironmentStateIsReadyForDeployment(t *testing.T) { var env struct { State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) assert.Equal(t, tt.want, env.State, "environment State must be READY_FOR_DEPLOYMENT (SCREAMING_SNAKE_CASE)") }) @@ -305,7 +306,7 @@ func TestHandler_EnvironmentStateRoundTrip(t *testing.T) { var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"staging"}`)) @@ -315,7 +316,7 @@ func TestHandler_EnvironmentStateRoundTrip(t *testing.T) { ID string `json:"Id"` State string `json:"State"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) assert.Equal(t, "READY_FOR_DEPLOYMENT", env.State) require.NotEmpty(t, env.ID) @@ -327,7 +328,7 @@ func TestHandler_EnvironmentStateRoundTrip(t *testing.T) { var getEnv struct { State string `json:"State"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getEnv)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getEnv, jsonx.V1())) assert.Equal(t, "READY_FOR_DEPLOYMENT", getEnv.State, "GetEnvironment must also return READY_FOR_DEPLOYMENT") // ListEnvironments must also return correct state. @@ -339,7 +340,7 @@ func TestHandler_EnvironmentStateRoundTrip(t *testing.T) { State string `json:"State"` } `json:"Items"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Items, 1) assert.Equal(t, "READY_FOR_DEPLOYMENT", listResp.Items[0].State, "ListEnvironments must also return READY_FOR_DEPLOYMENT") @@ -381,14 +382,14 @@ func TestHandler_DeleteEnvironment_DeletionProtectionCheck(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"name":"dpc-env"}`)) require.Equal(t, http.StatusCreated, rec.Code) var env appconfig.Environment - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &env, jsonx.V1())) path := "/applications/" + app.ID + "/environments/" + env.ID diff --git a/services/appconfig/handler_extensions_test.go b/services/appconfig/handler_extensions_test.go index 99c10de476..40e9e7ecf5 100644 --- a/services/appconfig/handler_extensions_test.go +++ b/services/appconfig/handler_extensions_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -86,7 +87,7 @@ func TestHandler_Extension_CRUD(t *testing.T) { if tt.wantField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantValue, resp[tt.wantField]) } }) @@ -103,7 +104,7 @@ func TestHandler_Extension_GetDeleteByID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) assert.NotEmpty(t, ext.ID) assert.Equal(t, "ext-to-delete", ext.Name) @@ -163,7 +164,7 @@ func TestHandler_Extension_ListPaginated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Items"].([]any) require.True(t, ok) assert.Len(t, items, 2) @@ -180,14 +181,14 @@ func TestHandler_ExtensionAssociation_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) // Create an application to use as resource. rec = doRequest(t, h, http.MethodPost, "/applications", []byte(`{"name":"assoc-app"}`)) require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) // List associations empty. rec = doRequest(t, h, http.MethodGet, "/extensionassociations", nil) @@ -202,7 +203,7 @@ func TestHandler_ExtensionAssociation_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var assoc appconfig.ExtensionAssociation - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc, jsonx.V1())) assert.NotEmpty(t, assoc.ID) assert.Equal(t, ext.Arn, assoc.ExtensionArn) @@ -215,7 +216,7 @@ func TestHandler_ExtensionAssociation_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items, ok := listResp["Items"].([]any) require.True(t, ok) assert.Len(t, items, 1) @@ -285,7 +286,7 @@ func TestHandler_UpdateExtension(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) assert.Equal(t, int32(1), ext.VersionNumber) // Update extension. @@ -294,7 +295,7 @@ func TestHandler_UpdateExtension(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "updated", updated.Description) assert.Equal(t, int32(2), updated.VersionNumber) } @@ -313,7 +314,7 @@ func TestHandler_UpdateExtension_OmittedDescriptionPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) // Update Actions only; Description is omitted. rec = doRequest(t, h, http.MethodPatch, "/extensions/"+ext.ID, @@ -321,7 +322,7 @@ func TestHandler_UpdateExtension_OmittedDescriptionPreserved(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "keep-me", updated.Description, "omitted Description must not be cleared") } @@ -344,7 +345,7 @@ func TestHandler_UpdateExtensionAssociation(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) resourceID := "arn:aws:appconfig:us-east-1:123456789012:application/abc" assocBody := []byte( @@ -354,7 +355,7 @@ func TestHandler_UpdateExtensionAssociation(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var assoc appconfig.ExtensionAssociation - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc, jsonx.V1())) // Update the association parameters. rec = doRequest(t, h, http.MethodPatch, "/extensionassociations/"+assoc.ID, @@ -362,7 +363,7 @@ func TestHandler_UpdateExtensionAssociation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated appconfig.ExtensionAssociation - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "value", updated.Parameters["key"]) } @@ -474,7 +475,7 @@ func TestHandler_Extension_VersionQueryParams(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var v1 appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &v1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &v1, jsonx.V1())) rec = doRequest(t, h, http.MethodPatch, "/extensions/"+v1.ID, []byte(`{"Description":"v2"}`)) @@ -485,7 +486,7 @@ func TestHandler_Extension_VersionQueryParams(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gotV1 appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gotV1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gotV1, jsonx.V1())) assert.Equal(t, "v1", gotV1.Description) assert.Equal(t, int32(1), gotV1.VersionNumber) @@ -494,7 +495,7 @@ func TestHandler_Extension_VersionQueryParams(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gotLatest appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gotLatest)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gotLatest, jsonx.V1())) assert.Equal(t, int32(2), gotLatest.VersionNumber) // Delete version 1 explicitly via the "version" query param; version 2 @@ -525,7 +526,7 @@ func TestHandler_DeleteExtension_ConflictWhenAssociated(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) assocBody := []byte( `{"ExtensionIdentifier":"` + ext.ID + @@ -555,7 +556,7 @@ func TestHandler_ListExtensions_NameFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Items"].([]any) require.True(t, ok) assert.Len(t, items, 1) @@ -564,7 +565,7 @@ func TestHandler_ListExtensions_NameFilter(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/extensions?name=nonexistent", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok = resp["Items"].([]any) require.True(t, ok) assert.Empty(t, items) @@ -601,14 +602,14 @@ func TestHandler_CreateExtension_TagsAppliedInline(t *testing.T) { body, err := json.Marshal(map[string]any{ "Name": "tagged-ext-" + tt.name, "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/extensions", body) require.Equal(t, http.StatusCreated, rec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ext, jsonx.V1())) require.NotEmpty(t, ext.Arn) tagsRec := doRequest(t, h, http.MethodGet, "/tags/"+ext.Arn, nil) @@ -617,7 +618,7 @@ func TestHandler_CreateExtension_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) @@ -662,21 +663,21 @@ func TestHandler_CreateExtensionAssociation_TagsAppliedInline(t *testing.T) { require.Equal(t, http.StatusCreated, extRec.Code) var ext appconfig.Extension - require.NoError(t, json.Unmarshal(extRec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(extRec.Body.Bytes(), &ext, jsonx.V1())) resourceID := "arn:aws:appconfig:us-east-1:123456789012:application/tagged-assoc-" + tt.name body, err := json.Marshal(map[string]any{ "ExtensionIdentifier": ext.ID, "ResourceIdentifier": resourceID, "Tags": tt.tags, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/extensionassociations", body) require.Equal(t, http.StatusCreated, rec.Code) var assoc appconfig.ExtensionAssociation - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assoc, jsonx.V1())) require.NotEmpty(t, assoc.Arn) tagsRec := doRequest(t, h, http.MethodGet, "/tags/"+assoc.Arn, nil) @@ -685,7 +686,7 @@ func TestHandler_CreateExtensionAssociation_TagsAppliedInline(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) if len(tt.tags) == 0 { assert.Empty(t, tagsResp.Tags) diff --git a/services/appconfig/handler_hosted_configuration_versions_test.go b/services/appconfig/handler_hosted_configuration_versions_test.go index b5387357e0..caead87390 100644 --- a/services/appconfig/handler_hosted_configuration_versions_test.go +++ b/services/appconfig/handler_hosted_configuration_versions_test.go @@ -2,7 +2,7 @@ package appconfig_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -27,7 +28,7 @@ func TestHandler_HostedConfigurationVersion_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) profileBody := []byte(`{"name":"hcv-profile","locationUri":"hosted","type":"AWS.Freeform"}`) rec = doRequest( @@ -40,7 +41,7 @@ func TestHandler_HostedConfigurationVersion_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var profile appconfig.ConfigurationProfile - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) // Create hosted configuration version with raw body. e := echo.New() @@ -146,7 +147,7 @@ func TestHandler_HostedConfigVersion_HTTP_ListEmpty(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appOut)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appOut, jsonx.V1())) profRec := doRequest( t, h, http.MethodPost, @@ -159,7 +160,7 @@ func TestHandler_HostedConfigVersion_HTTP_ListEmpty(t *testing.T) { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &profOut)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &profOut, jsonx.V1())) listRec := doRequest( t, @@ -193,7 +194,7 @@ func TestHandler_HostedConfigVersionResponseHeaders(t *testing.T) { var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", @@ -203,7 +204,7 @@ func TestHandler_HostedConfigVersionResponseHeaders(t *testing.T) { var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) base := "/applications/" + app.ID + "/configurationprofiles/" + prof.ID + "/hostedconfigurationversions" @@ -254,7 +255,7 @@ func TestHandler_CreateHostedConfigurationVersion_LatestVersionNumberConflict(t var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", @@ -264,7 +265,7 @@ func TestHandler_CreateHostedConfigurationVersion_LatestVersionNumberConflict(t var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) base := "/applications/" + app.ID + "/configurationprofiles/" + prof.ID + "/hostedconfigurationversions" diff --git a/services/appconfig/handler_list_summary_test.go b/services/appconfig/handler_list_summary_test.go index cc39e49353..bef7ac6677 100644 --- a/services/appconfig/handler_list_summary_test.go +++ b/services/appconfig/handler_list_summary_test.go @@ -1,7 +1,7 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -154,7 +155,7 @@ func listSingleItem(t *testing.T, h *appconfig.Handler, path string) map[string] require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Items"].([]any) require.True(t, ok, "response missing Items list, got keys: %v", mapKeys(resp)) @@ -175,7 +176,7 @@ func setupConfigurationProfileListLeak(t *testing.T, h *appconfig.Handler) map[s var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) profBody := []byte(`{ "Name": "cp-list-leak-profile", @@ -200,7 +201,7 @@ func setupDeploymentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"dep-list-leak-env"}`)) @@ -209,7 +210,7 @@ func setupDeploymentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var env struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"dep-list-leak-profile","LocationUri":"hosted"}`)) @@ -218,7 +219,7 @@ func setupDeploymentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) hcvRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles/"+prof.ID+"/hostedconfigurationversions", @@ -234,7 +235,7 @@ func setupDeploymentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var strat struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat, jsonx.V1())) depBody := []byte(`{ "ConfigurationProfileId": "` + prof.ID + `", @@ -249,7 +250,7 @@ func setupDeploymentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var dep struct { State string `json:"State"` } - require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &dep)) + require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &dep, jsonx.V1())) require.Equal(t, "COMPLETE", dep.State, "a zero-duration strategy must complete synchronously") return listSingleItem(t, h, "/applications/"+app.ID+"/environments/"+env.ID+"/deployments") @@ -315,7 +316,7 @@ func setupExtensionAssociationListLeak(t *testing.T, h *appconfig.Handler) map[s var ext struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(extRec.Body.Bytes(), &ext)) + require.NoError(t, json.Unmarshal(extRec.Body.Bytes(), &ext, jsonx.V1())) appRec := doRequest(t, h, http.MethodPost, "/applications", []byte(`{"Name":"ext-assoc-list-leak-app"}`)) require.Equal(t, http.StatusCreated, appRec.Code) @@ -323,7 +324,7 @@ func setupExtensionAssociationListLeak(t *testing.T, h *appconfig.Handler) map[s var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) resourceID := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID assocBody := []byte(`{ @@ -346,7 +347,7 @@ func setupHostedConfigurationVersionListLeak(t *testing.T, h *appconfig.Handler) var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) profRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles", []byte(`{"Name":"hcv-list-leak-profile","LocationUri":"hosted"}`)) @@ -355,7 +356,7 @@ func setupHostedConfigurationVersionListLeak(t *testing.T, h *appconfig.Handler) var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) hcvRec := doRequestWithHeader(t, h, http.MethodPost, "/applications/"+app.ID+"/configurationprofiles/"+prof.ID+"/hostedconfigurationversions", @@ -386,7 +387,7 @@ func setupEnvironmentListLeak(t *testing.T, h *appconfig.Handler) map[string]any var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"env-list-leak-env"}`)) @@ -422,7 +423,7 @@ func TestHandler_GetOps_NoCreatedAtUpdatedAt(t *testing.T) { require.Equal(t, http.StatusCreated, appRec.Code) var createdApp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &createdApp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &createdApp, jsonx.V1())) assert.NotContains(t, createdApp, "CreatedAt") assert.NotContains(t, createdApp, "UpdatedAt") @@ -433,7 +434,7 @@ func TestHandler_GetOps_NoCreatedAtUpdatedAt(t *testing.T) { require.Equal(t, http.StatusOK, getAppRec.Code) var gotApp map[string]any - require.NoError(t, json.Unmarshal(getAppRec.Body.Bytes(), &gotApp)) + require.NoError(t, json.Unmarshal(getAppRec.Body.Bytes(), &gotApp, jsonx.V1())) assert.NotContains(t, gotApp, "CreatedAt") assert.NotContains(t, gotApp, "UpdatedAt") @@ -443,7 +444,7 @@ func TestHandler_GetOps_NoCreatedAtUpdatedAt(t *testing.T) { require.Equal(t, http.StatusCreated, stratRec.Code) var createdStrat map[string]any - require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &createdStrat)) + require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &createdStrat, jsonx.V1())) assert.NotContains(t, createdStrat, "CreatedAt") assert.NotContains(t, createdStrat, "UpdatedAt") } diff --git a/services/appconfig/handler_tags_test.go b/services/appconfig/handler_tags_test.go index 885aca1866..b611e42e1e 100644 --- a/services/appconfig/handler_tags_test.go +++ b/services/appconfig/handler_tags_test.go @@ -1,13 +1,14 @@ package appconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -21,7 +22,7 @@ func TestHandler_TagResource(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &app, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID @@ -52,7 +53,7 @@ func TestHandler_TagResource_VerifyTags(t *testing.T) { require.Equal(t, http.StatusCreated, created.Code) var app appconfig.Application - require.NoError(t, json.Unmarshal(created.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(created.Body.Bytes(), &app, jsonx.V1())) resourceArn := "arn:aws:appconfig:us-east-1:123456789012:application/" + app.ID @@ -66,7 +67,7 @@ func TestHandler_TagResource_VerifyTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "prod", resp["Tags"]["env"]) assert.Equal(t, "1.0", resp["Tags"]["version"]) @@ -78,7 +79,7 @@ func TestHandler_TagResource_VerifyTags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/tags/"+resourceArn, nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Tags"], 1) assert.Equal(t, "1.0", resp["Tags"]["version"]) } diff --git a/services/appconfig/handler_test.go b/services/appconfig/handler_test.go index 6b859a5b23..b09c2a92af 100644 --- a/services/appconfig/handler_test.go +++ b/services/appconfig/handler_test.go @@ -2,7 +2,7 @@ package appconfig_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/appconfig" ) @@ -394,7 +395,7 @@ func TestHandler_ResourceIDsPresentAcrossResources(t *testing.T) { var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) require.NotEmpty(t, app.ID, "Application must have a non-empty Id") // Create environment. @@ -406,7 +407,7 @@ func TestHandler_ResourceIDsPresentAcrossResources(t *testing.T) { ID string `json:"Id"` ApplicationID string `json:"ApplicationId"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) require.NotEmpty(t, env.ID, "Environment must have a non-empty Id") assert.Equal(t, app.ID, env.ApplicationID, "Environment.ApplicationId must match the parent application") @@ -422,7 +423,7 @@ func TestHandler_ResourceIDsPresentAcrossResources(t *testing.T) { ApplicationID string `json:"ApplicationId"` LocationURI string `json:"LocationUri"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) require.NotEmpty(t, prof.ID, "ConfigurationProfile must have a non-empty Id") assert.Equal(t, app.ID, prof.ApplicationID) assert.Equal(t, "hosted", prof.LocationURI) @@ -436,7 +437,7 @@ func TestHandler_ResourceIDsPresentAcrossResources(t *testing.T) { ID string `json:"Id"` Name string `json:"Name"` } - require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat)) + require.NoError(t, json.Unmarshal(stratRec.Body.Bytes(), &strat, jsonx.V1())) require.NotEmpty(t, strat.ID, "DeploymentStrategy must have a non-empty Id") assert.Equal(t, "id-strat", strat.Name) } @@ -456,7 +457,7 @@ func TestHandler_ListResponsesUseItemsKey(t *testing.T) { var app struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &app, jsonx.V1())) envRec := doRequest(t, h, http.MethodPost, "/applications/"+app.ID+"/environments", []byte(`{"Name":"items-env"}`)) @@ -470,7 +471,7 @@ func TestHandler_ListResponsesUseItemsKey(t *testing.T) { var prof struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof)) + require.NoError(t, json.Unmarshal(profRec.Body.Bytes(), &prof, jsonx.V1())) stratRec := doRequest(t, h, http.MethodPost, "/deploymentstrategies", []byte(`{"Name":"items-strat","DeploymentDurationInMinutes":0,"GrowthFactor":100,"ReplicateTo":"NONE"}`)) @@ -479,7 +480,7 @@ func TestHandler_ListResponsesUseItemsKey(t *testing.T) { var env struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env)) + require.NoError(t, json.Unmarshal(envRec.Body.Bytes(), &env, jsonx.V1())) listPaths := []struct { name string @@ -506,7 +507,7 @@ func TestHandler_ListResponsesUseItemsKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "path: %s", tt.path) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["Items"] assert.True(t, ok, "%s: response must have 'Items' key, got keys: %v", tt.name, mapKeys(resp)) diff --git a/services/appconfig/persistence.go b/services/appconfig/persistence.go index 488fdddf54..d6d19609bd 100644 --- a/services/appconfig/persistence.go +++ b/services/appconfig/persistence.go @@ -2,7 +2,7 @@ package appconfig import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -54,7 +54,7 @@ const appconfigSnapshotVersion = 1 // deploymentTimers is deliberately NOT part of this snapshot -- see its doc // comment on InMemoryBackend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TagsByArn map[string]map[string]string `json:"tagsByArn"` VersionCounters map[string]map[string]int32 `json:"versionCounters"` DeploymentCounters map[string]map[string]int32 `json:"deploymentCounters"` diff --git a/services/appconfigdata/configuration.go b/services/appconfigdata/configuration.go index b711d04445..4d40980005 100644 --- a/services/appconfigdata/configuration.go +++ b/services/appconfigdata/configuration.go @@ -1,9 +1,11 @@ package appconfigdata import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // SetConfiguration stores or updates configuration content for a profile, @@ -33,7 +35,7 @@ func (b *InMemoryBackend) setConfiguration(app, env, profile, content, contentTy if isJSONContentType(contentType) { var v any - if err := json.Unmarshal([]byte(content), &v); err != nil { + if err := json.Unmarshal([]byte(content), &v, jsonx.V1()); err != nil { return ErrContentTypeMismatch } } diff --git a/services/appconfigdata/configuration_test.go b/services/appconfigdata/configuration_test.go index 2d1cfafed8..1f33af6fd2 100644 --- a/services/appconfigdata/configuration_test.go +++ b/services/appconfigdata/configuration_test.go @@ -1,7 +1,7 @@ package appconfigdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfigdata" ) @@ -361,14 +362,14 @@ func TestHandler_RetryAfterHeader(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": tt.pollInterval, - }) + }, jsonx.V1()) require.NoError(t, err) sessionRec := doRequest(t, h, http.MethodPost, "/configurationsessions", sessionBody) require.Equal(t, http.StatusCreated, sessionRec.Code) var sessionResp map[string]string - require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &sessionResp)) + require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &sessionResp, jsonx.V1())) tok := sessionResp["InitialConfigurationToken"] // First poll — gets content. @@ -635,13 +636,13 @@ func TestHandler_PollRateLimitEnforced(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": tt.pollInterval, - }) + }, jsonx.V1()) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", sessionBody) require.Equal(t, http.StatusCreated, rec.Code) var sessionResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sessionResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sessionResp, jsonx.V1())) tok := sessionResp["InitialConfigurationToken"] // First poll — succeeds. @@ -657,7 +658,7 @@ func TestHandler_PollRateLimitEnforced(t *testing.T) { assert.Equal(t, tt.wantRetry, rec2.Header().Get("Retry-After")) var body map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "BadRequestException", body["__type"]) assert.Equal(t, "InvalidParameters", body["Reason"]) diff --git a/services/appconfigdata/errors_test.go b/services/appconfigdata/errors_test.go index 75fc391fdb..b0a085d30c 100644 --- a/services/appconfigdata/errors_test.go +++ b/services/appconfigdata/errors_test.go @@ -1,13 +1,14 @@ package appconfigdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfigdata" ) @@ -134,7 +135,7 @@ func TestHandler_BadRequestException_Details(t *testing.T) { assert.Equal(t, "BadRequestException", rec.Header().Get("X-Amzn-Errortype")) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "BadRequestException", body["__type"]) assert.Equal(t, "InvalidParameters", body["Reason"]) @@ -158,14 +159,14 @@ func TestHandler_BadRequestException_Details(t *testing.T) { "EnvironmentIdentifier": "e", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": 60, - }) + }, jsonx.V1()) require.NoError(t, err) sessionRec := doRequest(t, h2, http.MethodPost, "/configurationsessions", sessionBody) require.Equal(t, http.StatusCreated, sessionRec.Code) var sessionResp map[string]string - require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &sessionResp)) + require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &sessionResp, jsonx.V1())) tok := sessionResp["InitialConfigurationToken"] // First poll succeeds. @@ -180,7 +181,7 @@ func TestHandler_BadRequestException_Details(t *testing.T) { assert.Equal(t, "BadRequestException", rec2.Header().Get("X-Amzn-Errortype")) var body map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "BadRequestException", body["__type"]) assert.Equal(t, "InvalidParameters", body["Reason"]) @@ -216,7 +217,7 @@ func TestHandler_ResourceNotFoundException_Structure(t *testing.T) { assert.Equal(t, "ResourceNotFoundException", rec.Header().Get("X-Amzn-Errortype")) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) assert.Equal(t, "Deployment", resp["ResourceType"]) @@ -245,7 +246,7 @@ func TestHandler_ResourceNotFoundException_Structure(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) }) } @@ -377,7 +378,7 @@ func TestHandler_ErrorResponseShape(t *testing.T) { "X-Amzn-ErrorType header must match exception type") var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantErrorType, body["__type"], "response body __type field must match exception type") assert.NotEmpty(t, body["message"], "error body must have a message field") diff --git a/services/appconfigdata/handler.go b/services/appconfigdata/handler.go index a12548cc2e..149f71ca57 100644 --- a/services/appconfigdata/handler.go +++ b/services/appconfigdata/handler.go @@ -2,7 +2,8 @@ package appconfigdata import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -191,7 +193,7 @@ func (h *Handler) handleStartConfigurationSession(c *echo.Context) error { log := logger.Load(c.Request().Context()) var req startSessionRequest - if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &req, jsonx.V1()); err != nil { log.Error("appconfigdata: failed to decode StartConfigurationSession request", "error", err) return writeAWSError(c, http.StatusBadRequest, exceptionBadRequest, "invalid request body") diff --git a/services/appconfigdata/handler_sdk_route_table_test.go b/services/appconfigdata/handler_sdk_route_table_test.go index cf6fc091b4..8b25b1c79a 100644 --- a/services/appconfigdata/handler_sdk_route_table_test.go +++ b/services/appconfigdata/handler_sdk_route_table_test.go @@ -2,7 +2,7 @@ package appconfigdata_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sdkRouteCases is the authoritative method+path for every real AppConfigData @@ -95,7 +97,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var decoded struct { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded, jsonx.V1())) assert.NotEqual(t, "not found", decoded.Message, "method=%s path=%s op=%s: dispatched to the unmatched-route default", tc.method, tc.path, tc.op) } diff --git a/services/appconfigdata/handler_test.go b/services/appconfigdata/handler_test.go index 7038514d52..e27bd7519c 100644 --- a/services/appconfigdata/handler_test.go +++ b/services/appconfigdata/handler_test.go @@ -2,7 +2,7 @@ package appconfigdata_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/appconfigdata" ) @@ -20,7 +21,7 @@ import ( func nowUTC() time.Time { return time.Now().UTC() } func mustMarshalJSON(v any) []byte { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { panic(err) } @@ -81,7 +82,7 @@ func startSession(t *testing.T, h *appconfigdata.Handler, app, env, profile stri require.Equal(t, http.StatusCreated, rec.Code, "startSession failed: %s", rec.Body.String()) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) token := resp["InitialConfigurationToken"] require.NotEmpty(t, token) @@ -93,7 +94,7 @@ func decodeErrorBody(t *testing.T, body string) (string, string) { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &m), "error body must be valid JSON") + require.NoError(t, json.Unmarshal([]byte(body), &m, jsonx.V1()), "error body must be valid JSON") errType, _ := m["__type"].(string) errMsg, _ := m["message"].(string) diff --git a/services/appconfigdata/persistence.go b/services/appconfigdata/persistence.go index 08a3c3a1a4..b8d4b362b1 100644 --- a/services/appconfigdata/persistence.go +++ b/services/appconfigdata/persistence.go @@ -2,7 +2,7 @@ package appconfigdata import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -44,8 +44,8 @@ const appconfigdataSnapshotVersion = 1 // material to disk was judged not worth the added attack surface for a // session-token integrity check. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. It implements diff --git a/services/appconfigdata/sessions_test.go b/services/appconfigdata/sessions_test.go index 4f8d00acaa..f85adb8e97 100644 --- a/services/appconfigdata/sessions_test.go +++ b/services/appconfigdata/sessions_test.go @@ -1,7 +1,7 @@ package appconfigdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appconfigdata" ) @@ -122,7 +123,7 @@ func TestHandler_StartConfigurationSession(t *testing.T) { if tt.wantToken { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["InitialConfigurationToken"]) } }) @@ -169,7 +170,7 @@ func TestHandler_PollIntervalValidation(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": tt.interval, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", bodyJSON) @@ -221,14 +222,14 @@ func TestHandler_PollIntervalHonored(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "profile", "RequiredMinimumPollIntervalInSeconds": tt.interval, - }) + }, jsonx.V1()) require.NoError(t, err) sessionRec := doRequest(t, h, http.MethodPost, "/configurationsessions", sessionBody) require.Equal(t, http.StatusCreated, sessionRec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sessionRec.Body.Bytes(), &resp, jsonx.V1())) token := resp["InitialConfigurationToken"] cfgRec := doRequest(t, h, http.MethodGet, "/configuration?configuration_token="+token, nil) @@ -257,7 +258,7 @@ func TestHandler_StartSession_IdentifierLength(t *testing.T) { "ApplicationIdentifier": longID, "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", - }) + }, jsonx.V1()) return b }(), @@ -269,7 +270,7 @@ func TestHandler_StartSession_IdentifierLength(t *testing.T) { "ApplicationIdentifier": "app", "EnvironmentIdentifier": longID, "ConfigurationProfileIdentifier": "p", - }) + }, jsonx.V1()) return b }(), @@ -281,7 +282,7 @@ func TestHandler_StartSession_IdentifierLength(t *testing.T) { "ApplicationIdentifier": "app", "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": longID, - }) + }, jsonx.V1()) return b }(), @@ -329,7 +330,7 @@ func TestHandler_StartSession_MaxPollInterval(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": tt.interval, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", bodyJSON) @@ -405,7 +406,7 @@ func TestHandler_StartSession_ExactMaxIdentifierLength(t *testing.T) { "ApplicationIdentifier": validID, "EnvironmentIdentifier": validID, "ConfigurationProfileIdentifier": validID, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", bodyJSON) @@ -419,7 +420,7 @@ func TestHandler_StartSession_ExactMaxIdentifierLength(t *testing.T) { "ApplicationIdentifier": invalidID, "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", bodyJSON) @@ -462,7 +463,7 @@ func TestHandler_StartSession_PollInterval_Boundary(t *testing.T) { "EnvironmentIdentifier": "env", "ConfigurationProfileIdentifier": "p", "RequiredMinimumPollIntervalInSeconds": tt.interval, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/configurationsessions", bodyJSON) diff --git a/services/appconfigdata/store.go b/services/appconfigdata/store.go index ce3d2ed916..071c622d47 100644 --- a/services/appconfigdata/store.go +++ b/services/appconfigdata/store.go @@ -6,12 +6,13 @@ import ( "crypto/rand" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "sync/atomic" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -64,8 +65,8 @@ func profileKey(app, env, profile string) string { func normalizedContentHash(content, contentType string) string { if isJSONContentType(contentType) { var v any - if err := json.Unmarshal([]byte(content), &v); err == nil { - if normalized, marshalErr := json.Marshal(v); marshalErr == nil { + if err := json.Unmarshal([]byte(content), &v, jsonx.V1()); err == nil { + if normalized, marshalErr := json.Marshal(v, jsonx.V1()); marshalErr == nil { sum := sha256.Sum256(normalized) return hex.EncodeToString(sum[:]) diff --git a/services/appintegrations/persistence.go b/services/appintegrations/persistence.go index c242097b1e..9457ecceb6 100644 --- a/services/appintegrations/persistence.go +++ b/services/appintegrations/persistence.go @@ -2,7 +2,7 @@ package appintegrations import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/applicationautoscaling/errors_test.go b/services/applicationautoscaling/errors_test.go index 5409bedd73..078c805037 100644 --- a/services/applicationautoscaling/errors_test.go +++ b/services/applicationautoscaling/errors_test.go @@ -9,7 +9,7 @@ package applicationautoscaling_test // InvalidNextTokenException. import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/applicationautoscaling" ) @@ -40,7 +41,7 @@ func TestHandler_PutScalingPolicy_RequiresRegisteredTarget(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ObjectNotFoundException", errBody["__type"]) } @@ -62,7 +63,7 @@ func TestHandler_PutScheduledAction_RequiresRegisteredTarget(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ObjectNotFoundException", errBody["__type"]) } @@ -84,7 +85,7 @@ func TestHandler_ObjectNotFoundException_Is400Not404(t *testing.T) { require.NotEqual(t, http.StatusNotFound, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ObjectNotFoundException", errBody["__type"]) } @@ -218,7 +219,7 @@ func TestHandler_DescribeOps_InvalidNextToken(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "InvalidNextTokenException", errBody["__type"]) }) } diff --git a/services/applicationautoscaling/handler.go b/services/applicationautoscaling/handler.go index dfe8db7295..fe95936868 100644 --- a/services/applicationautoscaling/handler.go +++ b/services/applicationautoscaling/handler.go @@ -2,7 +2,7 @@ package applicationautoscaling import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -177,13 +178,13 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // marshalError serialises a JSONErrorResponse into bytes. // Marshaling a struct with only string fields cannot fail; error is intentionally ignored. func marshalError(errType, message string) []byte { - payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}) + payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}, jsonx.V1()) return payload } @@ -206,7 +207,7 @@ func marshalResourceError(errType, message, resourceName string) []byte { Type string `json:"__type"` Message string `json:"message"` ResourceName string `json:"ResourceName,omitempty"` - }{Type: errType, Message: message, ResourceName: resourceName}) + }{Type: errType, Message: message, ResourceName: resourceName}, jsonx.V1()) return payload } @@ -234,9 +235,6 @@ func clientMessage(err error) string { } func (h *Handler) writeAPIError(_ context.Context, c *echo.Context, _ string, err error, msg string) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - resourceName := "" if rn, ok := errors.AsType[resourceNamer](err); ok { resourceName = rn.ResourceName() @@ -270,7 +268,7 @@ func (h *Handler) writeAPIError(_ context.Context, c *echo.Context, _ string, er case errors.Is(err, ErrValidation): return c.JSONBlob(http.StatusBadRequest, marshalError("ValidationException", msg)) case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSONBlob(http.StatusBadRequest, marshalError("ValidationException", msg)) // Server faults (HTTP 500). case errors.Is(err, ErrConcurrentUpdate): diff --git a/services/applicationautoscaling/handler_forecast_test.go b/services/applicationautoscaling/handler_forecast_test.go index 713da3de7b..5964d44712 100644 --- a/services/applicationautoscaling/handler_forecast_test.go +++ b/services/applicationautoscaling/handler_forecast_test.go @@ -1,12 +1,14 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetPredictiveScalingForecast(t *testing.T) { @@ -146,7 +148,7 @@ func TestHandler_GetPredictiveScalingForecast_HonestlyEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cf, ok := resp["CapacityForecast"].(map[string]any) require.True(t, ok, "expected CapacityForecast in response") diff --git a/services/applicationautoscaling/handler_scalable_targets_describe_test.go b/services/applicationautoscaling/handler_scalable_targets_describe_test.go index 3c40ab560d..c6b9327ac2 100644 --- a/services/applicationautoscaling/handler_scalable_targets_describe_test.go +++ b/services/applicationautoscaling/handler_scalable_targets_describe_test.go @@ -1,13 +1,15 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DeregisterScalableTarget(t *testing.T) { @@ -131,7 +133,7 @@ func TestHandler_DeregisterScalableTarget_CascadeDelete(t *testing.T) { "ServiceNamespace": "ecs", }) var targetsResp map[string]any - require.NoError(t, json.Unmarshal(targetsRec.Body.Bytes(), &targetsResp)) + require.NoError(t, json.Unmarshal(targetsRec.Body.Bytes(), &targetsResp, jsonx.V1())) targets, _ := targetsResp["ScalableTargets"].([]any) assert.Empty(t, targets, "scalable targets should be empty after deregister") @@ -140,7 +142,7 @@ func TestHandler_DeregisterScalableTarget_CascadeDelete(t *testing.T) { "ServiceNamespace": "ecs", }) var policiesResp map[string]any - require.NoError(t, json.Unmarshal(policiesRec.Body.Bytes(), &policiesResp)) + require.NoError(t, json.Unmarshal(policiesRec.Body.Bytes(), &policiesResp, jsonx.V1())) policies, _ := policiesResp["ScalingPolicies"].([]any) assert.Empty(t, policies, "scaling policies should be cascade-deleted") @@ -149,7 +151,7 @@ func TestHandler_DeregisterScalableTarget_CascadeDelete(t *testing.T) { "ServiceNamespace": "ecs", }) var actionsResp map[string]any - require.NoError(t, json.Unmarshal(actionsRec.Body.Bytes(), &actionsResp)) + require.NoError(t, json.Unmarshal(actionsRec.Body.Bytes(), &actionsResp, jsonx.V1())) actions, _ := actionsResp["ScheduledActions"].([]any) assert.Empty(t, actions, "scheduled actions should be cascade-deleted") } @@ -191,14 +193,14 @@ func TestDeregisterScalableTarget_Cascades(t *testing.T) { "ServiceNamespace": ns, }) var polOut map[string]any - require.NoError(t, json.Unmarshal(polRec.Body.Bytes(), &polOut)) + require.NoError(t, json.Unmarshal(polRec.Body.Bytes(), &polOut, jsonx.V1())) assert.Empty(t, polOut["ScalingPolicies"], "policies must be deleted when target is deregistered") actRec := doRequest(t, h, "DescribeScheduledActions", map[string]any{ "ServiceNamespace": ns, }) var actOut map[string]any - require.NoError(t, json.Unmarshal(actRec.Body.Bytes(), &actOut)) + require.NoError(t, json.Unmarshal(actRec.Body.Bytes(), &actOut, jsonx.V1())) assert.Empty(t, actOut["ScheduledActions"], "scheduled actions must be deleted when target is deregistered") } @@ -240,7 +242,7 @@ func TestHandler_DescribeScalableTargets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["ScalableTargets"].([]any) require.True(t, ok) assert.Len(t, targets, tt.wantCount) @@ -264,7 +266,7 @@ func TestHandler_DescribeScalableTargets_HasTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets := resp["ScalableTargets"].([]any) require.Len(t, targets, 1) @@ -287,7 +289,7 @@ func TestHandler_DescribeScalableTargets_OmitsTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var registerResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®isterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®isterResp, jsonx.V1())) doRequest(t, h, "TagResource", map[string]any{ "ResourceARN": registerResp["ScalableTargetARN"], @@ -298,7 +300,7 @@ func TestHandler_DescribeScalableTargets_OmitsTags(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) targets := resp["ScalableTargets"].([]any) require.Len(t, targets, 1) @@ -332,7 +334,7 @@ func TestHandler_DescribeScalableTargets_ResourceIdsFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["ScalableTargets"].([]any) require.True(t, ok) assert.Len(t, targets, 2, "expected exactly 2 targets when filtering by ResourceIds") @@ -390,7 +392,7 @@ func TestHandler_DescribeScalableTargets_ScalableDimensionFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["ScalableTargets"].([]any) require.True(t, ok) assert.Len(t, targets, tt.wantCount) @@ -431,7 +433,7 @@ func TestHandler_DescribeScalableTargets_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) targets := out["ScalableTargets"].([]any) assert.Len(t, targets, tc.wantCount) if tc.wantNext { @@ -481,7 +483,7 @@ func TestHandler_MaxResults_DescribeScalableTargets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["ScalableTargets"].([]any) require.True(t, ok) assert.Len(t, targets, tt.wantLen) @@ -512,7 +514,7 @@ func TestHandler_ApplyMaxResults_CapAt50(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, _ := resp["ScalableTargets"].([]any) assert.Len(t, targets, 50, "MaxResults=200 should be capped at 50") } diff --git a/services/applicationautoscaling/handler_scalable_targets_test.go b/services/applicationautoscaling/handler_scalable_targets_test.go index 8296e49099..878b5b8c04 100644 --- a/services/applicationautoscaling/handler_scalable_targets_test.go +++ b/services/applicationautoscaling/handler_scalable_targets_test.go @@ -1,13 +1,15 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_RegisterScalableTarget(t *testing.T) { @@ -42,7 +44,7 @@ func TestHandler_RegisterScalableTarget(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) assert.Contains(t, resp[tt.wantKey].(string), "arn:aws:application-autoscaling:") }) @@ -65,7 +67,7 @@ func TestHandler_RegisterScalableTarget_Upsert(t *testing.T) { rec1 := doRequest(t, h, "RegisterScalableTarget", body) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) // Update (upsert) - should update, not error body["MaxCapacity"] = int32(20) @@ -76,7 +78,7 @@ func TestHandler_RegisterScalableTarget_Upsert(t *testing.T) { descRec := doRequest(t, h, "DescribeScalableTargets", map[string]any{"ServiceNamespace": "ecs"}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) targets := descResp["ScalableTargets"].([]any) require.Len(t, targets, 1) target := targets[0].(map[string]any) @@ -171,7 +173,7 @@ func TestHandler_RegisterScalableTarget_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) targetARN, _ := regResp["ScalableTargetARN"].(string) require.NotEmpty(t, targetARN) @@ -180,7 +182,7 @@ func TestHandler_RegisterScalableTarget_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, tagRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp, jsonx.V1())) tags, ok := tagResp["Tags"].(map[string]any) require.True(t, ok, "expected Tags in response") assert.Equal(t, "prod", tags["env"]) @@ -207,7 +209,7 @@ func TestHandler_RegisterScalableTarget_WithRoleARN(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) targets, ok := descResp["ScalableTargets"].([]any) require.True(t, ok) require.Len(t, targets, 1) @@ -239,7 +241,7 @@ func TestHandler_RegisterScalableTarget_WithSuspendedState(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["ScalableTargets"].([]any) require.True(t, ok) require.Len(t, targets, 1) @@ -283,7 +285,7 @@ func TestHandler_RegisterScalableTarget_UpdateSuspendedState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, _ := resp["ScalableTargets"].([]any) require.Len(t, targets, 1) @@ -435,7 +437,7 @@ func TestRegisterDeregisterLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) targets := descOut["ScalableTargets"].([]any) require.Len(t, targets, 1) t0 := targets[0].(map[string]any) @@ -454,7 +456,7 @@ func TestRegisterDeregisterLifecycle(t *testing.T) { "ServiceNamespace": ns, }) var afterOut map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut, jsonx.V1())) assert.Empty(t, afterOut["ScalableTargets"]) } @@ -493,7 +495,7 @@ func TestHandler_RegisterScalableTarget_UpdateOmittedCapacityPreserved(t *testin require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) targets, _ := resp["ScalableTargets"].([]any) require.Len(t, targets, 1) target := targets[0].(map[string]any) diff --git a/services/applicationautoscaling/handler_scaling_policies_test.go b/services/applicationautoscaling/handler_scaling_policies_test.go index e396562d3c..88fc0e3027 100644 --- a/services/applicationautoscaling/handler_scaling_policies_test.go +++ b/services/applicationautoscaling/handler_scaling_policies_test.go @@ -1,7 +1,7 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutScalingPolicy(t *testing.T) { @@ -26,7 +28,7 @@ func TestHandler_PutScalingPolicy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policyARN, _ := resp["PolicyARN"].(string) assert.Contains(t, policyARN, "arn:aws:autoscaling:") assert.Contains(t, policyARN, "scalingPolicy:") @@ -125,7 +127,7 @@ func TestHandler_PutScalingPolicy_DefaultPolicyType(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ScalingPolicies"].([]any) require.True(t, ok) require.Len(t, policies, 1) @@ -161,7 +163,7 @@ func TestHandler_PutScalingPolicy_PreservesTypeOnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, _ := resp["ScalingPolicies"].([]any) require.Len(t, policies, 1) policy := policies[0].(map[string]any) @@ -284,7 +286,7 @@ func TestHandler_DescribeScalingPolicies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ScalingPolicies"].([]any) require.True(t, ok) assert.Len(t, policies, tt.wantCount) @@ -348,7 +350,7 @@ func TestHandler_DescribeScalingPolicies_RicherFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ScalingPolicies"].([]any) require.True(t, ok) assert.Len(t, policies, tt.wantCount) @@ -380,7 +382,7 @@ func TestHandler_DescribeScalingPolicies_IncludesConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, _ := resp["ScalingPolicies"].([]any) require.Len(t, policies, 1) policy := policies[0].(map[string]any) @@ -411,7 +413,7 @@ func TestHandler_MaxResults_DescribeScalingPolicies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ScalingPolicies"].([]any) require.True(t, ok) assert.Len(t, policies, 2) @@ -442,7 +444,7 @@ func TestScalingPolicyCRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, putRec.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) policyARN := putOut["PolicyARN"].(string) assert.NotEmpty(t, policyARN) @@ -450,7 +452,7 @@ func TestScalingPolicyCRUD(t *testing.T) { "ServiceNamespace": ns, }) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) policies := descOut["ScalingPolicies"].([]any) require.Len(t, policies, 1) p0 := policies[0].(map[string]any) @@ -471,7 +473,7 @@ func TestScalingPolicyCRUD(t *testing.T) { "ServiceNamespace": ns, }) var afterOut map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut, jsonx.V1())) assert.Empty(t, afterOut["ScalingPolicies"]) } @@ -538,7 +540,7 @@ func TestHandler_PutScalingPolicy_Alarms(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut, jsonx.V1())) _, putHasAlarms := putOut["Alarms"] assert.False(t, putHasAlarms, "PutScalingPolicy response Alarms should be omitted") @@ -549,7 +551,7 @@ func TestHandler_PutScalingPolicy_Alarms(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) policies := descOut["ScalingPolicies"].([]any) require.Len(t, policies, 1) _, descHasAlarms := policies[0].(map[string]any)["Alarms"] @@ -589,7 +591,7 @@ func TestHandler_PutScalingPolicy_PredictiveScalingConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) policies := descOut["ScalingPolicies"].([]any) require.Len(t, policies, 1) p0 := policies[0].(map[string]any) diff --git a/services/applicationautoscaling/handler_scheduled_actions_test.go b/services/applicationautoscaling/handler_scheduled_actions_test.go index af98f94cb8..03700a7e99 100644 --- a/services/applicationautoscaling/handler_scheduled_actions_test.go +++ b/services/applicationautoscaling/handler_scheduled_actions_test.go @@ -1,13 +1,15 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutScheduledAction(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_PutScheduledAction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ScheduledActionARN"], "arn:aws:autoscaling:") assert.Contains(t, resp["ScheduledActionARN"], "scheduledAction:") } @@ -54,7 +56,7 @@ func TestHandler_PutScheduledAction_Upsert(t *testing.T) { descRec := doRequest(t, h, "DescribeScheduledActions", map[string]any{"ServiceNamespace": "ecs"}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) actions := descResp["ScheduledActions"].([]any) assert.Len(t, actions, 1) } @@ -141,7 +143,7 @@ func TestHandler_PutScheduledAction_WithScalableTargetAction(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) require.Len(t, actions, 1) @@ -176,7 +178,7 @@ func TestHandler_PutScheduledAction_StartEndTimeTimezone(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) require.Len(t, actions, 1) @@ -318,7 +320,7 @@ func TestHandler_DescribeScheduledActions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) assert.Len(t, actions, tt.wantCount) @@ -377,7 +379,7 @@ func TestHandler_DescribeScheduledActions_RicherFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) assert.Len(t, actions, tt.wantCount) @@ -407,7 +409,7 @@ func TestHandler_MaxResults_DescribeScheduledActions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) assert.Len(t, actions, 2) @@ -439,7 +441,7 @@ func TestScheduledActionCRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, putRec.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) actionARN := putOut["ScheduledActionARN"].(string) assert.NotEmpty(t, actionARN) @@ -447,7 +449,7 @@ func TestScheduledActionCRUD(t *testing.T) { "ServiceNamespace": ns, }) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) actions := descOut["ScheduledActions"].([]any) require.Len(t, actions, 1) a0 := actions[0].(map[string]any) @@ -467,7 +469,7 @@ func TestScheduledActionCRUD(t *testing.T) { "ServiceNamespace": ns, }) var afterOut map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut, jsonx.V1())) assert.Empty(t, afterOut["ScheduledActions"]) } @@ -507,7 +509,7 @@ func TestHandler_PutScheduledAction_UpdateClearsOmittedStartEndTime(t *testing.T require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) require.Len(t, actions, 1) @@ -554,7 +556,7 @@ func TestHandler_PutScheduledAction_UpdateWithoutScheduleKeepsOld(t *testing.T) require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) actions, ok := resp["ScheduledActions"].([]any) require.True(t, ok) require.Len(t, actions, 1) diff --git a/services/applicationautoscaling/handler_tags_test.go b/services/applicationautoscaling/handler_tags_test.go index b54f97f773..5bf03e39d7 100644 --- a/services/applicationautoscaling/handler_tags_test.go +++ b/services/applicationautoscaling/handler_tags_test.go @@ -1,13 +1,15 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagResource(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_TagResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) resourceARN := createResp["ScalableTargetARN"] tagRec := doRequest(t, h, "TagResource", map[string]any{ @@ -84,7 +86,7 @@ func TestHandler_TagResource_ExceedsLimit(t *testing.T) { require.Equal(t, http.StatusOK, regRec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Resp, jsonx.V1())) targetARN, _ := regResp["ScalableTargetARN"].(string) require.NotEmpty(t, targetARN) @@ -110,7 +112,7 @@ func TestHandler_TagResource_ExceedsLimit(t *testing.T) { assert.Equal(t, http.StatusBadRequest, overRec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(overRec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(overRec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "TooManyTagsException", errBody["__type"]) assert.Equal(t, targetARN, errBody["ResourceName"]) } @@ -141,7 +143,7 @@ func TestHandler_RegisterScalableTarget_ExceedsTagLimit(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "LimitExceededException", errBody["__type"]) } @@ -187,7 +189,7 @@ func TestHandler_TagOps_NotFound_ResourceNotFoundException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errBody["__type"]) assert.Equal(t, missingARN, errBody["ResourceName"]) }) @@ -208,7 +210,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) resourceARN := createResp["ScalableTargetARN"] doRequest(t, h, "TagResource", map[string]any{ @@ -220,7 +222,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -234,7 +236,7 @@ func TestHandler_ListTagsForResource_EmptyARN(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errBody["__type"], "ListTagsForResource's own deserializeOpError switch types only ResourceNotFoundException") } @@ -253,7 +255,7 @@ func TestHandler_UntagResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) resourceARN := createResp["ScalableTargetARN"] doRequest(t, h, "TagResource", map[string]any{ @@ -269,7 +271,7 @@ func TestHandler_UntagResource(t *testing.T) { listRec := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": resourceARN}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) _, hasEnv := tags["env"] @@ -305,7 +307,7 @@ func TestHandler_TagResource_RoundTrip(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -320,7 +322,7 @@ func TestHandler_TagResource_RoundTrip(t *testing.T) { "ResourceARN": arn, }) var afterOut map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut, jsonx.V1())) afterTags := afterOut["Tags"].(map[string]any) assert.NotContains(t, afterTags, "env") assert.Equal(t, "platform", afterTags["team"]) diff --git a/services/applicationautoscaling/handler_test.go b/services/applicationautoscaling/handler_test.go index d49fe37375..66659d7861 100644 --- a/services/applicationautoscaling/handler_test.go +++ b/services/applicationautoscaling/handler_test.go @@ -2,7 +2,7 @@ package applicationautoscaling_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "log/slog" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/applicationautoscaling" ) @@ -35,7 +36,7 @@ func doRequest(t *testing.T, h *applicationautoscaling.Handler, action string, b var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -101,7 +102,7 @@ func seedTargetNS( require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ScalableTargetARN"].(string) } @@ -433,7 +434,7 @@ func TestApplicationAutoScaling_Handler_Reset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) targets, _ := out["ScalableTargets"].([]any) assert.Len(t, targets, tt.wantAfter) diff --git a/services/applicationautoscaling/multi_region_test.go b/services/applicationautoscaling/multi_region_test.go index 7720c4b5a6..a3b62f4a86 100644 --- a/services/applicationautoscaling/multi_region_test.go +++ b/services/applicationautoscaling/multi_region_test.go @@ -2,7 +2,7 @@ package applicationautoscaling_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/applicationautoscaling" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/applicationautoscaling/persistence.go b/services/applicationautoscaling/persistence.go index 0d0985e1f7..eab6e67a92 100644 --- a/services/applicationautoscaling/persistence.go +++ b/services/applicationautoscaling/persistence.go @@ -2,7 +2,7 @@ package applicationautoscaling import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -35,10 +35,10 @@ const applicationautoscalingSnapshotVersion = 1 // it too), so it remains ephemeral, reset to empty by both Reset and a // version-mismatch Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/applicationautoscaling/persistence_test.go b/services/applicationautoscaling/persistence_test.go index 94abafa699..7309609c79 100644 --- a/services/applicationautoscaling/persistence_test.go +++ b/services/applicationautoscaling/persistence_test.go @@ -1,13 +1,14 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/applicationautoscaling" ) @@ -368,7 +369,7 @@ func TestPersistence_SnapshotRestore(t *testing.T) { rec := doRequest(t, h2, "DescribeScalableTargets", map[string]any{"ServiceNamespace": "ecs"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets := resp["ScalableTargets"].([]any) assert.Len(t, targets, 1) } diff --git a/services/applicationautoscaling/scaling_activities_test.go b/services/applicationautoscaling/scaling_activities_test.go index 3bf19ffd8e..e675c1592d 100644 --- a/services/applicationautoscaling/scaling_activities_test.go +++ b/services/applicationautoscaling/scaling_activities_test.go @@ -1,13 +1,14 @@ package applicationautoscaling_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/applicationautoscaling" ) @@ -126,7 +127,7 @@ func TestHandler_DescribeScalingActivities(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) activities, ok := resp["ScalingActivities"].([]any) require.True(t, ok) assert.Empty(t, activities) @@ -154,7 +155,7 @@ func TestHandler_DescribeScalingActivities_AfterRegister(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) activities, ok := resp["ScalingActivities"].([]any) require.True(t, ok) require.Len(t, activities, 1) @@ -188,7 +189,7 @@ func TestHandler_DescribeScalingActivities_WithInput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) acts, ok := resp["ScalingActivities"].([]any) require.True(t, ok, "ScalingActivities should be an array") assert.Empty(t, acts, "expected empty array not null") @@ -213,7 +214,7 @@ func TestHandler_DescribeScalingActivities_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1 := out1["ScalingActivities"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["NextToken"].(string) @@ -226,7 +227,7 @@ func TestHandler_DescribeScalingActivities_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2 := out2["ScalingActivities"].([]any) assert.Len(t, page2, 2) } @@ -243,7 +244,7 @@ func TestHandler_DescribeScalingActivities_TypedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) activities := out["ScalingActivities"].([]any) require.Len(t, activities, 1) diff --git a/services/appmesh/handler.go b/services/appmesh/handler.go index 160bf2c1aa..8853733455 100644 --- a/services/appmesh/handler.go +++ b/services/appmesh/handler.go @@ -2,7 +2,8 @@ package appmesh import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -211,12 +213,12 @@ func metaToWire(m ResourceMeta) map[string]any { } // specOrEmpty returns a JSON object, ensuring we never return null. -func specOrEmpty(spec json.RawMessage) any { +func specOrEmpty(spec jsontext.Value) any { if len(spec) == 0 { return map[string]any{} } var v any - if err := json.Unmarshal(spec, &v); err != nil { + if err := json.Unmarshal(spec, &v, jsonx.V1()); err != nil { return map[string]any{} } diff --git a/services/appmesh/handler_meshes.go b/services/appmesh/handler_meshes.go index 5583569248..803cb0c54b 100644 --- a/services/appmesh/handler_meshes.go +++ b/services/appmesh/handler_meshes.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "github.com/labstack/echo/v5" @@ -71,10 +71,10 @@ func (h *Handler) handleMeshes(c *echo.Context, segs []string) error { func (h *Handler) handleCreateMesh(c *echo.Context) error { var body struct { - MeshName string `json:"meshName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + MeshName string `json:"meshName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.MeshName) { return c.JSON( @@ -104,8 +104,8 @@ func (h *Handler) handleDescribeMesh(c *echo.Context, meshName string) error { func (h *Handler) handleUpdateMesh(c *echo.Context, meshName string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) diff --git a/services/appmesh/handler_test.go b/services/appmesh/handler_test.go index 3141a2b0d2..3ff79ffc11 100644 --- a/services/appmesh/handler_test.go +++ b/services/appmesh/handler_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appmesh" ) @@ -26,7 +27,7 @@ func doRequest(t *testing.T, h *appmesh.Handler, method, path string, body any) t.Helper() var bodyStr string if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyStr = string(b) } @@ -45,7 +46,7 @@ func doRequest(t *testing.T, h *appmesh.Handler, method, path string, body any) func getBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/appmesh/handler_virtual_gateways.go b/services/appmesh/handler_virtual_gateways.go index 5bc6c92aa6..ca9e2443f8 100644 --- a/services/appmesh/handler_virtual_gateways.go +++ b/services/appmesh/handler_virtual_gateways.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "github.com/labstack/echo/v5" @@ -47,10 +47,10 @@ func (h *Handler) handleVirtualGateways(c *echo.Context, segs []string, meshName func (h *Handler) handleCreateVirtualGateway(c *echo.Context, meshName string) error { var body struct { - VirtualGatewayName string `json:"virtualGatewayName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + VirtualGatewayName string `json:"virtualGatewayName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.VirtualGatewayName) { return c.JSON( @@ -77,8 +77,8 @@ func (h *Handler) handleDescribeVirtualGateway(c *echo.Context, meshName, name s func (h *Handler) handleUpdateVirtualGateway(c *echo.Context, meshName, name string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) @@ -190,10 +190,10 @@ func (h *Handler) handleGatewayRoutes(c *echo.Context, segs []string, meshName s func (h *Handler) handleCreateGatewayRoute(c *echo.Context, meshName, vgName string) error { var body struct { - GatewayRouteName string `json:"gatewayRouteName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + GatewayRouteName string `json:"gatewayRouteName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.GatewayRouteName) { return c.JSON( @@ -220,8 +220,8 @@ func (h *Handler) handleDescribeGatewayRoute(c *echo.Context, meshName, vgName, func (h *Handler) handleUpdateGatewayRoute(c *echo.Context, meshName, vgName, routeName string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) diff --git a/services/appmesh/handler_virtual_nodes.go b/services/appmesh/handler_virtual_nodes.go index 4bc1dcce0e..0db9ddb14c 100644 --- a/services/appmesh/handler_virtual_nodes.go +++ b/services/appmesh/handler_virtual_nodes.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "github.com/labstack/echo/v5" @@ -47,10 +47,10 @@ func (h *Handler) handleVirtualNodes(c *echo.Context, segs []string, meshName st func (h *Handler) handleCreateVirtualNode(c *echo.Context, meshName string) error { var body struct { - VirtualNodeName string `json:"virtualNodeName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + VirtualNodeName string `json:"virtualNodeName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.VirtualNodeName) { return c.JSON( @@ -77,8 +77,8 @@ func (h *Handler) handleDescribeVirtualNode(c *echo.Context, meshName, name stri func (h *Handler) handleUpdateVirtualNode(c *echo.Context, meshName, name string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) diff --git a/services/appmesh/handler_virtual_routers.go b/services/appmesh/handler_virtual_routers.go index 140c21ddc8..7c288fa9c6 100644 --- a/services/appmesh/handler_virtual_routers.go +++ b/services/appmesh/handler_virtual_routers.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "github.com/labstack/echo/v5" @@ -47,10 +47,10 @@ func (h *Handler) handleVirtualRouters(c *echo.Context, segs []string, meshName func (h *Handler) handleCreateVirtualRouter(c *echo.Context, meshName string) error { var body struct { - VirtualRouterName string `json:"virtualRouterName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + VirtualRouterName string `json:"virtualRouterName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.VirtualRouterName) { return c.JSON( @@ -77,8 +77,8 @@ func (h *Handler) handleDescribeVirtualRouter(c *echo.Context, meshName, name st func (h *Handler) handleUpdateVirtualRouter(c *echo.Context, meshName, name string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) @@ -190,10 +190,10 @@ func (h *Handler) handleRoutes(c *echo.Context, segs []string, meshName string) func (h *Handler) handleCreateRoute(c *echo.Context, meshName, vrName string) error { var body struct { - RouteName string `json:"routeName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + RouteName string `json:"routeName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.RouteName) { return c.JSON( @@ -220,8 +220,8 @@ func (h *Handler) handleDescribeRoute(c *echo.Context, meshName, vrName, routeNa func (h *Handler) handleUpdateRoute(c *echo.Context, meshName, vrName, routeName string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) diff --git a/services/appmesh/handler_virtual_services.go b/services/appmesh/handler_virtual_services.go index e6bee819a8..2cd89ae43c 100644 --- a/services/appmesh/handler_virtual_services.go +++ b/services/appmesh/handler_virtual_services.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "github.com/labstack/echo/v5" @@ -47,10 +47,10 @@ func (h *Handler) handleVirtualServices(c *echo.Context, segs []string, meshName func (h *Handler) handleCreateVirtualService(c *echo.Context, meshName string) error { var body struct { - VirtualServiceName string `json:"virtualServiceName"` - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` - Tags []tagInput `json:"tags"` + VirtualServiceName string `json:"virtualServiceName"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` + Tags []tagInput `json:"tags"` } if err := c.Bind(&body); err != nil || !isValidResourceName(body.VirtualServiceName) { return c.JSON( @@ -77,8 +77,8 @@ func (h *Handler) handleDescribeVirtualService(c *echo.Context, meshName, name s func (h *Handler) handleUpdateVirtualService(c *echo.Context, meshName, name string) error { var body struct { - ClientToken string `json:"clientToken"` - Spec json.RawMessage `json:"spec"` + ClientToken string `json:"clientToken"` + Spec jsontext.Value `json:"spec"` } if err := c.Bind(&body); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) diff --git a/services/appmesh/interfaces.go b/services/appmesh/interfaces.go index 37e588e3d3..e061a17256 100644 --- a/services/appmesh/interfaces.go +++ b/services/appmesh/interfaces.go @@ -2,7 +2,7 @@ package appmesh import ( "context" - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -15,59 +15,59 @@ type StorageBackend interface { Restore(ctx context.Context, data []byte) error // Mesh operations - CreateMesh(name string, spec json.RawMessage, tags map[string]string) (*Mesh, error) + CreateMesh(name string, spec jsontext.Value, tags map[string]string) (*Mesh, error) DescribeMesh(name string) (*Mesh, error) - UpdateMesh(name string, spec json.RawMessage) (*Mesh, error) + UpdateMesh(name string, spec jsontext.Value) (*Mesh, error) DeleteMesh(name string) (*Mesh, error) ListMeshes(maxResults int32, nextToken string) ([]*MeshSummary, string, error) // VirtualNode operations - CreateVirtualNode(meshName, name string, spec json.RawMessage, tags map[string]string) (*VirtualNode, error) + CreateVirtualNode(meshName, name string, spec jsontext.Value, tags map[string]string) (*VirtualNode, error) DescribeVirtualNode(meshName, name string) (*VirtualNode, error) - UpdateVirtualNode(meshName, name string, spec json.RawMessage) (*VirtualNode, error) + UpdateVirtualNode(meshName, name string, spec jsontext.Value) (*VirtualNode, error) DeleteVirtualNode(meshName, name string) (*VirtualNode, error) ListVirtualNodes(meshName string, maxResults int32, nextToken string) ([]*VirtualNodeSummary, string, error) // VirtualRouter operations - CreateVirtualRouter(meshName, name string, spec json.RawMessage, tags map[string]string) (*VirtualRouter, error) + CreateVirtualRouter(meshName, name string, spec jsontext.Value, tags map[string]string) (*VirtualRouter, error) DescribeVirtualRouter(meshName, name string) (*VirtualRouter, error) - UpdateVirtualRouter(meshName, name string, spec json.RawMessage) (*VirtualRouter, error) + UpdateVirtualRouter(meshName, name string, spec jsontext.Value) (*VirtualRouter, error) DeleteVirtualRouter(meshName, name string) (*VirtualRouter, error) ListVirtualRouters(meshName string, maxResults int32, nextToken string) ([]*VirtualRouterSummary, string, error) // Route operations (nested under virtual router) CreateRoute( meshName, virtualRouterName, routeName string, - spec json.RawMessage, + spec jsontext.Value, tags map[string]string, ) (*Route, error) DescribeRoute(meshName, virtualRouterName, routeName string) (*Route, error) - UpdateRoute(meshName, virtualRouterName, routeName string, spec json.RawMessage) (*Route, error) + UpdateRoute(meshName, virtualRouterName, routeName string, spec jsontext.Value) (*Route, error) DeleteRoute(meshName, virtualRouterName, routeName string) (*Route, error) ListRoutes(meshName, virtualRouterName string, maxResults int32, nextToken string) ([]*RouteSummary, string, error) // VirtualService operations - CreateVirtualService(meshName, name string, spec json.RawMessage, tags map[string]string) (*VirtualService, error) + CreateVirtualService(meshName, name string, spec jsontext.Value, tags map[string]string) (*VirtualService, error) DescribeVirtualService(meshName, name string) (*VirtualService, error) - UpdateVirtualService(meshName, name string, spec json.RawMessage) (*VirtualService, error) + UpdateVirtualService(meshName, name string, spec jsontext.Value) (*VirtualService, error) DeleteVirtualService(meshName, name string) (*VirtualService, error) ListVirtualServices(meshName string, maxResults int32, nextToken string) ([]*VirtualServiceSummary, string, error) // VirtualGateway operations - CreateVirtualGateway(meshName, name string, spec json.RawMessage, tags map[string]string) (*VirtualGateway, error) + CreateVirtualGateway(meshName, name string, spec jsontext.Value, tags map[string]string) (*VirtualGateway, error) DescribeVirtualGateway(meshName, name string) (*VirtualGateway, error) - UpdateVirtualGateway(meshName, name string, spec json.RawMessage) (*VirtualGateway, error) + UpdateVirtualGateway(meshName, name string, spec jsontext.Value) (*VirtualGateway, error) DeleteVirtualGateway(meshName, name string) (*VirtualGateway, error) ListVirtualGateways(meshName string, maxResults int32, nextToken string) ([]*VirtualGatewaySummary, string, error) // GatewayRoute operations (nested under virtual gateway) CreateGatewayRoute( meshName, virtualGatewayName, routeName string, - spec json.RawMessage, + spec jsontext.Value, tags map[string]string, ) (*GatewayRoute, error) DescribeGatewayRoute(meshName, virtualGatewayName, routeName string) (*GatewayRoute, error) - UpdateGatewayRoute(meshName, virtualGatewayName, routeName string, spec json.RawMessage) (*GatewayRoute, error) + UpdateGatewayRoute(meshName, virtualGatewayName, routeName string, spec jsontext.Value) (*GatewayRoute, error) DeleteGatewayRoute(meshName, virtualGatewayName, routeName string) (*GatewayRoute, error) ListGatewayRoutes( meshName, virtualGatewayName string, @@ -102,7 +102,7 @@ type Mesh struct { Name string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // MeshSummary is a mesh entry in a list response. @@ -122,7 +122,7 @@ type VirtualNode struct { VirtualNodeName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // VirtualNodeSummary is a virtual node entry in a list response. @@ -143,7 +143,7 @@ type VirtualRouter struct { VirtualRouterName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // VirtualRouterSummary is a virtual router entry in a list response. @@ -165,7 +165,7 @@ type Route struct { RouteName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // RouteSummary is a route entry in a list response. @@ -187,7 +187,7 @@ type VirtualService struct { VirtualServiceName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // VirtualServiceSummary is a virtual service entry in a list response. @@ -208,7 +208,7 @@ type VirtualGateway struct { VirtualGatewayName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // VirtualGatewaySummary is a virtual gateway entry in a list response. @@ -230,7 +230,7 @@ type GatewayRoute struct { GatewayRouteName string Status string Meta ResourceMeta - Spec json.RawMessage + Spec jsontext.Value } // GatewayRouteSummary is a gateway route entry in a list response. diff --git a/services/appmesh/meshes.go b/services/appmesh/meshes.go index 19f93ffdf0..b54ac252c8 100644 --- a/services/appmesh/meshes.go +++ b/services/appmesh/meshes.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -12,7 +12,7 @@ func (b *InMemoryBackend) meshARN(meshName string) string { return arn.Build("appmesh", b.region, b.accountID, fmt.Sprintf("mesh/%s", meshName)) } -func (b *InMemoryBackend) CreateMesh(name string, spec json.RawMessage, tags map[string]string) (*Mesh, error) { +func (b *InMemoryBackend) CreateMesh(name string, spec jsontext.Value, tags map[string]string) (*Mesh, error) { b.mu.Lock("CreateMesh") defer b.mu.Unlock() if err := validateMeshSpec(spec); err != nil { @@ -47,7 +47,7 @@ func (b *InMemoryBackend) DescribeMesh(name string) (*Mesh, error) { return m, nil } -func (b *InMemoryBackend) UpdateMesh(name string, spec json.RawMessage) (*Mesh, error) { +func (b *InMemoryBackend) UpdateMesh(name string, spec jsontext.Value) (*Mesh, error) { b.mu.Lock("UpdateMesh") defer b.mu.Unlock() if err := validateMeshSpec(spec); err != nil { diff --git a/services/appmesh/meshes_test.go b/services/appmesh/meshes_test.go index f016fb4d6e..0511bae77b 100644 --- a/services/appmesh/meshes_test.go +++ b/services/appmesh/meshes_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/http" "strings" @@ -232,7 +232,7 @@ func TestBackend_SpecRoundTrip(t *testing.T) { t.Parallel() b := appmesh.NewInMemoryBackend("000000000000", "us-east-1") - _, err := b.CreateMesh("m1", json.RawMessage(`{"key":"val"}`), nil) + _, err := b.CreateMesh("m1", jsontext.Value(`{"key":"val"}`), nil) require.NoError(t, err) m, err := b.DescribeMesh("m1") diff --git a/services/appmesh/persistence.go b/services/appmesh/persistence.go index c58e2f41c1..0c94fd83a9 100644 --- a/services/appmesh/persistence.go +++ b/services/appmesh/persistence.go @@ -2,7 +2,7 @@ package appmesh import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -36,7 +36,7 @@ const appmeshSnapshotVersion = 1 // an incompatible (older or newer) build of this backend as though it were // the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/appmesh/spec_schema.go b/services/appmesh/spec_schema.go index 3d3b285d15..bf98c5ce97 100644 --- a/services/appmesh/spec_schema.go +++ b/services/appmesh/spec_schema.go @@ -2,7 +2,8 @@ package appmesh import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -12,6 +13,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errSpecDSL = errors.New("appmesh spec dsl") @@ -459,13 +461,13 @@ func mustSpecSchemas() *specSchemas { return s } -func (s *specSchemas) validate(shape string, spec json.RawMessage) error { +func (s *specSchemas) validate(shape string, spec jsontext.Value) error { if len(bytes.TrimSpace(spec)) == 0 { return nil } var v any - if err := json.Unmarshal(spec, &v); err != nil { + if err := json.Unmarshal(spec, &v, jsonx.V1()); err != nil { return awserr.Newf("spec: %s", awserr.ErrInvalidParameter, err) } diff --git a/services/appmesh/spec_schema_test.go b/services/appmesh/spec_schema_test.go index 502ea0adf5..765caa7c6e 100644 --- a/services/appmesh/spec_schema_test.go +++ b/services/appmesh/spec_schema_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -105,10 +105,10 @@ func TestBackend_DeepSpecValidation(t *testing.T) { require.NoError(t, err) _, err = b.CreateVirtualRouter("m", "r", nil, nil) require.NoError(t, err) - _, err = b.CreateVirtualGateway("m", "g", json.RawMessage(`{"listeners":[`+httpPortMapping+`]}`), nil) + _, err = b.CreateVirtualGateway("m", "g", jsontext.Value(`{"listeners":[`+httpPortMapping+`]}`), nil) require.NoError(t, err) - spec := json.RawMessage(tt.spec) + spec := jsontext.Value(tt.spec) switch tt.kind { case "node": diff --git a/services/appmesh/spec_validate.go b/services/appmesh/spec_validate.go index 9ca254f0e1..64d1a41503 100644 --- a/services/appmesh/spec_validate.go +++ b/services/appmesh/spec_validate.go @@ -1,9 +1,11 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Structural validation for the shallow spec shapes (MeshSpec, VirtualRouterSpec, @@ -36,7 +38,7 @@ func isValidPortProtocol(p string) bool { } // validateMeshSpec structurally validates a MeshSpec body (types.MeshSpec). -func validateMeshSpec(spec json.RawMessage) error { +func validateMeshSpec(spec jsontext.Value) error { if len(spec) == 0 { return nil } @@ -48,7 +50,7 @@ func validateMeshSpec(spec json.RawMessage) error { IPPreference string `json:"ipPreference"` } `json:"serviceDiscovery"` } - if err := json.Unmarshal(spec, &body); err != nil { + if err := json.Unmarshal(spec, &body, jsonx.V1()); err != nil { return awserr.Newf("spec: %s", awserr.ErrInvalidParameter, err) } if body.EgressFilter != nil && !isValidEgressFilterType(body.EgressFilter.Type) { @@ -89,7 +91,7 @@ func validatePortMapping(pm *portMappingBody) error { // validateVirtualRouterSpec structurally validates a VirtualRouterSpec body // (types.VirtualRouterSpec). -func validateVirtualRouterSpec(spec json.RawMessage) error { +func validateVirtualRouterSpec(spec jsontext.Value) error { if len(spec) == 0 { return nil } @@ -98,7 +100,7 @@ func validateVirtualRouterSpec(spec json.RawMessage) error { PortMapping *portMappingBody `json:"portMapping"` } `json:"listeners"` } - if err := json.Unmarshal(spec, &body); err != nil { + if err := json.Unmarshal(spec, &body, jsonx.V1()); err != nil { return awserr.Newf("spec: %s", awserr.ErrInvalidParameter, err) } for _, l := range body.Listeners { @@ -122,14 +124,14 @@ type virtualServiceProviderBody struct { // validateVirtualServiceSpec structurally validates a VirtualServiceSpec body // (types.VirtualServiceSpec). provider is a smithy union: exactly one of // virtualNode/virtualRouter may be set. -func validateVirtualServiceSpec(spec json.RawMessage) error { +func validateVirtualServiceSpec(spec jsontext.Value) error { if len(spec) == 0 { return nil } var body struct { Provider *virtualServiceProviderBody `json:"provider"` } - if err := json.Unmarshal(spec, &body); err != nil { + if err := json.Unmarshal(spec, &body, jsonx.V1()); err != nil { return awserr.Newf("spec: %s", awserr.ErrInvalidParameter, err) } if body.Provider == nil { diff --git a/services/appmesh/spec_validate_test.go b/services/appmesh/spec_validate_test.go index 5d5a43056d..909c27ea09 100644 --- a/services/appmesh/spec_validate_test.go +++ b/services/appmesh/spec_validate_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -37,9 +37,9 @@ func TestBackend_MeshSpecValidation(t *testing.T) { t.Parallel() b := appmesh.NewInMemoryBackend("000000000000", "us-east-1") - var raw json.RawMessage + var raw jsontext.Value if tt.spec != "" { - raw = json.RawMessage(tt.spec) + raw = jsontext.Value(tt.spec) } _, err := b.CreateMesh("m1", raw, nil) if tt.wantErr { @@ -102,9 +102,9 @@ func TestBackend_VirtualRouterSpecValidation(t *testing.T) { _, err := b.CreateMesh("m1", nil, nil) require.NoError(t, err) - var raw json.RawMessage + var raw jsontext.Value if tt.spec != "" { - raw = json.RawMessage(tt.spec) + raw = jsontext.Value(tt.spec) } _, err = b.CreateVirtualRouter("m1", "vr1", raw, nil) if tt.wantErr { @@ -168,9 +168,9 @@ func TestBackend_VirtualServiceSpecValidation(t *testing.T) { _, err := b.CreateMesh("m1", nil, nil) require.NoError(t, err) - var raw json.RawMessage + var raw jsontext.Value if tt.spec != "" { - raw = json.RawMessage(tt.spec) + raw = jsontext.Value(tt.spec) } _, err = b.CreateVirtualService("m1", "vs1", raw, nil) if tt.wantErr { diff --git a/services/appmesh/store.go b/services/appmesh/store.go index e0c1a2aa61..554cc46954 100644 --- a/services/appmesh/store.go +++ b/services/appmesh/store.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "maps" @@ -108,9 +108,9 @@ const statusActive = "ACTIVE" const statusDeleted = "DELETED" // normalizeSpec returns a non-nil JSON object if spec is nil or empty. -func normalizeSpec(spec json.RawMessage) json.RawMessage { +func normalizeSpec(spec jsontext.Value) jsontext.Value { if len(spec) == 0 { - return json.RawMessage(`{}`) + return jsontext.Value(`{}`) } return spec diff --git a/services/appmesh/virtual_gateways.go b/services/appmesh/virtual_gateways.go index e52765036a..7dc36f62c1 100644 --- a/services/appmesh/virtual_gateways.go +++ b/services/appmesh/virtual_gateways.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "time" @@ -23,7 +23,7 @@ func (b *InMemoryBackend) gatewayRouteARN(meshName, vgName, routeName string) st // ─── VirtualGateway ─── func (b *InMemoryBackend) CreateVirtualGateway( - meshName, name string, spec json.RawMessage, tags map[string]string, + meshName, name string, spec jsontext.Value, tags map[string]string, ) (*VirtualGateway, error) { b.mu.Lock("CreateVirtualGateway") defer b.mu.Unlock() @@ -67,7 +67,7 @@ func (b *InMemoryBackend) DescribeVirtualGateway(meshName, name string) (*Virtua return vg, nil } -func (b *InMemoryBackend) UpdateVirtualGateway(meshName, name string, spec json.RawMessage) (*VirtualGateway, error) { +func (b *InMemoryBackend) UpdateVirtualGateway(meshName, name string, spec jsontext.Value) (*VirtualGateway, error) { if err := b.schemas.validate("virtualGatewaySpec", spec); err != nil { return nil, err } @@ -145,7 +145,7 @@ func (b *InMemoryBackend) ListVirtualGateways( // ─── GatewayRoute ─── func (b *InMemoryBackend) CreateGatewayRoute( - meshName, virtualGatewayName, routeName string, spec json.RawMessage, tags map[string]string, + meshName, virtualGatewayName, routeName string, spec jsontext.Value, tags map[string]string, ) (*GatewayRoute, error) { if err := b.schemas.validate("gatewayRouteSpec", spec); err != nil { return nil, err @@ -197,7 +197,7 @@ func (b *InMemoryBackend) DescribeGatewayRoute(meshName, virtualGatewayName, rou } func (b *InMemoryBackend) UpdateGatewayRoute( - meshName, virtualGatewayName, routeName string, spec json.RawMessage, + meshName, virtualGatewayName, routeName string, spec jsontext.Value, ) (*GatewayRoute, error) { if err := b.schemas.validate("gatewayRouteSpec", spec); err != nil { return nil, err diff --git a/services/appmesh/virtual_gateways_test.go b/services/appmesh/virtual_gateways_test.go index 565f0ca038..d15c513717 100644 --- a/services/appmesh/virtual_gateways_test.go +++ b/services/appmesh/virtual_gateways_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/http" "testing" @@ -232,7 +232,7 @@ func TestBackend_UpdateVirtualGateway(t *testing.T) { tt.setup(b) vg, err := b.UpdateVirtualGateway( tt.meshName, tt.vgName, - json.RawMessage(`{"listeners":[{"portMapping":{"port":8080,"protocol":"http"}}]}`), + jsontext.Value(`{"listeners":[{"portMapping":{"port":8080,"protocol":"http"}}]}`), ) if tt.wantErr != nil { require.Error(t, err) @@ -306,7 +306,7 @@ func TestBackend_UpdateGatewayRoute(t *testing.T) { tt.meshName, tt.vgName, tt.routeName, - json.RawMessage(`{}`), + jsontext.Value(`{}`), ) if tt.wantErr != nil { require.Error(t, err) diff --git a/services/appmesh/virtual_nodes.go b/services/appmesh/virtual_nodes.go index 526d0a9e76..0e2c236dc2 100644 --- a/services/appmesh/virtual_nodes.go +++ b/services/appmesh/virtual_nodes.go @@ -1,12 +1,14 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (b *InMemoryBackend) virtualNodeARN(meshName, name string) string { @@ -14,7 +16,7 @@ func (b *InMemoryBackend) virtualNodeARN(meshName, name string) string { } func (b *InMemoryBackend) CreateVirtualNode( - meshName, name string, spec json.RawMessage, tags map[string]string, + meshName, name string, spec jsontext.Value, tags map[string]string, ) (*VirtualNode, error) { if err := b.schemas.validate("virtualNodeSpec", spec); err != nil { return nil, err @@ -58,7 +60,7 @@ func (b *InMemoryBackend) DescribeVirtualNode(meshName, name string) (*VirtualNo return vn, nil } -func (b *InMemoryBackend) UpdateVirtualNode(meshName, name string, spec json.RawMessage) (*VirtualNode, error) { +func (b *InMemoryBackend) UpdateVirtualNode(meshName, name string, spec jsontext.Value) (*VirtualNode, error) { if err := b.schemas.validate("virtualNodeSpec", spec); err != nil { return nil, err } @@ -87,7 +89,7 @@ func (b *InMemoryBackend) virtualNodeIsServiceProvider(meshName, name string) bo var body struct { Provider *virtualServiceProviderBody `json:"provider"` } - if err := json.Unmarshal(vs.Spec, &body); err != nil { + if err := json.Unmarshal(vs.Spec, &body, jsonx.V1()); err != nil { continue } if body.Provider != nil && body.Provider.VirtualNode != nil && diff --git a/services/appmesh/virtual_routers.go b/services/appmesh/virtual_routers.go index 3ecf3e9e59..fc0364f56a 100644 --- a/services/appmesh/virtual_routers.go +++ b/services/appmesh/virtual_routers.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "time" @@ -24,7 +24,7 @@ func (b *InMemoryBackend) routeARN(meshName, vrName, routeName string) string { //nolint:dupl // create pattern (incl. spec validation call) is structurally identical across resource types func (b *InMemoryBackend) CreateVirtualRouter( - meshName, name string, spec json.RawMessage, tags map[string]string, + meshName, name string, spec jsontext.Value, tags map[string]string, ) (*VirtualRouter, error) { b.mu.Lock("CreateVirtualRouter") defer b.mu.Unlock() @@ -68,7 +68,7 @@ func (b *InMemoryBackend) DescribeVirtualRouter(meshName, name string) (*Virtual return vr, nil } -func (b *InMemoryBackend) UpdateVirtualRouter(meshName, name string, spec json.RawMessage) (*VirtualRouter, error) { +func (b *InMemoryBackend) UpdateVirtualRouter(meshName, name string, spec jsontext.Value) (*VirtualRouter, error) { b.mu.Lock("UpdateVirtualRouter") defer b.mu.Unlock() if err := validateVirtualRouterSpec(spec); err != nil { @@ -146,7 +146,7 @@ func (b *InMemoryBackend) ListVirtualRouters( // ─── Route ─── func (b *InMemoryBackend) CreateRoute( - meshName, virtualRouterName, routeName string, spec json.RawMessage, tags map[string]string, + meshName, virtualRouterName, routeName string, spec jsontext.Value, tags map[string]string, ) (*Route, error) { if err := b.schemas.validate("routeSpec", spec); err != nil { return nil, err @@ -199,7 +199,7 @@ func (b *InMemoryBackend) DescribeRoute(meshName, virtualRouterName, routeName s func (b *InMemoryBackend) UpdateRoute( meshName, virtualRouterName, routeName string, - spec json.RawMessage, + spec jsontext.Value, ) (*Route, error) { if err := b.schemas.validate("routeSpec", spec); err != nil { return nil, err diff --git a/services/appmesh/virtual_routers_test.go b/services/appmesh/virtual_routers_test.go index 315941b6ff..2f14f1e1c8 100644 --- a/services/appmesh/virtual_routers_test.go +++ b/services/appmesh/virtual_routers_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/http" "testing" @@ -291,7 +291,7 @@ func TestBackend_UpdateVirtualRouter(t *testing.T) { t.Parallel() b := appmesh.NewInMemoryBackend("000000000000", "us-east-1") tt.setup(b) - vr, err := b.UpdateVirtualRouter(tt.meshName, tt.vrName, json.RawMessage(`{}`)) + vr, err := b.UpdateVirtualRouter(tt.meshName, tt.vrName, jsontext.Value(`{}`)) if tt.wantErr != nil { require.Error(t, err) assert.True(t, appmesh.ErrIs(err, tt.wantErr)) @@ -360,7 +360,7 @@ func TestBackend_UpdateRoute(t *testing.T) { t.Parallel() b := appmesh.NewInMemoryBackend("000000000000", "us-east-1") tt.setup(b) - r, err := b.UpdateRoute(tt.meshName, tt.vrName, tt.routeName, json.RawMessage(`{}`)) + r, err := b.UpdateRoute(tt.meshName, tt.vrName, tt.routeName, jsontext.Value(`{}`)) if tt.wantErr != nil { require.Error(t, err) assert.True(t, appmesh.ErrIs(err, tt.wantErr)) diff --git a/services/appmesh/virtual_services.go b/services/appmesh/virtual_services.go index 09fcdb6464..df8329e0cf 100644 --- a/services/appmesh/virtual_services.go +++ b/services/appmesh/virtual_services.go @@ -1,7 +1,7 @@ package appmesh import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "time" @@ -15,7 +15,7 @@ func (b *InMemoryBackend) virtualServiceARN(meshName, name string) string { //nolint:dupl // create pattern (incl. spec validation call) is structurally identical across resource types func (b *InMemoryBackend) CreateVirtualService( - meshName, name string, spec json.RawMessage, tags map[string]string, + meshName, name string, spec jsontext.Value, tags map[string]string, ) (*VirtualService, error) { b.mu.Lock("CreateVirtualService") defer b.mu.Unlock() @@ -59,7 +59,7 @@ func (b *InMemoryBackend) DescribeVirtualService(meshName, name string) (*Virtua return vs, nil } -func (b *InMemoryBackend) UpdateVirtualService(meshName, name string, spec json.RawMessage) (*VirtualService, error) { +func (b *InMemoryBackend) UpdateVirtualService(meshName, name string, spec jsontext.Value) (*VirtualService, error) { b.mu.Lock("UpdateVirtualService") defer b.mu.Unlock() if err := validateVirtualServiceSpec(spec); err != nil { diff --git a/services/appmesh/virtual_services_test.go b/services/appmesh/virtual_services_test.go index 3e33e7e5a3..0cbce486fd 100644 --- a/services/appmesh/virtual_services_test.go +++ b/services/appmesh/virtual_services_test.go @@ -1,7 +1,7 @@ package appmesh_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/http" "testing" @@ -126,7 +126,7 @@ func TestBackend_UpdateVirtualService(t *testing.T) { t.Parallel() b := appmesh.NewInMemoryBackend("000000000000", "us-east-1") tt.setup(b) - vs, err := b.UpdateVirtualService(tt.meshName, tt.vsName, json.RawMessage(`{}`)) + vs, err := b.UpdateVirtualService(tt.meshName, tt.vsName, jsontext.Value(`{}`)) if tt.wantErr != nil { require.Error(t, err) assert.True(t, appmesh.ErrIs(err, tt.wantErr)) diff --git a/services/apprunner/handler.go b/services/apprunner/handler.go index 6a7ef3fc06..a2a4d03c47 100644 --- a/services/apprunner/handler.go +++ b/services/apprunner/handler.go @@ -2,7 +2,7 @@ package apprunner import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -192,13 +193,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): return c.JSON(http.StatusBadRequest, map[string]string{ @@ -218,8 +216,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyType: invalidRequestType, keyMessage: errMessage(err), diff --git a/services/apprunner/handler_auto_scaling_configurations_test.go b/services/apprunner/handler_auto_scaling_configurations_test.go index 354b147eb1..09ed22698a 100644 --- a/services/apprunner/handler_auto_scaling_configurations_test.go +++ b/services/apprunner/handler_auto_scaling_configurations_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAutoScalingConfigurationCRUD(t *testing.T) { //nolint:paralleltest // existing issue. @@ -27,7 +29,7 @@ func TestAutoScalingConfigurationCRUD(t *testing.T) { //nolint:paralleltest // e check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["AutoScalingConfiguration"].(map[string]any) assert.Contains(t, cfg["AutoScalingConfigurationArn"], "autoscalingconfiguration/my-asg/1/") assert.Equal(t, "ACTIVE", cfg["Status"]) @@ -67,7 +69,7 @@ func TestAutoScalingConfigurationDescribeDeleteList(t *testing.T) { //nolint:par }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) asgArn := createResp["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationArn"].(string) tests := []struct { @@ -85,7 +87,7 @@ func TestAutoScalingConfigurationDescribeDeleteList(t *testing.T) { //nolint:par check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["AutoScalingConfiguration"].(map[string]any) assert.Equal(t, "cfg1", cfg["AutoScalingConfigurationName"]) assert.InDelta(t, float64(50), cfg["MaxConcurrency"], 0.0001) @@ -113,7 +115,7 @@ func TestAutoScalingConfigurationDescribeDeleteList(t *testing.T) { //nolint:par check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["AutoScalingConfigurationSummaryList"].([]any) // cfg1 plus the account's always-present DefaultConfiguration // (see ensureDefaultAutoScalingConfiguration). @@ -128,7 +130,7 @@ func TestAutoScalingConfigurationDescribeDeleteList(t *testing.T) { //nolint:par check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["AutoScalingConfiguration"].(map[string]any) assert.Equal(t, "cfg1", cfg["AutoScalingConfigurationName"]) }, @@ -152,13 +154,13 @@ func TestAutoScalingConfigurationRevisions(t *testing.T) { //nolint:paralleltest rec := doRequest(t, h, "CreateAutoScalingConfiguration", map[string]any{"AutoScalingConfigurationName": "my-asg"}) require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) asgArn1 := r1["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationArn"].(string) rec = doRequest(t, h, "CreateAutoScalingConfiguration", map[string]any{"AutoScalingConfigurationName": "my-asg"}) require.Equal(t, http.StatusOK, rec.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2, jsonx.V1())) rev2 := r2["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationRevision"].(float64) assert.InDelta(t, float64(2), rev2, 0.0001) @@ -169,21 +171,21 @@ func TestAutoScalingConfigurationRevisions(t *testing.T) { //nolint:paralleltest rec = doRequest(t, h, "ListAutoScalingConfigurations", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list := listResp["AutoScalingConfigurationSummaryList"].([]any) // my-asg's latest revision plus DefaultConfiguration. assert.Len(t, list, 2) rec = doRequest(t, h, "ListAutoScalingConfigurations", map[string]any{"LatestOnly": true}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list = listResp["AutoScalingConfigurationSummaryList"].([]any) // my-asg's latest revision plus DefaultConfiguration. assert.Len(t, list, 2) rec = doRequest(t, h, "ListAutoScalingConfigurations", map[string]any{"LatestOnly": false}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list = listResp["AutoScalingConfigurationSummaryList"].([]any) // my-asg's 2 revisions plus the account's always-present // DefaultConfiguration (see ensureDefaultAutoScalingConfiguration). @@ -194,7 +196,7 @@ func TestAutoScalingConfigurationRevisions(t *testing.T) { //nolint:paralleltest }) require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) cfg := updateResp["AutoScalingConfiguration"].(map[string]any) assert.Equal(t, true, cfg["IsDefault"]) } @@ -205,7 +207,7 @@ func TestListServicesForAutoScalingConfiguration(t *testing.T) { //nolint:parall rec := doRequest(t, h, "CreateAutoScalingConfiguration", map[string]any{"AutoScalingConfigurationName": "my-asg"}) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) asgArn := createResp["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationArn"].(string) tests := []struct { @@ -221,7 +223,7 @@ func TestListServicesForAutoScalingConfiguration(t *testing.T) { //nolint:parall check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["ServiceArnList"].([]any) assert.Empty(t, list) }, @@ -264,7 +266,7 @@ func TestDeleteAutoScalingConfiguration_RejectsDefaultAndInUse(t *testing.T) { / rec := doRequest(t, h, "ListAutoScalingConfigurations", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list := listResp["AutoScalingConfigurationSummaryList"].([]any) require.Len(t, list, 1, "only the seeded default configuration should exist yet") defaultArn := list[0].(map[string]any)["AutoScalingConfigurationArn"].(string) @@ -281,7 +283,7 @@ func TestDeleteAutoScalingConfiguration_RejectsDefaultAndInUse(t *testing.T) { / ) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) asgArn := createResp["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -293,7 +295,7 @@ func TestDeleteAutoScalingConfiguration_RejectsDefaultAndInUse(t *testing.T) { / }) require.Equal(t, http.StatusOK, rec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp, jsonx.V1())) svcArn := svcResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DeleteAutoScalingConfiguration", map[string]any{"AutoScalingConfigurationArn": asgArn}) diff --git a/services/apprunner/handler_connections_test.go b/services/apprunner/handler_connections_test.go index 8d19891c2c..b5520453b6 100644 --- a/services/apprunner/handler_connections_test.go +++ b/services/apprunner/handler_connections_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConnectionCRUD(t *testing.T) { //nolint:paralleltest // existing issue. @@ -27,7 +29,7 @@ func TestConnectionCRUD(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) conn := resp["Connection"].(map[string]any) assert.Contains(t, conn["ConnectionArn"], "connection/my-conn/") assert.Equal(t, "AVAILABLE", conn["Status"]) @@ -65,7 +67,7 @@ func TestConnectionDeleteList(t *testing.T) { //nolint:paralleltest // existing rec := doRequest(t, h, "CreateConnection", map[string]any{"ConnectionName": "conn1", "ProviderType": "GITHUB"}) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) connArn := createResp["Connection"].(map[string]any)["ConnectionArn"].(string) doRequest(t, h, "CreateConnection", map[string]any{"ConnectionName": "conn2", "ProviderType": "BITBUCKET"}) @@ -85,7 +87,7 @@ func TestConnectionDeleteList(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["ConnectionSummaryList"].([]any) assert.Len(t, list, 2) }, @@ -98,7 +100,7 @@ func TestConnectionDeleteList(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["ConnectionSummaryList"].([]any) assert.Len(t, list, 1) }, @@ -111,7 +113,7 @@ func TestConnectionDeleteList(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) conn := resp["Connection"].(map[string]any) assert.Equal(t, "conn1", conn["ConnectionName"]) }, @@ -157,7 +159,7 @@ func TestDeleteConnection_RejectsWhenServiceUsesIt(t *testing.T) { rec := doRequest(t, h, "CreateConnection", map[string]any{"ConnectionName": "gh-conn", "ProviderType": "GITHUB"}) require.Equal(t, http.StatusOK, rec.Code) var connResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &connResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &connResp, jsonx.V1())) connArn := connResp["Connection"].(map[string]any)["ConnectionArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -172,7 +174,7 @@ func TestDeleteConnection_RejectsWhenServiceUsesIt(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp, jsonx.V1())) svcArn := svcResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DeleteConnection", map[string]any{"ConnectionArn": connArn}) diff --git a/services/apprunner/handler_custom_domains_test.go b/services/apprunner/handler_custom_domains_test.go index 454337099e..bb00067f58 100644 --- a/services/apprunner/handler_custom_domains_test.go +++ b/services/apprunner/handler_custom_domains_test.go @@ -1,13 +1,15 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCustomDomainAssociateDescribeDisassociate(t *testing.T) { //nolint:paralleltest // existing issue. @@ -32,7 +34,7 @@ func TestCustomDomainAssociateDescribeDisassociate(t *testing.T) { //nolint:para check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cd := resp["CustomDomain"].(map[string]any) assert.Equal(t, "example.com", cd["DomainName"]) assert.Equal(t, "PENDING_CERTIFICATE_DNS_VALIDATION", cd["Status"]) @@ -107,7 +109,7 @@ func TestCustomDomainDescribeAndDisassociate(t *testing.T) { //nolint:parallelte check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) domains := resp["CustomDomains"].([]any) assert.Len(t, domains, 2) assert.NotEmpty(t, resp["ServiceArn"]) @@ -134,7 +136,7 @@ func TestCustomDomainDescribeAndDisassociate(t *testing.T) { //nolint:parallelte check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cd := resp["CustomDomain"].(map[string]any) assert.Equal(t, "example.com", cd["DomainName"]) }, @@ -180,7 +182,7 @@ func TestAssociateCustomDomainDNSTargetIsServiceURL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dnsTarget, ok := resp["DNSTarget"].(string) require.True(t, ok, "AssociateCustomDomain response must include DNSTarget string") @@ -211,7 +213,7 @@ func TestDisassociateCustomDomainDNSTargetIsServiceURL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dnsTarget, ok := resp["DNSTarget"].(string) require.True(t, ok, "DisassociateCustomDomain response must include DNSTarget string") diff --git a/services/apprunner/handler_observability_configurations_test.go b/services/apprunner/handler_observability_configurations_test.go index 23efe7d65c..4341ee4a6d 100644 --- a/services/apprunner/handler_observability_configurations_test.go +++ b/services/apprunner/handler_observability_configurations_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestObservabilityConfigurationCRUD(t *testing.T) { //nolint:paralleltest // existing issue. @@ -30,7 +32,7 @@ func TestObservabilityConfigurationCRUD(t *testing.T) { //nolint:paralleltest // check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["ObservabilityConfiguration"].(map[string]any) assert.Contains(t, cfg["ObservabilityConfigurationArn"], "observabilityconfiguration/my-obs/1/") assert.Equal(t, "ACTIVE", cfg["Status"]) @@ -65,7 +67,7 @@ func TestObservabilityConfigurationDescribeDeleteList(t *testing.T) { //nolint:p }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) obsArn := createResp["ObservabilityConfiguration"].(map[string]any)["ObservabilityConfigurationArn"].(string) tests := []struct { @@ -83,7 +85,7 @@ func TestObservabilityConfigurationDescribeDeleteList(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["ObservabilityConfiguration"].(map[string]any) assert.Equal(t, "obs1", cfg["ObservabilityConfigurationName"]) }, @@ -110,7 +112,7 @@ func TestObservabilityConfigurationDescribeDeleteList(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["ObservabilityConfigurationSummaryList"].([]any) assert.Len(t, list, 1) }, @@ -123,7 +125,7 @@ func TestObservabilityConfigurationDescribeDeleteList(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfg := resp["ObservabilityConfiguration"].(map[string]any) assert.Equal(t, "obs1", cfg["ObservabilityConfigurationName"]) }, @@ -156,7 +158,7 @@ func TestDeleteObservabilityConfiguration_RejectsWhenServiceUsesIt(t *testing.T) }) require.Equal(t, http.StatusOK, rec.Code) var obsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &obsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &obsResp, jsonx.V1())) obsArn := obsResp["ObservabilityConfiguration"].(map[string]any)["ObservabilityConfigurationArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -171,7 +173,7 @@ func TestDeleteObservabilityConfiguration_RejectsWhenServiceUsesIt(t *testing.T) }) require.Equal(t, http.StatusOK, rec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp, jsonx.V1())) svcArn := svcResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DeleteObservabilityConfiguration", map[string]any{"ObservabilityConfigurationArn": obsArn}) @@ -201,26 +203,26 @@ func TestObservabilityConfigurationRevisionsLatestOnlyDefault(t *testing.T) { // ) require.Equal(t, http.StatusOK, rec.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2, jsonx.V1())) rev2 := r2["ObservabilityConfiguration"].(map[string]any)["ObservabilityConfigurationRevision"].(float64) assert.InDelta(t, float64(2), rev2, 0.0001) rec = doRequest(t, h, "ListObservabilityConfigurations", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list := listResp["ObservabilityConfigurationSummaryList"].([]any) assert.Len(t, list, 1) rec = doRequest(t, h, "ListObservabilityConfigurations", map[string]any{"LatestOnly": true}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list = listResp["ObservabilityConfigurationSummaryList"].([]any) assert.Len(t, list, 1) rec = doRequest(t, h, "ListObservabilityConfigurations", map[string]any{"LatestOnly": false}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list = listResp["ObservabilityConfigurationSummaryList"].([]any) assert.Len(t, list, 2) } diff --git a/services/apprunner/handler_operations_test.go b/services/apprunner/handler_operations_test.go index 423c035fff..360b9a45ac 100644 --- a/services/apprunner/handler_operations_test.go +++ b/services/apprunner/handler_operations_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListOperations(t *testing.T) { @@ -22,7 +24,7 @@ func TestListOperations(t *testing.T) { rec := doRequest(t, h, "ListOperations", map[string]any{"ServiceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ops, ok := resp["OperationSummaryList"].([]any) require.True(t, ok) // CREATE + PAUSE + RESUME = 3 operations diff --git a/services/apprunner/handler_services_test.go b/services/apprunner/handler_services_test.go index 92eb4e3bca..cda5d87b7f 100644 --- a/services/apprunner/handler_services_test.go +++ b/services/apprunner/handler_services_test.go @@ -1,13 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apprunner" ) @@ -35,7 +36,7 @@ func TestCreateService_ReturnsServiceArn(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) svc, ok := resp["Service"].(map[string]any) require.True(t, ok) assert.Contains(t, svc["ServiceArn"], "arn:aws:apprunner:us-east-1:000000000000:service/") @@ -77,7 +78,7 @@ func TestServiceCRUD(t *testing.T) { rec := doRequest(t, h, "DescribeService", map[string]any{"ServiceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) svc := descResp["Service"].(map[string]any) assert.Equal(t, "test-service", svc["ServiceName"]) assert.Equal(t, "RUNNING", svc["Status"]) @@ -86,7 +87,7 @@ func TestServiceCRUD(t *testing.T) { rec = doRequest(t, h, "ListServices", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["ServiceSummaryList"], 1) // Delete @@ -109,7 +110,7 @@ func TestPauseResume(t *testing.T) { rec := doRequest(t, h, "PauseService", map[string]any{"ServiceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var pauseResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pauseResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pauseResp, jsonx.V1())) svc := pauseResp["Service"].(map[string]any) assert.Equal(t, "PAUSED", svc["Status"]) @@ -117,7 +118,7 @@ func TestPauseResume(t *testing.T) { rec = doRequest(t, h, "ResumeService", map[string]any{"ServiceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var resumeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resumeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resumeResp, jsonx.V1())) svc = resumeResp["Service"].(map[string]any) assert.Equal(t, "RUNNING", svc["Status"]) } @@ -131,7 +132,7 @@ func TestStartDeployment(t *testing.T) { rec := doRequest(t, h, "StartDeployment", map[string]any{"ServiceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["OperationId"]) } @@ -150,7 +151,7 @@ func TestUpdateService(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["OperationId"]) } @@ -161,7 +162,7 @@ func TestListServices_Empty(t *testing.T) { rec := doRequest(t, h, "ListServices", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["ServiceSummaryList"].([]any) require.True(t, ok) assert.Empty(t, services) @@ -201,7 +202,7 @@ func TestPauseService_StateGuard(t *testing.T) { //nolint:paralleltest // existi assert.Equal(t, tc.wantCode, rec.Code) if tc.wantType != "" { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tc.wantType, body["__type"]) } }) @@ -242,7 +243,7 @@ func TestResumeService_StateGuard(t *testing.T) { //nolint:paralleltest // exist assert.Equal(t, tc.wantCode, rec.Code) if tc.wantType != "" { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tc.wantType, body["__type"]) } }) @@ -286,7 +287,7 @@ func TestUpdateService_StateGuard(t *testing.T) { //nolint:paralleltest // exist assert.Equal(t, tc.wantCode, rec.Code) if tc.wantType != "" { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tc.wantType, body["__type"]) } }) @@ -333,7 +334,7 @@ func TestStartDeployment_StateGuard(t *testing.T) { //nolint:paralleltest // exi assert.Equal(t, tc.wantCode, rec.Code) if tc.wantType != "" { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tc.wantType, body["__type"]) } }) @@ -363,14 +364,14 @@ func TestServiceOutputInstanceConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) svcArn := createResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DescribeService", map[string]any{"ServiceArn": svcArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) svc := descResp["Service"].(map[string]any) ic, ok := svc["InstanceConfiguration"].(map[string]any) @@ -398,14 +399,14 @@ func TestServiceOutputSourceConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) svcArn := createResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DescribeService", map[string]any{"ServiceArn": svcArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) svc := descResp["Service"].(map[string]any) sc, ok := svc["SourceConfiguration"].(map[string]any) @@ -436,14 +437,14 @@ func TestDefaultInstanceConfigurationPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) svcArn := createResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DescribeService", map[string]any{"ServiceArn": svcArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) svc := descResp["Service"].(map[string]any) ic, ok := svc["InstanceConfiguration"].(map[string]any) @@ -474,7 +475,7 @@ func TestCreateService_InstanceRoleArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ic := resp["Service"].(map[string]any)["InstanceConfiguration"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/my-role", ic["InstanceRoleArn"]) } @@ -499,7 +500,7 @@ func TestCreateService_EncryptionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) enc, ok := resp["Service"].(map[string]any)["EncryptionConfiguration"].(map[string]any) require.True(t, ok, "EncryptionConfiguration must be present when a KmsKey was provided") assert.Equal(t, "arn:aws:kms:us-east-1:000000000000:key/abc", enc["KmsKey"]) @@ -515,7 +516,7 @@ func TestCreateService_EncryptionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, present := resp["Service"].(map[string]any)["EncryptionConfiguration"] assert.False(t, present, "EncryptionConfiguration must be absent when no KmsKey was provided") }) @@ -537,7 +538,7 @@ func TestCreateService_AutoScalingConfigurationAssociation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var asgResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &asgResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &asgResp, jsonx.V1())) asgArn := asgResp["AutoScalingConfiguration"].(map[string]any)["AutoScalingConfigurationArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -550,7 +551,7 @@ func TestCreateService_AutoScalingConfigurationAssociation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) svc := createResp["Service"].(map[string]any) svcArn := svc["ServiceArn"].(string) summary := svc["AutoScalingConfigurationSummary"].(map[string]any) @@ -563,7 +564,7 @@ func TestCreateService_AutoScalingConfigurationAssociation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) arns := listResp["ServiceArnList"].([]any) require.Len(t, arns, 1) assert.Equal(t, svcArn, arns[0]) @@ -575,7 +576,7 @@ func TestCreateService_AutoScalingConfigurationAssociation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) cfg := descResp["AutoScalingConfiguration"].(map[string]any) assert.Equal(t, false, cfg["HasAssociatedService"]) @@ -583,7 +584,7 @@ func TestCreateService_AutoScalingConfigurationAssociation(t *testing.T) { "AutoScalingConfigurationArn": asgArn, }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp["ServiceArnList"]) } @@ -600,7 +601,7 @@ func TestCreateService_DefaultAutoScalingConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary := resp["Service"].(map[string]any)["AutoScalingConfigurationSummary"].(map[string]any) assert.Equal(t, "DefaultConfiguration", summary["AutoScalingConfigurationName"]) assert.Equal(t, true, summary["IsDefault"]) @@ -625,7 +626,7 @@ func TestCreateService_UnknownAutoScalingConfigurationArn(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidRequestException", body["__type"]) } @@ -685,7 +686,7 @@ func TestCreateService_NetworkConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nc := resp["Service"].(map[string]any)["NetworkConfiguration"].(map[string]any) assert.Equal(t, "IPV4", nc["IpAddressType"]) assert.Equal(t, "DEFAULT", nc["EgressConfiguration"].(map[string]any)["EgressType"]) @@ -704,7 +705,7 @@ func TestCreateService_NetworkConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var vcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vcResp, jsonx.V1())) vcArn := vcResp["VpcConnector"].(map[string]any)["VpcConnectorArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -720,7 +721,7 @@ func TestCreateService_NetworkConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) nc := createResp["Service"].(map[string]any)["NetworkConfiguration"].(map[string]any) assert.Equal(t, "VPC", nc["EgressConfiguration"].(map[string]any)["EgressType"]) assert.Equal(t, vcArn, nc["EgressConfiguration"].(map[string]any)["VpcConnectorArn"]) @@ -764,7 +765,7 @@ func TestCreateService_HealthCheckConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hc := resp["Service"].(map[string]any)["HealthCheckConfiguration"].(map[string]any) assert.Equal(t, "TCP", hc["Protocol"]) assert.Equal(t, "/", hc["Path"]) @@ -796,7 +797,7 @@ func TestCreateService_HealthCheckConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) hc := createResp["Service"].(map[string]any)["HealthCheckConfiguration"].(map[string]any) assert.Equal(t, "HTTP", hc["Protocol"]) assert.Equal(t, "/healthz", hc["Path"]) @@ -822,7 +823,7 @@ func TestCreateService_ObservabilityConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var obsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &obsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &obsResp, jsonx.V1())) obsArn := obsResp["ObservabilityConfiguration"].(map[string]any)["ObservabilityConfigurationArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -838,7 +839,7 @@ func TestCreateService_ObservabilityConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) oc := createResp["Service"].(map[string]any)["ObservabilityConfiguration"].(map[string]any) assert.Equal(t, true, oc["ObservabilityEnabled"]) assert.Equal(t, obsArn, oc["ObservabilityConfigurationArn"]) @@ -884,7 +885,7 @@ func TestCreateService_CodeRepository(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var connResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &connResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &connResp, jsonx.V1())) connArn := connResp["Connection"].(map[string]any)["ConnectionArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -908,7 +909,7 @@ func TestCreateService_CodeRepository(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) sc := createResp["Service"].(map[string]any)["SourceConfiguration"].(map[string]any) cr := sc["CodeRepository"].(map[string]any) assert.Equal(t, "https://github.com/example/repo", cr["RepositoryUrl"]) @@ -962,7 +963,7 @@ func TestUpdateService_CannotSwitchSourceType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidRequestException", body["__type"]) } @@ -978,7 +979,7 @@ func TestListOperations_UpdatedAtPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ops := resp["OperationSummaryList"].([]any) require.NotEmpty(t, ops) @@ -1008,7 +1009,7 @@ func TestDeleteService_RejectsWhenActiveVpcIngressConnectionExists(t *testing.T) }) require.Equal(t, http.StatusOK, rec.Code) var vicResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vicResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vicResp, jsonx.V1())) vicArn := vicResp["VpcIngressConnection"].(map[string]any)["VpcIngressConnectionArn"].(string) rec = doRequest(t, h, "DeleteService", map[string]any{"ServiceArn": svcArn}) @@ -1017,7 +1018,7 @@ func TestDeleteService_RejectsWhenActiveVpcIngressConnectionExists(t *testing.T) ) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidStateException", body["__type"]) rec = doRequest(t, h, "DeleteVpcIngressConnection", map[string]any{"VpcIngressConnectionArn": vicArn}) diff --git a/services/apprunner/handler_tags_test.go b/services/apprunner/handler_tags_test.go index 34ebfbdce8..05f82d5259 100644 --- a/services/apprunner/handler_tags_test.go +++ b/services/apprunner/handler_tags_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags(t *testing.T) { @@ -29,7 +31,7 @@ func TestTags(t *testing.T) { rec = doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": svcArn}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 2) @@ -43,7 +45,7 @@ func TestTags(t *testing.T) { // Verify untag rec = doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": svcArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags = listResp["Tags"].([]any) assert.Len(t, tags, 1) } diff --git a/services/apprunner/handler_test.go b/services/apprunner/handler_test.go index 94e6e97b69..d1b1c2b789 100644 --- a/services/apprunner/handler_test.go +++ b/services/apprunner/handler_test.go @@ -2,7 +2,7 @@ package apprunner_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apprunner" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *apprunner.Handler, action string, body any) *htt if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -60,7 +61,7 @@ func createTestService(t *testing.T, h *apprunner.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc, ok := resp["Service"].(map[string]any) require.True(t, ok) diff --git a/services/apprunner/handler_vpc_connectors_test.go b/services/apprunner/handler_vpc_connectors_test.go index cb57c7adba..a69461850a 100644 --- a/services/apprunner/handler_vpc_connectors_test.go +++ b/services/apprunner/handler_vpc_connectors_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestVpcConnectorCRUD(t *testing.T) { //nolint:paralleltest // existing issue. @@ -31,7 +33,7 @@ func TestVpcConnectorCRUD(t *testing.T) { //nolint:paralleltest // existing issu check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vc := resp["VpcConnector"].(map[string]any) assert.Contains(t, vc["VpcConnectorArn"], "vpcconnector/my-vpc/1/") assert.Equal(t, "ACTIVE", vc["Status"]) @@ -74,7 +76,7 @@ func TestVpcConnectorDescribeDeleteList(t *testing.T) { //nolint:paralleltest // }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) vcArn := createResp["VpcConnector"].(map[string]any)["VpcConnectorArn"].(string) tests := []struct { @@ -92,7 +94,7 @@ func TestVpcConnectorDescribeDeleteList(t *testing.T) { //nolint:paralleltest // check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vc := resp["VpcConnector"].(map[string]any) assert.Equal(t, "vc1", vc["VpcConnectorName"]) }, @@ -119,7 +121,7 @@ func TestVpcConnectorDescribeDeleteList(t *testing.T) { //nolint:paralleltest // check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["VpcConnectors"].([]any) assert.Len(t, list, 1) }, @@ -132,7 +134,7 @@ func TestVpcConnectorDescribeDeleteList(t *testing.T) { //nolint:paralleltest // check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vc := resp["VpcConnector"].(map[string]any) assert.Equal(t, "vc1", vc["VpcConnectorName"]) }, @@ -180,7 +182,7 @@ func TestDeleteVpcConnector_RejectsWhenServiceUsesIt(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var vcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vcResp, jsonx.V1())) vcArn := vcResp["VpcConnector"].(map[string]any)["VpcConnectorArn"].(string) rec = doRequest(t, h, "CreateService", map[string]any{ @@ -194,7 +196,7 @@ func TestDeleteVpcConnector_RejectsWhenServiceUsesIt(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &svcResp, jsonx.V1())) svcArn := svcResp["Service"].(map[string]any)["ServiceArn"].(string) rec = doRequest(t, h, "DeleteVpcConnector", map[string]any{"VpcConnectorArn": vcArn}) diff --git a/services/apprunner/handler_vpc_ingress_connections_test.go b/services/apprunner/handler_vpc_ingress_connections_test.go index f6d1a80aa2..3dd396a596 100644 --- a/services/apprunner/handler_vpc_ingress_connections_test.go +++ b/services/apprunner/handler_vpc_ingress_connections_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestVpcIngressConnectionCRUD(t *testing.T) { //nolint:paralleltest // existing issue. @@ -35,7 +37,7 @@ func TestVpcIngressConnectionCRUD(t *testing.T) { //nolint:paralleltest // exist check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vic := resp["VpcIngressConnection"].(map[string]any) assert.Contains(t, vic["VpcIngressConnectionArn"], "vpcingressconnection/my-vic/") assert.Equal(t, "AVAILABLE", vic["Status"]) @@ -80,7 +82,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p }) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) vicArn := createResp["VpcIngressConnection"].(map[string]any)["VpcIngressConnectionArn"].(string) tests := []struct { @@ -98,7 +100,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vic := resp["VpcIngressConnection"].(map[string]any) assert.Equal(t, "vic1", vic["VpcIngressConnectionName"]) }, @@ -125,7 +127,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["VpcIngressConnectionSummaryList"].([]any) assert.Len(t, list, 1) }, @@ -138,7 +140,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["VpcIngressConnectionSummaryList"].([]any) assert.Len(t, list, 1) }, @@ -155,7 +157,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["VpcIngressConnectionSummaryList"].([]any) assert.Len(t, list, 1) }, @@ -168,7 +170,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) list := resp["VpcIngressConnectionSummaryList"].([]any) assert.Empty(t, list) }, @@ -187,7 +189,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vic := resp["VpcIngressConnection"].(map[string]any) ivc := vic["IngressVpcConfiguration"].(map[string]any) assert.Equal(t, "vpc-new", ivc["VpcId"]) @@ -207,7 +209,7 @@ func TestVpcIngressConnectionDescribeDeleteListUpdate(t *testing.T) { //nolint:p check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) vic := resp["VpcIngressConnection"].(map[string]any) assert.Equal(t, "vic1", vic["VpcIngressConnectionName"]) }, diff --git a/services/apprunner/multi_region_test.go b/services/apprunner/multi_region_test.go index eab901d70a..fd82d8d173 100644 --- a/services/apprunner/multi_region_test.go +++ b/services/apprunner/multi_region_test.go @@ -2,7 +2,7 @@ package apprunner_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apprunner" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/apprunner/observability_configuration_summary_wire_test.go b/services/apprunner/observability_configuration_summary_wire_test.go index 438c70b171..3676d98a5e 100644 --- a/services/apprunner/observability_configuration_summary_wire_test.go +++ b/services/apprunner/observability_configuration_summary_wire_test.go @@ -1,7 +1,7 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apprunner" ) @@ -36,7 +37,7 @@ func TestListObservabilityConfigurations_SummaryHasNoFabricatedFields(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["ObservabilityConfigurationSummaryList"].([]any) require.True(t, ok) diff --git a/services/apprunner/persistence.go b/services/apprunner/persistence.go index 610a54ea40..cac2ba732e 100644 --- a/services/apprunner/persistence.go +++ b/services/apprunner/persistence.go @@ -2,7 +2,7 @@ package apprunner import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -37,7 +37,7 @@ const apprunnerSnapshotVersion = 1 // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` CustomDomains map[string][]*storedCustomDomain `json:"customDomains"` Tags map[string]map[string]string `json:"tags"` Version int `json:"version"` diff --git a/services/apprunner/vpc_ingress_service_arn_test.go b/services/apprunner/vpc_ingress_service_arn_test.go index 15ea8eb15e..d806d16a06 100644 --- a/services/apprunner/vpc_ingress_service_arn_test.go +++ b/services/apprunner/vpc_ingress_service_arn_test.go @@ -1,12 +1,14 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateVpcIngressConnection_ServiceMustExist(t *testing.T) { @@ -42,7 +44,7 @@ func TestCreateVpcIngressConnection_ServiceMustExist(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["__type"], tt.wantType) } }) diff --git a/services/apprunner/wire_output_required_r80d_test.go b/services/apprunner/wire_output_required_r80d_test.go index f19589e0d3..a612ab6096 100644 --- a/services/apprunner/wire_output_required_r80d_test.go +++ b/services/apprunner/wire_output_required_r80d_test.go @@ -1,7 +1,7 @@ package apprunner_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/apprunner" ) @@ -77,7 +78,7 @@ func Test_CodeRepository_SourceCodeVersion_Required(t *testing.T) { ServiceArn string `json:"ServiceArn"` } `json:"Service"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) rec = doRequest(t, h, "DescribeService", map[string]any{ "ServiceArn": createOut.Service.ServiceArn, @@ -95,7 +96,7 @@ func Test_CodeRepository_SourceCodeVersion_Required(t *testing.T) { } `json:"SourceConfiguration"` } `json:"Service"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.Service.SourceConfiguration.CodeRepository.SourceCodeVersion) assert.Equal(t, "main", descOut.Service.SourceConfiguration.CodeRepository.SourceCodeVersion.Value) } diff --git a/services/appstream/app_blocks_test.go b/services/appstream/app_blocks_test.go index f28280c40c..75956703ec 100644 --- a/services/appstream/app_blocks_test.go +++ b/services/appstream/app_blocks_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -37,7 +38,7 @@ func TestAppStream_AppBlocks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ab := resp["AppBlock"].(map[string]any) assert.Equal(t, "my-block", ab["Name"]) assert.Contains(t, ab["Arn"], ":app-block/my-block") @@ -73,7 +74,7 @@ func TestAppStream_AppBlocks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) blocks := resp["AppBlocks"].([]any) assert.Len(t, blocks, 2) }, @@ -136,7 +137,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bb := resp["AppBlockBuilder"].(map[string]any) assert.Equal(t, "my-builder", bb["Name"]) assert.Equal(t, "STOPPED", bb["State"]) @@ -162,7 +163,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bb := resp["AppBlockBuilder"].(map[string]any) assert.Equal(t, "RUNNING", bb["State"]) }, @@ -180,7 +181,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bb := resp["AppBlockBuilder"].(map[string]any) assert.Equal(t, "STOPPED", bb["State"]) }, @@ -196,7 +197,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bb := resp["AppBlockBuilder"].(map[string]any) assert.Equal(t, "STOPPED", bb["State"]) }, @@ -212,7 +213,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bb := resp["AppBlockBuilder"].(map[string]any) assert.Equal(t, "updated", bb["Description"]) }, @@ -228,7 +229,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.NotEmpty(t, resp["StreamingURL"]) assert.NotEmpty(t, resp["Expires"]) }, @@ -263,7 +264,7 @@ func TestAppStream_AppBlockBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assocs := resp["AppBlockBuilderAppBlockAssociations"].([]any) assert.Len(t, assocs, 1) }, @@ -307,7 +308,7 @@ func TestAppStream_AppBlockARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ab := resp["AppBlock"].(map[string]any) assert.Contains(t, ab["Arn"], "arn:aws:appstream:") assert.Contains(t, ab["Arn"], "app-block/arn-appblock") @@ -330,7 +331,7 @@ func TestAppStream_AppBlockBuilderStreamingURL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["StreamingURL"]) assert.NotEmpty(t, resp["Expires"]) } @@ -355,7 +356,7 @@ func TestAppStream_DeleteAppBlock_InUseByApplication(t *testing.T) { require.Equal(t, http.StatusOK, abRec.Code) var abResp map[string]any - require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp)) + require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp, jsonx.V1())) ab := abResp["AppBlock"].(map[string]any) appRec := doRequest(t, h, "CreateApplication", map[string]any{ diff --git a/services/appstream/applications_test.go b/services/appstream/applications_test.go index 09f9a394e4..42b5d65afa 100644 --- a/services/appstream/applications_test.go +++ b/services/appstream/applications_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -40,7 +41,7 @@ func TestAppStream_Applications(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) app := resp["Application"].(map[string]any) assert.Equal(t, "my-app", app["Name"]) assert.Contains(t, app["Arn"], ":application/my-app") @@ -75,7 +76,7 @@ func TestAppStream_Applications(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) apps := resp["Applications"].([]any) assert.Len(t, apps, 2) }, @@ -91,7 +92,7 @@ func TestAppStream_Applications(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) app := resp["Application"].(map[string]any) assert.Equal(t, "Updated App", app["DisplayName"]) }, @@ -113,7 +114,7 @@ func TestAppStream_Applications(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) usages, ok := resp["AppLicenseUsages"] require.True(t, ok, "response must carry AppLicenseUsages") assert.Empty(t, usages) @@ -167,7 +168,7 @@ func TestAppStream_Applications(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assocs := resp["ApplicationFleetAssociations"].([]any) assert.Len(t, assocs, 1) }, @@ -262,7 +263,7 @@ func TestAppStream_CreateApplication_RequiredMembersRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) app := createOut["Application"].(map[string]any) icon, ok := app["IconS3Location"].(map[string]any) @@ -275,7 +276,7 @@ func TestAppStream_CreateApplication_RequiredMembersRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) apps := descOut["Applications"].([]any) require.Len(t, apps, 1) got := apps[0].(map[string]any) @@ -304,7 +305,7 @@ func TestAppStream_ApplicationARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) app := resp["Application"].(map[string]any) assert.Contains(t, app["Arn"], "arn:aws:appstream:") assert.Contains(t, app["Arn"], "application/arn-app") diff --git a/services/appstream/directory_configs_test.go b/services/appstream/directory_configs_test.go index 7d27954a04..81ae0fd6b7 100644 --- a/services/appstream/directory_configs_test.go +++ b/services/appstream/directory_configs_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -34,7 +35,7 @@ func TestAppStream_DirectoryConfigs(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) dc := resp["DirectoryConfig"].(map[string]any) assert.Equal(t, "corp.example.com", dc["DirectoryName"]) }, @@ -51,7 +52,7 @@ func TestAppStream_DirectoryConfigs(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) dcs := resp["DirectoryConfigs"].([]any) assert.Len(t, dcs, 1) }, @@ -122,7 +123,7 @@ func TestAppStream_DirectoryConfigRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp, jsonx.V1())) configs := resp["DirectoryConfigs"].([]any) require.Len(t, configs, 1) dc := configs[0].(map[string]any) @@ -154,7 +155,7 @@ func TestAppStream_DirectoryConfigServiceAccountCredentials(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) dc := createResp["DirectoryConfig"].(map[string]any) cred := dc["ServiceAccountCredentials"].(map[string]any) @@ -174,7 +175,7 @@ func TestAppStream_DirectoryConfigServiceAccountCredentials(t *testing.T) { require.Equal(t, http.StatusOK, recUpd.Code) var updResp map[string]any - require.NoError(t, json.Unmarshal(recUpd.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(recUpd.Body.Bytes(), &updResp, jsonx.V1())) updDC := updResp["DirectoryConfig"].(map[string]any) updCred := updDC["ServiceAccountCredentials"].(map[string]any) assert.Equal(t, "svc-account-2", updCred["AccountName"]) diff --git a/services/appstream/entitlements_test.go b/services/appstream/entitlements_test.go index 8b88b2a9ae..4b884009fa 100644 --- a/services/appstream/entitlements_test.go +++ b/services/appstream/entitlements_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -38,7 +39,7 @@ func TestAppStream_Entitlements(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ent := resp["Entitlement"].(map[string]any) assert.Equal(t, "my-ent", ent["Name"]) assert.Equal(t, "ALL", ent["AppVisibility"]) @@ -63,7 +64,7 @@ func TestAppStream_Entitlements(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ents := resp["Entitlements"].([]any) assert.Len(t, ents, 1) }, @@ -84,7 +85,7 @@ func TestAppStream_Entitlements(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ent := resp["Entitlement"].(map[string]any) assert.Equal(t, "updated", ent["Description"]) }, @@ -137,7 +138,7 @@ func TestAppStream_Entitlements(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) apps := resp["EntitledApplications"].([]any) assert.Len(t, apps, 1) }, @@ -206,7 +207,7 @@ func TestAppStream_EntitlementRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp, jsonx.V1())) entitlements := resp["Entitlements"].([]any) require.Len(t, entitlements, 1) ent := entitlements[0].(map[string]any) diff --git a/services/appstream/fleets_test.go b/services/appstream/fleets_test.go index ed9920fbf2..6a057d8649 100644 --- a/services/appstream/fleets_test.go +++ b/services/appstream/fleets_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -34,7 +35,7 @@ func TestAppStream_Fleets(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, "my-fleet", fleet["Name"]) assert.Equal(t, "STOPPED", fleet["State"]) @@ -64,7 +65,7 @@ func TestAppStream_Fleets(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) fleets := resp["Fleets"].([]any) assert.Len(t, fleets, 1) }, @@ -124,7 +125,7 @@ func TestAppStream_Fleets(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, "stream.compute.large", fleet["InstanceType"]) }, @@ -190,7 +191,7 @@ func TestAppStream_Associations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) names := resp["Names"].([]any) assert.Len(t, names, 1) assert.Equal(t, "fleet-2", names[0]) @@ -209,7 +210,7 @@ func TestAppStream_Associations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) names := resp["Names"].([]any) assert.Len(t, names, 1) assert.Equal(t, "stack-3", names[0]) @@ -377,7 +378,7 @@ func TestAppStream_FleetErrorCodes(t *testing.T) { if tc.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"], "wrong __type in error response") } }) @@ -400,7 +401,7 @@ func TestAppStream_FleetComputeCapacityStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) ccs, ok := fleet["ComputeCapacityStatus"].(map[string]any) @@ -426,7 +427,7 @@ func TestAppStream_FleetComputeCapacityStatus(t *testing.T) { require.Equal(t, http.StatusOK, recD.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(recD.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(recD.Body.Bytes(), &dr, jsonx.V1())) fleets := dr["Fleets"].([]any) require.Len(t, fleets, 1) f := fleets[0].(map[string]any) @@ -449,7 +450,7 @@ func TestAppStream_FleetComputeCapacityStatusDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) ccs, ok := fleet["ComputeCapacityStatus"].(map[string]any) @@ -473,7 +474,7 @@ func TestAppStream_FleetImageNameRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, "AppStream-WinServer2019-05-01-2023", fleet["ImageName"]) } @@ -493,7 +494,7 @@ func TestAppStream_FleetImageArnRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, imageArn, fleet["ImageArn"]) } @@ -512,7 +513,7 @@ func TestAppStream_FleetIdleDisconnectTimeout(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.EqualValues(t, 600, fleet["IdleDisconnectTimeoutInSeconds"]) } @@ -531,7 +532,7 @@ func TestAppStream_FleetEnableDefaultInternetAccess(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, true, fleet["EnableDefaultInternetAccess"]) } @@ -554,7 +555,7 @@ func TestAppStream_FleetUpdateImageName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, "new-image", fleet["ImageName"]) } @@ -578,7 +579,7 @@ func TestAppStream_FleetUpdateComputeCapacity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) ccs := fleet["ComputeCapacityStatus"].(map[string]any) assert.EqualValues(t, 5, ccs["Desired"]) @@ -597,7 +598,7 @@ func TestAppStream_FleetARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Regexp(t, `^arn:aws:appstream:[a-z0-9-]+:\d+:fleet/arn-fleet$`, fleet["Arn"]) } @@ -617,7 +618,7 @@ func TestAppStream_FleetDefaultsMatchAWS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleet"].(map[string]any) assert.Equal(t, "ON_DEMAND", fleet["FleetType"]) assert.EqualValues(t, 57600, fleet["MaxUserDurationInSeconds"]) @@ -639,7 +640,7 @@ func TestAppStream_FleetStartedStateRunning(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fleet := resp["Fleets"].([]any)[0].(map[string]any) assert.Equal(t, "RUNNING", fleet["State"]) } @@ -669,6 +670,6 @@ func TestAppStream_DeleteFleet_ClearsAssociations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Names"], "re-created fleet must not inherit the deleted fleet's stack associations") } diff --git a/services/appstream/handler.go b/services/appstream/handler.go index feb4e93923..2109579589 100644 --- a/services/appstream/handler.go +++ b/services/appstream/handler.go @@ -2,7 +2,7 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -155,7 +156,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // errorCodeStatus maps a backend error to the AWS exception name and HTTP @@ -657,7 +658,7 @@ type createStackInput struct { func (h *Handler) opCreateStack(_ context.Context, body []byte) (any, error) { var req createStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -698,10 +699,11 @@ type describeStacksInput struct { Names []string `json:"Names"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeStacks(_ context.Context, body []byte) (any, error) { var req describeStacksInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -744,7 +746,7 @@ type updateStackInput struct { func (h *Handler) opUpdateStack(_ context.Context, body []byte) (any, error) { var req updateStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -781,7 +783,7 @@ type deleteStackInput struct { func (h *Handler) opDeleteStack(_ context.Context, body []byte) (any, error) { var req deleteStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -879,7 +881,7 @@ type createFleetInput struct { func (h *Handler) opCreateFleet(_ context.Context, body []byte) (any, error) { var req createFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -925,10 +927,11 @@ type describeFleetsInput struct { Names []string `json:"Names"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeFleets(_ context.Context, body []byte) (any, error) { var req describeFleetsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -980,7 +983,7 @@ type updateFleetInput struct { func (h *Handler) opUpdateFleet(_ context.Context, body []byte) (any, error) { var req updateFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1030,7 +1033,7 @@ type deleteFleetInput struct { func (h *Handler) opDeleteFleet(_ context.Context, body []byte) (any, error) { var req deleteFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1047,7 +1050,7 @@ type fleetNameInput struct { func (h *Handler) opStartFleet(_ context.Context, body []byte) (any, error) { var req fleetNameInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1060,7 +1063,7 @@ func (h *Handler) opStartFleet(_ context.Context, body []byte) (any, error) { func (h *Handler) opStopFleet(_ context.Context, body []byte) (any, error) { var req fleetNameInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1080,7 +1083,7 @@ type associateFleetInput struct { func (h *Handler) opAssociateFleet(_ context.Context, body []byte) (any, error) { var req associateFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1093,7 +1096,7 @@ func (h *Handler) opAssociateFleet(_ context.Context, body []byte) (any, error) func (h *Handler) opDisassociateFleet(_ context.Context, body []byte) (any, error) { var req associateFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1111,7 +1114,7 @@ type listAssociatedFleetsInput struct { func (h *Handler) opListAssociatedFleets(_ context.Context, body []byte) (any, error) { var req listAssociatedFleetsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1135,7 +1138,7 @@ type listAssociatedStacksInput struct { func (h *Handler) opListAssociatedStacks(_ context.Context, body []byte) (any, error) { var req listAssociatedStacksInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1161,7 +1164,7 @@ type tagResourceInput struct { func (h *Handler) opTagResource(_ context.Context, body []byte) (any, error) { var req tagResourceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1180,7 +1183,7 @@ type untagResourceInput struct { func (h *Handler) opUntagResource(_ context.Context, body []byte) (any, error) { var req untagResourceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -1197,7 +1200,7 @@ type listTagsForResourceInput struct { func (h *Handler) opListTagsForResource(_ context.Context, body []byte) (any, error) { var req listTagsForResourceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } diff --git a/services/appstream/handler_appblock.go b/services/appstream/handler_appblock.go index d813f591af..e64b2b5919 100644 --- a/services/appstream/handler_appblock.go +++ b/services/appstream/handler_appblock.go @@ -2,11 +2,12 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- AppBlock handlers --- @@ -54,7 +55,7 @@ type createAppBlockInput struct { func (h *Handler) opCreateAppBlock(_ context.Context, body []byte) (any, error) { var req createAppBlockInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -79,7 +80,7 @@ type deleteAppBlockInput struct { func (h *Handler) opDeleteAppBlock(_ context.Context, body []byte) (any, error) { var req deleteAppBlockInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -95,10 +96,11 @@ type describeAppBlocksInput struct { Arns []string `json:"Arns"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeAppBlocks(_ context.Context, body []byte) (any, error) { var req describeAppBlocksInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -144,7 +146,7 @@ type createAppBlockBuilderInput struct { func (h *Handler) opCreateAppBlockBuilder(_ context.Context, body []byte) (any, error) { var req createAppBlockBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -177,7 +179,7 @@ type deleteAppBlockBuilderInput struct { func (h *Handler) opDeleteAppBlockBuilder(_ context.Context, body []byte) (any, error) { var req deleteAppBlockBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -193,10 +195,11 @@ type describeAppBlockBuildersInput struct { Names []string `json:"Names"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeAppBlockBuilders(_ context.Context, body []byte) (any, error) { var req describeAppBlockBuildersInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -225,7 +228,7 @@ type appBlockBuilderNameInput struct { func (h *Handler) opStartAppBlockBuilder(_ context.Context, body []byte) (any, error) { var req appBlockBuilderNameInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -243,7 +246,7 @@ func (h *Handler) opStartAppBlockBuilder(_ context.Context, body []byte) (any, e func (h *Handler) opStopAppBlockBuilder(_ context.Context, body []byte) (any, error) { var req appBlockBuilderNameInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -275,7 +278,7 @@ type updateAppBlockBuilderInput struct { func (h *Handler) opUpdateAppBlockBuilder(_ context.Context, body []byte) (any, error) { var req updateAppBlockBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -317,7 +320,7 @@ type createAppBlockBuilderStreamingURLInput struct { func (h *Handler) opCreateAppBlockBuilderStreamingURL(_ context.Context, body []byte) (any, error) { var req createAppBlockBuilderStreamingURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -341,7 +344,7 @@ type appBlockBuilderAppBlockInput struct { func (h *Handler) opAssociateAppBlockBuilderAppBlock(_ context.Context, body []byte) (any, error) { var req appBlockBuilderAppBlockInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -358,7 +361,7 @@ func (h *Handler) opAssociateAppBlockBuilderAppBlock(_ context.Context, body []b func (h *Handler) opDisassociateAppBlockBuilderAppBlock(_ context.Context, body []byte) (any, error) { var req appBlockBuilderAppBlockInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -378,7 +381,7 @@ type describeAppBlockBuilderAppBlockAssociationsInput struct { func (h *Handler) opDescribeAppBlockBuilderAppBlockAssociations(_ context.Context, body []byte) (any, error) { var req describeAppBlockBuilderAppBlockAssociationsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } diff --git a/services/appstream/handler_application.go b/services/appstream/handler_application.go index cbccb591ec..c5fabec8c6 100644 --- a/services/appstream/handler_application.go +++ b/services/appstream/handler_application.go @@ -2,11 +2,12 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Application handlers --- @@ -46,7 +47,7 @@ type createApplicationInput struct { func (h *Handler) opCreateApplication(_ context.Context, body []byte) (any, error) { var req createApplicationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -68,7 +69,7 @@ type deleteApplicationInput struct { func (h *Handler) opDeleteApplication(_ context.Context, body []byte) (any, error) { var req deleteApplicationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -84,10 +85,11 @@ type describeApplicationsInput struct { Arns []string `json:"Arns"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeApplications(_ context.Context, body []byte) (any, error) { var req describeApplicationsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -124,7 +126,7 @@ type updateApplicationInput struct { func (h *Handler) opUpdateApplication(_ context.Context, body []byte) (any, error) { var req updateApplicationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -153,7 +155,7 @@ func (h *Handler) opUpdateApplication(_ context.Context, body []byte) (any, erro func (h *Handler) opDescribeAppLicenseUsage(_ context.Context, body []byte) (any, error) { var req pageReq if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -186,7 +188,7 @@ type applicationFleetInput struct { func (h *Handler) opAssociateApplicationFleet(_ context.Context, body []byte) (any, error) { var req applicationFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -203,7 +205,7 @@ func (h *Handler) opAssociateApplicationFleet(_ context.Context, body []byte) (a func (h *Handler) opDisassociateApplicationFleet(_ context.Context, body []byte) (any, error) { var req applicationFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -223,7 +225,7 @@ type describeApplicationFleetAssociationsInput struct { func (h *Handler) opDescribeApplicationFleetAssociations(_ context.Context, body []byte) (any, error) { var req describeApplicationFleetAssociationsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -273,7 +275,7 @@ func toEntitlementAttributes(attrs []entitlementAttributeJSON) []EntitlementAttr func (h *Handler) opCreateEntitlement(_ context.Context, body []byte) (any, error) { var req createEntitlementInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -295,7 +297,7 @@ type deleteEntitlementInput struct { func (h *Handler) opDeleteEntitlement(_ context.Context, body []byte) (any, error) { var req deleteEntitlementInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -315,7 +317,7 @@ type describeEntitlementsInput struct { func (h *Handler) opDescribeEntitlements(_ context.Context, body []byte) (any, error) { var req describeEntitlementsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -353,7 +355,7 @@ type updateEntitlementInput struct { func (h *Handler) opUpdateEntitlement(_ context.Context, body []byte) (any, error) { var req updateEntitlementInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -376,7 +378,7 @@ type associateApplicationToEntitlementInput struct { func (h *Handler) opAssociateApplicationToEntitlement(_ context.Context, body []byte) (any, error) { var req associateApplicationToEntitlementInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -397,7 +399,7 @@ type disassociateApplicationFromEntitlementInput struct { func (h *Handler) opDisassociateApplicationFromEntitlement(_ context.Context, body []byte) (any, error) { var req disassociateApplicationFromEntitlementInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -418,7 +420,7 @@ type listEntitledApplicationsInput struct { func (h *Handler) opListEntitledApplications(_ context.Context, body []byte) (any, error) { var req listEntitledApplicationsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -486,7 +488,7 @@ type createDirectoryConfigInput struct { func (h *Handler) opCreateDirectoryConfig(_ context.Context, body []byte) (any, error) { var req createDirectoryConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -507,7 +509,7 @@ type deleteDirectoryConfigInput struct { func (h *Handler) opDeleteDirectoryConfig(_ context.Context, body []byte) (any, error) { var req deleteDirectoryConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -523,10 +525,11 @@ type describeDirectoryConfigsInput struct { DirectoryNames []string `json:"DirectoryNames"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeDirectoryConfigs(_ context.Context, body []byte) (any, error) { var req describeDirectoryConfigsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -563,7 +566,7 @@ type updateDirectoryConfigInput struct { func (h *Handler) opUpdateDirectoryConfig(_ context.Context, body []byte) (any, error) { var req updateDirectoryConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } diff --git a/services/appstream/handler_image.go b/services/appstream/handler_image.go index 67548ac94a..ba3efa717d 100644 --- a/services/appstream/handler_image.go +++ b/services/appstream/handler_image.go @@ -2,11 +2,12 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Image handlers --- @@ -20,7 +21,7 @@ type copyImageInput struct { func (h *Handler) opCopyImage(_ context.Context, body []byte) (any, error) { var req copyImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -46,7 +47,7 @@ type createImportedImageInput struct { func (h *Handler) opCreateImportedImage(_ context.Context, body []byte) (any, error) { var req createImportedImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -84,7 +85,7 @@ type createUpdatedImageInput struct { func (h *Handler) opCreateUpdatedImage(_ context.Context, body []byte) (any, error) { var req createUpdatedImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -112,7 +113,7 @@ type deleteImageInput struct { func (h *Handler) opDeleteImage(_ context.Context, body []byte) (any, error) { var req deleteImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -134,7 +135,7 @@ type describeImagesInput struct { func (h *Handler) opDescribeImages(_ context.Context, body []byte) (any, error) { var req describeImagesInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -173,7 +174,7 @@ type imagePermissionsInput struct { func (h *Handler) opUpdateImagePermissions(_ context.Context, body []byte) (any, error) { var req imagePermissionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -197,7 +198,7 @@ type deleteImagePermissionsInput struct { func (h *Handler) opDeleteImagePermissions(_ context.Context, body []byte) (any, error) { var req deleteImagePermissionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -216,7 +217,7 @@ type describeImagePermissionsInput struct { func (h *Handler) opDescribeImagePermissions(_ context.Context, body []byte) (any, error) { var req describeImagePermissionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -280,7 +281,7 @@ type createImageBuilderInput struct { func (h *Handler) opCreateImageBuilder(_ context.Context, body []byte) (any, error) { var req createImageBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -315,7 +316,7 @@ type deleteImageBuilderInput struct { func (h *Handler) opDeleteImageBuilder(_ context.Context, body []byte) (any, error) { var req deleteImageBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -332,10 +333,11 @@ type describeImageBuildersInput struct { Names []string `json:"Names"` } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) opDescribeImageBuilders(_ context.Context, body []byte) (any, error) { var req describeImageBuildersInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -368,7 +370,7 @@ type startImageBuilderInput struct { // this handler invented one, which real SDK clients would never receive. func (h *Handler) opStartImageBuilder(_ context.Context, body []byte) (any, error) { var req startImageBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -392,7 +394,7 @@ type stopImageBuilderInput struct { func (h *Handler) opStopImageBuilder(_ context.Context, body []byte) (any, error) { var req stopImageBuilderInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -411,7 +413,7 @@ type createImageBuilderStreamingURLInput struct { func (h *Handler) opCreateImageBuilderStreamingURL(_ context.Context, body []byte) (any, error) { var req createImageBuilderStreamingURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -435,7 +437,7 @@ type associateSoftwareInput struct { func (h *Handler) opAssociateSoftwareToImageBuilder(_ context.Context, body []byte) (any, error) { var req associateSoftwareInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -448,7 +450,7 @@ func (h *Handler) opAssociateSoftwareToImageBuilder(_ context.Context, body []by func (h *Handler) opDisassociateSoftwareFromImageBuilder(_ context.Context, body []byte) (any, error) { var req associateSoftwareInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -466,7 +468,7 @@ type describeSoftwareAssociationsInput struct { func (h *Handler) opDescribeSoftwareAssociations(_ context.Context, body []byte) (any, error) { var req describeSoftwareAssociationsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -505,7 +507,7 @@ type startSoftwareDeploymentInput struct { func (h *Handler) opStartSoftwareDeploymentToImageBuilder(_ context.Context, body []byte) (any, error) { var req startSoftwareDeploymentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -533,7 +535,7 @@ type createExportImageTaskInput struct { func (h *Handler) opCreateExportImageTask(_ context.Context, body []byte) (any, error) { var req createExportImageTaskInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -553,7 +555,7 @@ type getExportImageTaskInput struct { func (h *Handler) opGetExportImageTask(_ context.Context, body []byte) (any, error) { var req getExportImageTaskInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -594,7 +596,7 @@ func stateFilterValues(filters []filterInput) []string { func (h *Handler) opListExportImageTasks(_ context.Context, body []byte) (any, error) { var req listExportImageTasksInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } diff --git a/services/appstream/handler_test.go b/services/appstream/handler_test.go index 7b97b91695..a501bb4792 100644 --- a/services/appstream/handler_test.go +++ b/services/appstream/handler_test.go @@ -2,7 +2,7 @@ package appstream_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -62,7 +63,7 @@ func doRequest(t *testing.T, h *appstream.Handler, action string, body any) *htt var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -174,7 +175,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { require.Equal(t, http.StatusOK, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appArn := appResp["Application"].(map[string]any)["Arn"].(string) require.NotEmpty(t, appArn) @@ -185,7 +186,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { require.Equal(t, http.StatusOK, abRec.Code) var abResp map[string]any - require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp)) + require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp, jsonx.V1())) appBlockArn := abResp["AppBlock"].(map[string]any)["Arn"].(string) require.NotEmpty(t, appBlockArn) @@ -193,7 +194,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { require.Equal(t, http.StatusOK, imgRec.Code) var imgResp map[string]any - require.NoError(t, json.Unmarshal(imgRec.Body.Bytes(), &imgResp)) + require.NoError(t, json.Unmarshal(imgRec.Body.Bytes(), &imgResp, jsonx.V1())) imageArn := imgResp["Image"].(map[string]any)["Arn"].(string) require.NotEmpty(t, imageArn) @@ -204,7 +205,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps := resp["Applications"].([]any) require.Len(t, apps, 1) assert.Equal(t, "arn-app", apps[0].(map[string]any)["Name"]) @@ -217,7 +218,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) blocks := resp["AppBlocks"].([]any) require.Len(t, blocks, 1) assert.Equal(t, "arn-appblock", blocks[0].(map[string]any)["Name"]) @@ -230,7 +231,7 @@ func TestAppStream_DescribeByARN(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) images := resp["Images"].([]any) require.Len(t, images, 1) assert.Equal(t, "arn-image", images[0].(map[string]any)["Name"]) @@ -260,7 +261,7 @@ func TestAppStream_AssociationsAcceptARNIdentifiers(t *testing.T) { require.Equal(t, http.StatusOK, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appArn := appResp["Application"].(map[string]any)["Arn"].(string) require.NotEmpty(t, appArn) @@ -275,7 +276,7 @@ func TestAppStream_AssociationsAcceptARNIdentifiers(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assocs := descResp["ApplicationFleetAssociations"].([]any) require.Len(t, assocs, 1) assert.Equal(t, "assoc-arn-fleet", assocs[0].(map[string]any)["FleetName"]) @@ -294,7 +295,7 @@ func TestAppStream_AssociationsAcceptARNIdentifiers(t *testing.T) { require.Equal(t, http.StatusOK, abRec.Code) var abResp map[string]any - require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp)) + require.NoError(t, json.Unmarshal(abRec.Body.Bytes(), &abResp, jsonx.V1())) appBlockArn := abResp["AppBlock"].(map[string]any)["Arn"].(string) require.NotEmpty(t, appBlockArn) @@ -309,7 +310,7 @@ func TestAppStream_AssociationsAcceptARNIdentifiers(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assocs := descResp["AppBlockBuilderAppBlockAssociations"].([]any) require.Len(t, assocs, 1) assert.Equal(t, appBlockArn, assocs[0].(map[string]any)["AppBlockArn"]) diff --git a/services/appstream/handler_user.go b/services/appstream/handler_user.go index 2cd48732d8..6ea21d28c9 100644 --- a/services/appstream/handler_user.go +++ b/services/appstream/handler_user.go @@ -2,11 +2,12 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- User handlers --- @@ -21,7 +22,7 @@ type createUserInput struct { func (h *Handler) opCreateUser(_ context.Context, body []byte) (any, error) { var req createUserInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -57,7 +58,7 @@ type deleteUserInput struct { func (h *Handler) opDeleteUser(_ context.Context, body []byte) (any, error) { var req deleteUserInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -76,7 +77,7 @@ type describeUsersInput struct { func (h *Handler) opDescribeUsers(_ context.Context, body []byte) (any, error) { var req describeUsersInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -111,7 +112,7 @@ type userAuthInput struct { func (h *Handler) opDisableUser(_ context.Context, body []byte) (any, error) { var req userAuthInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -124,7 +125,7 @@ func (h *Handler) opDisableUser(_ context.Context, body []byte) (any, error) { func (h *Handler) opEnableUser(_ context.Context, body []byte) (any, error) { var req userAuthInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -157,7 +158,7 @@ func (h *Handler) opBatchAssociateUserStack( //nolint:dupl // existing issue. body []byte, ) (any, error) { var req batchAssociateUserStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -197,7 +198,7 @@ func (h *Handler) opBatchDisassociateUserStack( //nolint:dupl // existing issue. body []byte, ) (any, error) { var req batchAssociateUserStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -242,7 +243,7 @@ type describeUserStackAssociationsInput struct { func (h *Handler) opDescribeUserStackAssociations(_ context.Context, body []byte) (any, error) { var req describeUserStackAssociationsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -286,7 +287,7 @@ type describeSessionsInput struct { func (h *Handler) opDescribeSessions(_ context.Context, body []byte) (any, error) { var req describeSessionsInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -321,7 +322,7 @@ type sessionIDInput struct { func (h *Handler) opDrainSessionInstance(_ context.Context, body []byte) (any, error) { var req sessionIDInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -334,7 +335,7 @@ func (h *Handler) opDrainSessionInstance(_ context.Context, body []byte) (any, e func (h *Handler) opExpireSession(_ context.Context, body []byte) (any, error) { var req sessionIDInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -354,7 +355,7 @@ type createStreamingURLInput struct { func (h *Handler) opCreateStreamingURL(_ context.Context, body []byte) (any, error) { var req createStreamingURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -394,7 +395,7 @@ func (h *Handler) opDeleteUsageReportSubscription(_ context.Context, _ []byte) ( func (h *Handler) opDescribeUsageReportSubscriptions(_ context.Context, body []byte) (any, error) { var req pageReq if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } } @@ -450,7 +451,7 @@ type createThemeForStackInput struct { func (h *Handler) opCreateThemeForStack(_ context.Context, body []byte) (any, error) { var req createThemeForStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -473,7 +474,7 @@ func (h *Handler) opCreateThemeForStack(_ context.Context, body []byte) (any, er func (h *Handler) opDeleteThemeForStack(_ context.Context, body []byte) (any, error) { var req themeStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -486,7 +487,7 @@ func (h *Handler) opDeleteThemeForStack(_ context.Context, body []byte) (any, er func (h *Handler) opDescribeThemeForStack(_ context.Context, body []byte) (any, error) { var req themeStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } @@ -514,7 +515,7 @@ type updateThemeForStackInput struct { func (h *Handler) opUpdateThemeForStack(_ context.Context, body []byte) (any, error) { var req updateThemeForStackInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, awserr.New(errInvalidParameter, awserr.ErrInvalidParameter) } diff --git a/services/appstream/images_test.go b/services/appstream/images_test.go index d3f9f6ac83..619d39db3f 100644 --- a/services/appstream/images_test.go +++ b/services/appstream/images_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -31,7 +32,7 @@ func TestAppStream_Images(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) img := resp["Image"].(map[string]any) assert.Equal(t, "my-img", img["Name"]) assert.Equal(t, "AVAILABLE", img["State"]) @@ -49,7 +50,7 @@ func TestAppStream_Images(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) imgs := resp["Images"].([]any) assert.Len(t, imgs, 2) }, @@ -69,7 +70,7 @@ func TestAppStream_Images(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Equal(t, "dst-img", resp["DestinationImageName"]) }, }, @@ -125,7 +126,7 @@ func TestAppStream_Images(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) perms := resp["SharedImagePermissionsList"].([]any) assert.Len(t, perms, 1) }, @@ -185,7 +186,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ib := resp["ImageBuilder"].(map[string]any) // Real CreateImageBuilder launches the build instance // immediately -- terraform-provider-aws's resource waits @@ -208,7 +209,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ib := resp["ImageBuilder"].(map[string]any) assert.Equal(t, "RUNNING", ib["State"]) // Real StartImageBuilderOutput carries only ImageBuilder -- @@ -229,7 +230,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ib := resp["ImageBuilder"].(map[string]any) assert.Equal(t, "STOPPED", ib["State"]) }, @@ -245,7 +246,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) ib := resp["ImageBuilder"].(map[string]any) assert.Equal(t, "STOPPED", ib["State"]) }, @@ -261,7 +262,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.NotEmpty(t, resp["StreamingURL"]) assert.NotEmpty(t, resp["Expires"]) }, @@ -294,7 +295,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Equal(t, "list-sw-ib", resp["AssociatedResource"]) assocs := resp["SoftwareAssociations"].([]any) assert.Len(t, assocs, 1) @@ -311,7 +312,7 @@ func TestAppStream_ImageBuilders(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Equal(t, "sw-assoc-image", resp["AssociatedResource"]) assocs := resp["SoftwareAssociations"].([]any) assert.Empty(t, assocs) @@ -397,7 +398,7 @@ func TestAppStream_ExportImageTasks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) task := resp["ExportImageTask"].(map[string]any) assert.NotEmpty(t, task["TaskId"]) assert.Equal(t, "exported-ami", task["AmiName"]) @@ -423,7 +424,7 @@ func TestAppStream_ExportImageTasks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) tasks := resp["ExportImageTasks"].([]any) assert.Len(t, tasks, 1) }, @@ -459,7 +460,7 @@ func TestAppStream_GetExportImageTask(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) task := createResp["ExportImageTask"].(map[string]any) taskID := task["TaskId"].(string) @@ -467,7 +468,7 @@ func TestAppStream_GetExportImageTask(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) gotTask := getResp["ExportImageTask"].(map[string]any) assert.Equal(t, taskID, gotTask["TaskId"]) } @@ -484,7 +485,7 @@ func TestAppStream_ImageBuilderARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) builder := resp["ImageBuilder"].(map[string]any) assert.Regexp(t, `^arn:aws:appstream:[a-z0-9-]+:\d+:image-builder/arn-builder$`, builder["Arn"]) } diff --git a/services/appstream/persistence.go b/services/appstream/persistence.go index 8f789989b5..e3b24c5bcc 100644 --- a/services/appstream/persistence.go +++ b/services/appstream/persistence.go @@ -2,7 +2,7 @@ package appstream import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -38,7 +38,7 @@ const appstreamSnapshotVersion = 2 // subscription exists at a time), so it is also persisted directly rather // than through a table. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Associations map[string]map[string]bool `json:"associations"` Tags map[string]map[string]string `json:"tags"` AppBlockBuilderAssoc map[string]map[string]bool `json:"appBlockBuilderAssoc"` diff --git a/services/appstream/rpcv2cbor.go b/services/appstream/rpcv2cbor.go index 3c544e88cd..d43edeb433 100644 --- a/services/appstream/rpcv2cbor.go +++ b/services/appstream/rpcv2cbor.go @@ -2,7 +2,7 @@ package appstream import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -12,6 +12,7 @@ import ( "github.com/aws/smithy-go/encoding/cbor" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -157,7 +158,7 @@ func cborBodyToJSON(body []byte) ([]byte, error) { return nil, fmt.Errorf("%w: got %T", errExpectedCBORMap, v) } - return json.Marshal(cborToGo(m)) + return json.Marshal(cborToGo(m), jsonx.V1()) } // cborToGo converts a decoded CBOR value tree into the generic Go value @@ -221,7 +222,7 @@ func awstimeSeconds(sec int64, nsec int) float64 { // a CBOR value tree suitable for the wire. func jsonToCBOR(body []byte) (cbor.Value, error) { var v any - if err := json.Unmarshal(body, &v); err != nil { + if err := json.Unmarshal(body, &v, jsonx.V1()); err != nil { return nil, err } diff --git a/services/appstream/sessions_test.go b/services/appstream/sessions_test.go index a9615472d7..9f89e3c699 100644 --- a/services/appstream/sessions_test.go +++ b/services/appstream/sessions_test.go @@ -1,7 +1,7 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -40,7 +41,7 @@ func TestAppStream_Sessions(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.NotEmpty(t, resp["StreamingURL"]) assert.NotEmpty(t, resp["Expires"]) }, @@ -66,7 +67,7 @@ func TestAppStream_Sessions(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) sessions := resp["Sessions"].([]any) assert.Len(t, sessions, 1) }, @@ -112,7 +113,7 @@ func TestAppStream_SessionLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) sessions := descResp["Sessions"].([]any) require.Len(t, sessions, 1) sessionID := sessions[0].(map[string]any)["Id"].(string) @@ -142,7 +143,7 @@ func TestAppStream_DrainSessionInstance(t *testing.T) { "StackName": "drain-stk", "FleetName": "drain-fleet", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) sessions := descResp["Sessions"].([]any) require.Len(t, sessions, 1) sessionID := sessions[0].(map[string]any)["Id"].(string) @@ -178,7 +179,7 @@ func TestAppStream_SessionStreamingURL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["StreamingURL"]) expires, ok := resp["Expires"].(float64) @@ -220,7 +221,7 @@ func TestAppStream_DescribeSessionsFilterByStack(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sessions, ok := resp["Sessions"].([]any) require.True(t, ok) assert.Len(t, sessions, 2) diff --git a/services/appstream/stacks_test.go b/services/appstream/stacks_test.go index 58bcca5525..95a254fd71 100644 --- a/services/appstream/stacks_test.go +++ b/services/appstream/stacks_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -30,7 +31,7 @@ func TestAppStream_Stacks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) stack, ok := resp["Stack"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-stack", stack["Name"]) @@ -58,7 +59,7 @@ func TestAppStream_Stacks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) stacks, ok := resp["Stacks"].([]any) require.True(t, ok) assert.Len(t, stacks, 1) @@ -76,7 +77,7 @@ func TestAppStream_Stacks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) stacks := resp["Stacks"].([]any) assert.Len(t, stacks, 1) s := stacks[0].(map[string]any) @@ -100,7 +101,7 @@ func TestAppStream_Stacks(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) stack := resp["Stack"].(map[string]any) assert.Equal(t, "Updated", stack["DisplayName"]) }, @@ -224,7 +225,7 @@ func TestAppStream_StackErrorCodes(t *testing.T) { if tc.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"], "wrong __type in error response") } }) @@ -241,7 +242,7 @@ func TestAppStream_StackARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stack := resp["Stack"].(map[string]any) assert.Regexp(t, `^arn:aws:appstream:[a-z0-9-]+:\d+:stack/arn-stack$`, stack["Arn"]) } diff --git a/services/appstream/tags_test.go b/services/appstream/tags_test.go index 98ee5d1d7e..531a832c71 100644 --- a/services/appstream/tags_test.go +++ b/services/appstream/tags_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -29,7 +30,7 @@ func TestAppStream_Tags(t *testing.T) { createStack(t, h, "tag-stack") rec := doRequest(t, h, "DescribeStacks", map[string]any{"Names": []string{"tag-stack"}}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Stacks"].([]any)[0].(map[string]any)["Arn"].(string) }, @@ -45,7 +46,7 @@ func TestAppStream_Tags(t *testing.T) { createStack(t, h, "listtag-stack") rec := doRequest(t, h, "DescribeStacks", map[string]any{"Names": []string{"listtag-stack"}}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["Stacks"].([]any)[0].(map[string]any)["Arn"].(string) doRequest( t, @@ -63,7 +64,7 @@ func TestAppStream_Tags(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) tags, ok := resp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "staging", tags["env"]) @@ -76,7 +77,7 @@ func TestAppStream_Tags(t *testing.T) { createStack(t, h, "untag-stack") rec := doRequest(t, h, "DescribeStacks", map[string]any{"Names": []string{"untag-stack"}}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["Stacks"].([]any)[0].(map[string]any)["Arn"].(string) doRequest(t, h, "TagResource", map[string]any{"ResourceArn": arn, "Tags": map[string]string{"k": "v"}}) @@ -124,14 +125,14 @@ func TestAppStream_TagLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) stackArn := createResp["Stack"].(map[string]any)["Arn"].(string) recList := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": stackArn}) require.Equal(t, http.StatusOK, recList.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -143,7 +144,7 @@ func TestAppStream_TagLifecycle(t *testing.T) { recList2 := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": stackArn}) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listResp2, jsonx.V1())) tags2 := listResp2["Tags"].(map[string]any) assert.NotContains(t, tags2, "env") assert.Equal(t, "platform", tags2["team"]) diff --git a/services/appstream/themes_test.go b/services/appstream/themes_test.go index 7761f8ab7e..a6e4d89084 100644 --- a/services/appstream/themes_test.go +++ b/services/appstream/themes_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -56,7 +57,7 @@ func TestAppStream_Themes(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) th := resp["Theme"].(map[string]any) assert.Equal(t, "theme-stk", th["StackName"]) assert.Equal(t, "ENABLED", th["State"]) diff --git a/services/appstream/usage_report_subscriptions_test.go b/services/appstream/usage_report_subscriptions_test.go index e39c850dc3..caebb1dc49 100644 --- a/services/appstream/usage_report_subscriptions_test.go +++ b/services/appstream/usage_report_subscriptions_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -31,7 +32,7 @@ func TestAppStream_UsageReports(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Equal(t, "DAILY", resp["Schedule"]) assert.NotEmpty(t, resp["S3BucketName"]) }, @@ -48,7 +49,7 @@ func TestAppStream_UsageReports(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) subs := resp["UsageReportSubscriptions"].([]any) assert.Len(t, subs, 1) }, @@ -103,7 +104,7 @@ func TestAppStream_UsageReportSubscriptionRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp, jsonx.V1())) subs := resp["UsageReportSubscriptions"].([]any) require.Len(t, subs, 1) sub := subs[0].(map[string]any) diff --git a/services/appstream/users_test.go b/services/appstream/users_test.go index ba12217f8a..e33409b676 100644 --- a/services/appstream/users_test.go +++ b/services/appstream/users_test.go @@ -1,13 +1,14 @@ package appstream_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appstream" ) @@ -56,7 +57,7 @@ func TestAppStream_Users(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) users := resp["Users"].([]any) assert.Len(t, users, 2) }, @@ -136,7 +137,7 @@ func TestAppStream_UserARNPartition(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) users := resp["Users"].([]any) require.Len(t, users, 1) assert.Equal( @@ -161,7 +162,7 @@ func TestAppStream_UserARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp, jsonx.V1())) users := resp["Users"].([]any) require.Len(t, users, 1) user := users[0].(map[string]any) @@ -187,7 +188,7 @@ func TestAppStream_UserStatusEnabled(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) users := resp["Users"].([]any) require.Len(t, users, 1) user := users[0].(map[string]any) @@ -226,7 +227,7 @@ func TestAppStream_UserStackAssociations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) errs := resp["errors"].([]any) assert.Empty(t, errs) }, @@ -247,7 +248,7 @@ func TestAppStream_UserStackAssociations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) errs := resp["errors"].([]any) assert.Len(t, errs, 1) }, @@ -274,7 +275,7 @@ func TestAppStream_UserStackAssociations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assocs := resp["UserStackAssociations"].([]any) assert.Len(t, assocs, 1) }, @@ -344,7 +345,7 @@ func TestAppStream_BatchAssociateUserStackErrors(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // errors field may be empty or present depending on implementation // but response must be 200 with valid JSON assert.NotNil(t, resp) @@ -374,7 +375,7 @@ func TestAppStream_BatchAssociateUserStack_StackNotFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) errs := resp["errors"].([]any) require.Len(t, errs, 1) assert.Equal(t, "STACK_NOT_FOUND", errs[0].(map[string]any)["ErrorCode"]) @@ -383,7 +384,7 @@ func TestAppStream_BatchAssociateUserStack_StackNotFound(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Empty(t, descResp["UserStackAssociations"]) } @@ -416,7 +417,7 @@ func TestAppStream_DescribeUserStackAssociations(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &resp, jsonx.V1())) assocs := resp["UserStackAssociations"].([]any) assert.Len(t, assocs, 1) assoc := assocs[0].(map[string]any) @@ -446,7 +447,7 @@ func TestAppStream_BatchDisassociateUserStack_StackNotFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) errs := resp["errors"].([]any) require.Len(t, errs, 1) assert.Equal(t, "STACK_NOT_FOUND", errs[0].(map[string]any)["ErrorCode"]) diff --git a/services/appsync/api_keys_test.go b/services/appsync/api_keys_test.go index 135b7bf81b..0a86dfe1be 100644 --- a/services/appsync/api_keys_test.go +++ b/services/appsync/api_keys_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -367,7 +368,7 @@ func TestListAPIKeys_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var apiOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut, jsonx.V1())) apiID := apiOut["graphqlApi"].(map[string]any)["apiId"].(string) for range 4 { @@ -407,7 +408,7 @@ func TestListAPIKeys_Pagination(t *testing.T) { NextToken string `json:"nextToken"` APIKeys []map[string]any `json:"apiKeys"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.APIKeys, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/auth.go b/services/appsync/auth.go index 90d53f5551..1feba9f624 100644 --- a/services/appsync/auth.go +++ b/services/appsync/auth.go @@ -2,7 +2,7 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // errUnexpectedJWTSigningMethod mirrors the sentinel @@ -255,7 +256,7 @@ func (b *InMemoryBackend) checkLambdaAuth( }, } - payload, marshalErr := json.Marshal(event) + payload, marshalErr := json.Marshal(event, jsonx.V1()) if marshalErr != nil { return nil, false } @@ -267,7 +268,7 @@ func (b *InMemoryBackend) checkLambdaAuth( } var resp lambdaAuthorizerResponse - if jsonErr := json.Unmarshal(result, &resp); jsonErr != nil { + if jsonErr := json.Unmarshal(result, &resp, jsonx.V1()); jsonErr != nil { return nil, false } diff --git a/services/appsync/auth_test.go b/services/appsync/auth_test.go index bba2be11d9..c608f2362c 100644 --- a/services/appsync/auth_test.go +++ b/services/appsync/auth_test.go @@ -4,7 +4,7 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -136,7 +137,7 @@ func TestHandleGraphQL_APIKeyAuth_HTTPShape(t *testing.T) { require.Equal(t, http.StatusUnauthorized, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Real AppSync's transport-level auth failure body is exactly // {"message":"Unauthorized"} -- no "code"/"__type" field, unlike this // package's control-plane errorResponse() convention. @@ -182,7 +183,7 @@ func TestExecuteGraphQL_LambdaAuth(t *testing.T) { } `json:"requestContext"` AuthorizationToken string `json:"authorizationToken"` } - require.NoError(t, json.Unmarshal(mock.calls[0].payload, &event)) + require.NoError(t, json.Unmarshal(mock.calls[0].payload, &event, jsonx.V1())) assert.Equal(t, "secret-tok", event.AuthorizationToken) assert.Equal(t, api.APIID, event.RequestContext.APIID) assert.Contains(t, event.RequestContext.QueryString, "hello") diff --git a/services/appsync/caching_test.go b/services/appsync/caching_test.go index a722481b73..185981f779 100644 --- a/services/appsync/caching_test.go +++ b/services/appsync/caching_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -291,7 +292,7 @@ func TestAPICache_UnmarshalJSON_HealthMetricsConfig(t *testing.T) { t.Parallel() var got appsync.APICache - err := json.Unmarshal([]byte(tt.body), &got) + err := json.Unmarshal([]byte(tt.body), &got, jsonx.V1()) if tt.wantErr { require.Error(t, err) diff --git a/services/appsync/data_sources_config_test.go b/services/appsync/data_sources_config_test.go index 90ebeb1237..40bf9b72ba 100644 --- a/services/appsync/data_sources_config_test.go +++ b/services/appsync/data_sources_config_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -661,7 +662,7 @@ func TestListDataSources_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var apiOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut, jsonx.V1())) apiID := apiOut["graphqlApi"].(map[string]any)["apiId"].(string) for i := range 4 { @@ -702,7 +703,7 @@ func TestListDataSources_Pagination(t *testing.T) { NextToken string `json:"nextToken"` DataSources []map[string]any `json:"dataSources"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.DataSources, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/ddb_ops.go b/services/appsync/ddb_ops.go index b37902df42..1d8c65fccd 100644 --- a/services/appsync/ddb_ops.go +++ b/services/appsync/ddb_ops.go @@ -3,11 +3,13 @@ package appsync import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" "reflect" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -234,10 +236,10 @@ func intOf(v any) int { return int(n) case int: return n - case json.Number: - i, _ := n.Int64() + case jsonx.Number: + i, _ := n.Int() - return int(i) + return i default: return 0 } @@ -305,7 +307,7 @@ func ddbQueryScan( } func encodeNextToken(key map[string]any) string { - b, _ := json.Marshal(key) + b, _ := json.Marshal(key, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(b) } @@ -317,7 +319,7 @@ func decodeNextToken(tok string) (map[string]any, error) { } var key map[string]any - if err = json.Unmarshal(raw, &key); err != nil || len(key) == 0 { + if err = json.Unmarshal(raw, &key, jsonx.V1()); err != nil || len(key) == 0 { return nil, fmt.Errorf("%w: invalid nextToken", ErrValidation) } diff --git a/services/appsync/ddb_resolver_flow_test.go b/services/appsync/ddb_resolver_flow_test.go index dfb0541b65..76aeaf6b36 100644 --- a/services/appsync/ddb_resolver_flow_test.go +++ b/services/appsync/ddb_resolver_flow_test.go @@ -2,13 +2,14 @@ package appsync_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -123,7 +124,7 @@ type Mutation { putTodo(id: ID!, title: String): Todo }`) require.Len(t, out, 1) for _, v := range out { - got, mErr := json.Marshal(v) + got, mErr := json.Marshal(v, jsonx.V1()) require.NoError(t, mErr) assert.JSONEq(t, tt.want, string(got)) } @@ -182,7 +183,7 @@ type Query { todo: Todo todos: [Todo] }`) out, err := b.ExecuteGraphQL(t.Context(), api.APIID, tt.query, "", nil, appsync.GraphQLAuth{APIKey: key.ID}) require.NoError(t, err) - got, mErr := json.Marshal(out) + got, mErr := json.Marshal(out, jsonx.V1()) require.NoError(t, mErr) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/appsync/ddb_transact.go b/services/appsync/ddb_transact.go index 3f209704b5..b07b1a70e6 100644 --- a/services/appsync/ddb_transact.go +++ b/services/appsync/ddb_transact.go @@ -3,10 +3,12 @@ package appsync import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -190,7 +192,7 @@ func ddbSync(ctx context.Context, ddb DynamoDBBackend, cfg *DynamoDBDataSourceCo raw, err := base64.RawURLEncoding.DecodeString(tok) var st syncToken - if err != nil || json.Unmarshal(raw, &st) != nil || len(st.Key) == 0 { + if err != nil || json.Unmarshal(raw, &st, jsonx.V1()) != nil || len(st.Key) == 0 { return nil, fmt.Errorf("%w: invalid nextToken", ErrValidation) } @@ -210,7 +212,7 @@ func ddbSync(ctx context.Context, ddb DynamoDBBackend, cfg *DynamoDBDataSourceCo var next any if len(page.LastKey) > 0 { - b, _ := json.Marshal(syncToken{Key: page.LastKey, StartedAt: startedAt}) + b, _ := json.Marshal(syncToken{Key: page.LastKey, StartedAt: startedAt}, jsonx.V1()) next = base64.RawURLEncoding.EncodeToString(b) } diff --git a/services/appsync/domain_names_test.go b/services/appsync/domain_names_test.go index dafcd64472..9fec038a01 100644 --- a/services/appsync/domain_names_test.go +++ b/services/appsync/domain_names_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -352,7 +353,7 @@ func TestListDomainNames_Pagination(t *testing.T) { NextToken string `json:"nextToken"` DomainNameConfigs []map[string]any `json:"domainNameConfigs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.DomainNameConfigs, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/ds_external.go b/services/appsync/ds_external.go index e352511b62..0719aafb70 100644 --- a/services/appsync/ds_external.go +++ b/services/appsync/ds_external.go @@ -2,13 +2,15 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" "net/url" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -194,7 +196,7 @@ func eventBridgeEntry(m map[string]any, busARN string) (EventBridgeEntry, error) e.Detail = d case nil: default: - raw, err := json.Marshal(d) + raw, err := json.Marshal(d, jsonx.V1()) if err != nil { return e, fmt.Errorf("%w: event detail is not JSON-serializable", ErrValidation) } @@ -275,7 +277,7 @@ func parseSearchBody(body []byte) any { } var v any - if err := json.Unmarshal(body, &v); err != nil { + if err := json.Unmarshal(body, &v, jsonx.V1()); err != nil { return string(body) } @@ -313,7 +315,7 @@ func openSearchRequest(endpoint string, m map[string]any) (OpenSearchRequest, er case string: req.Body = []byte(body) default: - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) if err != nil { return req, fmt.Errorf("%w: request body is not JSON-serializable", ErrValidation) } diff --git a/services/appsync/ds_external_test.go b/services/appsync/ds_external_test.go index 00b24d8e50..b25e448896 100644 --- a/services/appsync/ds_external_test.go +++ b/services/appsync/ds_external_test.go @@ -2,7 +2,8 @@ package appsync_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -142,7 +144,7 @@ func TestEventBridgeDataSource_Request(t *testing.T) { assert.JSONEq(t, `{"k":"v"}`, eb.entries[0].Detail) assert.Equal(t, []string{"r1"}, eb.entries[0].Resources) - raw, _ := json.Marshal(out["run"]) + raw, _ := json.Marshal(out["run"], jsonx.V1()) assert.JSONEq(t, `{"Entries":[{"EventId":"ev-1"}],"FailedEntryCount":0}`, string(raw)) } @@ -165,7 +167,7 @@ func TestRDSHelpers(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - ctxJSON, _ := json.Marshal(map[string]any{"result": result}) + ctxJSON, _ := json.Marshal(map[string]any{"result": result}, jsonx.V1()) out, err := b.EvaluateMappingTemplate(tt.template, string(ctxJSON)) require.NoError(t, err) assert.JSONEq(t, tt.want, out) @@ -218,16 +220,16 @@ export function response(ctx) { return ctx.result; }` t.Run(tt.kind, func(t *testing.T) { t.Parallel() - ctxJSON, _ := json.Marshal(map[string]any{"arguments": map[string]any{"kind": tt.kind}}) + ctxJSON, _ := json.Marshal(map[string]any{"arguments": map[string]any{"kind": tt.kind}}, jsonx.V1()) out, err := b.EvaluateCode(code, string(ctxJSON), "request", "APPSYNC_JS") require.NoError(t, err) var got struct { - Version string `json:"version"` - Statements json.RawMessage `json:"statements"` - VariableMap json.RawMessage `json:"variableMap"` + Version string `json:"version"` + Statements jsontext.Value `json:"statements"` + VariableMap jsontext.Value `json:"variableMap"` } - require.NoError(t, json.Unmarshal([]byte(out), &got)) + require.NoError(t, json.Unmarshal([]byte(out), &got, jsonx.V1())) assert.JSONEq(t, tt.wantStmts, string(got.Statements)) assert.JSONEq(t, tt.wantVars, string(got.VariableMap)) assert.Equal(t, "2018-05-29", got.Version) diff --git a/services/appsync/ds_http.go b/services/appsync/ds_http.go index d47732aafa..dd6b0113a5 100644 --- a/services/appsync/ds_http.go +++ b/services/appsync/ds_http.go @@ -5,7 +5,7 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -17,6 +17,7 @@ import ( v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -147,7 +148,7 @@ func buildHTTPRequest(ctx context.Context, endpoint string, req map[string]any) case string: payload = strings.NewReader(b) default: - raw, mErr := json.Marshal(b) + raw, mErr := json.Marshal(b, jsonx.V1()) if mErr != nil { return nil, fmt.Errorf("%w: request body is not JSON-serializable", ErrValidation) } diff --git a/services/appsync/ds_rds.go b/services/appsync/ds_rds.go index 4a0d475284..e02fd9545a 100644 --- a/services/appsync/ds_rds.go +++ b/services/appsync/ds_rds.go @@ -2,8 +2,10 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // invokeRDSDataSource runs the mapped statements through the RDS Data API and @@ -51,7 +53,7 @@ func invokeRDSDataSource( results = append(results, out) } - raw, err := json.Marshal(map[string]any{"sqlStatementResults": results}) + raw, err := json.Marshal(map[string]any{"sqlStatementResults": results}, jsonx.V1()) if err != nil { return nil, fmt.Errorf("rds result is not JSON-serializable: %w", err) } diff --git a/services/appsync/evaluation.go b/services/appsync/evaluation.go index 1e53352f50..0587453825 100644 --- a/services/appsync/evaluation.go +++ b/services/appsync/evaluation.go @@ -1,9 +1,11 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // evalContext decodes an EvaluateMappingTemplate/EvaluateCode context JSON @@ -12,7 +14,7 @@ func evalContext(contextJSON string) (map[string]any, error) { ctx := map[string]any{} if strings.TrimSpace(contextJSON) != "" { - if err := json.Unmarshal([]byte(contextJSON), &ctx); err != nil { + if err := json.Unmarshal([]byte(contextJSON), &ctx, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid context JSON", ErrValidation) } } @@ -116,7 +118,7 @@ func (b *InMemoryBackend) EvaluateCodeLogs(code, contextJSON, function, runtime return "", out.Logs, err } - raw, err := json.Marshal(out.Value) + raw, err := json.Marshal(out.Value, jsonx.V1()) if err != nil { return "", out.Logs, fmt.Errorf("%w: result is not JSON-serializable", ErrValidation) } diff --git a/services/appsync/evaluation_logs_test.go b/services/appsync/evaluation_logs_test.go index 0179c20ce8..5c6348b63e 100644 --- a/services/appsync/evaluation_logs_test.go +++ b/services/appsync/evaluation_logs_test.go @@ -1,12 +1,14 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AppSync developer guide, "Logging utilities": $util.log.info/error output is @@ -49,7 +51,7 @@ func TestEvaluateMappingTemplate_Logs(t *testing.T) { Result string `json:"evaluationResult"` Logs []string `json:"logs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantLogs, out.Logs) assert.Equal(t, tt.wantErr, out.Error != nil) }) diff --git a/services/appsync/export_test.go b/services/appsync/export_test.go index 9948b998af..104750e588 100644 --- a/services/appsync/export_test.go +++ b/services/appsync/export_test.go @@ -2,7 +2,9 @@ // These exports exist solely for testing and must not be called from production code. package appsync -import "encoding/json" +import ( + "encoding/json/jsontext" +) // RenderVTL is the exported test hook for the VTL template renderer. // It is only exposed for package-level tests and should not be used in production code. @@ -22,13 +24,13 @@ func ToDynamoDBJSON(val any) string { // the explicit apiKeys handling, version guard, and Tags-close housekeeping // that the production persistence.go's Snapshot/Restore layer adds on top -- // without needing an unexported field accessor. -func (b *InMemoryBackend) SnapshotRegistry() (map[string]json.RawMessage, error) { +func (b *InMemoryBackend) SnapshotRegistry() (map[string]jsontext.Value, error) { return b.registry.SnapshotAll() } // RestoreRegistry is the exported test hook for the backend's internal // store.Registry.RestoreAll. See SnapshotRegistry. -func (b *InMemoryBackend) RestoreRegistry(data map[string]json.RawMessage) error { +func (b *InMemoryBackend) RestoreRegistry(data map[string]jsontext.Value) error { return b.registry.RestoreAll(data) } diff --git a/services/appsync/functions_test.go b/services/appsync/functions_test.go index ef896ef7b7..2169b7da32 100644 --- a/services/appsync/functions_test.go +++ b/services/appsync/functions_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -513,7 +514,7 @@ func TestListFunctions_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var apiOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut, jsonx.V1())) apiID := apiOut["graphqlApi"].(map[string]any)["apiId"].(string) rec = doRequest(t, h, http.MethodPost, fmt.Sprintf("/v1/apis/%s/datasources", apiID), map[string]any{ @@ -561,7 +562,7 @@ func TestListFunctions_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Functions []map[string]any `json:"functions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Functions, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/graphql.go b/services/appsync/graphql.go index 2754f28a40..4cf0f01344 100644 --- a/services/appsync/graphql.go +++ b/services/appsync/graphql.go @@ -3,7 +3,8 @@ package appsync import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "slices" @@ -12,6 +13,8 @@ import ( "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/validator/rules" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -380,12 +383,12 @@ func resolveValue(val *ast.Value, variables map[string]any) any { return val.Raw == "true" case ast.IntValue: var i float64 - _ = json.Unmarshal([]byte(val.Raw), &i) + _ = json.Unmarshal([]byte(val.Raw), &i, jsonx.V1()) return i case ast.FloatValue: var f float64 - _ = json.Unmarshal([]byte(val.Raw), &f) + _ = json.Unmarshal([]byte(val.Raw), &f, jsonx.V1()) return f case ast.StringValue, ast.BlockValue, ast.EnumValue: @@ -461,7 +464,7 @@ func functionMappingUnit(f *Function, source any) mappingUnit { // object -- callers that require one check the decoded type themselves). func unmarshalOrRaw(s string) any { var v any - if err := json.Unmarshal([]byte(s), &v); err == nil { + if err := json.Unmarshal([]byte(s), &v, jsonx.V1()); err == nil { return v } @@ -634,7 +637,7 @@ func invokeLambdaDataSource( var lambdaResult any - if jsonErr := json.Unmarshal(result, &lambdaResult); jsonErr != nil { + if jsonErr := json.Unmarshal(result, &lambdaResult, jsonx.V1()); jsonErr != nil { lambdaResult = string(result) } @@ -660,10 +663,10 @@ func buildLambdaEvent(mu mappingUnit, args map[string]any, prevResult any) ([]by } } - return json.Marshal(value) + return json.Marshal(value, jsonx.V1()) } - return json.Marshal(defaultLambdaEvent(mu, args)) + return json.Marshal(defaultLambdaEvent(mu, args), jsonx.V1()) } func defaultLambdaEvent(mu mappingUnit, args map[string]any) map[string]any { @@ -747,10 +750,9 @@ func invokeNoneDataSource(mu mappingUnit, args map[string]any, prevResult any) ( // parseGraphQLRequest parses the GraphQL request body. func parseGraphQLRequest(body []byte) (*graphqlRequest, error) { var req graphqlRequest - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) - if err := dec.Decode(&req); err != nil { + if err := json.UnmarshalDecode(dec, &req, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fmt.Errorf("invalid GraphQL request: %w", err) } diff --git a/services/appsync/graphql_apis_config_test.go b/services/appsync/graphql_apis_config_test.go index 7effb0871b..fe3ab95b67 100644 --- a/services/appsync/graphql_apis_config_test.go +++ b/services/appsync/graphql_apis_config_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -659,7 +660,7 @@ func TestListGraphqlAPIs_Pagination(t *testing.T) { NextToken string `json:"nextToken"` GraphqlAPIs []map[string]any `json:"graphqlApis"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.GraphqlAPIs, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) @@ -703,7 +704,7 @@ func TestListGraphqlAPIs_FullPagination(t *testing.T) { NextToken string `json:"nextToken"` GraphqlAPIs []map[string]any `json:"graphqlApis"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.LessOrEqual(t, len(out.GraphqlAPIs), 2) for _, api := range out.GraphqlAPIs { diff --git a/services/appsync/handler_api_keys.go b/services/appsync/handler_api_keys.go index 8d1ee3dddc..24fa7f547b 100644 --- a/services/appsync/handler_api_keys.go +++ b/services/appsync/handler_api_keys.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleAPIKeys handles /v1/apis/{apiId}/apikeys[/{keyId}]. @@ -52,7 +53,7 @@ func (h *Handler) createAPIKey(ctx context.Context, c *echo.Context, apiID strin Expires int64 `json:"expires"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -105,7 +106,7 @@ func (h *Handler) updateAPIKey(ctx context.Context, c *echo.Context, apiID, keyI Expires int64 `json:"expires"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_api_keys_test.go b/services/appsync/handler_api_keys_test.go index 2dedfe77d7..aea0516b81 100644 --- a/services/appsync/handler_api_keys_test.go +++ b/services/appsync/handler_api_keys_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -56,7 +58,7 @@ func TestHandler_CreateApiKey(t *testing.T) { if tt.wantKeyID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) key, ok := resp["apiKey"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, key["id"]) @@ -75,7 +77,7 @@ func TestHandler_CreateAPIKey_Da2Prefix(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) gqlAPI := resp["graphqlApi"].(map[string]any) apiID := gqlAPI["apiId"].(string) @@ -84,7 +86,7 @@ func TestHandler_CreateAPIKey_Da2Prefix(t *testing.T) { require.Equal(t, http.StatusCreated, rec2.Code) var keyResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&keyResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &keyResp, jsonx.V1())) apiKey := keyResp["apiKey"].(map[string]any) keyID := apiKey["id"].(string) require.GreaterOrEqual(t, len(keyID), 4, "key ID must be at least 4 characters") @@ -107,7 +109,7 @@ func TestHandler_ListApiKeys(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) keys := resp["apiKeys"].([]any) assert.Len(t, keys, 2) } @@ -146,7 +148,7 @@ func TestHandler_UpdateApiKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apiKey := resp["apiKey"].(map[string]any) assert.Equal(t, "updated", apiKey["description"]) } diff --git a/services/appsync/handler_caching.go b/services/appsync/handler_caching.go index 848204b6d0..69be67d11f 100644 --- a/services/appsync/handler_caching.go +++ b/services/appsync/handler_caching.go @@ -2,12 +2,13 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleAPICaches handles /v1/apis/{apiId}/ApiCaches[/entries]. @@ -56,7 +57,7 @@ func (h *Handler) createAPICache(ctx context.Context, c *echo.Context, apiID str } var cache APICache - if jsonErr := json.Unmarshal(body, &cache); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &cache, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -95,7 +96,7 @@ func (h *Handler) updateAPICache(ctx context.Context, c *echo.Context, apiID str } var cache APICache - if jsonErr := json.Unmarshal(body, &cache); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &cache, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_caching_test.go b/services/appsync/handler_caching_test.go index de845829bc..b24c8548a2 100644 --- a/services/appsync/handler_caching_test.go +++ b/services/appsync/handler_caching_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -102,7 +104,7 @@ func TestHandler_CreateAPICache_Validation(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) gqlAPI := resp["graphqlApi"].(map[string]any) apiID := gqlAPI["apiId"].(string) @@ -130,7 +132,7 @@ func TestHandler_GetApiCache(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotNil(t, resp["apiCache"]) } @@ -174,7 +176,7 @@ func TestHandler_UpdateApiCache(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) cache := resp["apiCache"].(map[string]any) assert.Equal(t, "LARGE", cache["type"]) } diff --git a/services/appsync/handler_channel_namespaces.go b/services/appsync/handler_channel_namespaces.go index 13aaf86f9d..86182e4ab6 100644 --- a/services/appsync/handler_channel_namespaces.go +++ b/services/appsync/handler_channel_namespaces.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleChannelNamespacesCollection(ctx context.Context, c *echo.Context, apiID string) error { @@ -50,7 +51,7 @@ func (h *Handler) createChannelNamespace(ctx context.Context, c *echo.Context, a SubscribeAuthModes []AuthMode `json:"subscribeAuthModes"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -116,7 +117,7 @@ func (h *Handler) updateChannelNamespace(ctx context.Context, c *echo.Context, a SubscribeAuthModes []AuthMode `json:"subscribeAuthModes"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_channel_namespaces_test.go b/services/appsync/handler_channel_namespaces_test.go index 2a4a1c8ade..83e98cef87 100644 --- a/services/appsync/handler_channel_namespaces_test.go +++ b/services/appsync/handler_channel_namespaces_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -64,7 +66,7 @@ func TestHandler_CreateChannelNamespace(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ns, ok := resp["channelNamespace"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, ns["name"]) @@ -83,7 +85,7 @@ func TestHandler_ChannelNamespace_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &apiResp, jsonx.V1())) apiID := apiResp["api"].(map[string]any)["apiId"].(string) // Create namespace. @@ -96,7 +98,7 @@ func TestHandler_ChannelNamespace_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec3.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec3.Body), &listResp, jsonx.V1())) items := listResp["channelNamespaces"].([]any) assert.Len(t, items, 1) @@ -105,7 +107,7 @@ func TestHandler_ChannelNamespace_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec4.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec4.Body), &getResp, jsonx.V1())) ns := getResp["channelNamespace"].(map[string]any) assert.Equal(t, "ns1", ns["name"]) diff --git a/services/appsync/handler_data_sources.go b/services/appsync/handler_data_sources.go index d890c75824..6347308e2e 100644 --- a/services/appsync/handler_data_sources.go +++ b/services/appsync/handler_data_sources.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDataSources handles /v1/apis/{apiId}/datasources[/{name}]. @@ -50,7 +51,7 @@ func (h *Handler) createDataSource(ctx context.Context, c *echo.Context, apiID s } var ds DataSource - if jsonErr := json.Unmarshal(body, &ds); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &ds, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -109,7 +110,7 @@ func (h *Handler) updateDataSource(ctx context.Context, c *echo.Context, apiID, } var ds DataSource - if jsonErr := json.Unmarshal(body, &ds); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &ds, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -180,7 +181,7 @@ func (h *Handler) startDataSourceIntrospection(ctx context.Context, c *echo.Cont RDSDataAPIConfig *RDSDataAPIConfig `json:"rdsDataApiConfig"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_data_sources_test.go b/services/appsync/handler_data_sources_test.go index 0ff16444cc..11f27bad32 100644 --- a/services/appsync/handler_data_sources_test.go +++ b/services/appsync/handler_data_sources_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -143,7 +145,7 @@ func TestHandler_DataSourceIntrospection_StartThenGet(t *testing.T) { IntrospectionID string `json:"introspectionId"` IntrospectionStatus string `json:"introspectionStatus"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut.IntrospectionID) assert.Equal(t, "SUCCESS", startOut.IntrospectionStatus) @@ -157,7 +159,7 @@ func TestHandler_DataSourceIntrospection_StartThenGet(t *testing.T) { Models []any `json:"models"` } `json:"introspectionResult"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, startOut.IntrospectionID, getOut.IntrospectionID) assert.Equal(t, "SUCCESS", getOut.IntrospectionStatus) assert.Empty(t, getOut.IntrospectionResult.Models) @@ -202,7 +204,7 @@ func TestHandler_CreateAndGetDataSource(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ds, ok := resp["dataSource"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, ds["name"]) @@ -233,7 +235,7 @@ func TestHandler_ListDataSources(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) dss, ok := resp["dataSources"].([]any) require.True(t, ok) assert.Len(t, dss, 1) @@ -275,7 +277,7 @@ func TestHandler_UpdateDataSource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ds := resp["dataSource"].(map[string]any) assert.Equal(t, "updated", ds["description"]) diff --git a/services/appsync/handler_domain_names.go b/services/appsync/handler_domain_names.go index fdf8cdbd7e..77a4e4a21d 100644 --- a/services/appsync/handler_domain_names.go +++ b/services/appsync/handler_domain_names.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDomainNames handles /v1/domainnames[/{domainName}[/apiassociation]]. @@ -81,7 +82,7 @@ func (h *Handler) createDomainName(ctx context.Context, c *echo.Context) error { Description string `json:"description"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -112,7 +113,7 @@ func (h *Handler) associateAPI(ctx context.Context, c *echo.Context, domainName APIID string `json:"apiId"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -189,7 +190,7 @@ func (h *Handler) updateDomainName(ctx context.Context, c *echo.Context, domainN CertificateARN string `json:"certificateArn"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_domain_names_test.go b/services/appsync/handler_domain_names_test.go index 8246a2a0ee..fcd7659d1c 100644 --- a/services/appsync/handler_domain_names_test.go +++ b/services/appsync/handler_domain_names_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -53,7 +55,7 @@ func TestHandler_CreateDomainName(t *testing.T) { if tt.wantDomainName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) cfg, ok := resp["domainNameConfig"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantDomainName, cfg["domainName"]) @@ -147,7 +149,7 @@ func TestHandler_DomainNameCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &listResp, jsonx.V1())) dns := listResp["domainNameConfigs"].([]any) assert.Len(t, dns, 1) @@ -198,7 +200,7 @@ func TestHandler_DisassociateAPI(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var apiResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&apiResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &apiResp, jsonx.V1())) apiID := apiResp["graphqlApi"].(map[string]any)["apiId"].(string) doRequest(t, h, http.MethodPost, "/v1/domainnames/api.example.com/apiassociation", @@ -228,7 +230,7 @@ func TestHandler_UpdateDomainName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) dn := resp["domainNameConfig"].(map[string]any) assert.Equal(t, "updated description", dn["description"]) } diff --git a/services/appsync/handler_evaluation.go b/services/appsync/handler_evaluation.go index 645759d8ec..919358e7cb 100644 --- a/services/appsync/handler_evaluation.go +++ b/services/appsync/handler_evaluation.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDataplaneEvaluations handles /v1/dataplane-evaluations/{template|code}. @@ -43,7 +44,7 @@ func (h *Handler) evaluateMappingTemplate(ctx context.Context, c *echo.Context) Context string `json:"context"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -75,7 +76,7 @@ func (h *Handler) evaluateCode(ctx context.Context, c *echo.Context) error { } `json:"runtime"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_events.go b/services/appsync/handler_events.go index 7b2b186f02..12e324b94d 100644 --- a/services/appsync/handler_events.go +++ b/services/appsync/handler_events.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleV2APIs handles /v2/apis[/{apiId}[/channelNamespaces[/{name}]]]. @@ -69,7 +70,7 @@ func (h *Handler) createAPI(ctx context.Context, c *echo.Context) error { OwnerContact string `json:"ownerContact"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -138,7 +139,7 @@ func (h *Handler) updateAPI(ctx context.Context, c *echo.Context, apiID string) OwnerContact string `json:"ownerContact"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_events_test.go b/services/appsync/handler_events_test.go index 909c7d0c20..d082ea7119 100644 --- a/services/appsync/handler_events_test.go +++ b/services/appsync/handler_events_test.go @@ -1,12 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateApi(t *testing.T) { @@ -44,7 +47,7 @@ func TestHandler_CreateApi(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) api, ok := resp["api"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, api["name"]) @@ -64,7 +67,7 @@ func TestHandler_EventAPI_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) apiID := createResp["api"].(map[string]any)["apiId"].(string) // List APIs. @@ -72,7 +75,7 @@ func TestHandler_EventAPI_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &listResp, jsonx.V1())) items := listResp["apis"].([]any) assert.Len(t, items, 1) @@ -81,7 +84,7 @@ func TestHandler_EventAPI_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec3.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec3.Body), &getResp, jsonx.V1())) assert.Equal(t, "MyEventAPI", getResp["api"].(map[string]any)["name"]) // Delete API. @@ -97,7 +100,7 @@ func TestHandler_EventAPI_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec6.Code) var listResp2 map[string]any - require.NoError(t, json.NewDecoder(rec6.Body).Decode(&listResp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec6.Body), &listResp2, jsonx.V1())) assert.Empty(t, listResp2["apis"]) } @@ -129,7 +132,7 @@ func TestHandler_UpdateAPI(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &createResp, jsonx.V1())) apiID := createResp["api"].(map[string]any)["apiId"].(string) rec2 := doRequest(t, h, http.MethodPut, "/v2/apis/"+apiID, @@ -137,7 +140,7 @@ func TestHandler_UpdateAPI(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp, jsonx.V1())) api := resp["api"].(map[string]any) assert.Equal(t, "UpdatedAPI", api["name"]) assert.Equal(t, "ops@example.com", api["ownerContact"]) diff --git a/services/appsync/handler_functions.go b/services/appsync/handler_functions.go index 34674bbd09..e9833ee2c9 100644 --- a/services/appsync/handler_functions.go +++ b/services/appsync/handler_functions.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleFunctions handles /v1/apis/{apiId}/functions[/{functionId}[/resolvers]]. @@ -61,7 +62,7 @@ func (h *Handler) createFunction(ctx context.Context, c *echo.Context, apiID str } var f Function - if jsonErr := json.Unmarshal(body, &f); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &f, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -124,7 +125,7 @@ func (h *Handler) updateFunction(ctx context.Context, c *echo.Context, apiID, fu } var f Function - if jsonErr := json.Unmarshal(body, &f); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &f, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_functions_test.go b/services/appsync/handler_functions_test.go index 8cf15cf639..18ad8666cc 100644 --- a/services/appsync/handler_functions_test.go +++ b/services/appsync/handler_functions_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -78,7 +80,7 @@ func TestHandler_CreateFunction(t *testing.T) { if tt.wantFuncName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) fn, ok := resp["functionConfiguration"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantFuncName, fn["name"]) @@ -102,7 +104,7 @@ func TestHandler_ListFunctions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) fns := resp["functions"].([]any) assert.Len(t, fns, 1) } @@ -121,7 +123,7 @@ func TestHandler_GetFunction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotNil(t, resp["functionConfiguration"]) } @@ -158,7 +160,7 @@ func TestHandler_UpdateFunction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) fnConfig := resp["functionConfiguration"].(map[string]any) assert.Equal(t, "updated fn", fnConfig["description"]) } diff --git a/services/appsync/handler_graphql_apis.go b/services/appsync/handler_graphql_apis.go index 8b914b8b4c..7721458136 100644 --- a/services/appsync/handler_graphql_apis.go +++ b/services/appsync/handler_graphql_apis.go @@ -2,7 +2,7 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strconv" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createGraphqlAPI handles POST /v1/apis. @@ -39,7 +40,7 @@ func (h *Handler) createGraphqlAPI(ctx context.Context, c *echo.Context) error { XrayEnabled bool `json:"xrayEnabled"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -202,7 +203,7 @@ func (h *Handler) updateGraphqlAPI(ctx context.Context, c *echo.Context, apiID s ResolverCountLimit int32 `json:"resolverCountLimit"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -255,7 +256,7 @@ func (h *Handler) handleEnvironmentVariables(ctx context.Context, c *echo.Contex EnvironmentVariables map[string]string `json:"environmentVariables"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_graphql_apis_test.go b/services/appsync/handler_graphql_apis_test.go index c4065cbf7e..a6f3a8c63a 100644 --- a/services/appsync/handler_graphql_apis_test.go +++ b/services/appsync/handler_graphql_apis_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -61,7 +63,7 @@ func TestHandler_CreateAndGetGraphqlAPI(t *testing.T) { if tt.wantAPIName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) api, ok := resp["graphqlApi"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantAPIName, api["name"]) @@ -162,7 +164,7 @@ func TestHandler_GraphQLExecution(t *testing.T) { if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) if _, hasErrors := resp["errors"]; !hasErrors { data, ok := resp["data"].(map[string]any) @@ -208,7 +210,7 @@ func TestHandler_ListGraphqlAPIs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apis, ok := resp["graphqlApis"].([]any) require.True(t, ok) assert.Len(t, apis, tt.wantCount) @@ -348,7 +350,7 @@ func TestHandler_UpdateGraphqlAPI(t *testing.T) { if tt.wantName != "" && tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) gqlAPI := resp["graphqlApi"].(map[string]any) assert.Equal(t, tt.wantName, gqlAPI["name"]) } @@ -366,7 +368,7 @@ func TestHandler_CreateAndUpdateGraphqlAPI_OwnerContact(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) createdAPI := createResp["graphqlApi"].(map[string]any) assert.Equal(t, "team-a@example.com", createdAPI["ownerContact"]) apiID := createdAPI["apiId"].(string) @@ -375,7 +377,7 @@ func TestHandler_CreateAndUpdateGraphqlAPI_OwnerContact(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getResp, jsonx.V1())) assert.Equal(t, "team-a@example.com", getResp["graphqlApi"].(map[string]any)["ownerContact"]) updateRec := doRequest(t, h, http.MethodPatch, "/v1/apis/"+apiID, @@ -383,7 +385,7 @@ func TestHandler_CreateAndUpdateGraphqlAPI_OwnerContact(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &updateResp, jsonx.V1())) assert.Equal(t, "team-b@example.com", updateResp["graphqlApi"].(map[string]any)["ownerContact"]) } @@ -399,7 +401,7 @@ func TestHandler_EnvironmentVariables(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &getResp, jsonx.V1())) assert.Empty(t, getResp["environmentVariables"]) // Put env vars. @@ -408,7 +410,7 @@ func TestHandler_EnvironmentVariables(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var putResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&putResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &putResp, jsonx.V1())) envVars := putResp["environmentVariables"].(map[string]any) assert.Equal(t, "value1", envVars["KEY1"]) assert.Equal(t, "value2", envVars["KEY2"]) @@ -418,7 +420,7 @@ func TestHandler_EnvironmentVariables(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var getResp2 map[string]any - require.NoError(t, json.NewDecoder(rec3.Body).Decode(&getResp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec3.Body), &getResp2, jsonx.V1())) envVars2 := getResp2["environmentVariables"].(map[string]any) assert.Equal(t, "value1", envVars2["KEY1"]) } @@ -480,7 +482,7 @@ func TestHandler_CreateGraphqlAPI_XrayEnabled(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apiObj := resp["graphqlApi"].(map[string]any) assert.NotEmpty(t, apiObj["apiId"]) @@ -540,7 +542,7 @@ func TestHandler_UpdateGraphqlAPI_XrayEnabled(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apiObj := resp["graphqlApi"].(map[string]any) // xrayEnabled omits false in JSON (omitempty), so nil == false. gotXray, _ := apiObj["xrayEnabled"].(bool) @@ -560,7 +562,7 @@ func TestHandler_ListGraphqlAPIs_ApiTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apis := resp["graphqlApis"].([]any) assert.Len(t, apis, 1) } diff --git a/services/appsync/handler_resolvers.go b/services/appsync/handler_resolvers.go index bdd749bae2..f3d60a21f4 100644 --- a/services/appsync/handler_resolvers.go +++ b/services/appsync/handler_resolvers.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleTypeResolvers handles /v1/apis/{apiId}/types/{typeName}/resolvers[/{fieldName}]. @@ -54,7 +55,7 @@ func (h *Handler) createResolver(ctx context.Context, c *echo.Context, apiID, ty } var r Resolver - if jsonErr := json.Unmarshal(body, &r); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &r, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -113,7 +114,7 @@ func (h *Handler) updateResolver(ctx context.Context, c *echo.Context, apiID, ty } var r Resolver - if jsonErr := json.Unmarshal(body, &r); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &r, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_resolvers_test.go b/services/appsync/handler_resolvers_test.go index 66982b80fe..f2767c4929 100644 --- a/services/appsync/handler_resolvers_test.go +++ b/services/appsync/handler_resolvers_test.go @@ -1,13 +1,15 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -57,7 +59,7 @@ func TestHandler_CreateAndGetResolver(t *testing.T) { if tt.wantFieldName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) r, ok := resp["resolver"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantFieldName, r["fieldName"]) @@ -88,7 +90,7 @@ func TestHandler_ListResolvers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) res, ok := resp["resolvers"].([]any) require.True(t, ok) assert.Len(t, res, 1) @@ -123,7 +125,7 @@ func TestHandler_UpdateResolver(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) resolver := resp["resolver"].(map[string]any) assert.Equal(t, "$util.toJson($context.args)", resolver["requestMappingTemplate"]) } @@ -149,7 +151,7 @@ func TestHandler_ListResolversByFunction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) resolvers := resp["resolvers"].([]any) assert.Len(t, resolvers, 1) assert.Equal(t, "getItem", resolvers[0].(map[string]any)["fieldName"]) diff --git a/services/appsync/handler_routing_test.go b/services/appsync/handler_routing_test.go index 3519460c42..94f8ba41ed 100644 --- a/services/appsync/handler_routing_test.go +++ b/services/appsync/handler_routing_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -924,7 +926,7 @@ func Test_TagResource_RealPath(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &listResp, jsonx.V1())) tagMap, ok := listResp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tagMap["env"]) @@ -934,7 +936,7 @@ func Test_TagResource_RealPath(t *testing.T) { require.Equal(t, http.StatusNoContent, rec3.Code) rec4 := doRequest(t, h, http.MethodGet, tagPath, nil) - require.NoError(t, json.NewDecoder(rec4.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec4.Body), &listResp, jsonx.V1())) assert.Empty(t, listResp["tags"]) } @@ -956,7 +958,7 @@ func Test_TagResource_RealPath_EventAPI(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp, jsonx.V1())) tagMap, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "core", tagMap["team"]) @@ -994,7 +996,7 @@ func Test_ApiCache_RealPaths(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) cache := resp["apiCache"].(map[string]any) assert.Equal(t, "LARGE", cache["type"]) @@ -1043,7 +1045,7 @@ func Test_ListSourceApiAssociations_ByAPIID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) // The real AWS SDK's ListSourceApiAssociationsOutput wraps the list under // "sourceApiAssociationSummaries", not "sourceApiAssociations" (the latter is only // the URL path segment name). @@ -1066,7 +1068,7 @@ func Test_ListApis_ResponseKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) apis, ok := resp["apis"].([]any) require.True(t, ok, "response must wrap the list under \"apis\", got keys %v", resp) assert.Len(t, apis, 1) diff --git a/services/appsync/handler_schema.go b/services/appsync/handler_schema.go index a6d2bbfaeb..bdbbc0c0e4 100644 --- a/services/appsync/handler_schema.go +++ b/services/appsync/handler_schema.go @@ -3,12 +3,13 @@ package appsync import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleSchemaCreation handles /v1/apis/{apiId}/schemacreation. @@ -34,7 +35,7 @@ func (h *Handler) startSchemaCreation(ctx context.Context, c *echo.Context, apiI Definition string `json:"definition"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_schema_test.go b/services/appsync/handler_schema_test.go index 683d4e9658..53d4e75fa6 100644 --- a/services/appsync/handler_schema_test.go +++ b/services/appsync/handler_schema_test.go @@ -2,13 +2,15 @@ package appsync_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -61,7 +63,7 @@ func TestHandler_StartSchemaCreation(t *testing.T) { if tt.wantStatus2 != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, tt.wantStatus2, resp["status"]) } }) @@ -79,7 +81,7 @@ func TestHandler_GetSchemaCreationStatus(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, string(appsync.SchemaStatusActive), resp["status"]) } @@ -149,10 +151,10 @@ func TestHandler_GetIntrospectionSchema_Format(t *testing.T) { checkBody: func(t *testing.T, body string) { t.Helper() - require.True(t, json.Valid([]byte(body))) + require.True(t, jsontext.Value([]byte(body)).IsValid(jsonx.V1())) var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &doc)) + require.NoError(t, json.Unmarshal([]byte(body), &doc, jsonx.V1())) data, ok := doc["data"].(map[string]any) require.True(t, ok) @@ -207,7 +209,7 @@ func TestHandler_GetIntrospectionSchema_InvalidSchema_JSON(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "GraphQLSchemaException", resp["code"]) } diff --git a/services/appsync/handler_schema_types.go b/services/appsync/handler_schema_types.go index e73944cfab..7ffa23678f 100644 --- a/services/appsync/handler_schema_types.go +++ b/services/appsync/handler_schema_types.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleTypes handles /v1/apis/{apiId}/types[/{typeName}[/resolvers[/{fieldName}]]]. @@ -68,7 +69,7 @@ func (h *Handler) createTypeHandler(ctx context.Context, c *echo.Context, apiID Format string `json:"format"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -149,7 +150,7 @@ func (h *Handler) updateType(ctx context.Context, c *echo.Context, apiID, typeNa Format string `json:"format"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_schema_types_test.go b/services/appsync/handler_schema_types_test.go index 50fec249b2..d8ad2e54a0 100644 --- a/services/appsync/handler_schema_types_test.go +++ b/services/appsync/handler_schema_types_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -139,7 +141,7 @@ func TestHandler_CreateType(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) typ, ok := resp["type"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, typ["name"]) @@ -158,7 +160,7 @@ func TestHandler_CreateType_DuplicateRejected(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) gqlAPI := resp["graphqlApi"].(map[string]any) apiID := gqlAPI["apiId"].(string) @@ -187,7 +189,7 @@ func TestHandler_ListTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) types := resp["types"].([]any) assert.Len(t, types, 1) } @@ -206,7 +208,7 @@ func TestHandler_GetType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotNil(t, resp["type"]) } @@ -243,7 +245,7 @@ func TestHandler_UpdateType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) typeDef := resp["type"].(map[string]any) assert.Contains(t, typeDef["definition"], "name") } diff --git a/services/appsync/handler_source_api_associations.go b/services/appsync/handler_source_api_associations.go index 893f64aeeb..9f99b9f0a9 100644 --- a/services/appsync/handler_source_api_associations.go +++ b/services/appsync/handler_source_api_associations.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleSourceAPIs handles /v1/sourceApis/{sourceApiIdentifier}/mergedApiAssociations[/{assocId}]. @@ -86,7 +87,7 @@ func (h *Handler) associateMergedGraphqlAPI(ctx context.Context, c *echo.Context } var input associateAPIInput - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -173,7 +174,7 @@ func (h *Handler) associateSourceGraphqlAPI(ctx context.Context, c *echo.Context } var input associateAPIInput - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -237,7 +238,7 @@ func (h *Handler) updateSourceAPIAssociation( Description string `json:"description"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_source_api_associations_test.go b/services/appsync/handler_source_api_associations_test.go index 9d5a9a80f2..d1cb7ecf57 100644 --- a/services/appsync/handler_source_api_associations_test.go +++ b/services/appsync/handler_source_api_associations_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -95,7 +97,7 @@ func TestHandler_AssociateMergedGraphqlApi(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assoc, ok := resp["sourceApiAssociation"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, assoc["associationId"]) @@ -138,7 +140,7 @@ func TestHandler_AssociateSourceGraphqlApi(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assoc, ok := resp["sourceApiAssociation"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, assoc["associationId"]) @@ -178,7 +180,7 @@ func TestHandler_SourceAPIAssociations_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &createResp, jsonx.V1())) assocID := createResp["sourceApiAssociation"].(map[string]any)["associationId"].(string) // List source API associations. @@ -186,7 +188,7 @@ func TestHandler_SourceAPIAssociations_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &listResp, jsonx.V1())) assocs := listResp["sourceApiAssociationSummaries"].([]any) assert.Len(t, assocs, 1) @@ -195,7 +197,7 @@ func TestHandler_SourceAPIAssociations_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec3.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec3.Body), &getResp, jsonx.V1())) assoc := getResp["sourceApiAssociation"].(map[string]any) assert.Equal(t, mrgAPI.APIID, assoc["mergedApiId"]) @@ -225,7 +227,7 @@ func TestHandler_DisassociateMergedGraphqlApi(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &createResp, jsonx.V1())) assocID := createResp["sourceApiAssociation"].(map[string]any)["associationId"].(string) // Disassociate. @@ -246,7 +248,7 @@ func TestHandler_ListSourceApiAssociations_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Empty(t, resp["sourceApiAssociationSummaries"]) } @@ -318,7 +320,7 @@ func TestHandler_StartSchemaMerge(t *testing.T) { } var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, appsync.SourceAPIAssociationStatusMergeSuccess, resp["sourceApiAssociationStatus"]) }) } diff --git a/services/appsync/handler_tags.go b/services/appsync/handler_tags.go index 50f47ce91e..f3dae2c8f7 100644 --- a/services/appsync/handler_tags.go +++ b/services/appsync/handler_tags.go @@ -2,13 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleTagsByARN handles /v1/tags/{resourceArn} — the real AWS SDK path for @@ -91,7 +92,7 @@ func (h *Handler) tagResource(ctx context.Context, c *echo.Context, apiID string Tags map[string]string `json:"tags"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/appsync/handler_tags_test.go b/services/appsync/handler_tags_test.go index f77d361f6d..1ef66c8559 100644 --- a/services/appsync/handler_tags_test.go +++ b/services/appsync/handler_tags_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -30,7 +32,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp, jsonx.V1())) tags := resp["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -54,7 +56,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var resp4 map[string]any - require.NoError(t, json.NewDecoder(rec4.Body).Decode(&resp4)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec4.Body), &resp4, jsonx.V1())) tags4 := resp4["tags"].(map[string]any) assert.NotContains(t, tags4, "env") assert.Equal(t, "platform", tags4["team"]) diff --git a/services/appsync/helpers_test.go b/services/appsync/helpers_test.go index a2ff9a5565..55f3622442 100644 --- a/services/appsync/helpers_test.go +++ b/services/appsync/helpers_test.go @@ -7,7 +7,7 @@ package appsync_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -36,7 +37,7 @@ func doRequest(t *testing.T, handler *appsync.Handler, method, path string, body var buf *bytes.Buffer if body != nil { - b, _ := json.Marshal(body) + b, _ := json.Marshal(body, jsonx.V1()) buf = bytes.NewBuffer(b) } else { buf = bytes.NewBuffer(nil) @@ -65,7 +66,7 @@ func doRequestWithHeaders( var buf *bytes.Buffer if body != nil { - b, _ := json.Marshal(body) + b, _ := json.Marshal(body, jsonx.V1()) buf = bytes.NewBuffer(b) } else { buf = bytes.NewBuffer(nil) @@ -94,7 +95,7 @@ func doV2Request(t *testing.T, handler *appsync.Handler, method, path string, bo var buf *bytes.Buffer if body != nil { - b, _ := json.Marshal(body) + b, _ := json.Marshal(body, jsonx.V1()) buf = bytes.NewBuffer(b) } else { buf = bytes.NewBuffer(nil) diff --git a/services/appsync/identity_test.go b/services/appsync/identity_test.go index 3855759181..01a307d564 100644 --- a/services/appsync/identity_test.go +++ b/services/appsync/identity_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -199,7 +200,7 @@ func whoMap(t *testing.T, v any) map[string]any { require.True(t, ok, "unexpected who: %#v", v) var m map[string]any - require.NoError(t, json.Unmarshal([]byte(s), &m)) + require.NoError(t, json.Unmarshal([]byte(s), &m, jsonx.V1())) return m } diff --git a/services/appsync/introspection.go b/services/appsync/introspection.go index 9e1f0c46d5..6d9fdeb586 100644 --- a/services/appsync/introspection.go +++ b/services/appsync/introspection.go @@ -1,10 +1,12 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "slices" "github.com/vektah/gqlparser/v2/ast" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // The types below mirror the GraphQL specification's introspection system @@ -110,7 +112,7 @@ func marshalIntrospectionSchema(schema *ast.Schema, includeDirectives bool) ([]b }, } - return json.Marshal(doc) + return json.Marshal(doc, jsonx.V1()) } func buildRootRef(def *ast.Definition) *rootTypeRef { diff --git a/services/appsync/introspection_test.go b/services/appsync/introspection_test.go index db8049167a..610d0e410d 100644 --- a/services/appsync/introspection_test.go +++ b/services/appsync/introspection_test.go @@ -1,12 +1,14 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -109,10 +111,10 @@ func fieldByName(fields []testField, name string) *testField { func decodeIntrospection(t *testing.T, raw []byte) testIntrospectionResponse { t.Helper() - require.True(t, json.Valid(raw)) + require.True(t, jsontext.Value(raw).IsValid(jsonx.V1())) var doc testIntrospectionResponse - require.NoError(t, json.Unmarshal(raw, &doc)) + require.NoError(t, json.Unmarshal(raw, &doc, jsonx.V1())) return doc } diff --git a/services/appsync/jseval_test.go b/services/appsync/jseval_test.go index 50c791cb6a..6dc9fd0af3 100644 --- a/services/appsync/jseval_test.go +++ b/services/appsync/jseval_test.go @@ -1,11 +1,13 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // twoHandlerCode defines both request and response handlers, used to exercise @@ -168,6 +170,6 @@ func TestInMemoryBackend_EvaluateCode_ResultIsValidJSON(t *testing.T) { require.NoError(t, err) var decoded map[string]any - require.NoError(t, json.Unmarshal([]byte(out), &decoded)) + require.NoError(t, json.Unmarshal([]byte(out), &decoded, jsonx.V1())) assert.Equal(t, "Scan", decoded["operation"]) } diff --git a/services/appsync/lambda_batch.go b/services/appsync/lambda_batch.go index 0f5a66c18d..9269df0451 100644 --- a/services/appsync/lambda_batch.go +++ b/services/appsync/lambda_batch.go @@ -2,12 +2,14 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "strings" "github.com/vektah/gqlparser/v2/ast" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -197,7 +199,7 @@ func (e *execEnv) invokeBatch( payloads[i] = ent.payload } - body, err := json.Marshal(payloads) + body, err := json.Marshal(payloads, jsonx.V1()) if err != nil { fail(err) @@ -217,7 +219,7 @@ func (e *execEnv) invokeBatch( if invType == lambdaInvokeEvent { results = make([]any, len(chunk)) - } else if err = json.Unmarshal(raw, &results); err != nil || len(results) != len(chunk) { + } else if err = json.Unmarshal(raw, &results, jsonx.V1()); err != nil || len(results) != len(chunk) { fail(fmt.Errorf("%w: BatchInvoke response must be a list of %d items", ErrValidation, len(chunk))) return diff --git a/services/appsync/lambda_batch_test.go b/services/appsync/lambda_batch_test.go index 067bc6939c..6b22d07d82 100644 --- a/services/appsync/lambda_batch_test.go +++ b/services/appsync/lambda_batch_test.go @@ -2,7 +2,7 @@ package appsync_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" @@ -11,6 +11,8 @@ import ( appsynctypes "github.com/aws/aws-sdk-go-v2/service/appsync/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type batchInvoker struct { @@ -27,7 +29,7 @@ func (b *batchInvoker) InvokeFunction(_ context.Context, name, _ string, payload } var items []any - if err := json.Unmarshal(payload, &items); err != nil { + if err := json.Unmarshal(payload, &items, jsonx.V1()); err != nil { return nil, 0, err } @@ -35,7 +37,7 @@ func (b *batchInvoker) InvokeFunction(_ context.Context, name, _ string, payload b.sizes = append(b.sizes, len(items)) b.mu.Unlock() - out, err := json.Marshal(b.respond(items)) + out, err := json.Marshal(b.respond(items), jsonx.V1()) return out, 200, err } @@ -166,7 +168,7 @@ type Query { posts: [Post] }`) Data map[string]any `json:"data"` Errors []any `json:"errors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &reply), rec.Body.String()) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &reply, jsonx.V1()), rec.Body.String()) assert.Equal(t, tt.wantSizes, inv.sizes) assert.Len(t, reply.Errors, max(tt.wantErrs, boolInt(tt.wantData == nil))) diff --git a/services/appsync/models.go b/services/appsync/models.go index c71d1f43b9..1df7ccd832 100644 --- a/services/appsync/models.go +++ b/services/appsync/models.go @@ -2,11 +2,13 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/vektah/gqlparser/v2/ast" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -227,7 +229,7 @@ func (r Resolver) MarshalJSON() ([]byte, error) { return json.Marshal(struct { PipelineConfig *pipelineConfigWire `json:"pipelineConfig,omitempty"` alias - }{alias: alias(r), PipelineConfig: pc}) + }{alias: alias(r), PipelineConfig: pc}, jsonx.V1()) } // UnmarshalJSON is the inverse of [Resolver.MarshalJSON], needed so a @@ -241,7 +243,7 @@ func (r *Resolver) UnmarshalJSON(data []byte) error { PipelineConfig *pipelineConfigWire `json:"pipelineConfig,omitempty"` }{alias: (*alias)(r)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -456,10 +458,10 @@ func (a *APICache) UnmarshalJSON(data []byte) error { aux := struct { *alias - HealthMetricsConfig json.RawMessage `json:"healthMetricsConfig,omitempty"` + HealthMetricsConfig jsontext.Value `json:"healthMetricsConfig,omitempty"` }{alias: (*alias)(a)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -468,12 +470,12 @@ func (a *APICache) UnmarshalJSON(data []byte) error { } // A real string (or null, a no-op unmarshal target) decodes here directly. - if err := json.Unmarshal(aux.HealthMetricsConfig, &a.HealthMetricsConfig); err == nil { + if err := json.Unmarshal(aux.HealthMetricsConfig, &a.HealthMetricsConfig, jsonx.V1()); err == nil { return nil } var legacy bool - if err := json.Unmarshal(aux.HealthMetricsConfig, &legacy); err != nil { + if err := json.Unmarshal(aux.HealthMetricsConfig, &legacy, jsonx.V1()); err != nil { return fmt.Errorf("APICache: invalid healthMetricsConfig %s: %w", aux.HealthMetricsConfig, err) } diff --git a/services/appsync/multi_region_test.go b/services/appsync/multi_region_test.go index 59ab92a007..e4bd963cea 100644 --- a/services/appsync/multi_region_test.go +++ b/services/appsync/multi_region_test.go @@ -2,7 +2,8 @@ package appsync_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -39,7 +41,7 @@ func regionDo(t *testing.T, h *appsync.Handler, region, method, path, body strin out := map[string]any{} if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) } return rec.Code, out @@ -158,9 +160,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/appsync/nested_resolver_test.go b/services/appsync/nested_resolver_test.go index 6d3bd2c5bf..dac026fe6f 100644 --- a/services/appsync/nested_resolver_test.go +++ b/services/appsync/nested_resolver_test.go @@ -1,12 +1,13 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -95,7 +96,7 @@ type Query { post: Post posts: [Post] }`) ) require.NoError(t, err) - got, mErr := json.Marshal(out) + got, mErr := json.Marshal(out, jsonx.V1()) require.NoError(t, mErr) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/appsync/null_propagation_test.go b/services/appsync/null_propagation_test.go index 0d3aac9e37..ac898dac66 100644 --- a/services/appsync/null_propagation_test.go +++ b/services/appsync/null_propagation_test.go @@ -1,13 +1,14 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -105,7 +106,7 @@ func TestNullPropagation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var got gqlReply - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantData, got.Data) require.Len(t, got.Errors, 1, rec.Body.String()) assert.Equal(t, tt.wantMsg, got.Errors[0].Message) diff --git a/services/appsync/persistence.go b/services/appsync/persistence.go index 2eb26b3f7e..66b080411a 100644 --- a/services/appsync/persistence.go +++ b/services/appsync/persistence.go @@ -10,7 +10,7 @@ package appsync // every "clean" table directly, same as here). import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -48,7 +48,7 @@ const appsyncSnapshotVersion = 2 // func(*APIKey) string that could reproduce the "#" composite // key from the value alone). It is persisted explicitly here instead. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` APIKeys map[string]map[string]*APIKey `json:"apiKeys,omitempty"` Version int `json:"version"` } diff --git a/services/appsync/realtime.go b/services/appsync/realtime.go index 678b29009f..890d9f02f7 100644 --- a/services/appsync/realtime.go +++ b/services/appsync/realtime.go @@ -3,7 +3,8 @@ package appsync import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +17,8 @@ import ( "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/validator/rules" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -59,9 +62,9 @@ type rtSub struct { } type rtMessage struct { - ID string `json:"id,omitempty"` - Type string `json:"type"` - Payload json.RawMessage `json:"payload,omitempty"` + ID string `json:"id,omitempty"` + Type string `json:"type"` + Payload jsontext.Value `json:"payload,omitempty"` } type rtStartPayload struct { @@ -95,7 +98,7 @@ func realtimeAuthFromQuery(r *http.Request) map[string]string { } } - _ = json.Unmarshal(dec, &out) + _ = json.Unmarshal(dec, &out, jsonx.V1()) return out } @@ -153,14 +156,14 @@ func (b *InMemoryBackend) startSubscription( ctx context.Context, apiID string, conn *rtConn, msg rtMessage, defaultAuth map[string]string, r *http.Request, ) { var payload rtStartPayload - if err := json.Unmarshal(msg.Payload, &payload); err != nil { + if err := json.Unmarshal(msg.Payload, &payload, jsonx.V1()); err != nil { _ = conn.send(rtError(msg.ID, rtErrBadRequest, "invalid start payload")) return } var req graphqlRequest - if err := json.Unmarshal([]byte(payload.Data), &req); err != nil { + if err := json.Unmarshal([]byte(payload.Data), &req, jsonx.V1()); err != nil { _ = conn.send(rtError(msg.ID, rtErrBadRequest, "payload.data must be a JSON string")) return diff --git a/services/appsync/realtime_test.go b/services/appsync/realtime_test.go index b2d86d7496..8e757c3282 100644 --- a/services/appsync/realtime_test.go +++ b/services/appsync/realtime_test.go @@ -2,7 +2,7 @@ package appsync_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -74,7 +75,7 @@ func newRealtimeFixture(t *testing.T) *rtFixture { func (f *rtFixture) dial(t *testing.T, apiKey string) *websocket.Conn { t.Helper() - hdr, err := json.Marshal(map[string]string{"x-api-key": apiKey}) + hdr, err := json.Marshal(map[string]string{"x-api-key": apiKey}, jsonx.V1()) require.NoError(t, err) d := websocket.Dialer{Subprotocols: []string{"graphql-ws"}} @@ -99,7 +100,7 @@ func readMsg(t *testing.T, conn *websocket.Conn) map[string]any { func startMsg(t *testing.T, id, query, apiKey string) map[string]any { t.Helper() - data, err := json.Marshal(map[string]any{"query": query}) + data, err := json.Marshal(map[string]any{"query": query}, jsonx.V1()) require.NoError(t, err) return map[string]any{ @@ -164,7 +165,7 @@ func TestRealtime_SubscriptionDelivery(t *testing.T) { assert.Equal(t, "data", msg["type"]) assert.Equal(t, "sub1", msg["id"]) - payload, mErr := json.Marshal(msg["payload"]) + payload, mErr := json.Marshal(msg["payload"], jsonx.V1()) require.NoError(t, mErr) assert.JSONEq(t, `{"data":`+tt.want+`}`, string(payload)) diff --git a/services/appsync/resolver_cache.go b/services/appsync/resolver_cache.go index ceb51e4fca..78664c5294 100644 --- a/services/appsync/resolver_cache.go +++ b/services/appsync/resolver_cache.go @@ -1,11 +1,13 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "strings" "time" "github.com/vektah/gqlparser/v2/ast" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -122,7 +124,7 @@ func (p cachePolicy) keyParts(ctxMap map[string]any) map[string]any { } func encodeKey(typeName, fieldName string, parts map[string]any) string { - raw, _ := json.Marshal(parts) + raw, _ := json.Marshal(parts, jsonx.V1()) return typeName + "." + fieldName + "|" + string(raw) } @@ -246,7 +248,7 @@ func (b *InMemoryBackend) evictMatching(apiID, prefix string, want map[string]an } var have map[string]any - if json.Unmarshal([]byte(rest), &have) != nil { + if json.Unmarshal([]byte(rest), &have, jsonx.V1()) != nil { continue } diff --git a/services/appsync/resolver_cache_test.go b/services/appsync/resolver_cache_test.go index 6714696bf4..95baac12c6 100644 --- a/services/appsync/resolver_cache_test.go +++ b/services/appsync/resolver_cache_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -92,10 +93,10 @@ func (f *cacheFixture) query(t *testing.T, q string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out gqlReply - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Empty(t, out.Errors, rec.Body.String()) - raw, err := json.Marshal(out.Data) + raw, err := json.Marshal(out.Data, jsonx.V1()) require.NoError(t, err) return string(raw) diff --git a/services/appsync/resolver_runtime_e2e_test.go b/services/appsync/resolver_runtime_e2e_test.go index ccb2e27ea5..6170f08461 100644 --- a/services/appsync/resolver_runtime_e2e_test.go +++ b/services/appsync/resolver_runtime_e2e_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "sync" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -216,7 +217,7 @@ func (e *runtimeEnv) exec(t *testing.T, query string) gqlReply { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out gqlReply - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out), rec.Body.String()) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()), rec.Body.String()) return out } diff --git a/services/appsync/resolvers_config_test.go b/services/appsync/resolvers_config_test.go index 27e52e78dc..8d53918c36 100644 --- a/services/appsync/resolvers_config_test.go +++ b/services/appsync/resolvers_config_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -503,7 +504,7 @@ func TestListResolvers_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var apiOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut, jsonx.V1())) apiID := apiOut["graphqlApi"].(map[string]any)["apiId"].(string) rec = doRequest(t, h, http.MethodPost, fmt.Sprintf("/v1/apis/%s/datasources", apiID), map[string]any{ @@ -557,7 +558,7 @@ func TestListResolvers_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Resolvers []map[string]any `json:"resolvers"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Resolvers, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/schema_types_test.go b/services/appsync/schema_types_test.go index f1593ecb8a..80b400b107 100644 --- a/services/appsync/schema_types_test.go +++ b/services/appsync/schema_types_test.go @@ -1,7 +1,7 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -266,7 +267,7 @@ func TestListTypes_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var apiOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &apiOut, jsonx.V1())) apiID := apiOut["graphqlApi"].(map[string]any)["apiId"].(string) for i := range 4 { @@ -307,7 +308,7 @@ func TestListTypes_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Types []map[string]any `json:"types"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Types, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) diff --git a/services/appsync/subscription_filter.go b/services/appsync/subscription_filter.go index 9f704f3b9b..7872181764 100644 --- a/services/appsync/subscription_filter.go +++ b/services/appsync/subscription_filter.go @@ -2,7 +2,7 @@ package appsync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "reflect" @@ -10,6 +10,8 @@ import ( "strings" "github.com/vektah/gqlparser/v2/ast" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -30,14 +32,14 @@ type subFilterSet [][]subFilter // plainJSON converts a VTL/JS value into plain JSON-shaped Go values. func plainJSON(v any) (any, error) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, err } var out any - err = json.Unmarshal(raw, &out) + err = json.Unmarshal(raw, &out, jsonx.V1()) return out, err } diff --git a/services/appsync/subscription_filter_test.go b/services/appsync/subscription_filter_test.go index 61ce89079d..5978c89311 100644 --- a/services/appsync/subscription_filter_test.go +++ b/services/appsync/subscription_filter_test.go @@ -1,12 +1,13 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -102,7 +103,7 @@ func TestSubscriptionFilter_AppliedToPublishedEvents(t *testing.T) { msg := next() require.Equal(t, "data", msg["type"], msg) - raw, err := json.Marshal(msg["payload"]) + raw, err := json.Marshal(msg["payload"], jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, `{"data":{"onPut":{"id":"2","title":"`+tt.want+`"}}}`, string(raw), "the filtered-out mutation must not be delivered before the matching one") @@ -207,7 +208,7 @@ export function response(ctx) { f.put(t, "1", "sell") f.put(t, "2", "buy") - raw, err := json.Marshal(next()["payload"]) + raw, err := json.Marshal(next()["payload"], jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, `{"data":{"onPut":{"id":"2","title":"buy"}}}`, string(raw)) } diff --git a/services/appsync/util_ddb.go b/services/appsync/util_ddb.go index 174712b7c1..4ea90d21cc 100644 --- a/services/appsync/util_ddb.go +++ b/services/appsync/util_ddb.go @@ -1,10 +1,12 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func ddbTyped(v any) any { @@ -118,7 +120,7 @@ func utilDynamo() map[string]utilFn { return nil, err } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) return string(b), err } diff --git a/services/appsync/util_esdsl.go b/services/appsync/util_esdsl.go index 8dbe37573c..78ce3da062 100644 --- a/services/appsync/util_esdsl.go +++ b/services/appsync/util_esdsl.go @@ -1,8 +1,10 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -24,7 +26,7 @@ func toQueryDSL(_ *utilEnv, a []any) (any, error) { return nil, err } - b, err := json.Marshal(q) + b, err := json.Marshal(q, jsonx.V1()) return string(b), err } diff --git a/services/appsync/util_extra.go b/services/appsync/util_extra.go index b7ac8f7349..236956226e 100644 --- a/services/appsync/util_extra.go +++ b/services/appsync/util_extra.go @@ -2,7 +2,8 @@ package appsync import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -12,6 +13,7 @@ import ( "golang.org/x/text/unicode/norm" "github.com/blackbirdworks/gopherstack/pkgs/ctxval" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -88,7 +90,7 @@ func s3Object(a []any) (any, error) { ref.Version = argStr(a, s3ObjectArgs) } - b, err := json.Marshal(map[string]any{"s3": ref}) + b, err := json.Marshal(map[string]any{"s3": ref}, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode s3 object: %w", err) } @@ -102,7 +104,7 @@ func s3ObjectJSON(_ *utilEnv, a []any) (any, error) { return nil, err } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) return string(b), err } @@ -112,7 +114,7 @@ func fromS3ObjectJSON(doc string) (any, error) { S3 *s3Ref `json:"s3"` } - if err := json.Unmarshal([]byte(doc), &wrapper); err != nil || wrapper.S3 == nil { + if err := json.Unmarshal([]byte(doc), &wrapper, jsonx.V1()); err != nil || wrapper.S3 == nil { return nil, fmt.Errorf("%w: not a DynamoDB S3 object", ErrValidation) } @@ -234,10 +236,9 @@ func xmlToJSONString(u *utilEnv, a []any) (any, error) { var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) + enc := jsontext.NewEncoder(&buf, jsonx.V1(), jsontext.EscapeForHTML(false)) - if err = enc.Encode(m); err != nil { + if err = json.MarshalEncode(enc, m); err != nil { return nil, fmt.Errorf("encode xml: %w", err) } diff --git a/services/appsync/util_funcs.go b/services/appsync/util_funcs.go index 4d69793682..30e0a48141 100644 --- a/services/appsync/util_funcs.go +++ b/services/appsync/util_funcs.go @@ -3,7 +3,7 @@ package appsync import ( "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "math/big" @@ -14,6 +14,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resolverError is an error raised from a mapping template or handler @@ -168,13 +170,13 @@ func utilCore() map[string]utilFn { return newKSUID(), nil }, "toJson": func(_ *utilEnv, a []any) (any, error) { - b, err := json.Marshal(argAt(a, 0)) + b, err := json.Marshal(argAt(a, 0), jsonx.V1()) return string(b), err }, "parseJson": func(_ *utilEnv, a []any) (any, error) { var v any - if err := json.Unmarshal([]byte(argStr(a, 0)), &v); err != nil { + if err := json.Unmarshal([]byte(argStr(a, 0)), &v, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON", ErrValidation) } @@ -405,7 +407,7 @@ func fmtNum(v any) string { return strconv.Itoa(t) case float64: return strconv.FormatFloat(t, 'f', -1, 64) - case json.Number: + case jsonx.Number: return t.String() } diff --git a/services/appsync/util_rds.go b/services/appsync/util_rds.go index cbbf19a5a3..b17c94b005 100644 --- a/services/appsync/util_rds.go +++ b/services/appsync/util_rds.go @@ -1,8 +1,10 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const rdsLongValue = "longValue" @@ -21,7 +23,7 @@ func utilRDS() map[string]utilFn { return nil, err } - raw, err := json.Marshal(objs) + raw, err := json.Marshal(objs, jsonx.V1()) return string(raw), err }, @@ -52,7 +54,7 @@ func rdsResultRoot(v any) (map[string]any, error) { return t, nil case string: var m map[string]any - if err := json.Unmarshal([]byte(t), &m); err != nil { + if err := json.Unmarshal([]byte(t), &m, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid RDS result JSON", ErrValidation) } diff --git a/services/appsync/vtl.go b/services/appsync/vtl.go index b7385fe9b1..38a4d9efbc 100644 --- a/services/appsync/vtl.go +++ b/services/appsync/vtl.go @@ -1,15 +1,17 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // renderVTL renders tmpl with the given arguments and result as the context. func renderVTL(tmpl string, args map[string]any, result any) (string, error) { if tmpl == "" { if result != nil { - b, err := json.Marshal(result) + b, err := json.Marshal(result, jsonx.V1()) return string(b), err } @@ -39,7 +41,7 @@ func returnedString(v any) string { return s } - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return string(b) } @@ -59,7 +61,7 @@ func vtlOutputValue(out vtlOutcome) any { } func toDynamoDBJSON(val any) string { - b, _ := json.Marshal(ddbTyped(val)) + b, _ := json.Marshal(ddbTyped(val), jsonx.V1()) return string(b) } diff --git a/services/appsync/vtl_bounds_test.go b/services/appsync/vtl_bounds_test.go new file mode 100644 index 0000000000..acddcc4f71 --- /dev/null +++ b/services/appsync/vtl_bounds_test.go @@ -0,0 +1,37 @@ +package appsync_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/services/appsync" +) + +func TestRenderVTL_ListIndexBounds(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + tmpl string + want string + }{ + {name: "in_range", tmpl: `#set($l=["a","b"])$l.remove(1)|$l.size()`, want: "b|1"}, + { + name: "beyond_int32", + tmpl: `#set($l=["a","b"])$l.remove(5000000000)|$l.size()`, + want: "$l.remove(5000000000)|2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := appsync.RenderVTL(tt.tmpl, nil, nil) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/services/appsync/vtl_eval.go b/services/appsync/vtl_eval.go index c44bc5044b..18ddf38015 100644 --- a/services/appsync/vtl_eval.go +++ b/services/appsync/vtl_eval.go @@ -1,12 +1,14 @@ package appsync import ( - "encoding/json" + "encoding/json/v2" "fmt" "math" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -23,7 +25,7 @@ func (l *vlist) MarshalJSON() ([]byte, error) { return []byte("[]"), nil } - return json.Marshal(l.items) + return json.Marshal(l.items, jsonx.V1()) } // vns is a utility namespace reference ($util, $util.time, ...). @@ -763,7 +765,7 @@ func toNum(v any) (float64, bool, bool) { return float64(t), true, true case float64: return t, false, true - case json.Number: + case jsonx.Number: if i, err := t.Int64(); err == nil { return float64(i), true, true } @@ -788,6 +790,14 @@ func toInt(v any) (int, bool) { return 0, false } + return smallInt(f) +} + +func smallInt(f float64) (int, bool) { + if f < math.MinInt32 || f > math.MaxInt32 { + return 0, false + } + return int(f), true } @@ -805,7 +815,7 @@ func vrender(v any) string { return strconv.Itoa(t) case float64: return renderFloat(t) - case json.Number: + case jsonx.Number: return t.String() case *vlist: parts := make([]string, len(t.items)) @@ -867,7 +877,7 @@ func vnorm(v any) any { } return t - case json.Number: + case jsonx.Number: if i, err := t.Int64(); err == nil { return i } diff --git a/services/appsync/vtl_methods.go b/services/appsync/vtl_methods.go index 6f099213f2..ff4af63f11 100644 --- a/services/appsync/vtl_methods.go +++ b/services/appsync/vtl_methods.go @@ -263,8 +263,8 @@ func listIndexOf(l *vlist, v any) int { func listRemove(l *vlist, v any) any { if f, isInt, ok := toNum(v); ok && isInt { - i := int(f) - if i < 0 || i >= len(l.items) { + i, small := smallInt(f) + if !small || i < 0 || i >= len(l.items) { return unknownMethod() } diff --git a/services/appsync/wire_field_fixes_test.go b/services/appsync/wire_field_fixes_test.go index f77ce59de4..1010828c22 100644 --- a/services/appsync/wire_field_fixes_test.go +++ b/services/appsync/wire_field_fixes_test.go @@ -1,7 +1,8 @@ package appsync_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -126,7 +128,7 @@ func TestGraphqlApi_EnvironmentVariablesNotLeaked(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) graphqlAPI, ok := resp["graphqlApi"].(map[string]any) require.True(t, ok) @@ -138,7 +140,7 @@ func TestGraphqlApi_EnvironmentVariablesNotLeaked(t *testing.T) { require.Equal(t, 200, envRec.Code) var envResp map[string]any - require.NoError(t, json.NewDecoder(envRec.Body).Decode(&envResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(envRec.Body), &envResp, jsonx.V1())) envVars, ok := envResp["environmentVariables"].(map[string]any) require.True(t, ok) assert.Equal(t, "super-secret-value", envVars["DB_PASSWORD"]) diff --git a/services/appsync/wire_shape.go b/services/appsync/wire_shape.go index 331babb185..1074ad6c59 100644 --- a/services/appsync/wire_shape.go +++ b/services/appsync/wire_shape.go @@ -1,17 +1,21 @@ package appsync -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // wireShape re-encodes v without the internal-only keys drop from its top-level // object (or from each element when v is a slice). func wireShape(v any, drop ...string) any { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } var decoded any - if json.Unmarshal(raw, &decoded) != nil { + if json.Unmarshal(raw, &decoded, jsonx.V1()) != nil { return v } diff --git a/services/appsync/wire_shape_test.go b/services/appsync/wire_shape_test.go index 6117d2f026..906404383d 100644 --- a/services/appsync/wire_shape_test.go +++ b/services/appsync/wire_shape_test.go @@ -1,13 +1,14 @@ package appsync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/appsync" ) @@ -62,7 +63,7 @@ func TestWire_NoInternalAPIID(t *testing.T) { require.Equal(t, http.StatusOK, list.Code) var out map[string][]map[string]any - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out[tt.listKey]) for _, item := range out[tt.listKey] { diff --git a/services/arczonalshift/handler_ops.go b/services/arczonalshift/handler_ops.go index 4cafe0a6e1..23283a1804 100644 --- a/services/arczonalshift/handler_ops.go +++ b/services/arczonalshift/handler_ops.go @@ -2,9 +2,11 @@ package arczonalshift import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -121,9 +123,9 @@ func (h *Handler) updatePractice(_ context.Context, req *restroute.Request) (any return nil, 0, err } - var present map[string]json.RawMessage + var present map[string]jsontext.Value if len(req.Body) > 0 { - _ = json.Unmarshal(req.Body, &present) + _ = json.Unmarshal(req.Body, &present, jsonx.V1()) } set := map[string]bool{} diff --git a/services/arczonalshift/managed_more.go b/services/arczonalshift/managed_more.go index 189b10f9d9..c9c34717b7 100644 --- a/services/arczonalshift/managed_more.go +++ b/services/arczonalshift/managed_more.go @@ -1,12 +1,13 @@ package arczonalshift import ( - "encoding/json" + "encoding/json/v2" "slices" "strings" awsarn "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" "github.com/blackbirdworks/gopherstack/pkgs/service" asgbackend "github.com/blackbirdworks/gopherstack/services/autoscaling" @@ -162,7 +163,7 @@ func clusterZonalShiftEnabled(c *eksbackend.Cluster) bool { Enabled bool `json:"enabled"` } - return json.Unmarshal(raw, &cfg) == nil && cfg.Enabled + return json.Unmarshal(raw, &cfg, jsonx.V1()) == nil && cfg.Enabled } func (b *InMemoryBackend) managedCluster(arn string, p awsarn.ARN) (managedResource, error) { diff --git a/services/arczonalshift/managed_resources_test.go b/services/arczonalshift/managed_resources_test.go index 4c4027f9c3..0c13b9a4af 100644 --- a/services/arczonalshift/managed_resources_test.go +++ b/services/arczonalshift/managed_resources_test.go @@ -2,7 +2,7 @@ package arczonalshift_test import ( "context" - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -67,7 +67,7 @@ func (w *managedWorld) group(t *testing.T, name string, enabled bool) string { return g.AutoScalingGroupARN } -func (w *managedWorld) cluster(t *testing.T, name string, zonalShift json.RawMessage) string { +func (w *managedWorld) cluster(t *testing.T, name string, zonalShift jsontext.Value) string { t.Helper() vpc, err := w.ec2.CreateVpc("10.0.0.0/16", "") @@ -84,7 +84,7 @@ func (w *managedWorld) cluster(t *testing.T, name string, zonalShift json.RawMes opts := eksbk.ClusterOptionalConfig{} if zonalShift != nil { - opts.ConfigBlocks = map[string]json.RawMessage{"zonalShiftConfig": zonalShift} + opts.ConfigBlocks = map[string]jsontext.Value{"zonalShiftConfig": zonalShift} } c, err := w.eks.CreateCluster(name, "1.31", "arn:aws:iam::"+testAccount+":role/eks", @@ -127,7 +127,7 @@ func TestManagedResourcesAutoScalingAndEKS(t *testing.T) { create: func(t *testing.T, w *managedWorld) string { t.Helper() - return w.cluster(t, "k8s", json.RawMessage(`{"enabled":true}`)) + return w.cluster(t, "k8s", jsontext.Value(`{"enabled":true}`)) }, }, { @@ -135,7 +135,7 @@ func TestManagedResourcesAutoScalingAndEKS(t *testing.T) { create: func(t *testing.T, w *managedWorld) string { t.Helper() - return w.cluster(t, "k8s", json.RawMessage(`{"enabled":false}`)) + return w.cluster(t, "k8s", jsontext.Value(`{"enabled":false}`)) }, }, { diff --git a/services/arczonalshift/misc_test.go b/services/arczonalshift/misc_test.go index 067f3017e1..dccd0539d5 100644 --- a/services/arczonalshift/misc_test.go +++ b/services/arczonalshift/misc_test.go @@ -1,7 +1,7 @@ package arczonalshift_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdkcheck" zs "github.com/blackbirdworks/gopherstack/services/arczonalshift" ) @@ -158,7 +159,7 @@ func TestErrorBodyShape(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Header().Get("X-Amzn-Errortype")) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body["message"]) assert.Equal(t, body["message"], body["Message"]) assert.False(t, strings.HasPrefix(body["message"].(string), tt.wantCode)) diff --git a/services/arczonalshift/persistence.go b/services/arczonalshift/persistence.go index 4e237a5ddc..6251681699 100644 --- a/services/arczonalshift/persistence.go +++ b/services/arczonalshift/persistence.go @@ -2,7 +2,7 @@ package arczonalshift import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -13,8 +13,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. It implements persistence.Persistable. diff --git a/services/athena/audit_athena_test.go b/services/athena/audit_athena_test.go index 4d1b1e16bf..c9fbfb1d91 100644 --- a/services/athena/audit_athena_test.go +++ b/services/athena/audit_athena_test.go @@ -1,13 +1,15 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAuditAthena_ResultReuseConfiguration_StoredAndReturned verifies that @@ -254,7 +256,7 @@ func TestAuditAthena_EnforceWorkGroupConfiguration_OverridesOutputLocation(t *te wgCfgJSON, _ := json.Marshal(map[string]any{ "EnforceWorkGroupConfiguration": tc.enforceWGConfig, "ResultConfiguration": map[string]any{"OutputLocation": tc.wgOutputLocation}, - }) + }, jsonx.V1()) rec := a1Do(t, h, "CreateWorkGroup", fmt.Sprintf(`{"Name":"enforce-wg","Configuration":%s}`, string(wgCfgJSON))) require.Equal(t, http.StatusOK, rec.Code) @@ -266,7 +268,7 @@ func TestAuditAthena_EnforceWorkGroupConfiguration_OverridesOutputLocation(t *te "ResultConfiguration": map[string]any{ "OutputLocation": tc.perQueryLocation, }, - }) + }, jsonx.V1()) rec2 := a1Do(t, h, "StartQueryExecution", string(startBody)) require.Equal(t, http.StatusOK, rec2.Code) @@ -400,7 +402,7 @@ func TestAuditAthena_BatchGetQueryExecution_Shape(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - idsJSON, _ := json.Marshal(tc.ids) + idsJSON, _ := json.Marshal(tc.ids, jsonx.V1()) batchRec := a1Do(t, h, "BatchGetQueryExecution", fmt.Sprintf(`{"QueryExecutionIds":%s}`, string(idsJSON))) require.Equal(t, http.StatusOK, batchRec.Code) @@ -471,7 +473,7 @@ func TestAuditAthena_WorkGroup_EnforceEncryption(t *testing.T) { "EncryptionOption": "SSE_S3", }, }, - }) + }, jsonx.V1()) rec := a1Do(t, h, "CreateWorkGroup", fmt.Sprintf(`{"Name":"enc-wg","Configuration":%s}`, string(wgCfg))) diff --git a/services/athena/client_token_test.go b/services/athena/client_token_test.go index 711a9dbc09..09ed31c8fb 100644 --- a/services/athena/client_token_test.go +++ b/services/athena/client_token_test.go @@ -1,12 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ClientRequestTokenReplay(t *testing.T) { @@ -153,7 +155,7 @@ func TestHandler_StartSessionTags(t *testing.T) { require.Equal(t, http.StatusOK, tagged.Code, tagged.Body.String()) var out tagList - require.NoError(t, json.Unmarshal(tagged.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(tagged.Body.Bytes(), &out, jsonx.V1())) got := map[string]string{} for _, tg := range out.Tags { @@ -182,7 +184,7 @@ func TestHandler_StartSessionCopyWorkGroupTags(t *testing.T) { require.Equal(t, http.StatusOK, tagged.Code, tagged.Body.String()) var out tagList - require.NoError(t, json.Unmarshal(tagged.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(tagged.Body.Bytes(), &out, jsonx.V1())) got := map[string]string{} for _, tg := range out.Tags { diff --git a/services/athena/ddl_test.go b/services/athena/ddl_test.go index 00f0bb7b0a..ab89b2c897 100644 --- a/services/athena/ddl_test.go +++ b/services/athena/ddl_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -278,7 +279,7 @@ func startQueryBody(query string) string { "Catalog": testCatalog, "Database": testDatabase, }, - }) + }, jsonx.V1()) return string(body) } @@ -306,7 +307,7 @@ func TestExecuteStatement_InsertThenSelect(t *testing.T) { insExec := doRequest(t, h, "GetQueryExecution", `{"QueryExecutionId":"`+insID+`"}`) var insResp map[string]any - require.NoError(t, json.Unmarshal(insExec.Body.Bytes(), &insResp)) + require.NoError(t, json.Unmarshal(insExec.Body.Bytes(), &insResp, jsonx.V1())) status := insResp["QueryExecution"].(map[string]any)["Status"].(map[string]any) assert.Equal(t, "SUCCEEDED", status["State"]) @@ -316,7 +317,7 @@ func TestExecuteStatement_InsertThenSelect(t *testing.T) { require.Equal(t, http.StatusOK, results.Code) var resResp map[string]any - require.NoError(t, json.Unmarshal(results.Body.Bytes(), &resResp)) + require.NoError(t, json.Unmarshal(results.Body.Bytes(), &resResp, jsonx.V1())) rs := resResp["ResultSet"].(map[string]any) rows := rs["Rows"].([]any) diff --git a/services/athena/engine_complex.go b/services/athena/engine_complex.go index e32bb1b14a..4f1663d821 100644 --- a/services/athena/engine_complex.go +++ b/services/athena/engine_complex.go @@ -3,7 +3,8 @@ package athena import ( "bytes" "database/sql/driver" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "sort" @@ -11,6 +12,8 @@ import ( "strings" sqlitedriver "modernc.org/sqlite" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // containerMark prefixes the JSON text of an ARRAY/MAP value so results render @@ -54,11 +57,10 @@ func complexTypeOf(glueType string) (string, bool) { } func decodeComplex(s string) (any, bool) { - dec := json.NewDecoder(strings.NewReader(strings.TrimPrefix(s, containerMark))) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(strings.TrimPrefix(s, containerMark))) var out any - if err := dec.Decode(&out); err != nil { + if err := json.UnmarshalDecode(dec, &out, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, false } @@ -79,7 +81,7 @@ func trinoString(v any) string { return t case bool: return strconv.FormatBool(t) - case json.Number: + case jsonx.Number: return t.String() case []any: parts := make([]string, len(t)) diff --git a/services/athena/engine_funcs_agg.go b/services/athena/engine_funcs_agg.go index 36849420a5..54c58c1e0b 100644 --- a/services/athena/engine_funcs_agg.go +++ b/services/athena/engine_funcs_agg.go @@ -2,13 +2,15 @@ package athena import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "errors" "math" "sort" "strings" sqlitedriver "modernc.org/sqlite" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aggState is one running aggregate; step ignores NULL inputs unless nullsToo. @@ -152,7 +154,7 @@ func jsonElem(v driver.Value) any { if s, ok := v.(string); ok { var parsed any if t := strings.TrimPrefix(s, containerMark); len(t) > 0 && (t[0] == '[' || t[0] == '{') && - json.Unmarshal([]byte(t), &parsed) == nil { + json.Unmarshal([]byte(t), &parsed, jsonx.V1()) == nil { return parsed } } diff --git a/services/athena/engine_funcs_json.go b/services/athena/engine_funcs_json.go index f30a414645..1d81363033 100644 --- a/services/athena/engine_funcs_json.go +++ b/services/athena/engine_funcs_json.go @@ -2,13 +2,15 @@ package athena import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "fmt" "math" "slices" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // arrays and maps live in SQLite as JSON text. @@ -20,7 +22,7 @@ func parseJSONArg(v driver.Value) (any, bool) { } var out any - if err := json.Unmarshal([]byte(strings.TrimPrefix(s, containerMark)), &out); err != nil { + if err := json.Unmarshal([]byte(strings.TrimPrefix(s, containerMark)), &out, jsonx.V1()); err != nil { return nil, false } @@ -29,7 +31,7 @@ func parseJSONArg(v driver.Value) (any, bool) { // jsonText encodes an ARRAY/MAP value; containerMark tags it so results render Trino-style. func jsonText(v any) driver.Value { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return sqlNull() } @@ -39,7 +41,7 @@ func jsonText(v any) driver.Value { // jsonPlain encodes a JSON-typed value, which renders as raw JSON text. func jsonPlain(v any) driver.Value { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return sqlNull() } @@ -385,7 +387,7 @@ func udfArrayDistinct(_ *fnCtx, args []driver.Value) (driver.Value, error) { out := make([]any, 0, len(a)) for _, e := range a { - k, _ := json.Marshal(e) + k, _ := json.Marshal(e, jsonx.V1()) if !seen[string(k)] { seen[string(k)] = true out = append(out, e) diff --git a/services/athena/engine_iceberg_meta.go b/services/athena/engine_iceberg_meta.go index 8b03e9da91..1de0a75ce3 100644 --- a/services/athena/engine_iceberg_meta.go +++ b/services/athena/engine_iceberg_meta.go @@ -3,12 +3,13 @@ package athena import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "sort" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/iceberg" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // icebergMetaSuffix splits "tbl$snapshots" into the table and metadata table names. @@ -32,7 +33,7 @@ const colSnapshotID = "snapshot_id" func tsText(ms int64) string { return time.UnixMilli(ms).UTC().Format(tsMillisLayout) } func mapJSON(m map[string]string) string { - b, _ := json.Marshal(m) + b, _ := json.Marshal(m, jsonx.V1()) return string(b) } @@ -221,7 +222,7 @@ func (l *tableLoader) filesTable(ctx context.Context, snap iceberg.Snapshot, has for _, f := range append(files.data, files.deletes...) { df := f.entry.DataFile - part, _ := json.Marshal(df.Partition) + part, _ := json.Marshal(df.Partition, jsonx.V1()) rows = append(rows, []any{ int64(df.Content), df.Path, df.Format, int64(f.spec), string(part), df.RecordCount, df.FileSize, }) diff --git a/services/athena/engine_load.go b/services/athena/engine_load.go index 6cf53bc662..799b94a4d6 100644 --- a/services/athena/engine_load.go +++ b/services/athena/engine_load.go @@ -7,7 +7,8 @@ import ( "context" "database/sql" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -17,6 +18,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type dataFormat int @@ -454,12 +457,12 @@ func jsonRaw(v any) string { return "" case string: return t - case json.Number: + case jsonx.Number: return t.String() case bool: return strconv.FormatBool(t) default: - b, _ := json.Marshal(t) + b, _ := json.Marshal(t, jsonx.V1()) return string(b) } @@ -475,12 +478,11 @@ func (l *tableLoader) decodeJSON(ctx context.Context, rd io.Reader, f tableForma continue } - dec := json.NewDecoder(bytes.NewReader(line)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(line)) var obj map[string]any - if err := dec.Decode(&obj); err != nil { + if err := json.UnmarshalDecode(dec, &obj, jsonx.V1(), jsonx.UseNumber()); err != nil { if f.ignoreBad { continue } diff --git a/services/athena/engine_parquet.go b/services/athena/engine_parquet.go index 6b0c57108a..6c6ff513c7 100644 --- a/services/athena/engine_parquet.go +++ b/services/athena/engine_parquet.go @@ -3,13 +3,14 @@ package athena import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -124,7 +125,7 @@ func parquetCell(v any) string { case bool: return strconv.FormatBool(t) default: - b, err := json.Marshal(t) + b, err := json.Marshal(t, jsonx.V1()) if err != nil { return fmt.Sprint(t) } diff --git a/services/athena/engine_write_format.go b/services/athena/engine_write_format.go index 61de56b966..78b83648ec 100644 --- a/services/athena/engine_write_format.go +++ b/services/athena/engine_write_format.go @@ -3,10 +3,12 @@ package athena import ( "bytes" "encoding/csv" - "encoding/json" + "encoding/json/v2" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func encodeTextRows(res *sqlResult, rows []int, cols []outCol, delim string) []byte { @@ -63,7 +65,7 @@ func encodeJSONLines(res *sqlResult, rows []int, cols []outCol) []byte { buf.WriteByte(',') } - key, _ := json.Marshal(c.name) + key, _ := json.Marshal(c.name, jsonx.V1()) buf.Write(key) buf.WriteByte(':') @@ -99,7 +101,7 @@ func jsonLiteral(v string, null bool, typ string) string { case kindString, kindText: } - b, _ := json.Marshal(strings.ToValidUTF8(v, "�")) + b, _ := json.Marshal(strings.ToValidUTF8(v, "�"), jsonx.V1()) return string(b) } diff --git a/services/athena/error_codes_test.go b/services/athena/error_codes_test.go index 2df0489093..6106a6af88 100644 --- a/services/athena/error_codes_test.go +++ b/services/athena/error_codes_test.go @@ -1,12 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_DistinctErrorCodes verifies that Athena operations surface the @@ -110,7 +112,7 @@ func TestHandler_DistinctErrorCodes(t *testing.T) { "Athena client errors are HTTP 400") var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"], "error __type must be AWS-accurate for %s", tt.action) diff --git a/services/athena/handler.go b/services/athena/handler.go index 7d1742534f..01967675aa 100644 --- a/services/athena/handler.go +++ b/services/athena/handler.go @@ -2,7 +2,7 @@ package athena import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -266,7 +267,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var env resourceNameEnvelope - if uerr := json.Unmarshal(body, &env); uerr != nil { + if uerr := json.Unmarshal(body, &env, jsonx.V1()); uerr != nil { return "" } @@ -336,7 +337,7 @@ func (h *Handler) doDispatch(_ context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a standardized error response back to the client. @@ -387,7 +388,7 @@ func (h *Handler) handleError( Message: stripExceptionPrefix(reqErr.Error()), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/athena/handler_calculations.go b/services/athena/handler_calculations.go index 369c5ec406..ec285e8322 100644 --- a/services/athena/handler_calculations.go +++ b/services/athena/handler_calculations.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type startCalculationInput struct { SessionID string `json:"SessionId"` @@ -24,7 +28,7 @@ func (h *Handler) calcCoreOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "StartCalculationExecution": func(b []byte) (any, error) { var input startCalculationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -52,7 +56,7 @@ func (h *Handler) calcCoreOps() map[string]athenaActionFn { }, "GetCalculationExecution": func(b []byte) (any, error) { var input calculationIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -73,7 +77,7 @@ func (h *Handler) calcCoreOps() map[string]athenaActionFn { }, "GetCalculationExecutionStatus": func(b []byte) (any, error) { var input calculationIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -86,7 +90,7 @@ func (h *Handler) calcCoreOps() map[string]athenaActionFn { }, "GetCalculationExecutionCode": func(b []byte) (any, error) { var input calculationIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -104,7 +108,7 @@ func (h *Handler) calcControlOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "StopCalculationExecution": func(b []byte) (any, error) { var input calculationIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -117,7 +121,7 @@ func (h *Handler) calcControlOps() map[string]athenaActionFn { }, "ListCalculationExecutions": func(b []byte) (any, error) { var input listCalculationsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_capacity_reservations.go b/services/athena/handler_capacity_reservations.go index cc3b71622c..7eb649ce0f 100644 --- a/services/athena/handler_capacity_reservations.go +++ b/services/athena/handler_capacity_reservations.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type cancelCapacityReservationInput struct { Name string `json:"Name"` @@ -38,7 +42,7 @@ func (h *Handler) capacityReservationOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreateCapacityReservation": func(b []byte) (any, error) { var input createCapacityReservationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -48,7 +52,7 @@ func (h *Handler) capacityReservationOps() map[string]athenaActionFn { }, "CancelCapacityReservation": func(b []byte) (any, error) { var input cancelCapacityReservationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -56,7 +60,7 @@ func (h *Handler) capacityReservationOps() map[string]athenaActionFn { }, "DeleteCapacityReservation": func(b []byte) (any, error) { var input deleteCapacityReservationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -69,7 +73,7 @@ func (h *Handler) capacityExtraOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "GetCapacityReservation": func(b []byte) (any, error) { var input capacityReservationNameInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -82,7 +86,7 @@ func (h *Handler) capacityExtraOps() map[string]athenaActionFn { }, "ListCapacityReservations": func(b []byte) (any, error) { var input listPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -106,7 +110,7 @@ func (h *Handler) capacityExtraOps() map[string]athenaActionFn { }, "UpdateCapacityReservation": func(b []byte) (any, error) { var input updateCapacityReservationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -114,7 +118,7 @@ func (h *Handler) capacityExtraOps() map[string]athenaActionFn { }, "PutCapacityAssignmentConfiguration": func(b []byte) (any, error) { var input putCapacityAssignmentInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -124,7 +128,7 @@ func (h *Handler) capacityExtraOps() map[string]athenaActionFn { }, "GetCapacityAssignmentConfiguration": func(b []byte) (any, error) { var input getCapacityAssignmentInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_capacity_reservations_test.go b/services/athena/handler_capacity_reservations_test.go index e7cf59bddf..72d2851bb5 100644 --- a/services/athena/handler_capacity_reservations_test.go +++ b/services/athena/handler_capacity_reservations_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -51,7 +52,7 @@ func TestHandler_CreateCapacityReservation(t *testing.T) { if tt.wantErr { var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"]) } }) @@ -239,7 +240,7 @@ func TestHandler_CapacityReservation_LastAllocationStruct(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cr := resp["CapacityReservation"].(map[string]any) assert.Equal(t, "ACTIVE", cr["Status"]) @@ -263,7 +264,7 @@ func TestHandler_CapacityReservation_UpdateLastAllocation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cr := resp["CapacityReservation"].(map[string]any) assert.InDelta(t, float64(30), cr["TargetDpus"], 0.001) @@ -330,7 +331,7 @@ func TestHandler_GetCapacityReservation_NoInventedTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasTags := resp["CapacityReservation"]["Tags"] assert.False(t, hasTags, "CapacityReservation response must not carry an invented Tags field") @@ -339,7 +340,7 @@ func TestHandler_GetCapacityReservation_NoInventedTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string][]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) require.Len(t, tagsResp["Tags"], 1, "the tag set at creation must still be visible via ListTagsForResource") assert.Equal(t, "owner", tagsResp["Tags"][0]["Key"]) } diff --git a/services/athena/handler_data_catalogs.go b/services/athena/handler_data_catalogs.go index cbae21ed03..7fd7e37500 100644 --- a/services/athena/handler_data_catalogs.go +++ b/services/athena/handler_data_catalogs.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // dataCatalogRespKey is the JSON response key CreateDataCatalog, // GetDataCatalog, and DeleteDataCatalog all wrap their DataCatalog payload @@ -40,7 +44,7 @@ func (h *Handler) dataCatalogOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreateDataCatalog": func(b []byte) (any, error) { var input createDataCatalogInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -59,7 +63,7 @@ func (h *Handler) dataCatalogOps() map[string]athenaActionFn { }, "GetDataCatalog": func(b []byte) (any, error) { var input getDataCatalogInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -72,7 +76,7 @@ func (h *Handler) dataCatalogOps() map[string]athenaActionFn { }, "ListDataCatalogs": func(b []byte) (any, error) { var input listDataCatalogsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -90,7 +94,7 @@ func (h *Handler) dataCatalogOps() map[string]athenaActionFn { }, "UpdateDataCatalog": func(b []byte) (any, error) { var input updateDataCatalogInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -100,7 +104,7 @@ func (h *Handler) dataCatalogOps() map[string]athenaActionFn { }, "DeleteDataCatalog": func(b []byte) (any, error) { var input deleteDataCatalogInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_data_catalogs_test.go b/services/athena/handler_data_catalogs_test.go index e92165fa74..1aa73a4f20 100644 --- a/services/athena/handler_data_catalogs_test.go +++ b/services/athena/handler_data_catalogs_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -86,7 +87,7 @@ func TestHandler_GetDataCatalog(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dc, _ := resp["DataCatalog"].(map[string]any) require.NotNil(t, dc) assert.Equal(t, tt.wantType, dc["Type"]) @@ -110,7 +111,7 @@ func TestHandler_CreateDataCatalog_ReturnsDataCatalog(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dc, ok := resp["DataCatalog"].(map[string]any) require.True(t, ok, "CreateDataCatalogOutput must carry a DataCatalog object") assert.Equal(t, "created-cat", dc["Name"]) @@ -145,7 +146,7 @@ func TestHandler_ListDataCatalogs(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, _ := resp["DataCatalogsSummary"].([]any) assert.GreaterOrEqual(t, len(list), 1) }) @@ -239,7 +240,7 @@ func TestHandler_DeleteDataCatalog_ReturnsDataCatalog(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dc, ok := resp["DataCatalog"].(map[string]any) require.True(t, ok, "DeleteDataCatalogOutput must carry the deleted DataCatalog object") assert.Equal(t, "del-cat2", dc["Name"]) @@ -300,7 +301,7 @@ func TestHandler_DeleteDataCatalog_FederatedReportsDeleteComplete(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dc, ok := resp["DataCatalog"].(map[string]any) require.True(t, ok) assert.Equal(t, "DELETE_COMPLETE", dc["Status"]) @@ -386,7 +387,7 @@ func TestHandler_DataCatalog_FederatedStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dc := resp["DataCatalog"].(map[string]any) assert.Equal(t, tt.wantStatus, dc["Status"]) @@ -408,7 +409,7 @@ func TestHandler_DataCatalog_ListIncludesStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["DataCatalogsSummary"].([]any) require.True(t, ok) @@ -505,7 +506,7 @@ func TestListDataCatalogs_NextTokenOmittedWhenEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasNextToken := resp["NextToken"] assert.False(t, hasNextToken, @@ -627,7 +628,7 @@ func TestAwsDataCatalog_BuiltIn(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) diff --git a/services/athena/handler_databases.go b/services/athena/handler_databases.go index af22e51b24..b1d85b9519 100644 --- a/services/athena/handler_databases.go +++ b/services/athena/handler_databases.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type getDatabaseInput struct { CatalogName string `json:"CatalogName"` @@ -31,7 +35,7 @@ func (h *Handler) databaseOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "GetDatabase": func(b []byte) (any, error) { var input getDatabaseInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -44,7 +48,7 @@ func (h *Handler) databaseOps() map[string]athenaActionFn { }, "ListDatabases": func(b []byte) (any, error) { var input listDatabasesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -68,7 +72,7 @@ func (h *Handler) databaseOps() map[string]athenaActionFn { }, "GetTableMetadata": func(b []byte) (any, error) { var input getTableMetadataInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -81,7 +85,7 @@ func (h *Handler) databaseOps() map[string]athenaActionFn { }, "ListTableMetadata": func(b []byte) (any, error) { var input listTableMetadataInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_named_queries.go b/services/athena/handler_named_queries.go index c458c88472..97d0c416e4 100644 --- a/services/athena/handler_named_queries.go +++ b/services/athena/handler_named_queries.go @@ -1,8 +1,10 @@ package athena import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createNamedQueryInput struct { @@ -44,7 +46,7 @@ func (h *Handler) namedQueryOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreateNamedQuery": func(b []byte) (any, error) { var input createNamedQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -65,7 +67,7 @@ func (h *Handler) namedQueryOps() map[string]athenaActionFn { }, "GetNamedQuery": func(b []byte) (any, error) { var input getNamedQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -78,7 +80,7 @@ func (h *Handler) namedQueryOps() map[string]athenaActionFn { }, "ListNamedQueries": func(b []byte) (any, error) { var input listNamedQueriesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -100,7 +102,7 @@ func (h *Handler) namedQueryOps() map[string]athenaActionFn { }, "BatchGetNamedQuery": func(b []byte) (any, error) { var input batchGetNamedQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -121,7 +123,7 @@ func (h *Handler) namedQueryOps() map[string]athenaActionFn { }, "DeleteNamedQuery": func(b []byte) (any, error) { var input deleteNamedQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -136,7 +138,7 @@ func (h *Handler) namedQueryUpdateOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "UpdateNamedQuery": func(b []byte) (any, error) { var input updateNamedQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_named_queries_test.go b/services/athena/handler_named_queries_test.go index dc103000fc..c712851e37 100644 --- a/services/athena/handler_named_queries_test.go +++ b/services/athena/handler_named_queries_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -39,7 +40,7 @@ func TestHandler_CreateNamedQuery(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["NamedQueryId"]) } }) @@ -80,7 +81,7 @@ func TestHandler_GetNamedQuery(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) queryID = created["NamedQueryId"] } @@ -89,7 +90,7 @@ func TestHandler_GetNamedQuery(t *testing.T) { if tt.wantQuery { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nq, _ := resp["NamedQuery"].(map[string]any) require.NotNil(t, nq) assert.Equal(t, "q", nq["Name"]) @@ -127,7 +128,7 @@ func TestHandler_ListNamedQueries(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string][]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["NamedQueryIds"], tt.wantCount) }) } @@ -157,7 +158,7 @@ func TestHandler_BatchGetNamedQuery(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) id := created["NamedQueryId"] rec := doRequest(t, h, "BatchGetNamedQuery", @@ -165,7 +166,7 @@ func TestHandler_BatchGetNamedQuery(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found, _ := resp["NamedQueries"].([]any) assert.Len(t, found, 1) unprocessed, _ := resp["UnprocessedNamedQueryIds"].([]any) @@ -203,7 +204,7 @@ func TestHandler_DeleteNamedQuery(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) rec := doRequest(t, h, "DeleteNamedQuery", `{"NamedQueryId":"`+cr["NamedQueryId"]+`"}`) assert.Equal(t, tt.wantStatus, rec.Code) @@ -430,14 +431,14 @@ func TestBatchGetNamedQuery_UnprocessedShape(t *testing.T) { } } - body, err := json.Marshal(map[string]any{"NamedQueryIds": tt.ids}) + body, err := json.Marshal(map[string]any{"NamedQueryIds": tt.ids}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, "BatchGetNamedQuery", string(body)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found, _ := resp["NamedQueries"].([]any) assert.Len(t, found, tt.wantFound) @@ -492,7 +493,7 @@ func TestCreateNamedQuery_WorkgroupValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "InvalidRequestException") } }) diff --git a/services/athena/handler_notebooks.go b/services/athena/handler_notebooks.go index b709648519..e891982adf 100644 --- a/services/athena/handler_notebooks.go +++ b/services/athena/handler_notebooks.go @@ -1,8 +1,10 @@ package athena import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createNotebookInput has no Tags: the real CreateNotebookInput carries only Name/WorkGroup/ClientRequestToken. @@ -88,7 +90,7 @@ func (h *Handler) notebookOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreateNotebook": func(b []byte) (any, error) { var input createNotebookInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -105,7 +107,7 @@ func (h *Handler) notebookOps() map[string]athenaActionFn { }, "CreatePresignedNotebookUrl": func(b []byte) (any, error) { var input createPresignedNotebookURLInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -129,7 +131,7 @@ func (h *Handler) notebookOps() map[string]athenaActionFn { }, "DeleteNotebook": func(b []byte) (any, error) { var input deleteNotebookInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -137,7 +139,7 @@ func (h *Handler) notebookOps() map[string]athenaActionFn { }, "ExportNotebook": func(b []byte) (any, error) { var input exportNotebookInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -158,7 +160,7 @@ func (h *Handler) notebookExtraOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "GetNotebookMetadata": func(b []byte) (any, error) { var input notebookIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -171,7 +173,7 @@ func (h *Handler) notebookExtraOps() map[string]athenaActionFn { }, "ListNotebookMetadata": func(b []byte) (any, error) { var input listNotebookMetadataInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -195,7 +197,7 @@ func (h *Handler) notebookExtraOps() map[string]athenaActionFn { }, "ImportNotebook": func(b []byte) (any, error) { var input importNotebookInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -218,7 +220,7 @@ func (h *Handler) notebookExtraOps() map[string]athenaActionFn { }, "UpdateNotebook": func(b []byte) (any, error) { var input updateNotebookInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -226,7 +228,7 @@ func (h *Handler) notebookExtraOps() map[string]athenaActionFn { }, "UpdateNotebookMetadata": func(b []byte) (any, error) { var input updateNotebookMetadataInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_notebooks_test.go b/services/athena/handler_notebooks_test.go index 2b46915723..dcec04c9ea 100644 --- a/services/athena/handler_notebooks_test.go +++ b/services/athena/handler_notebooks_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -38,7 +39,7 @@ func TestHandler_CreateNotebook(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["NotebookId"]) } }) @@ -74,7 +75,7 @@ func TestHandler_CreatePresignedNotebookUrl(t *testing.T) { if tt.wantURL { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["NotebookUrl"], tt.sessionID) assert.NotEmpty(t, resp["AuthToken"]) assert.Positive(t, resp["AuthTokenExpirationTime"]) @@ -98,7 +99,7 @@ func TestHandler_DeleteNotebook(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) return cr["NotebookId"] }, @@ -142,7 +143,7 @@ func TestHandler_ExportNotebook(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) return cr["NotebookId"] }, @@ -170,7 +171,7 @@ func TestHandler_ExportNotebook(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta, _ := resp["NotebookMetadata"].(map[string]any) require.NotNil(t, meta, "NotebookMetadata should be present") assert.Equal(t, tt.wantName, meta["Name"]) diff --git a/services/athena/handler_prepared_statements.go b/services/athena/handler_prepared_statements.go index 96ef54cb51..0f0487c292 100644 --- a/services/athena/handler_prepared_statements.go +++ b/services/athena/handler_prepared_statements.go @@ -1,8 +1,10 @@ package athena import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // batchGetPreparedStatementInput's StatementNames field is tagged @@ -49,7 +51,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreatePreparedStatement": func(b []byte) (any, error) { var input createPreparedStatementInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -59,7 +61,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { }, "BatchGetPreparedStatement": func(b []byte) (any, error) { var input batchGetPreparedStatementInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -83,7 +85,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { }, "DeletePreparedStatement": func(b []byte) (any, error) { var input deletePreparedStatementInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -94,7 +96,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { }, "GetPreparedStatement": func(b []byte) (any, error) { var input getPreparedStatementInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -107,7 +109,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { }, "ListPreparedStatements": func(b []byte) (any, error) { var input listPreparedStatementsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -129,7 +131,7 @@ func (h *Handler) preparedStatementOps() map[string]athenaActionFn { }, "UpdatePreparedStatement": func(b []byte) (any, error) { var input updatePreparedStatementInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_prepared_statements_test.go b/services/athena/handler_prepared_statements_test.go index a6ba564dc1..2466f86f98 100644 --- a/services/athena/handler_prepared_statements_test.go +++ b/services/athena/handler_prepared_statements_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -52,7 +53,7 @@ func TestHandler_CreatePreparedStatement(t *testing.T) { if tt.wantErr { var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"]) } }) @@ -97,7 +98,7 @@ func TestHandler_BatchGetPreparedStatement(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found, _ := resp["PreparedStatements"].([]any) assert.Len(t, found, tt.wantFound) unprocessed, _ := resp["UnprocessedPreparedStatementNames"].([]any) @@ -225,7 +226,7 @@ func TestHandler_GetPreparedStatement(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ps, _ := resp["PreparedStatement"].(map[string]any) require.NotNil(t, ps, "PreparedStatement must be present") assert.Equal(t, tt.wantName, ps["StatementName"]) @@ -277,7 +278,7 @@ func TestHandler_ListPreparedStatements(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string][]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["PreparedStatements"], tt.wantCount) }) } @@ -294,7 +295,7 @@ func TestHandler_ListPreparedStatements_ReturnsSummary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string][]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp["PreparedStatements"], 1) sum := resp["PreparedStatements"][0] diff --git a/services/athena/handler_query_executions.go b/services/athena/handler_query_executions.go index 0f7db22af4..335c2cbcff 100644 --- a/services/athena/handler_query_executions.go +++ b/services/athena/handler_query_executions.go @@ -1,8 +1,10 @@ package athena import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type startQueryExecutionInput struct { @@ -46,7 +48,7 @@ func (h *Handler) queryExecutionOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "StartQueryExecution": func(b []byte) (any, error) { var input startQueryExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -76,7 +78,7 @@ func (h *Handler) queryExecutionOps() map[string]athenaActionFn { }, "StopQueryExecution": func(b []byte) (any, error) { var input stopQueryExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -84,7 +86,7 @@ func (h *Handler) queryExecutionOps() map[string]athenaActionFn { }, "GetQueryExecution": func(b []byte) (any, error) { var input getQueryExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -98,7 +100,7 @@ func (h *Handler) queryExecutionOps() map[string]athenaActionFn { "ListQueryExecutions": h.handleListQueryExecutions, "BatchGetQueryExecution": func(b []byte) (any, error) { var input batchGetQueryExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -120,7 +122,7 @@ func (h *Handler) queryExecutionOps() map[string]athenaActionFn { "GetQueryResults": h.handleGetQueryResults, "GetQueryRuntimeStatistics": func(b []byte) (any, error) { var input getQueryRuntimeStatsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -142,7 +144,7 @@ const athenaMaxQueryResultsPageSize = 1000 // opaque-token pagination. func (h *Handler) handleListQueryExecutions(b []byte) (any, error) { var input listQueryExecutionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -176,7 +178,7 @@ type getQueryResultsInput struct { // Real rows are returned for SELECT queries against registered in-memory tables. func (h *Handler) handleGetQueryResults(b []byte) (any, error) { var input getQueryResultsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrValidation, err) } diff --git a/services/athena/handler_query_executions_test.go b/services/athena/handler_query_executions_test.go index 96c54cf59f..54eb8396cb 100644 --- a/services/athena/handler_query_executions_test.go +++ b/services/athena/handler_query_executions_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -41,7 +42,7 @@ func TestHandler_StartQueryExecution(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["QueryExecutionId"]) } }) @@ -81,7 +82,7 @@ func TestHandler_GetQueryExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) execID = created["QueryExecutionId"] } @@ -90,7 +91,7 @@ func TestHandler_GetQueryExecution(t *testing.T) { if tt.wantState != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) qe, ok := resp["QueryExecution"].(map[string]any) require.True(t, ok) status, ok := qe["Status"].(map[string]any) @@ -118,7 +119,7 @@ func TestHandler_StopQueryExecution(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr, jsonx.V1())) execID := cr["QueryExecutionId"] // Override state to RUNNING so it can be stopped. @@ -142,7 +143,7 @@ func TestHandler_StopQueryExecution(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr, jsonx.V1())) return cr["QueryExecutionId"] // default state is SUCCEEDED }, @@ -191,7 +192,7 @@ func TestHandler_ListQueryExecutions(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, _ := resp["QueryExecutionIds"].([]any) assert.Len(t, ids, tt.wantCount) }) @@ -221,7 +222,7 @@ func TestHandler_BatchGetQueryExecution(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &cr, jsonx.V1())) id := cr["QueryExecutionId"] rec := doRequest(t, h, "BatchGetQueryExecution", @@ -229,7 +230,7 @@ func TestHandler_BatchGetQueryExecution(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found, _ := resp["QueryExecutions"].([]any) assert.Len(t, found, 1) unprocessed, _ := resp["UnprocessedQueryExecutionIds"].([]any) @@ -272,7 +273,7 @@ func TestHandler_QueryExecution_EngineVersionAndStatementType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) qe := resp["QueryExecution"].(map[string]any) assert.Equal(t, tt.wantStatementType, qe["StatementType"]) @@ -302,7 +303,7 @@ func TestHandler_QueryExecution_ExecutionParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) qe := resp["QueryExecution"].(map[string]any) params, ok := qe["ExecutionParameters"].([]any) @@ -334,7 +335,7 @@ func TestHandler_ResultConfiguration_AclAndOwner(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) qe := resp["QueryExecution"].(map[string]any) rc := qe["ResultConfiguration"].(map[string]any) @@ -522,7 +523,7 @@ func TestStartQueryExecution_EmptyQueryStringRejected(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "InvalidRequestException", "empty QueryString must return InvalidRequestException") } @@ -560,7 +561,7 @@ func TestListQueryExecutions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.QueryExecutionIDs), 2, "page exceeds MaxResults") for _, id := range resp.QueryExecutionIDs { @@ -595,7 +596,7 @@ func TestListQueryExecutions_NextTokenOmittedOnLastPage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasToken := resp["NextToken"] assert.False(t, hasToken, "NextToken must be omitted on the last page") } @@ -760,14 +761,14 @@ func TestBatchGetQueryExecution_UnprocessedShape(t *testing.T) { } } - body, err := json.Marshal(map[string]any{"QueryExecutionIds": tt.ids}) + body, err := json.Marshal(map[string]any{"QueryExecutionIds": tt.ids}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, "BatchGetQueryExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found, _ := resp["QueryExecutions"].([]any) assert.Len(t, found, tt.wantFound) @@ -822,7 +823,7 @@ func TestStartQueryExecution_WorkgroupValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "InvalidRequestException") } }) diff --git a/services/athena/handler_sessions.go b/services/athena/handler_sessions.go index 926628ff9d..39df596131 100644 --- a/services/athena/handler_sessions.go +++ b/services/athena/handler_sessions.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) const ( keySessionID = "SessionId" @@ -72,7 +76,7 @@ type getResourceDashboardInput struct { func (h *Handler) handleStartSession(b []byte) (any, error) { var input startSessionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -123,7 +127,7 @@ func (h *Handler) sessionCoreOps() map[string]athenaActionFn { "StartSession": h.handleStartSession, "GetSession": func(b []byte) (any, error) { var input sessionIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -147,7 +151,7 @@ func (h *Handler) sessionCoreOps() map[string]athenaActionFn { }, "GetSessionStatus": func(b []byte) (any, error) { var input sessionIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -160,7 +164,7 @@ func (h *Handler) sessionCoreOps() map[string]athenaActionFn { }, "GetSessionEndpoint": func(b []byte) (any, error) { var input sessionIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -177,7 +181,7 @@ func (h *Handler) sessionCoreOps() map[string]athenaActionFn { }, "TerminateSession": func(b []byte) (any, error) { var input sessionIDInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -195,7 +199,7 @@ func (h *Handler) sessionListOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "ListSessions": func(b []byte) (any, error) { var input listSessionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -219,7 +223,7 @@ func (h *Handler) sessionListOps() map[string]athenaActionFn { }, "ListNotebookSessions": func(b []byte) (any, error) { var input listNotebookSessionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -250,7 +254,7 @@ func (h *Handler) sessionInfoOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "ListExecutors": func(b []byte) (any, error) { var input listExecutorsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -274,7 +278,7 @@ func (h *Handler) sessionInfoOps() map[string]athenaActionFn { }, "ListEngineVersions": func(b []byte) (any, error) { var input listPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -293,7 +297,7 @@ func (h *Handler) sessionInfoOps() map[string]athenaActionFn { }, "ListApplicationDPUSizes": func(b []byte) (any, error) { var input listPageInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -309,7 +313,7 @@ func (h *Handler) sessionInfoOps() map[string]athenaActionFn { }, "GetResourceDashboard": func(b []byte) (any, error) { var input getResourceDashboardInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_sessions_test.go b/services/athena/handler_sessions_test.go index 4d684ae137..737d6e6cd4 100644 --- a/services/athena/handler_sessions_test.go +++ b/services/athena/handler_sessions_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -17,7 +18,7 @@ func jsonNested(t *testing.T, body []byte, keys ...string) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1())) cur := m for _, k := range keys { @@ -296,7 +297,7 @@ func TestHandler_ListSessions(t *testing.T) { if tt.wantNonEmpty { var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) assert.NotEmpty(t, m["Sessions"]) } }) @@ -409,7 +410,7 @@ func TestHandler_ListNotebookSessions(t *testing.T) { var body struct { NotebookSessionsList []map[string]any `json:"NotebookSessionsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body.NotebookSessionsList, 1, "expected the started session to be linked to its notebook") } }) @@ -458,7 +459,7 @@ func TestHandler_ListEngineVersions_NoInventedAuthEngineVersionField(t *testing. require.Equal(t, http.StatusOK, rec.Code) var resp map[string][]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp["EngineVersions"]) for _, ev := range resp["EngineVersions"] { @@ -744,7 +745,7 @@ func TestGetSessionEndpoint_UsesConfiguredRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) url, _ := resp["EndpointUrl"].(string) assert.Contains(t, url, tt.wantPrefix, diff --git a/services/athena/handler_tags.go b/services/athena/handler_tags.go index 09e77c5e77..27c78fb264 100644 --- a/services/athena/handler_tags.go +++ b/services/athena/handler_tags.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type tagResourceInput struct { ResourceARN string `json:"ResourceARN"` @@ -22,7 +26,7 @@ func (h *Handler) tagOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "TagResource": func(b []byte) (any, error) { var input tagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -30,7 +34,7 @@ func (h *Handler) tagOps() map[string]athenaActionFn { }, "UntagResource": func(b []byte) (any, error) { var input untagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -38,7 +42,7 @@ func (h *Handler) tagOps() map[string]athenaActionFn { }, "ListTagsForResource": func(b []byte) (any, error) { var input listTagsForResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_tags_test.go b/services/athena/handler_tags_test.go index 50c7ae6180..dc35997bfc 100644 --- a/services/athena/handler_tags_test.go +++ b/services/athena/handler_tags_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -98,7 +99,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string][]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Tags"], tt.wantCount) assert.Equal(t, "team", resp["Tags"][0]["Key"]) assert.Equal(t, "platform", resp["Tags"][0]["Value"]) @@ -126,7 +127,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) tags1, _ := page1["Tags"].([]any) require.Len(t, tags1, 2) nextToken, ok := page1["NextToken"].(string) @@ -138,7 +139,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) tags2, _ := page2["Tags"].([]any) require.Len(t, tags2, 1) _, hasNextToken := page2["NextToken"] diff --git a/services/athena/handler_test.go b/services/athena/handler_test.go index b695e90060..f7784b160e 100644 --- a/services/athena/handler_test.go +++ b/services/athena/handler_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -51,7 +52,7 @@ func jsonField(t *testing.T, body []byte, key string) string { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1())) v, ok := m[key] require.True(t, ok, "key %q missing", key) @@ -75,7 +76,7 @@ func athenaDoPass5(t *testing.T, h *athena.Handler, action, body string) *httpte func athenaUnmarshalPass5(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -104,7 +105,7 @@ func a1Unmarshal(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -136,7 +137,7 @@ func TestHandler_UnknownOperation(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"]) }) } diff --git a/services/athena/handler_work_groups.go b/services/athena/handler_work_groups.go index 9835e00e5f..88eb4eddb7 100644 --- a/services/athena/handler_work_groups.go +++ b/services/athena/handler_work_groups.go @@ -1,6 +1,10 @@ package athena -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type listWorkGroupsInput struct { NextToken string `json:"NextToken"` @@ -37,7 +41,7 @@ func (h *Handler) workGroupOps() map[string]athenaActionFn { return map[string]athenaActionFn{ "CreateWorkGroup": func(b []byte) (any, error) { var input createWorkGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -51,7 +55,7 @@ func (h *Handler) workGroupOps() map[string]athenaActionFn { }, "GetWorkGroup": func(b []byte) (any, error) { var input getWorkGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -64,7 +68,7 @@ func (h *Handler) workGroupOps() map[string]athenaActionFn { }, "ListWorkGroups": func(b []byte) (any, error) { var input listWorkGroupsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } list, nextToken, err := h.Backend.ListWorkGroups(input.NextToken, input.MaxResults) @@ -80,7 +84,7 @@ func (h *Handler) workGroupOps() map[string]athenaActionFn { }, "UpdateWorkGroup": func(b []byte) (any, error) { var input updateWorkGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -90,7 +94,7 @@ func (h *Handler) workGroupOps() map[string]athenaActionFn { }, "DeleteWorkGroup": func(b []byte) (any, error) { var input deleteWorkGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/athena/handler_work_groups_test.go b/services/athena/handler_work_groups_test.go index 89a98c38a5..990a069216 100644 --- a/services/athena/handler_work_groups_test.go +++ b/services/athena/handler_work_groups_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -65,7 +66,7 @@ func TestHandler_CreateWorkGroup(t *testing.T) { if tt.wantErr { var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"]) } }) @@ -106,7 +107,7 @@ func TestHandler_GetWorkGroup(t *testing.T) { if tt.wantName != "" { var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["WorkGroup"]["Name"]) } }) @@ -122,7 +123,7 @@ func TestHandler_ListWorkGroups(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string][]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.GreaterOrEqual(t, len(resp["WorkGroups"]), 1) found := false @@ -267,7 +268,7 @@ func TestHandler_GetWorkGroup_NoInventedTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasTags := resp["WorkGroup"]["Tags"] assert.False(t, hasTags, "WorkGroup response must not carry an invented Tags field") @@ -276,7 +277,7 @@ func TestHandler_GetWorkGroup_NoInventedTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string][]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) require.Len(t, tagsResp["Tags"], 1, "the tag set at creation must still be visible via ListTagsForResource") assert.Equal(t, "env", tagsResp["Tags"][0]["Key"]) } @@ -329,7 +330,7 @@ func TestHandler_WorkGroupConfiguration_EnforceAndExtras(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wg := resp["WorkGroup"].(map[string]any) cfg := wg["Configuration"].(map[string]any) @@ -358,7 +359,7 @@ func TestHandler_WorkGroupSummary_IncludesFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string][]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) var found map[string]any for _, wg := range resp["WorkGroups"] { @@ -537,7 +538,7 @@ func TestWorkGroup_StateValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "InvalidRequestException", "invalid State must return InvalidRequestException") } @@ -563,7 +564,7 @@ func TestCreateWorkGroup_AlwaysEnabled_NoStateMember(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wg, ok := resp["WorkGroup"].(map[string]any) require.True(t, ok) assert.Equal(t, "ENABLED", wg["State"], "CreateWorkGroupInput has no State member; must always be ENABLED") @@ -821,7 +822,7 @@ func TestHandler_UpdateWorkGroup_PreservesUnmentionedConfiguration(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wg, ok := resp["WorkGroup"].(map[string]any) require.True(t, ok) cfg, ok := wg["Configuration"].(map[string]any) diff --git a/services/athena/multi_region_test.go b/services/athena/multi_region_test.go index 5181d71bf5..47a77fc6dd 100644 --- a/services/athena/multi_region_test.go +++ b/services/athena/multi_region_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "slices" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -40,7 +41,7 @@ func regionCall(t *testing.T, h *athena.Handler, region, op, body string) map[st require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/athena/pagination_opaque_test.go b/services/athena/pagination_opaque_test.go index e2ad72f24f..4a365fbc2b 100644 --- a/services/athena/pagination_opaque_test.go +++ b/services/athena/pagination_opaque_test.go @@ -1,13 +1,14 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -24,7 +25,7 @@ func listPage(t *testing.T, h *athena.Handler, maxResults int, token string) ([] NextToken string `json:"NextToken"` QueryExecutionIDs []string `json:"QueryExecutionIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp.QueryExecutionIDs, resp.NextToken } @@ -53,7 +54,7 @@ func itoa(n int) string { } func jsonString(s string) string { - b, _ := json.Marshal(s) + b, _ := json.Marshal(s, jsonx.V1()) return string(b) } @@ -142,7 +143,7 @@ func TestPagination_ForgedTokenRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errResp["__type"]) }) } diff --git a/services/athena/persistence.go b/services/athena/persistence.go index 9ab6e55d55..396586a58c 100644 --- a/services/athena/persistence.go +++ b/services/athena/persistence.go @@ -29,7 +29,7 @@ package athena // mirroring services/appsync's apiKeys field. import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -189,7 +189,7 @@ func restoreQueryResults(m map[string]*sqlResultSnapshot) map[string]*sqlResult // - ResourceTags (resourceTags map[string]map[string]string): values are // map[string]string, not *T; already JSON-friendly as-is. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` QueryResults map[string]*sqlResultSnapshot `json:"queryResults,omitempty"` TableData map[string][]map[string]any `json:"tableData,omitempty"` ResourceTags map[string]map[string]string `json:"resourceTags,omitempty"` @@ -310,7 +310,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreDirtyTablesLocked restores the "dirty" tables (databases, tables) // from tables via the ephemeral DTO registry, converting each DTO back to // its live type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, dbDTOs, tblDTOs := newDirtyTableRegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/athena/prepared_exec_test.go b/services/athena/prepared_exec_test.go index 7ec335e2ab..1ed3459dd7 100644 --- a/services/athena/prepared_exec_test.go +++ b/services/athena/prepared_exec_test.go @@ -1,12 +1,13 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -125,7 +126,7 @@ func fetchResultRows(t *testing.T, h *athena.Handler, id string) [][]string { rec := doRequest(t, h, "GetQueryResults", `{"QueryExecutionId":"`+id+`"}`) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rs, _ := resp["ResultSet"].(map[string]any) require.NotNil(t, rs) diff --git a/services/athena/query_results_test.go b/services/athena/query_results_test.go index 03ffbad80c..f1656ce9a3 100644 --- a/services/athena/query_results_test.go +++ b/services/athena/query_results_test.go @@ -1,7 +1,7 @@ package athena_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/athena" ) @@ -107,7 +108,7 @@ func TestGetQueryResults_SQLExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rs, _ := resp["ResultSet"].(map[string]any) require.NotNil(t, rs) @@ -182,7 +183,7 @@ func TestGetQueryResults_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) nextToken, _ := resp1["NextToken"].(string) assert.NotEmpty(t, nextToken, "page 1 must have NextToken") @@ -199,7 +200,7 @@ func TestGetQueryResults_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Empty(t, resp2["NextToken"], "last page must have no NextToken") rs2 := resp2["ResultSet"].(map[string]any) @@ -235,7 +236,7 @@ func TestGetQueryResults_CatalogQualifiedTable(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rs := resp["ResultSet"].(map[string]any) allRows := rs["Rows"].([]any) @@ -311,7 +312,7 @@ func TestHandler_GetQueryResults(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rs, _ := resp["ResultSet"].(map[string]any) require.NotNil(t, rs, "ResultSet should be present") rows, _ := rs["Rows"].([]any) @@ -448,7 +449,7 @@ func TestGetQueryResults_NonSucceededQueryRejected(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "InvalidRequestException", "GetQueryResults on a non-SUCCEEDED query must return InvalidRequestException") msg, _ := errResp["message"].(string) diff --git a/services/athena/result_object.go b/services/athena/result_object.go index 9d33002272..965cdd6e1f 100644 --- a/services/athena/result_object.go +++ b/services/athena/result_object.go @@ -3,12 +3,14 @@ package athena import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "strings" "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" sdk_s3_types "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -80,7 +82,7 @@ func resultMetadata(result *sqlResult) []byte { cols[i] = col{Name: c.name, Type: c.typ} } - out, _ := json.Marshal(map[string]any{"columns": cols}) + out, _ := json.Marshal(map[string]any{"columns": cols}, jsonx.V1()) return out } diff --git a/services/autoscaling/persistence.go b/services/autoscaling/persistence.go index 11c000ed3a..5e68d1c130 100644 --- a/services/autoscaling/persistence.go +++ b/services/autoscaling/persistence.go @@ -2,7 +2,7 @@ package autoscaling import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -35,7 +35,7 @@ const autoscalingSnapshotVersion = 1 // map[string][]T/[]*T fields -- see store_setup.go's registerAllTables doc // for why those three are not store.Table-backed. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Activities map[string][]ScalingActivity `json:"activities"` InstanceRefreshes map[string][]*InstanceRefresh `json:"instanceRefreshes"` NotificationConfigs map[string][]*NotificationConfiguration `json:"notificationConfigs"` diff --git a/services/awsconfig/accounts.go b/services/awsconfig/accounts.go index 96c030130f..002786c91a 100644 --- a/services/awsconfig/accounts.go +++ b/services/awsconfig/accounts.go @@ -1,11 +1,13 @@ package awsconfig import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "regexp" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -98,7 +100,7 @@ func (s *accountSet) snapshot(base []byte, snap func(*Handler) []byte) []byte { } s.mu.RLock("snapshot") - accounts := make(map[string]json.RawMessage, len(s.peers)) + accounts := make(map[string]jsontext.Value, len(s.peers)) for a, p := range s.peers { if data := snap(p); data != nil { @@ -111,19 +113,19 @@ func (s *accountSet) snapshot(base []byte, snap func(*Handler) []byte) []byte { return base } - doc := make(map[string]json.RawMessage) - if err := json.Unmarshal(base, &doc); err != nil { + doc := make(map[string]jsontext.Value) + if err := json.Unmarshal(base, &doc, jsonx.V1()); err != nil { return base } - enc, err := json.Marshal(accounts) + enc, err := json.Marshal(accounts, jsonx.V1()) if err != nil { return base } doc[accountsSnapshotKey] = enc - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return base } @@ -142,10 +144,10 @@ func (s *accountSet) restore(data []byte, restore func(*Handler, []byte) error) } var doc struct { - Accounts map[string]json.RawMessage `json:"accounts"` + Accounts map[string]jsontext.Value `json:"accounts"` } - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &doc, jsonx.V1()); err != nil { return err } diff --git a/services/awsconfig/conformance_pack_template.go b/services/awsconfig/conformance_pack_template.go index df28b10f3d..80a1720823 100644 --- a/services/awsconfig/conformance_pack_template.go +++ b/services/awsconfig/conformance_pack_template.go @@ -1,10 +1,13 @@ package awsconfig import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "sort" "gopkg.in/yaml.v3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resourceTypeConfigRule is the CloudFormation resource type a conformance @@ -32,7 +35,7 @@ type conformancePackConfigRuleResourceProperties struct { ConfigRuleName string `json:"ConfigRuleName,omitempty"` Description string `json:"Description,omitempty"` MaximumExecutionFrequency string `json:"MaximumExecutionFrequency,omitempty"` - InputParameters json.RawMessage `json:"InputParameters,omitempty"` + InputParameters jsontext.Value `json:"InputParameters,omitempty"` } // conformancePackTemplate is the minimal top-level shape parsed out of a @@ -57,24 +60,24 @@ func parseConformancePackConfigRules( jsonBody := []byte(templateBody) var doc map[string]any - if err := json.Unmarshal(jsonBody, &doc); err != nil { + if err := json.Unmarshal(jsonBody, &doc, jsonx.V1()); err != nil { converted, yamlErr := yamlToJSON(jsonBody) if yamlErr != nil { return nil } - if jsonErr := json.Unmarshal(converted, &doc); jsonErr != nil { + if jsonErr := json.Unmarshal(converted, &doc, jsonx.V1()); jsonErr != nil { return nil } } - resolved, err := json.Marshal(resolveTemplateRefs(doc, templateParamValues(doc, params))) + resolved, err := json.Marshal(resolveTemplateRefs(doc, templateParamValues(doc, params)), jsonx.V1()) if err != nil { return nil } var tmpl conformancePackTemplate - if err = json.Unmarshal(resolved, &tmpl); err != nil { + if err = json.Unmarshal(resolved, &tmpl, jsonx.V1()); err != nil { return nil } @@ -193,7 +196,7 @@ func yamlToJSON(body []byte) ([]byte, error) { return nil, err } - return json.Marshal(v) + return json.Marshal(v, jsonx.V1()) } // rewriteRefTags turns the YAML short form "!Ref Name" into the long form diff --git a/services/awsconfig/delivery_status.go b/services/awsconfig/delivery_status.go index a5a802fe24..462152426b 100644 --- a/services/awsconfig/delivery_status.go +++ b/services/awsconfig/delivery_status.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" s3pkg "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -384,7 +385,7 @@ func buildConfigSnapshotBody(snapshotID string, items []*ResourceConfigItem) ([] envelope.ConfigurationItems = append(envelope.ConfigurationItems, *item) } - encoded, err := json.Marshal(envelope) + encoded, err := json.Marshal(envelope, jsonx.V1()) if err != nil { return nil, err } @@ -461,7 +462,7 @@ func buildSnapshotSNSMessage(snapshotID, bucket, key string, t time.Time) string RecordVersion: configSnapshotNotificationRecordVersion, } - encoded, err := json.Marshal(msg) + encoded, err := json.Marshal(msg, jsonx.V1()) if err != nil { return "" } diff --git a/services/awsconfig/evaluation.go b/services/awsconfig/evaluation.go index bba427a0e3..37c72636cd 100644 --- a/services/awsconfig/evaluation.go +++ b/services/awsconfig/evaluation.go @@ -1,11 +1,13 @@ package awsconfig import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Compliance type constants used by the evaluation engine. @@ -109,7 +111,7 @@ func parseConfiguration(configuration string) map[string]any { } var m map[string]any - if err := json.Unmarshal([]byte(configuration), &m); err != nil { + if err := json.Unmarshal([]byte(configuration), &m, jsonx.V1()); err != nil { return map[string]any{} } @@ -125,7 +127,7 @@ func parseInputParameters(input string) map[string]string { } var raw map[string]any - if err := json.Unmarshal([]byte(input), &raw); err != nil { + if err := json.Unmarshal([]byte(input), &raw, jsonx.V1()); err != nil { return out } @@ -134,7 +136,7 @@ func parseInputParameters(input string) map[string]string { case string: out[k] = tv default: - b, _ := json.Marshal(tv) + b, _ := json.Marshal(tv, jsonx.V1()) out[k] = string(b) } } diff --git a/services/awsconfig/evaluation_test.go b/services/awsconfig/evaluation_test.go index e45be7f172..7f68e1eea2 100644 --- a/services/awsconfig/evaluation_test.go +++ b/services/awsconfig/evaluation_test.go @@ -1,7 +1,7 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -399,7 +400,7 @@ func TestHandler_DescribeConfigRulesPagination(t *testing.T) { NextToken string `json:"NextToken"` ConfigRules []awsconfig.ConfigRule `json:"ConfigRules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.ConfigRules, 100) require.NotEmpty(t, page1.NextToken) @@ -410,7 +411,7 @@ func TestHandler_DescribeConfigRulesPagination(t *testing.T) { NextToken string `json:"NextToken"` ConfigRules []awsconfig.ConfigRule `json:"ConfigRules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.ConfigRules, 50) assert.Empty(t, page2.NextToken) @@ -446,7 +447,7 @@ func TestHandler_GetComplianceDetailsByConfigRule_RealResults(t *testing.T) { var out struct { EvaluationResults []awsconfig.DetailedEvaluationResult `json:"EvaluationResults"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.EvaluationResults, 2) got := complianceByResource(out.EvaluationResults) @@ -475,7 +476,7 @@ func TestHandler_StartResourceEvaluationAndSummary(t *testing.T) { var started struct { ResourceEvaluationID string `json:"ResourceEvaluationId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) require.NotEmpty(t, started.ResourceEvaluationID) assert.NotEqual(t, "eval-stub", started.ResourceEvaluationID) @@ -514,7 +515,7 @@ func TestHandler_GetResourceConfigHistoryPagination(t *testing.T) { NextToken string `json:"nextToken"` ConfigurationItems []awsconfig.ResourceConfigItem `json:"configurationItems"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConfigurationItems, 1) assert.Equal(t, `{"v":2}`, out.ConfigurationItems[0].Configuration) require.NotEmpty(t, out.NextToken) @@ -531,7 +532,7 @@ func TestHandler_GetResourceConfigHistoryPagination(t *testing.T) { NextToken string `json:"nextToken"` ConfigurationItems []awsconfig.ResourceConfigItem `json:"configurationItems"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2, jsonx.V1())) require.Len(t, out2.ConfigurationItems, 1) assert.Equal(t, `{"v":1}`, out2.ConfigurationItems[0].Configuration) assert.Empty(t, out2.NextToken) diff --git a/services/awsconfig/handler.go b/services/awsconfig/handler.go index 5889902770..cb98e61f41 100644 --- a/services/awsconfig/handler.go +++ b/services/awsconfig/handler.go @@ -2,7 +2,8 @@ package awsconfig import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -202,8 +204,8 @@ type configNameBody struct { // extractNamedField extracts a top-level string field by key from a JSON body. func extractNamedField(body []byte, key string) string { - var m map[string]json.RawMessage - if err := json.Unmarshal(body, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { return "" } @@ -213,7 +215,7 @@ func extractNamedField(body []byte, key string) string { } var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return "" } @@ -279,13 +281,13 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // marshalError serialises a typed AWS error response into bytes. // Marshaling a struct with only string fields cannot fail; error is intentionally ignored. func marshalError(errType, message string) []byte { - payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}) + payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}, jsonx.V1()) return payload } @@ -350,15 +352,12 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err } } - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - // configservice@v1.68.4 has no single code that fits every operation here: // ValidationException is modeled on only 38 of its ~102 operations, the rest // use InvalidParameterValueException or model no bad-request-class error at // all (e.g. DeleteConfigurationAggregator declares only // NoSuchConfigurationAggregatorException). Left untyped rather than guessing. - if errors.Is(err, errUnknownAction) || errors.As(err, &syntaxErr) || errors.As(err, &typeErr) { + if errors.Is(err, errUnknownAction) || jsonx.IsSyntaxError(err) || jsonx.IsTypeError(err) { return c.JSON(http.StatusBadRequest, map[string]string{"message": err.Error()}) } diff --git a/services/awsconfig/handler_config_rules_test.go b/services/awsconfig/handler_config_rules_test.go index 61c03acbab..82ce0a4259 100644 --- a/services/awsconfig/handler_config_rules_test.go +++ b/services/awsconfig/handler_config_rules_test.go @@ -1,7 +1,8 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -61,7 +63,7 @@ func TestDescribeConfigRulesAcceptsFilters(t *testing.T) { ConfigRuleName string `json:"ConfigRuleName"` } `json:"ConfigRules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConfigRules, 1) assert.Equal(t, "rule-detective", out.ConfigRules[0].ConfigRuleName) } @@ -125,7 +127,7 @@ func TestConfigRuleScopeRoundtrip(t *testing.T) { } `json:"Scope"` } `json:"ConfigRules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConfigRules, 1) require.NotNil(t, out.ConfigRules[0].Scope) assert.Contains(t, out.ConfigRules[0].Scope.ComplianceResourceTypes, "AWS::S3::Bucket") @@ -251,7 +253,7 @@ func TestAWSConfigHandler_DescribeConfigRulesAndCompliance(t *testing.T) { } var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out[tt.wantField]) }) } @@ -394,11 +396,11 @@ func TestAWSConfigHandler_DescribeConfigRules_BackedByStorage(t *testing.T) { rec := doAWSConfigRequest(t, h, "DescribeConfigRules", tt.body) assert.Equal(t, tt.wantCode, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var rules []any - require.NoError(t, json.Unmarshal(out["ConfigRules"], &rules)) + require.NoError(t, json.Unmarshal(out["ConfigRules"], &rules, jsonx.V1())) assert.Len(t, rules, tt.wantCount) }) } diff --git a/services/awsconfig/handler_configuration_recorders.go b/services/awsconfig/handler_configuration_recorders.go index 4b2e534e3d..1484972f48 100644 --- a/services/awsconfig/handler_configuration_recorders.go +++ b/services/awsconfig/handler_configuration_recorders.go @@ -2,11 +2,12 @@ package awsconfig import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -50,7 +51,7 @@ type extractConfigRecorderNameInput struct { func extractConfigRecorderName(body []byte) string { var req extractConfigRecorderNameInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -59,7 +60,7 @@ func extractConfigRecorderName(body []byte) string { func extractTopLevelRecorderName(body []byte) string { var req configurationRecorderNameInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -72,7 +73,7 @@ type extractFirstRecorderNameInput struct { func extractFirstRecorderName(body []byte) string { var req extractFirstRecorderNameInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } if len(req.ConfigurationRecorderNames) > 0 { diff --git a/services/awsconfig/handler_configuration_recorders_test.go b/services/awsconfig/handler_configuration_recorders_test.go index 333f288072..60326b84ba 100644 --- a/services/awsconfig/handler_configuration_recorders_test.go +++ b/services/awsconfig/handler_configuration_recorders_test.go @@ -1,13 +1,15 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -84,7 +86,7 @@ func TestRecorderRecordingGroupRoundtrip(t *testing.T) { } `json:"recordingGroup"` } `json:"ConfigurationRecorders"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConfigurationRecorders, 1) require.NotNil(t, out.ConfigurationRecorders[0].RecordingGroup) assert.True(t, out.ConfigurationRecorders[0].RecordingGroup.AllSupported) @@ -130,7 +132,7 @@ func TestListConfigurationRecordersSummaries(t *testing.T) { RecordingScope string `json:"recordingScope"` } `json:"ConfigurationRecorderSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConfigurationRecorderSummaries, 1) assert.Equal(t, "default", out.ConfigurationRecorderSummaries[0].Name) assert.Contains(t, out.ConfigurationRecorderSummaries[0].Arn, "arn:aws:config:") @@ -330,7 +332,7 @@ func TestAWSConfigHandler_DescribeConfigurationRecorderStatus(t *testing.T) { } `json:"ConfigurationRecordersStatus"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.ConfigurationRecordersStatus, tt.wantRecordingLen) if tt.wantRecordingLen > 0 { @@ -529,11 +531,11 @@ func TestAWSConfigHandler_DescribeConfigurationRecorders_NameFilter(t *testing.T return } - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var recorders []any - require.NoError(t, json.Unmarshal(out["ConfigurationRecorders"], &recorders)) + require.NoError(t, json.Unmarshal(out["ConfigurationRecorders"], &recorders, jsonx.V1())) assert.Len(t, recorders, tt.wantCount) }) } @@ -565,7 +567,7 @@ func TestAWSConfigHandler_ServiceLinkedConfigurationRecorder(t *testing.T) { Arn string `json:"Arn"` Name string `json:"Name"` } - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) assert.Equal(t, "AWSConfigurationRecorderForGuardduty", putOut.Name) assert.NotEmpty(t, putOut.Arn) @@ -578,7 +580,7 @@ func TestAWSConfigHandler_ServiceLinkedConfigurationRecorder(t *testing.T) { Arn string `json:"Arn"` Name string `json:"Name"` } - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delOut)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delOut, jsonx.V1())) assert.Equal(t, putOut.Name, delOut.Name) // A second delete now 404s (NoSuchConfigurationRecorderException). @@ -682,7 +684,7 @@ func TestAWSConfigHandler_ConnectorsRoundtrip(t *testing.T) { var putOut struct { Arn string `json:"Arn"` } - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) require.NotEmpty(t, putOut.Arn) getRec := doAWSConfigRequest(t, h, "GetConnector", map[string]any{"Arn": putOut.Arn}) @@ -698,7 +700,7 @@ func TestAWSConfigHandler_ConnectorsRoundtrip(t *testing.T) { } `json:"connectorConfiguration"` } `json:"Connector"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, putOut.Arn, getOut.Connector.Arn) assert.Equal(t, "client-1", getOut.Connector.ConnectorConfiguration.Azure.ClientIdentifier) @@ -711,7 +713,7 @@ func TestAWSConfigHandler_ConnectorsRoundtrip(t *testing.T) { Provider string `json:"provider"` } `json:"ConnectorSummaries"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.ConnectorSummaries, 1) assert.Equal(t, putOut.Arn, listOut.ConnectorSummaries[0].Arn) assert.Equal(t, "AZURE", listOut.ConnectorSummaries[0].Provider) @@ -735,7 +737,7 @@ func TestAWSConfigHandler_PutThirdPartyServiceLinkedConfigurationRecorder(t *tes var connOut struct { Arn string `json:"Arn"` } - require.NoError(t, json.Unmarshal(putConnRec.Body.Bytes(), &connOut)) + require.NoError(t, json.Unmarshal(putConnRec.Body.Bytes(), &connOut, jsonx.V1())) body := map[string]any{ "ServicePrincipal": "thirdparty.amazonaws.com", @@ -753,7 +755,7 @@ func TestAWSConfigHandler_PutThirdPartyServiceLinkedConfigurationRecorder(t *tes Arn string `json:"Arn"` Name string `json:"Name"` } - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) assert.NotEmpty(t, putOut.Name) assert.NotEmpty(t, putOut.Arn) @@ -773,7 +775,7 @@ func TestAWSConfigHandler_PutThirdPartyServiceLinkedConfigurationRecorder(t *tes var connOut2 struct { Arn string `json:"Arn"` } - require.NoError(t, json.Unmarshal(putConnRec2.Body.Bytes(), &connOut2)) + require.NoError(t, json.Unmarshal(putConnRec2.Body.Bytes(), &connOut2, jsonx.V1())) conflictBody := map[string]any{ "ServicePrincipal": "thirdparty.amazonaws.com", diff --git a/services/awsconfig/handler_conformance_packs_test.go b/services/awsconfig/handler_conformance_packs_test.go index 66691da435..2027f96109 100644 --- a/services/awsconfig/handler_conformance_packs_test.go +++ b/services/awsconfig/handler_conformance_packs_test.go @@ -1,7 +1,7 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -66,7 +67,7 @@ func TestConformancePackARN(t *testing.T) { DeliveryS3Bucket string `json:"DeliveryS3Bucket"` } `json:"ConformancePackDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConformancePackDetails, 1) assert.Contains(t, out.ConformancePackDetails[0].ConformancePackArn, "arn:aws:config:") assert.NotEmpty(t, out.ConformancePackDetails[0].ConformancePackID) @@ -195,7 +196,7 @@ func TestAWSConfigHandler_PutConformancePack_TemplateBodyDeploysRules(t *testing ComplianceType string `json:"ComplianceType"` } `json:"ConformancePackRuleComplianceList"` } - require.NoError(t, json.Unmarshal(complianceRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(complianceRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConformancePackRuleComplianceList, 1) assert.Equal(t, "rule-a", out.ConformancePackRuleComplianceList[0].ConfigRuleName) assert.Equal(t, "INSUFFICIENT_DATA", out.ConformancePackRuleComplianceList[0].ComplianceType) @@ -233,7 +234,7 @@ func TestAWSConfigHandler_ListConformancePackComplianceScores(t *testing.T) { Score string `json:"Score"` } `json:"ConformancePackComplianceScores"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ConformancePackComplianceScores, 1) assert.Equal(t, "pack1", out.ConformancePackComplianceScores[0].ConformancePackName) assert.Equal(t, "INSUFFICIENT_DATA", out.ConformancePackComplianceScores[0].Score) diff --git a/services/awsconfig/handler_delivery_channels.go b/services/awsconfig/handler_delivery_channels.go index 546375f06a..5a4037a5c7 100644 --- a/services/awsconfig/handler_delivery_channels.go +++ b/services/awsconfig/handler_delivery_channels.go @@ -2,8 +2,9 @@ package awsconfig import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -33,7 +34,7 @@ type extractDeliveryChannelNameInput struct { func extractDeliveryChannelName(body []byte) string { var req extractDeliveryChannelNameInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -46,7 +47,7 @@ type extractFirstDeliveryChannelNameInput struct { func extractFirstDeliveryChannelName(body []byte) string { var req extractFirstDeliveryChannelNameInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } if len(req.DeliveryChannelNames) > 0 { diff --git a/services/awsconfig/handler_delivery_channels_test.go b/services/awsconfig/handler_delivery_channels_test.go index a326843aa1..296889c987 100644 --- a/services/awsconfig/handler_delivery_channels_test.go +++ b/services/awsconfig/handler_delivery_channels_test.go @@ -1,13 +1,15 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -252,11 +254,11 @@ func TestAWSConfigHandler_DescribeDeliveryChannels_NameFilter(t *testing.T) { return } - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var channels []any - require.NoError(t, json.Unmarshal(out["DeliveryChannels"], &channels)) + require.NoError(t, json.Unmarshal(out["DeliveryChannels"], &channels, jsonx.V1())) assert.Len(t, channels, tt.wantCount) }) } diff --git a/services/awsconfig/handler_remediation_test.go b/services/awsconfig/handler_remediation_test.go index 3518b0fc36..a54f9381c9 100644 --- a/services/awsconfig/handler_remediation_test.go +++ b/services/awsconfig/handler_remediation_test.go @@ -1,13 +1,14 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -43,7 +44,7 @@ func TestAWSConfigHandler_StartAndDescribeRemediationExecution(t *testing.T) { State string `json:"State"` } `json:"RemediationExecutionStatuses"` } - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.RemediationExecutionStatuses, 1) assert.Equal(t, "SUCCEEDED", out.RemediationExecutionStatuses[0].State) } diff --git a/services/awsconfig/handler_stored_queries_test.go b/services/awsconfig/handler_stored_queries_test.go index 28da729d77..6095781f5a 100644 --- a/services/awsconfig/handler_stored_queries_test.go +++ b/services/awsconfig/handler_stored_queries_test.go @@ -1,12 +1,14 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListStoredQueriesMetadata verifies ListStoredQueries returns StoredQueryMetadata. @@ -27,7 +29,7 @@ func TestListStoredQueriesMetadata(t *testing.T) { QueryName string `json:"QueryName"` } `json:"StoredQueryMetadata"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.StoredQueryMetadata, 1) assert.Equal(t, "my-query", out.StoredQueryMetadata[0].QueryName) assert.Contains(t, out.StoredQueryMetadata[0].QueryArn, "arn:aws:config:") diff --git a/services/awsconfig/handler_test.go b/services/awsconfig/handler_test.go index 8aa9f3df51..33b44e967d 100644 --- a/services/awsconfig/handler_test.go +++ b/services/awsconfig/handler_test.go @@ -2,7 +2,7 @@ package awsconfig_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -29,7 +30,7 @@ func doAWSConfigRequest(t *testing.T, h *awsconfig.Handler, action string, body var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") diff --git a/services/awsconfig/multi_region_test.go b/services/awsconfig/multi_region_test.go index d8c40df6eb..5a57667fc9 100644 --- a/services/awsconfig/multi_region_test.go +++ b/services/awsconfig/multi_region_test.go @@ -2,7 +2,7 @@ package awsconfig_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/awsconfig/persistence.go b/services/awsconfig/persistence.go index 003a287047..6100adc5f4 100644 --- a/services/awsconfig/persistence.go +++ b/services/awsconfig/persistence.go @@ -2,7 +2,7 @@ package awsconfig import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -126,8 +126,8 @@ func fromRemediationExecutionSnapshot(v *remediationExecutionSnapshot) *Remediat // rather than closing it, per the mechanical-conversion (not parity-fix) // scope of that rollout. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -222,7 +222,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // its "remediationExecutions" DTO entry in tables (gopherstack-ltj0d; // b.remediationExecutions is not on b.registry, see store_setup.go's package // doc and remediationExecutionSnapshot above). -func (b *InMemoryBackend) restoreRemediationExecutionsFromDTO(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreRemediationExecutionsFromDTO(tables map[string]jsontext.Value) error { remDTOReg := store.NewRegistry() remDTOs := store.Register(remDTOReg, "remediationExecutions", store.New(remediationExecutionSnapshotKey)) diff --git a/services/awsconfig/select_query.go b/services/awsconfig/select_query.go index 3efd7ef1df..8caf636af4 100644 --- a/services/awsconfig/select_query.go +++ b/services/awsconfig/select_query.go @@ -1,11 +1,13 @@ package awsconfig import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // select_query.go implements a minimal evaluator for the SQL-like query @@ -170,7 +172,7 @@ func evaluateSelectQuery(items []*ResourceConfigItem, expression string) ([]stri for _, item := range sorted { var config map[string]any - _ = json.Unmarshal([]byte(item.Configuration), &config) + _ = json.Unmarshal([]byte(item.Configuration), &config, jsonx.V1()) if config == nil { config = map[string]any{} @@ -207,7 +209,7 @@ func marshalSelectedFields(item *ResourceConfigItem, config map[string]any, fiel } } - encoded, err := json.Marshal(row) + encoded, err := json.Marshal(row, jsonx.V1()) if err != nil { return "", false } diff --git a/services/awsconfig/select_query_test.go b/services/awsconfig/select_query_test.go index 12c9ed08db..c56fde5c28 100644 --- a/services/awsconfig/select_query_test.go +++ b/services/awsconfig/select_query_test.go @@ -1,12 +1,13 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/v2" "sort" "testing" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -80,7 +81,7 @@ func TestSelectResourceConfig_SelectsRequestedFields(t *testing.T) { } var row map[string]any - if unmarshalErr := json.Unmarshal([]byte(results[0]), &row); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal([]byte(results[0]), &row, jsonx.V1()); unmarshalErr != nil { t.Fatalf("unmarshal row: %v", unmarshalErr) } @@ -116,7 +117,7 @@ func extractResourceIDs(t *testing.T, results []string) []string { for _, r := range results { var row map[string]any - if err := json.Unmarshal([]byte(r), &row); err != nil { + if err := json.Unmarshal([]byte(r), &row, jsonx.V1()); err != nil { t.Fatalf("unmarshal row: %v", err) } diff --git a/services/awsconfig/tags_test.go b/services/awsconfig/tags_test.go index fdd7773579..e6aae1b14d 100644 --- a/services/awsconfig/tags_test.go +++ b/services/awsconfig/tags_test.go @@ -1,12 +1,14 @@ package awsconfig_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/awsconfig" ) @@ -122,7 +124,7 @@ func TestCreationTags_Decoded(t *testing.T) { var out struct { Arn string `json:"Arn"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) return out.Arn }, diff --git a/services/azureappconfig/http_test.go b/services/azureappconfig/http_test.go index c5283516d3..1b1acd452b 100644 --- a/services/azureappconfig/http_test.go +++ b/services/azureappconfig/http_test.go @@ -1,7 +1,7 @@ package azureappconfig_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const av = "api-version=2023-11-01" @@ -22,7 +24,7 @@ func readJSON(t *testing.T, resp *http.Response) map[string]any { var out map[string]any - require.NoError(t, json.Unmarshal(raw, &out), string(raw)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1()), string(raw)) return out } diff --git a/services/azureappconfig/server.go b/services/azureappconfig/server.go index f095c1eb74..2273f31114 100644 --- a/services/azureappconfig/server.go +++ b/services/azureappconfig/server.go @@ -2,7 +2,7 @@ package azureappconfig import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net" "net/http" @@ -11,6 +11,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var apiVersions = map[string]bool{ //nolint:gochecknoglobals // supported api-version set, read-only @@ -156,7 +158,7 @@ func (h *Handler) fail(req *request, err error) error { } func mustJSON(v any) []byte { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return []byte("{}") } @@ -270,7 +272,7 @@ func decodeBody(body []byte, out any) error { return nil } - if err := json.Unmarshal(body, out); err != nil { + if err := json.Unmarshal(body, out, jsonx.V1()); err != nil { return ErrInvalidArgument } diff --git a/services/azurearm/aci_wire.go b/services/azurearm/aci_wire.go index bc98abdeca..b172f1ed8a 100644 --- a/services/azurearm/aci_wire.go +++ b/services/azurearm/aci_wire.go @@ -3,11 +3,14 @@ package azurearm import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -67,7 +70,7 @@ type aciContainerPropsWire struct { VolumeMounts []aciMountWire `json:"volumeMounts,omitempty"` LivenessProbe *aciProbeWire `json:"livenessProbe,omitempty"` ReadinessProbe *aciProbeWire `json:"readinessProbe,omitempty"` - InstanceView json.RawMessage `json:"instanceView,omitempty"` + InstanceView jsontext.Value `json:"instanceView,omitempty"` } type aciContainerWire struct { @@ -97,7 +100,7 @@ type aciAzureFileWire struct { } type aciVolumeWire struct { - EmptyDir *json.RawMessage `json:"emptyDir,omitempty"` + EmptyDir *jsontext.Value `json:"emptyDir,omitempty"` Secret map[string]string `json:"secret,omitempty"` AzureFile *aciAzureFileWire `json:"azureFile,omitempty"` GitRepo *aciGitRepoWire `json:"gitRepo,omitempty"` @@ -114,7 +117,7 @@ type aciPropsWire struct { Priority string `json:"priority,omitempty"` ProvisioningState string `json:"provisioningState,omitempty"` OSType string `json:"osType,omitempty"` - InstanceView json.RawMessage `json:"instanceView,omitempty"` + InstanceView jsontext.Value `json:"instanceView,omitempty"` Volumes []aciVolumeWire `json:"volumes,omitempty"` InitContainers []aciInitWire `json:"initContainers,omitempty"` SubnetIDs []aciSubnetWire `json:"subnetIds,omitempty"` @@ -134,15 +137,14 @@ func aciBad(format string, args ...any) error { func parseACIProps(raw any) (aciPropsWire, error) { var p aciPropsWire - buf, err := json.Marshal(raw) + buf, err := json.Marshal(raw, jsonx.V1()) if err != nil { return p, aciBad("properties: %v", err) } - dec := json.NewDecoder(bytes.NewReader(buf)) - dec.DisallowUnknownFields() + dec := jsontext.NewDecoder(bytes.NewReader(buf)) - if err = dec.Decode(&p); err != nil { + if err = json.UnmarshalDecode(dec, &p, jsonx.V1(), json.RejectUnknownMembers(true)); err != nil { if name, ok := strings.CutPrefix(err.Error(), "json: unknown field "); ok { return p, aciUnsupported("property " + name) } diff --git a/services/azurearm/aci_wire_ext.go b/services/azurearm/aci_wire_ext.go index 2eb689bb17..c8bcf00f09 100644 --- a/services/azurearm/aci_wire_ext.go +++ b/services/azurearm/aci_wire_ext.go @@ -1,7 +1,7 @@ package azurearm import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/netip" "strings" @@ -47,11 +47,11 @@ type aciProbeWire struct { } type aciInitPropsWire struct { - Image string `json:"image"` - Command []string `json:"command,omitempty"` - EnvironmentVariables []aciEnvWire `json:"environmentVariables,omitempty"` - VolumeMounts []aciMountWire `json:"volumeMounts,omitempty"` - InstanceView json.RawMessage `json:"instanceView,omitempty"` + Image string `json:"image"` + Command []string `json:"command,omitempty"` + EnvironmentVariables []aciEnvWire `json:"environmentVariables,omitempty"` + VolumeMounts []aciMountWire `json:"volumeMounts,omitempty"` + InstanceView jsontext.Value `json:"instanceView,omitempty"` } type aciInitWire struct { diff --git a/services/azurearm/aks_sdk_test.go b/services/azurearm/aks_sdk_test.go index d3b7dd46e2..62880fd3e4 100644 --- a/services/azurearm/aks_sdk_test.go +++ b/services/azurearm/aks_sdk_test.go @@ -3,7 +3,7 @@ package azurearm_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -1089,7 +1090,7 @@ func TestAKSSubnetReferences(t *testing.T) { func mustJSON(t *testing.T, v any) string { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/azurearm/apim_asyncop_test.go b/services/azurearm/apim_asyncop_test.go index e6f5f544a9..13967299b2 100644 --- a/services/azurearm/apim_asyncop_test.go +++ b/services/azurearm/apim_asyncop_test.go @@ -1,7 +1,7 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" @@ -16,6 +16,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const apimSvcBody = `{"location":"westus","sku":{"name":"Developer","capacity":1},` + @@ -53,7 +55,7 @@ func statusDoc(t *testing.T, body string) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &m)) + require.NoError(t, json.Unmarshal([]byte(body), &m, jsonx.V1())) return m } diff --git a/services/azurearm/apim_gateway_test.go b/services/azurearm/apim_gateway_test.go index c4e8c90cf2..668cecc2d0 100644 --- a/services/azurearm/apim_gateway_test.go +++ b/services/azurearm/apim_gateway_test.go @@ -2,7 +2,8 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net" "net/http" @@ -15,6 +16,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type echoReply struct { @@ -33,7 +36,7 @@ func newEchoBackend(t *testing.T) *httptest.Server { b, _ := io.ReadAll(r.Body) w.Header().Set("X-Backend", "yes") w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(echoReply{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), echoReply{ Method: r.Method, Path: r.URL.Path, Query: r.URL.RawQuery, @@ -101,7 +104,7 @@ func (e *gwEnv) call(t *testing.T, method, path, body string, extra map[string]s func (e *gwEnv) policy(t *testing.T, scope, doc string, wantStatus int) string { t.Helper() - b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": doc}}) + b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": doc}}, jsonx.V1()) require.NoError(t, err) status, body, _ := e.raw(t, http.MethodPut, e.armPath(scope+"/policies/policy"), string(b), nil) @@ -129,7 +132,7 @@ func decodeEcho(t *testing.T, body string) echoReply { var r echoReply - require.NoError(t, json.Unmarshal([]byte(body), &r), body) + require.NoError(t, json.Unmarshal([]byte(body), &r, jsonx.V1()), body) return r } diff --git a/services/azurearm/apim_policy_test.go b/services/azurearm/apim_policy_test.go index 7f63067614..c8ed9736ef 100644 --- a/services/azurearm/apim_policy_test.go +++ b/services/azurearm/apim_policy_test.go @@ -1,7 +1,7 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAPIMPolicy_Validation(t *testing.T) { @@ -142,7 +144,10 @@ func TestAPIMPolicy_Validation(t *testing.T) { format = "xml" } - b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": format, "value": tc.doc}}) + b, err := json.Marshal( + map[string]any{"properties": map[string]any{"format": format, "value": tc.doc}}, + jsonx.V1(), + ) require.NoError(t, err) status, body, _ := s.raw(t, http.MethodPut, s.armPath("/apis/echo/policies/policy"), string(b), nil) @@ -713,7 +718,7 @@ func TestAPIMGateway_PolicyInheritance(t *testing.T) { e := newGWEnv(t) svcDoc := policyDoc(`s`, "", "") - b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": svcDoc}}) + b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": svcDoc}}, jsonx.V1()) require.NoError(t, err) status, body, _ := e.raw(t, http.MethodPut, e.armPath("/policies/policy"), string(b), nil) diff --git a/services/azurearm/apim_sources_test.go b/services/azurearm/apim_sources_test.go index 94ad5cfbe7..df8da8ad04 100644 --- a/services/azurearm/apim_sources_test.go +++ b/services/azurearm/apim_sources_test.go @@ -1,7 +1,7 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -12,6 +12,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func serviceBody(sku string, capacity int) string { @@ -26,7 +28,7 @@ func decodeProps(t *testing.T, body string) map[string]any { Properties map[string]any `json:"properties"` } - require.NoError(t, json.Unmarshal([]byte(body), &out), body) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1()), body) return out.Properties } @@ -122,7 +124,10 @@ func TestAPIMLimits_PolicySize(t *testing.T) { s.putAPI(t, "echo", "echo", "") doc := "" + strings.Repeat("", tc.bases) + "" - b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": doc}}) + b, err := json.Marshal( + map[string]any{"properties": map[string]any{"format": "xml", "value": doc}}, + jsonx.V1(), + ) require.NoError(t, err) status, body, _ := s.raw(t, http.MethodPut, s.armPath("/apis/echo/policies/policy"), string(b), nil) @@ -581,7 +586,10 @@ func TestAPIMPolicy_ReferenceRules(t *testing.T) { s.devService(t) s.putAPI(t, "echo", "echo", "") - b, err := json.Marshal(map[string]any{"properties": map[string]any{"format": "xml", "value": tc.doc}}) + b, err := json.Marshal( + map[string]any{"properties": map[string]any{"format": "xml", "value": tc.doc}}, + jsonx.V1(), + ) require.NoError(t, err) status, body, _ := s.raw(t, http.MethodPut, s.armPath(tc.scope+"/policies/policy"), string(b), nil) diff --git a/services/azurearm/directory_test.go b/services/azurearm/directory_test.go index 5ac05623bb..225dee39c3 100644 --- a/services/azurearm/directory_test.go +++ b/services/azurearm/directory_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -108,7 +109,7 @@ func TestTokenEndpointClientAuthentication(t *testing.T) { require.Equal(t, tt.status, rec.Code, rec.Body.String()) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) if tt.wantCode != 0 { assert.Equal(t, "invalid_client", body["error"]) diff --git a/services/azurearm/export_test.go b/services/azurearm/export_test.go index fa18fb2638..3f29d2430a 100644 --- a/services/azurearm/export_test.go +++ b/services/azurearm/export_test.go @@ -1,11 +1,13 @@ package azurearm import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // This file exposes internals needed by the blackbox (azurearm_test) test @@ -52,7 +54,7 @@ func NewTestHandlerWithCosmos(dataPlane CosmosResources, settings Settings) *Han // EventHubSnapshotJSON marshals the provider's ARM-side snapshot. func EventHubSnapshotJSON(p *EventHubProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -63,7 +65,7 @@ func EventHubRestoreJSON(t *testing.T, p *EventHubProvider, data []byte) { var s eventHubSnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } @@ -78,7 +80,7 @@ func NewTestHandlerWithEventGrid(dataPlane EventGrid, settings Settings) *Handle // EventGridSnapshotJSON marshals the provider's ARM-side snapshot. func EventGridSnapshotJSON(p *EventGridProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -89,7 +91,7 @@ func EventGridRestoreJSON(t *testing.T, p *EventGridProvider, data []byte) { var s eventGridSnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } @@ -104,7 +106,7 @@ func NewTestHandlerWithAppConfig(dataPlane AppConfig, settings Settings) *Handle // AppConfigSnapshotJSON marshals the provider's ARM-side snapshot. func AppConfigSnapshotJSON(p *AppConfigProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -115,7 +117,7 @@ func AppConfigRestoreJSON(t *testing.T, p *AppConfigProvider, data []byte) { var s appConfigSnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } @@ -144,7 +146,7 @@ func NewTestHandlerWithContainerRegistry(dataPlane ContainerRegistries, settings // ContainerRegistrySnapshotJSON marshals the provider's ARM-side snapshot. func ContainerRegistrySnapshotJSON(p *ContainerRegistryProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -155,7 +157,7 @@ func ContainerRegistryRestoreJSON(t *testing.T, p *ContainerRegistryProvider, da var s containerRegistrySnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } @@ -172,7 +174,7 @@ func NewTestHandlerWithRedis(dataPlane Redis, now func() time.Time) (*Handler, * // RedisSnapshotJSON marshals the provider's ARM-side snapshot. func RedisSnapshotJSON(p *RedisProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -183,7 +185,7 @@ func RedisRestoreJSON(t *testing.T, p *RedisProvider, data []byte) { var s redisSnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } @@ -199,7 +201,7 @@ func NewTestHandlerWithSQL(dataPlane SQL) (*Handler, *SQLProvider) { // SQLSnapshotJSON marshals the provider's ARM-side snapshot. func SQLSnapshotJSON(p *SQLProvider) []byte { - b, _ := json.Marshal(p.snapshot()) + b, _ := json.Marshal(p.snapshot(), jsonx.V1()) return b } @@ -210,6 +212,6 @@ func SQLRestoreJSON(t *testing.T, p *SQLProvider, data []byte) { var s sqlSnapshot - require.NoError(t, json.Unmarshal(data, &s)) + require.NoError(t, json.Unmarshal(data, &s, jsonx.V1())) p.restore(&s) } diff --git a/services/azurearm/flexdb_mysql_test.go b/services/azurearm/flexdb_mysql_test.go index 53a43f1307..d515bb0920 100644 --- a/services/azurearm/flexdb_mysql_test.go +++ b/services/azurearm/flexdb_mysql_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const myNS = "Microsoft.DBforMySQL" @@ -87,7 +89,7 @@ func TestMySQLFlexServerCRUD(t *testing.T) { status, raw := s.raw(t, http.MethodGet, flexServerPath(myNS, "my-crud"), "", flexAPIMy) require.Equal(t, http.StatusOK, status) - enc, err := json.Marshal(raw) + enc, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(enc), flexPassword) assert.NotContains(t, string(enc), "administratorLoginPassword") diff --git a/services/azurearm/flexdb_pg_test.go b/services/azurearm/flexdb_pg_test.go index df044fc44a..2667172c40 100644 --- a/services/azurearm/flexdb_pg_test.go +++ b/services/azurearm/flexdb_pg_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -12,6 +12,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const pgNS = "Microsoft.DBforPostgreSQL" @@ -71,7 +73,7 @@ func TestPGFlexServerCRUD(t *testing.T) { status, raw := s.raw(t, http.MethodGet, flexServerPath(pgNS, "pg-crud"), "", flexAPIPG) require.Equal(t, http.StatusOK, status) - enc, err := json.Marshal(raw) + enc, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(enc), flexPassword) assert.NotContains(t, string(enc), "administratorLoginPassword") diff --git a/services/azurearm/flexdb_stack_test.go b/services/azurearm/flexdb_stack_test.go index 9c9baf19de..731dd95453 100644 --- a/services/azurearm/flexdb_stack_test.go +++ b/services/azurearm/flexdb_stack_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -18,6 +18,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -130,7 +131,7 @@ func (s *flexStack) raw(t *testing.T, method, path, body, apiVersion string) (in var out map[string]any if len(data) > 0 { - _ = json.Unmarshal(data, &out) + _ = json.Unmarshal(data, &out, jsonx.V1()) } return resp.StatusCode, out diff --git a/services/azurearm/handler.go b/services/azurearm/handler.go index c6e8a5854e..e74892e425 100644 --- a/services/azurearm/handler.go +++ b/services/azurearm/handler.go @@ -3,7 +3,8 @@ package azurearm import ( "context" "crypto/tls" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net" @@ -18,6 +19,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/aadauth" "github.com/blackbirdworks/gopherstack/pkgs/devtls" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -568,7 +570,7 @@ func decodeJSONBody(r *http.Request) (map[string]any, error) { } var body map[string]any - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &body, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidRequestBody, err) } diff --git a/services/azurearm/handler_test.go b/services/azurearm/handler_test.go index c7fe8c3862..07e5b3c490 100644 --- a/services/azurearm/handler_test.go +++ b/services/azurearm/handler_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/tls" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "net" @@ -23,6 +23,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/aadauth" "github.com/blackbirdworks/gopherstack/pkgs/devtls" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -74,7 +75,7 @@ func doRequest(t *testing.T, h *azurearm.Handler, method, path string, body []by var decoded map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &decoded) + _ = json.Unmarshal(rec.Body.Bytes(), &decoded, jsonx.V1()) } return rec.Code, decoded @@ -96,7 +97,7 @@ func TestHandler_MetadataEndpoints(t *testing.T) { var doc map[string]any - require.NoError(t, json.Unmarshal(body, &doc)) + require.NoError(t, json.Unmarshal(body, &doc, jsonx.V1())) assert.Equal(t, "gopherstack", doc["name"]) } @@ -150,7 +151,7 @@ func TestHandler_TokenEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Bearer", resp["token_type"]) assert.NotEmpty(t, resp["access_token"]) }) @@ -512,7 +513,7 @@ func TestHandler_MetadataEndpoints_HonorsRequestHostPort(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var doc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1())) assert.Equal(t, "https://localhost:18006/", doc["resourceManagerEndpoint"]) } diff --git a/services/azurearm/rp_aks.go b/services/azurearm/rp_aks.go index 232f689c2f..22b772d06b 100644 --- a/services/azurearm/rp_aks.go +++ b/services/azurearm/rp_aks.go @@ -4,12 +4,13 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "sync" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -122,7 +123,7 @@ func aksResourceID(sub, rg string, types, names []string) ResourceID { func aksEtag(t time.Time) string { return acaEtag(t) } func aksJSON(v any) string { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "" } diff --git a/services/azurearm/rp_aks_persist.go b/services/azurearm/rp_aks_persist.go index 5fdf1bbc2d..f3c28ce45e 100644 --- a/services/azurearm/rp_aks_persist.go +++ b/services/azurearm/rp_aks_persist.go @@ -1,6 +1,10 @@ package azurearm -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type containerServiceSnapshot struct { Clusters map[string]*storedAKSCluster `json:"clusters"` @@ -10,13 +14,13 @@ func (p *ContainerServiceProvider) snapshot() *containerServiceSnapshot { p.mu.RLock("snapshot") defer p.mu.RUnlock() - buf, err := json.Marshal(&containerServiceSnapshot{Clusters: p.clusters}) + buf, err := json.Marshal(&containerServiceSnapshot{Clusters: p.clusters}, jsonx.V1()) if err != nil { return &containerServiceSnapshot{} } var out containerServiceSnapshot - if err = json.Unmarshal(buf, &out); err != nil { + if err = json.Unmarshal(buf, &out, jsonx.V1()); err != nil { return &containerServiceSnapshot{} } diff --git a/services/azurearm/rp_apim_exec.go b/services/azurearm/rp_apim_exec.go index 80b9abf393..79b6282606 100644 --- a/services/azurearm/rp_apim_exec.go +++ b/services/azurearm/rp_apim_exec.go @@ -3,7 +3,7 @@ package azurearm import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // gwState carries one gateway request through the policy pipeline. @@ -125,7 +127,7 @@ func (g *gwState) subAll(in []string) []string { } func (g *gwState) fail(status int, msg string) { - body, _ := json.Marshal(map[string]any{apimCStatusCode: status, fieldMessage: msg}) + body, _ := json.Marshal(map[string]any{apimCStatusCode: status, fieldMessage: msg}, jsonx.V1()) g.setResponse(status, http.Header{apimCContentType: {"application/json"}}, body) g.short = true } @@ -373,7 +375,7 @@ func firstExample(examples map[string]any) ([]byte, bool) { return []byte(s), true } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) return b, err == nil } diff --git a/services/azurearm/rp_apim_gateway.go b/services/azurearm/rp_apim_gateway.go index 0b0854217b..78042df730 100644 --- a/services/azurearm/rp_apim_gateway.go +++ b/services/azurearm/rp_apim_gateway.go @@ -1,7 +1,8 @@ package azurearm import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net" "net/http" @@ -10,6 +11,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -160,7 +162,10 @@ func gatewayTarget(r *http.Request) (string, string, bool) { func writeGW(w http.ResponseWriter, status int, msg string) { w.Header().Set(apimCContentType, "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]any{apimCStatusCode: status, fieldMessage: msg}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]any{apimCStatusCode: status, fieldMessage: msg}, + ) } // ServeGateway answers a request addressed to a service's gateway (Host .azure-api.net, or the path prefix diff --git a/services/azurearm/rp_apim_import.go b/services/azurearm/rp_apim_import.go index 0009fc36df..53a9762ccc 100644 --- a/services/azurearm/rp_apim_import.go +++ b/services/azurearm/rp_apim_import.go @@ -1,11 +1,13 @@ package azurearm import ( - "encoding/json" + "encoding/json/v2" "regexp" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -56,7 +58,7 @@ func parseAPISpec(format, value string) (*apimAPISpec, error) { } var doc map[string]any - if err := json.Unmarshal([]byte(value), &doc); err != nil { + if err := json.Unmarshal([]byte(value), &doc, jsonx.V1()); err != nil { return nil, apimInvalid("The API definition is not valid JSON (YAML is not supported): %v", err) } diff --git a/services/azurearm/rp_apim_persist.go b/services/azurearm/rp_apim_persist.go index d8266bc774..9072b9d9eb 100644 --- a/services/azurearm/rp_apim_persist.go +++ b/services/azurearm/rp_apim_persist.go @@ -1,24 +1,30 @@ package azurearm -import "encoding/json" +import ( + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // apimSnapshot is the persisted state of the API Management provider, subscription keys, secret named values and // backend credentials included (as with the other azurearm credentials). type apimSnapshot struct { - Services json.RawMessage `json:"services,omitempty"` - Operations json.RawMessage `json:"operations,omitempty"` + Services jsontext.Value `json:"services,omitempty"` + Operations jsontext.Value `json:"operations,omitempty"` } func (p *APIMProvider) snapshot() *apimSnapshot { p.mu.RLock(apimCSnapshot) defer p.mu.RUnlock() - raw, err := json.Marshal(p.svcs) + raw, err := json.Marshal(p.svcs, jsonx.V1()) if err != nil { return &apimSnapshot{} } - ops, err := json.Marshal(p.ops) + ops, err := json.Marshal(p.ops, jsonx.V1()) if err != nil { return &apimSnapshot{Services: raw} } @@ -39,7 +45,7 @@ func (p *APIMProvider) restore(snap *apimSnapshot) { } var ops map[string]*apimOp - if err := json.Unmarshal(snap.Operations, &ops); err == nil { + if err := json.Unmarshal(snap.Operations, &ops, jsonx.V1()); err == nil { for id, o := range ops { if o != nil { p.ops[id] = o @@ -48,7 +54,7 @@ func (p *APIMProvider) restore(snap *apimSnapshot) { } var svcs map[string]*apimService - if err := json.Unmarshal(snap.Services, &svcs); err != nil { + if err := json.Unmarshal(snap.Services, &svcs, jsonx.V1()); err != nil { return } diff --git a/services/azurearm/rp_authorization.go b/services/azurearm/rp_authorization.go index 0d0daaf1be..532609235c 100644 --- a/services/azurearm/rp_authorization.go +++ b/services/azurearm/rp_authorization.go @@ -3,7 +3,7 @@ package azurearm import ( "context" _ "embed" // role and policy catalogs - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "regexp" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -187,14 +188,14 @@ func NewAuthorizationProvider(tenantID string, msi *ManagedIdentityProvider) *Au p.resetMaps() var roles []*builtinRole - if err := json.Unmarshal(authRolesJSON, &roles); err == nil { + if err := json.Unmarshal(authRolesJSON, &roles, jsonx.V1()); err == nil { for _, r := range roles { p.builtinRoles[strings.ToLower(r.Name)] = r } } var cat builtinPolicyCatalog - if err := json.Unmarshal(authPoliciesJSON, &cat); err == nil { + if err := json.Unmarshal(authPoliciesJSON, &cat, jsonx.V1()); err == nil { for i := range cat.Definitions { p.builtinPols[strings.ToLower(cat.Definitions[i].Name)] = &cat.Definitions[i] } diff --git a/services/azurearm/rp_authorization_sdk_test.go b/services/azurearm/rp_authorization_sdk_test.go index fb5658daf7..375756d0a5 100644 --- a/services/azurearm/rp_authorization_sdk_test.go +++ b/services/azurearm/rp_authorization_sdk_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -126,7 +127,7 @@ func (s *authStack) raw(t *testing.T, method, path, body string) (int, map[strin require.NoError(t, err) var out map[string]any - _ = json.Unmarshal(data, &out) + _ = json.Unmarshal(data, &out, jsonx.V1()) return resp.StatusCode, out } diff --git a/services/azurearm/rp_compute_ext_test.go b/services/azurearm/rp_compute_ext_test.go index 039f814761..5c814e69f0 100644 --- a/services/azurearm/rp_compute_ext_test.go +++ b/services/azurearm/rp_compute_ext_test.go @@ -1,13 +1,15 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const extSecret = "s3cr3t-protected-value" @@ -169,7 +171,7 @@ func TestComputeSDK_ExtensionSecretsNotPersisted(t *testing.T) { require.NoError(t, err) snap := src.handler.Snapshot(t.Context()) - raw, err := json.Marshal(snap) + raw, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(raw), extSecret) @@ -374,7 +376,7 @@ func TestComputeSDK_ManagedRunCommands(t *testing.T) { assert.Equal(t, "Succeeded", *got.Properties.ProvisioningState) assert.Equal(t, "Unknown", string(*got.Properties.InstanceView.ExecutionState)) - raw, err := json.Marshal(s.handler.Snapshot(t.Context())) + raw, err := json.Marshal(s.handler.Snapshot(t.Context()), jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(raw), extSecret) diff --git a/services/azurearm/rp_compute_sets_test.go b/services/azurearm/rp_compute_sets_test.go index dcaddafe07..f1b392c5e9 100644 --- a/services/azurearm/rp_compute_sets_test.go +++ b/services/azurearm/rp_compute_sets_test.go @@ -1,7 +1,8 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "strings" @@ -10,6 +11,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v9" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestComputeSDK_AvailabilitySets(t *testing.T) { @@ -462,7 +465,7 @@ func TestComputeSDK_ListPagingAndPatch(t *testing.T) { } var page listPage - require.NoError(t, json.NewDecoder(resp.Body).Decode(&page)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &page, jsonx.V1())) assert.Len(t, page.Value, 2) require.NotEmpty(t, page.NextLink) @@ -470,7 +473,7 @@ func TestComputeSDK_ListPagingAndPatch(t *testing.T) { resp = s.rawDo(t, http.MethodGet, next, "") var last listPage - require.NoError(t, json.NewDecoder(resp.Body).Decode(&last)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &last, jsonx.V1())) assert.Len(t, last.Value, 1) assert.Empty(t, last.NextLink) diff --git a/services/azurearm/rp_containerapps_app.go b/services/azurearm/rp_containerapps_app.go index cfa66f75a4..4ab10ed62b 100644 --- a/services/azurearm/rp_containerapps_app.go +++ b/services/azurearm/rp_containerapps_app.go @@ -2,7 +2,7 @@ package azurearm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -10,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -88,7 +89,7 @@ func acaTemplateKey(t map[string]any) string { c := acaClone(t) delete(c, fieldRevSuffix) - b, _ := json.Marshal(c) + b, _ := json.Marshal(c, jsonx.V1()) return string(b) } diff --git a/services/azurearm/rp_containerapps_persist.go b/services/azurearm/rp_containerapps_persist.go index 1d44920f27..7ee0979aa4 100644 --- a/services/azurearm/rp_containerapps_persist.go +++ b/services/azurearm/rp_containerapps_persist.go @@ -1,6 +1,10 @@ package azurearm -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) type containerAppsSnapshot struct { Envs map[string]*storedACAEnv `json:"envs"` @@ -23,13 +27,13 @@ func (p *ContainerAppsProvider) snapshot() *containerAppsSnapshot { snap := &containerAppsSnapshot{Envs: p.envs, Apps: p.apps, Jobs: p.jobs} - buf, err := json.Marshal(snap) + buf, err := json.Marshal(snap, jsonx.V1()) if err != nil { return &containerAppsSnapshot{} } var out containerAppsSnapshot - if err = json.Unmarshal(buf, &out); err != nil { + if err = json.Unmarshal(buf, &out, jsonx.V1()); err != nil { return &containerAppsSnapshot{} } diff --git a/services/azurearm/rp_containerapps_revision.go b/services/azurearm/rp_containerapps_revision.go index b830e53e7b..b1b5b8c814 100644 --- a/services/azurearm/rp_containerapps_revision.go +++ b/services/azurearm/rp_containerapps_revision.go @@ -3,7 +3,7 @@ package azurearm import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "net/http" @@ -91,7 +91,7 @@ func acaVolumeWires(t map[string]any, env *storedACAEnv, secrets map[string]stri switch strings.ToLower(strOf(v, "storageType")) { case "", "emptydir": - raw := json.RawMessage("{}") + raw := jsontext.Value("{}") w.EmptyDir = &raw case "secret": w.Secret = acaSecretVolume(acaList(v[fieldSecrets]), secrets) diff --git a/services/azurearm/rp_containerapps_wire.go b/services/azurearm/rp_containerapps_wire.go index 651889833a..31c0ee8273 100644 --- a/services/azurearm/rp_containerapps_wire.go +++ b/services/azurearm/rp_containerapps_wire.go @@ -1,13 +1,15 @@ package azurearm import ( - "encoding/json" + "encoding/json/v2" "math" "net/http" "net/url" "regexp" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -111,13 +113,13 @@ func acaClone(m map[string]any) map[string]any { return nil } - buf, err := json.Marshal(m) + buf, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil } var out map[string]any - if err = json.Unmarshal(buf, &out); err != nil { + if err = json.Unmarshal(buf, &out, jsonx.V1()); err != nil { return nil } diff --git a/services/azurearm/rp_cosmos_sql.go b/services/azurearm/rp_cosmos_sql.go index 75b2c96684..d6f50f9251 100644 --- a/services/azurearm/rp_cosmos_sql.go +++ b/services/azurearm/rp_cosmos_sql.go @@ -1,12 +1,14 @@ package azurearm import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type cosmosChildKind int @@ -608,7 +610,7 @@ func rawJSON(v any) []byte { return nil } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil } @@ -618,7 +620,7 @@ func rawJSON(v any) []byte { func jsonOr(raw []byte, def map[string]any) map[string]any { var m map[string]any - if len(raw) > 0 && json.Unmarshal(raw, &m) == nil && m != nil { + if len(raw) > 0 && json.Unmarshal(raw, &m, jsonx.V1()) == nil && m != nil { return m } diff --git a/services/azurearm/rp_cosmos_test.go b/services/azurearm/rp_cosmos_test.go index e5d150eaf7..0e264e1c53 100644 --- a/services/azurearm/rp_cosmos_test.go +++ b/services/azurearm/rp_cosmos_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "sort" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -234,7 +235,7 @@ func cosmosDo(t *testing.T, h *azurearm.Handler, method, path string, body any) if body != nil { var err error - raw, err = json.Marshal(body) + raw, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -734,9 +735,9 @@ func TestCosmos_LegacyThroughputSnapshotMigrates(t *testing.T) { putCosmosAccount(t, h, map[string]any{}) var env map[string]any - require.NoError(t, json.Unmarshal(h.Snapshot(context.Background()), &env)) + require.NoError(t, json.Unmarshal(h.Snapshot(context.Background()), &env, jsonx.V1())) inject(t, env, map[string]any{"acct1/db1": map[string]any{"autoscaleMax": 4000}}) - raw, err := json.Marshal(env) + raw, err := json.Marshal(env, jsonx.V1()) require.NoError(t, err) h2, fake2 := newCosmosHandler() diff --git a/services/azurearm/rp_identity_sdk_test.go b/services/azurearm/rp_identity_sdk_test.go index 0c4c2a28ef..94e2ecc94e 100644 --- a/services/azurearm/rp_identity_sdk_test.go +++ b/services/azurearm/rp_identity_sdk_test.go @@ -1,7 +1,7 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -387,7 +388,7 @@ func TestIdentitySDK_WebSiteLegacySnapshot(t *testing.T) { require.NoError(t, err) var snap map[string]any - require.NoError(t, json.Unmarshal(s.handler.Snapshot(t.Context()), &snap)) + require.NoError(t, json.Unmarshal(s.handler.Snapshot(t.Context()), &snap, jsonx.V1())) sites, _ := snap["web"].(map[string]any)["sites"].(map[string]any) require.Len(t, sites, 1) @@ -398,7 +399,7 @@ func TestIdentitySDK_WebSiteLegacySnapshot(t *testing.T) { m["identity"] = map[string]any{"type": "SystemAssigned"} } - legacy, err := json.Marshal(snap) + legacy, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) dst := newCmpStack(t) diff --git a/services/azurearm/rp_keyvault_deleted_test.go b/services/azurearm/rp_keyvault_deleted_test.go index 1ed3f290b6..c4ddb037a6 100644 --- a/services/azurearm/rp_keyvault_deleted_test.go +++ b/services/azurearm/rp_keyvault_deleted_test.go @@ -1,7 +1,7 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -24,7 +25,7 @@ func newKVHandler() (*azurearm.Handler, *fakeKeyVaults) { func putVault(t *testing.T, h *azurearm.Handler, name string, props map[string]any) int { t.Helper() - raw, err := json.Marshal(map[string]any{"location": "westus", "properties": props}) + raw, err := json.Marshal(map[string]any{"location": "westus", "properties": props}, jsonx.V1()) require.NoError(t, err) status, _ := doRequest(t, h, "PUT", @@ -177,7 +178,7 @@ func TestKeyVault_CheckNameAvailability(t *testing.T) { require.Less(t, putVault(t, h, "gone1", map[string]any{}), 300) deleteVault(t, h, "gone1") - raw, err := json.Marshal(map[string]any{"name": tt.check, "type": "Microsoft.KeyVault/vaults"}) + raw, err := json.Marshal(map[string]any{"name": tt.check, "type": "Microsoft.KeyVault/vaults"}, jsonx.V1()) require.NoError(t, err) st, body := doRequest(t, h, "POST", diff --git a/services/azurearm/rp_monitor_test.go b/services/azurearm/rp_monitor_test.go index aaae7e257b..b85a8f5e4f 100644 --- a/services/azurearm/rp_monitor_test.go +++ b/services/azurearm/rp_monitor_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -97,7 +98,7 @@ func newMonitorHandler(t *testing.T, dp azurearm.MonitorLogs) *azurearm.Handler func monitorDo(t *testing.T, h *azurearm.Handler, method, path string, body any) (int, map[string]any) { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(method, path, bytes.NewReader(raw)) @@ -106,7 +107,7 @@ func monitorDo(t *testing.T, h *azurearm.Handler, method, path string, body any) newEchoServer(h).ServeHTTP(rec, req) var out map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) return rec.Code, out } diff --git a/services/azurearm/rp_network_pe_sdk_test.go b/services/azurearm/rp_network_pe_sdk_test.go index f19c896062..dfe3d23222 100644 --- a/services/azurearm/rp_network_pe_sdk_test.go +++ b/services/azurearm/rp_network_pe_sdk_test.go @@ -1,7 +1,8 @@ package azurearm_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (s *netStack) rawJSON(t *testing.T, method, path, body string) (int, map[string]any) { @@ -18,7 +21,7 @@ func (s *netStack) rawJSON(t *testing.T, method, path, body string) (int, map[st var out map[string]any - _ = json.NewDecoder(resp.Body).Decode(&out) + _ = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1()) return resp.StatusCode, out } diff --git a/services/azurearm/rp_network_sdk_test.go b/services/azurearm/rp_network_sdk_test.go index 0268e3dee3..0880e598c3 100644 --- a/services/azurearm/rp_network_sdk_test.go +++ b/services/azurearm/rp_network_sdk_test.go @@ -2,7 +2,8 @@ package azurearm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "maps" "net/http" @@ -22,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -1126,5 +1128,5 @@ func TestNetworkSDK_Protocol(t *testing.T) { } func jsonDecode(resp *http.Response, v any) error { - return json.NewDecoder(resp.Body).Decode(v) + return json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), v, jsonx.V1()) } diff --git a/services/azurearm/rp_redis_test.go b/services/azurearm/rp_redis_test.go index 3d5a954c10..fe5a9e2f2f 100644 --- a/services/azurearm/rp_redis_test.go +++ b/services/azurearm/rp_redis_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -176,7 +177,7 @@ func (e *redisEnv) do(t *testing.T, method, path string, body any) (int, map[str var rdr *bytes.Reader if body != nil { - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rdr = bytes.NewReader(raw) @@ -191,7 +192,7 @@ func (e *redisEnv) do(t *testing.T, method, path string, body any) (int, map[str var out map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) } return rec.Code, out, rec.Header() diff --git a/services/azurearm/rp_sql_test.go b/services/azurearm/rp_sql_test.go index cb68687df8..0f9fc1023e 100644 --- a/services/azurearm/rp_sql_test.go +++ b/services/azurearm/rp_sql_test.go @@ -2,7 +2,7 @@ package azurearm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurearm" ) @@ -116,7 +117,7 @@ func newSQLEnv() *sqlEnv { func (e *sqlEnv) do(t *testing.T, method, path string, body any) (int, map[string]any, http.Header) { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) if body == nil { @@ -130,7 +131,7 @@ func (e *sqlEnv) do(t *testing.T, method, path string, body any) (int, map[strin var out map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) } return rec.Code, out, rec.Header() diff --git a/services/azurecontainerregistry/acrapi_test.go b/services/azurecontainerregistry/acrapi_test.go index cfa88f52f8..55ea379f1c 100644 --- a/services/azurecontainerregistry/acrapi_test.go +++ b/services/azurecontainerregistry/acrapi_test.go @@ -1,13 +1,14 @@ package azurecontainerregistry_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" ) @@ -179,7 +180,7 @@ func TestACRTagsAndManifests(t *testing.T) { } func patch(enabledKey string, value bool) []byte { - raw, _ := json.Marshal(map[string]any{"value": map[string]any{enabledKey: value}}) + raw, _ := json.Marshal(map[string]any{"value": map[string]any{enabledKey: value}}, jsonx.V1()) return raw } diff --git a/services/azurecontainerregistry/helpers_test.go b/services/azurecontainerregistry/helpers_test.go index 62ba571299..514c4f85e6 100644 --- a/services/azurecontainerregistry/helpers_test.go +++ b/services/azurecontainerregistry/helpers_test.go @@ -2,13 +2,14 @@ package azurecontainerregistry_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" acr "github.com/blackbirdworks/gopherstack/services/azurecontainerregistry" ) @@ -72,7 +73,7 @@ func decode(t *testing.T, body []byte) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out } diff --git a/services/azurecontainerregistry/hooks.go b/services/azurecontainerregistry/hooks.go index a41c04f1e6..a2955ea7ea 100644 --- a/services/azurecontainerregistry/hooks.go +++ b/services/azurecontainerregistry/hooks.go @@ -1,11 +1,12 @@ package azurecontainerregistry import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry" ) @@ -36,7 +37,7 @@ func loadChange(app http.Handler, r *http.Request, repo, ref string) (ManifestCh } var doc manifestDoc - if json.Unmarshal(m.Body, &doc) == nil { + if json.Unmarshal(m.Body, &doc, jsonx.V1()) == nil { c.ConfigType = doc.Config.MediaType if doc.MediaType != "" { diff --git a/services/azurecontainerregistry/serve.go b/services/azurecontainerregistry/serve.go index 3ff0dae425..6c9f848730 100644 --- a/services/azurecontainerregistry/serve.go +++ b/services/azurecontainerregistry/serve.go @@ -2,13 +2,15 @@ package azurecontainerregistry import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/url" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry" ) @@ -48,7 +50,7 @@ type errorEntry struct { } func writeJSON(w http.ResponseWriter, status int, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { status, raw = http.StatusInternalServerError, []byte(`{"errors":[{"code":"UNKNOWN","message":"encode"}]}`) } @@ -303,7 +305,7 @@ func parseForm(w http.ResponseWriter, r *http.Request) (url.Values, bool) { func decodeBody(w http.ResponseWriter, r *http.Request, out any) bool { r.Body = http.MaxBytesReader(w, r.Body, maxFormBytes) - if err := json.NewDecoder(r.Body).Decode(out); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(r.Body), out, jsonx.V1()); err != nil { writeRegistryError(w, http.StatusBadRequest, "INVALID_REQUEST", "The request body is not valid JSON.") return false diff --git a/services/azurecontainerregistry/webhooks.go b/services/azurecontainerregistry/webhooks.go index 804818e3d8..b46c47d12f 100644 --- a/services/azurecontainerregistry/webhooks.go +++ b/services/azurecontainerregistry/webhooks.go @@ -3,7 +3,7 @@ package azurecontainerregistry import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "maps" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -292,7 +293,7 @@ func (b *InMemoryBackend) deliver(reg string, w Webhook, content EventContent) { } func (b *InMemoryBackend) send(ctx context.Context, reg string, w Webhook, content EventContent) { - body, err := json.Marshal(content) + body, err := json.Marshal(content, jsonx.V1()) if err != nil { return } diff --git a/services/azurecontainerregistry/webhooks_test.go b/services/azurecontainerregistry/webhooks_test.go index 080e0acff4..6e151c3b8a 100644 --- a/services/azurecontainerregistry/webhooks_test.go +++ b/services/azurecontainerregistry/webhooks_test.go @@ -1,7 +1,7 @@ package azurecontainerregistry_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" acr "github.com/blackbirdworks/gopherstack/services/azurecontainerregistry" ) @@ -44,7 +45,7 @@ func newReceiver(t *testing.T) *receiver { var d delivery d.header = req.Header.Clone() - _ = json.Unmarshal(raw, &d.content) + _ = json.Unmarshal(raw, &d.content, jsonx.V1()) r.mu.Lock() r.got = append(r.got, d) @@ -88,7 +89,7 @@ func pushHelmChart(t *testing.T, c ocitest.Client, repo, tag string) ocitest.Ima "mediaType": "application/vnd.cncf.helm.chart.content.v1.tar+gzip", "size": len(layer), "digest": layerDigest, }}, - }) + }, jsonx.V1()) require.NoError(t, err) put := c.Do(http.MethodPut, "/v2/"+repo+"/manifests/"+tag, body, diff --git a/services/azureeventgrid/armscope_test.go b/services/azureeventgrid/armscope_test.go index 72d244398e..74b05e8fd6 100644 --- a/services/azureeventgrid/armscope_test.go +++ b/services/azureeventgrid/armscope_test.go @@ -1,13 +1,14 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/v2" "maps" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -138,7 +139,7 @@ func TestPublishResourceEvent(t *testing.T) { for _, g := range got { var ev map[string]any - require.NoError(t, json.Unmarshal([]byte(g.text), &ev)) + require.NoError(t, json.Unmarshal([]byte(g.text), &ev, jsonx.V1())) assert.Equal(t, tt.wantTopics[g.queue], ev["topic"]) assert.Equal(t, tt.ev.ResourceURI, ev["subject"]) assert.Equal(t, tt.ev.EventType, ev["eventType"]) diff --git a/services/azureeventgrid/attributes.go b/services/azureeventgrid/attributes.go index 3d06009b66..84e68a58eb 100644 --- a/services/azureeventgrid/attributes.go +++ b/services/azureeventgrid/attributes.go @@ -1,10 +1,12 @@ package azureeventgrid import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -36,13 +38,13 @@ func (d Destination) mappings() ([]attrMapping, error) { return nil, nil } - data, err := json.Marshal(raw) + data, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, invalidSpec("deliveryAttributeMappings is not valid JSON") } var out []attrMapping - if err = json.Unmarshal(data, &out); err != nil { + if err = json.Unmarshal(data, &out, jsonx.V1()); err != nil { return nil, invalidSpec("deliveryAttributeMappings is malformed: %v", err) } @@ -133,7 +135,7 @@ func headerValue(v any) string { return s } - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) return string(raw) } diff --git a/services/azureeventgrid/batching_test.go b/services/azureeventgrid/batching_test.go index 78a6bd692d..cfa2d56c58 100644 --- a/services/azureeventgrid/batching_test.go +++ b/services/azureeventgrid/batching_test.go @@ -1,7 +1,7 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/v2" "io" "maps" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -68,7 +69,7 @@ func TestWebhookBatching(t *testing.T) { for i := range tt.events { ev := egEvent("e"+string(rune('a'+i)), "/s") ev.Fields["data"] = map[string]any{"pad": strings.Repeat("x", tt.padding)} - raw, _ := json.Marshal(ev.Fields) + raw, _ := json.Marshal(ev.Fields, jsonx.V1()) ev.Raw = raw events = append(events, ev) } @@ -85,7 +86,7 @@ func TestWebhookBatching(t *testing.T) { for _, body := range h.bodies { var arr []map[string]any - require.NoError(t, json.Unmarshal(body, &arr)) + require.NoError(t, json.Unmarshal(body, &arr, jsonx.V1())) sizes = append(sizes, len(arr)) total += len(arr) } @@ -127,7 +128,7 @@ func TestWebhookBatchFailureRetriesWholeBatch(t *testing.T) { var arr []map[string]any - require.NoError(t, json.Unmarshal(h.bodies[1], &arr)) + require.NoError(t, json.Unmarshal(h.bodies[1], &arr, jsonx.V1())) assert.Len(t, arr, 2) assert.Equal(t, "1", h.requests[1].Header.Get("Aeg-Delivery-Count")) } @@ -297,6 +298,6 @@ func TestCloudEventsBatchedDelivery(t *testing.T) { var arr []map[string]any - require.NoError(t, json.Unmarshal(body, &arr)) + require.NoError(t, json.Unmarshal(body, &arr, jsonx.V1())) assert.Len(t, arr, 2) } diff --git a/services/azureeventgrid/deadletter.go b/services/azureeventgrid/deadletter.go index abbd794d90..f3b1f889d4 100644 --- a/services/azureeventgrid/deadletter.go +++ b/services/azureeventgrid/deadletter.go @@ -1,14 +1,16 @@ package azureeventgrid import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func decodeFields(raw []byte) map[string]any { var m map[string]any - _ = json.Unmarshal(raw, &m) + _ = json.Unmarshal(raw, &m, jsonx.V1()) return m } @@ -25,7 +27,7 @@ func deadLetterPayload(item deadItem) ([]byte, error) { m["publishTime"] = item.d.Published.UTC().Format(time.RFC3339Nano) m["lastDeliveryAttemptTime"] = item.d.LastAttempt.UTC().Format(time.RFC3339Nano) - return json.Marshal(m) + return json.Marshal(m, jsonx.V1()) } func blobName(item deadItem, at time.Time) string { diff --git a/services/azureeventgrid/delivery_test.go b/services/azureeventgrid/delivery_test.go index 8447c877f0..5937f49057 100644 --- a/services/azureeventgrid/delivery_test.go +++ b/services/azureeventgrid/delivery_test.go @@ -1,7 +1,8 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -43,9 +45,12 @@ func newHook(t *testing.T, code func(n int) int) *hook { if !h.manual { var ev []map[string]any - _ = json.Unmarshal(body, &ev) + _ = json.Unmarshal(body, &ev, jsonx.V1()) code, _ := ev[0]["data"].(map[string]any)["validationCode"].(string) - _ = json.NewEncoder(w).Encode(map[string]string{"validationResponse": code}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"validationResponse": code}, + ) } return @@ -83,7 +88,10 @@ func TestWebhookHandshake(t *testing.T) { schema: azureeventgrid.SchemaEventGrid, wantState: azureeventgrid.StateSucceeded, respond: func(w http.ResponseWriter, _ *http.Request, code string) { - _ = json.NewEncoder(w).Encode(map[string]string{"validationResponse": code}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"validationResponse": code}, + ) }, }, { @@ -139,7 +147,7 @@ func TestWebhookHandshake(t *testing.T) { seen.mu.Lock() seen.headers, seen.method = r.Header.Clone(), r.Method - _ = json.Unmarshal(body, &seen.event) + _ = json.Unmarshal(body, &seen.event, jsonx.V1()) seen.mu.Unlock() code := "" @@ -192,7 +200,7 @@ func TestManualValidationUnlocksDelivery(t *testing.T) { var ev []map[string]any - require.NoError(t, json.Unmarshal(h.shakeBody, &ev)) + require.NoError(t, json.Unmarshal(h.shakeBody, &ev, jsonx.V1())) validationURL, _ = ev[0]["data"].(map[string]any)["validationUrl"].(string) h.mu.Unlock() @@ -236,7 +244,7 @@ func TestWebhookDeliveryShape(t *testing.T) { var got []map[string]any - require.NoError(t, json.Unmarshal(h.bodies[0], &got)) + require.NoError(t, json.Unmarshal(h.bodies[0], &got, jsonx.V1())) require.Len(t, got, 1) assert.Equal(t, "e1", got[0]["id"]) assert.Equal(t, topicRID, got[0]["topic"]) @@ -323,7 +331,7 @@ func TestRetryAndDeadLetter(t *testing.T) { var out map[string]any - require.NoError(t, json.Unmarshal(blobs[0].body, &out)) + require.NoError(t, json.Unmarshal(blobs[0].body, &out, jsonx.V1())) assert.Equal(t, tt.wantReason, out["deadLetterReason"]) assert.EqualValues(t, tt.wantCalls, out["deliveryAttempts"]) assert.Equal(t, "e1", out["id"]) @@ -400,7 +408,7 @@ func TestTargetDelivery(t *testing.T) { var arr []map[string]any - require.NoError(t, json.Unmarshal(got.body, &arr)) + require.NoError(t, json.Unmarshal(got.body, &arr, jsonx.V1())) assert.Equal(t, "e1", arr[0]["id"]) }, }, @@ -437,12 +445,12 @@ func TestTargetDelivery(t *testing.T) { } } -func mustField(t *testing.T, raw []byte, key string) json.RawMessage { +func mustField(t *testing.T, raw []byte, key string) jsontext.Value { t.Helper() - var m map[string]json.RawMessage + var m map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw, &m)) + require.NoError(t, json.Unmarshal(raw, &m, jsonx.V1())) return m[key] } diff --git a/services/azureeventgrid/domains_test.go b/services/azureeventgrid/domains_test.go index 11814db2d6..cebcb04fa2 100644 --- a/services/azureeventgrid/domains_test.go +++ b/services/azureeventgrid/domains_test.go @@ -1,7 +1,7 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" eg "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -123,7 +124,7 @@ func TestDomainPublishRouting(t *testing.T) { for _, s := range e.rec.all() { var got map[string]any - require.NoError(t, json.Unmarshal([]byte(s.text), &got)) + require.NoError(t, json.Unmarshal([]byte(s.text), &got, jsonx.V1())) assert.True(t, strings.HasPrefix(got["topic"].(string), domainRID+"/topics/"), "%v", got["topic"]) assert.Equal(t, "1", got["metadataVersion"]) } @@ -334,7 +335,7 @@ func TestDomainSubscriptionWebhookBatchingAndHeaders(t *testing.T) { var events []eg.Event for _, id := range []string{"a", "b", "c"} { - raw, _ := json.Marshal(domainEvent("orders", id, "/s")) + raw, _ := json.Marshal(domainEvent("orders", id, "/s"), jsonx.V1()) parsed, perr := eg.ParseEvents(raw, eg.SchemaEventGrid) require.NoError(t, perr) @@ -350,7 +351,7 @@ func TestDomainSubscriptionWebhookBatchingAndHeaders(t *testing.T) { var arr []map[string]any - require.NoError(t, json.Unmarshal(h.bodies[0], &arr)) + require.NoError(t, json.Unmarshal(h.bodies[0], &arr, jsonx.V1())) assert.Len(t, arr, 3) assert.Equal(t, "v", h.requests[0].Header.Get("X-Secret")) assert.Equal(t, domainRID+"/topics/orders", arr[0]["topic"]) diff --git a/services/azureeventgrid/filter.go b/services/azureeventgrid/filter.go index d45f80eaa9..35f6bb162b 100644 --- a/services/azureeventgrid/filter.go +++ b/services/azureeventgrid/filter.go @@ -1,8 +1,9 @@ package azureeventgrid import ( - "encoding/json" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var attrAliases = map[string]string{ //nolint:gochecknoglobals // static alias table @@ -162,7 +163,7 @@ func (a AdvancedFilter) matchScalar(v any) bool { return a.matchString(x) case float64: return a.matchNumber(x) - case json.Number: + case jsonx.Number: f, err := x.Float64() return err == nil && a.matchNumber(f) @@ -230,7 +231,7 @@ func toFloat(v any) (float64, bool) { return x, true case int: return float64(x), true - case json.Number: + case jsonx.Number: f, err := x.Float64() return f, err == nil diff --git a/services/azureeventgrid/filter_test.go b/services/azureeventgrid/filter_test.go index 2d45601683..c61b552a41 100644 --- a/services/azureeventgrid/filter_test.go +++ b/services/azureeventgrid/filter_test.go @@ -1,12 +1,13 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" eg "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -21,7 +22,7 @@ func decode(t *testing.T, raw string) map[string]any { var m map[string]any - require.NoError(t, json.Unmarshal([]byte(raw), &m)) + require.NoError(t, json.Unmarshal([]byte(raw), &m, jsonx.V1())) return m } diff --git a/services/azureeventgrid/helpers_test.go b/services/azureeventgrid/helpers_test.go index d0ba346b29..dbc5d9dffd 100644 --- a/services/azureeventgrid/helpers_test.go +++ b/services/azureeventgrid/helpers_test.go @@ -1,7 +1,7 @@ package azureeventgrid_test import ( - "encoding/json" + "encoding/json/v2" "maps" "sync" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureeventgrid" ) @@ -105,7 +106,7 @@ func egEvent(id, subject string) azureeventgrid.Event { raw, _ := json.Marshal(map[string]any{ "id": id, "subject": subject, "eventType": "App.Thing", "eventTime": "2026-10-20T12:00:00Z", "dataVersion": "1", "data": map[string]any{"n": 1}, - }) + }, jsonx.V1()) events, err := azureeventgrid.ParseEvents(raw, azureeventgrid.SchemaEventGrid) if err != nil { diff --git a/services/azureeventgrid/http_routes.go b/services/azureeventgrid/http_routes.go index 12d2061d38..339d9e595c 100644 --- a/services/azureeventgrid/http_routes.go +++ b/services/azureeventgrid/http_routes.go @@ -1,7 +1,7 @@ package azureeventgrid import ( - "encoding/json" + "encoding/json/v2" "errors" "io" "net" @@ -11,6 +11,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -283,7 +285,12 @@ func (h *Handler) handlePull(c *echo.Context, rt route) error { LockTokens []string `json:"lockTokens"` } - if err = json.Unmarshal(body, &in); err != nil || len(in.LockTokens) == 0 || len(in.LockTokens) > maxLockTokens { + if err = json.Unmarshal( + body, + &in, + jsonx.V1(), + ); err != nil || len(in.LockTokens) == 0 || + len(in.LockTokens) > maxLockTokens { return writeError(c, http.StatusBadRequest, outcomeBadRequest, "lockTokens must hold 1 to 100 tokens.") } diff --git a/services/azureeventgrid/model.go b/services/azureeventgrid/model.go index 7a14e3086d..1f435665d2 100644 --- a/services/azureeventgrid/model.go +++ b/services/azureeventgrid/model.go @@ -1,7 +1,7 @@ package azureeventgrid import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -69,14 +69,14 @@ type Spec struct { } type delivery struct { - Due time.Time `json:"due"` - Published time.Time `json:"published"` - LastAttempt time.Time `json:"lastAttempt"` - ID string `json:"id"` - LastOutcome string `json:"lastOutcome,omitempty"` - Event json.RawMessage `json:"event"` - Attempts int `json:"attempts"` - InFlight bool `json:"-"` + Due time.Time `json:"due"` + Published time.Time `json:"published"` + LastAttempt time.Time `json:"lastAttempt"` + ID string `json:"id"` + LastOutcome string `json:"lastOutcome,omitempty"` + Event jsontext.Value `json:"event"` + Attempts int `json:"attempts"` + InFlight bool `json:"-"` } type subscription struct { @@ -99,12 +99,12 @@ type topic struct { } type pullEvent struct { - AvailableAt time.Time `json:"availableAt"` - LockedUntil time.Time `json:"lockedUntil"` - ExpiresAt time.Time `json:"expiresAt"` - LockToken string `json:"lockToken,omitempty"` - Event json.RawMessage `json:"event"` - Count int `json:"count"` + AvailableAt time.Time `json:"availableAt"` + LockedUntil time.Time `json:"lockedUntil"` + ExpiresAt time.Time `json:"expiresAt"` + LockToken string `json:"lockToken,omitempty"` + Event jsontext.Value `json:"event"` + Count int `json:"count"` } type pullSub struct { diff --git a/services/azureeventgrid/publish.go b/services/azureeventgrid/publish.go index 808e5f755e..fb3000cfaa 100644 --- a/services/azureeventgrid/publish.go +++ b/services/azureeventgrid/publish.go @@ -2,9 +2,12 @@ package azureeventgrid import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -18,7 +21,7 @@ const ( type Event struct { Fields map[string]any ID string - Raw json.RawMessage + Raw jsontext.Value } func invalidEvent(format string, args ...any) error { @@ -33,14 +36,14 @@ func ParseEvents(body []byte, schema string) ([]Event, error) { return nil, invalidEvent("the request body is empty") } - var raws []json.RawMessage + var raws []jsontext.Value if trimmed[0] == '[' { - if err := json.Unmarshal(trimmed, &raws); err != nil { + if err := json.Unmarshal(trimmed, &raws, jsonx.V1()); err != nil { return nil, invalidEvent("the request body is not valid JSON") } } else { - raws = []json.RawMessage{trimmed} + raws = []jsontext.Value{trimmed} } if len(raws) == 0 { @@ -65,9 +68,9 @@ func ParseEvents(body []byte, schema string) ([]Event, error) { return out, nil } -func parseEvent(raw json.RawMessage, schema string) (Event, error) { +func parseEvent(raw jsontext.Value, schema string) (Event, error) { var fields map[string]any - if err := json.Unmarshal(raw, &fields); err != nil || fields == nil { + if err := json.Unmarshal(raw, &fields, jsonx.V1()); err != nil || fields == nil { return Event{}, invalidEvent("the event is not a JSON object") } @@ -99,7 +102,7 @@ func (e *Event) stamp(topicRID, schema string) error { e.Fields["topic"], e.Fields["metadataVersion"] = topicRID, "1" } - raw, err := json.Marshal(e.Fields) + raw, err := json.Marshal(e.Fields, jsonx.V1()) if err != nil { return invalidEvent("the event cannot be encoded") } diff --git a/services/azureeventgrid/pull.go b/services/azureeventgrid/pull.go index 94a130a9bd..c4107177d9 100644 --- a/services/azureeventgrid/pull.go +++ b/services/azureeventgrid/pull.go @@ -2,12 +2,14 @@ package azureeventgrid import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/iso8601" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -46,7 +48,7 @@ type pullProps struct { // Received is one event handed out by a receive call. type Received struct { LockToken string - Event json.RawMessage + Event jsontext.Value DeliveryCount int } @@ -84,19 +86,19 @@ func (s *pullSub) props() map[string]any { }, } - raw, _ := json.Marshal(p) + raw, _ := json.Marshal(p, jsonx.V1()) out := map[string]any{} - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) out[propProvisioning] = StateSucceeded return out } func parsePullProps(props map[string]any) (pullProps, error) { - raw, _ := json.Marshal(props) + raw, _ := json.Marshal(props, jsonx.V1()) var p pullProps - if err := json.Unmarshal(raw, &p); err != nil { + if err := json.Unmarshal(raw, &p, jsonx.V1()); err != nil { return p, invalidSpec("properties are malformed: %v", err) } diff --git a/services/azureeventgrid/subscription.go b/services/azureeventgrid/subscription.go index b67e24727e..ff79d3081f 100644 --- a/services/azureeventgrid/subscription.go +++ b/services/azureeventgrid/subscription.go @@ -2,12 +2,14 @@ package azureeventgrid import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/url" "sort" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -16,13 +18,13 @@ const ( ) func parseSpec(props map[string]any, inputSchema string) (Spec, error) { - raw, err := json.Marshal(props) + raw, err := json.Marshal(props, jsonx.V1()) if err != nil { return Spec{}, invalidSpec("properties are not valid JSON") } var spec Spec - if err = json.Unmarshal(raw, &spec); err != nil { + if err = json.Unmarshal(raw, &spec, jsonx.V1()); err != nil { return Spec{}, invalidSpec("properties are malformed: %v", err) } @@ -70,9 +72,9 @@ func normalizeSpec(spec *Spec, inputSchema string) error { // specProps renders the ARM properties of a subscription. Webhook secrets in the // query string are withheld (see FullURL). func specProps(spec Spec, state, topicRID string) map[string]any { - raw, _ := json.Marshal(spec) + raw, _ := json.Marshal(spec, jsonx.V1()) out := map[string]any{} - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) if dest, ok := out["destination"].(map[string]any); ok && spec.Destination.EndpointType == EndpointWebHook { dest["properties"] = webhookProps(spec.Destination) @@ -199,13 +201,13 @@ func mergeSpecPatch(cur Spec, patch map[string]any) (map[string]any, error) { } } - raw, err := json.Marshal(cur) + raw, err := json.Marshal(cur, jsonx.V1()) if err != nil { return nil, invalidSpec("current spec is not valid JSON") } merged := map[string]any{} - if err = json.Unmarshal(raw, &merged); err != nil { + if err = json.Unmarshal(raw, &merged, jsonx.V1()); err != nil { return nil, invalidSpec("current spec is not valid JSON") } diff --git a/services/azureeventgrid/webhook.go b/services/azureeventgrid/webhook.go index 0073ddb324..a2e9be97ee 100644 --- a/services/azureeventgrid/webhook.go +++ b/services/azureeventgrid/webhook.go @@ -3,7 +3,7 @@ package azureeventgrid import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -11,6 +11,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -96,7 +98,7 @@ func validationEvent(topicRID, code, validationURL string) ([]byte, error) { "id": uuid.NewString(), "topic": topicRID, fieldSubject: "", fieldEventType: validationEventType, fieldEventTime: time.Now().UTC().Format(time.RFC3339Nano), "metadataVersion": "1", fieldDataVersion: "2", "data": map[string]any{"validationCode": code, "validationUrl": validationURL}, - }}) + }}, jsonx.V1()) } // postHandshake sends the SubscriptionValidationEvent; echoed reports whether the @@ -138,7 +140,7 @@ func (b *InMemoryBackend) postHandshake( } data, _ := io.ReadAll(io.LimitReader(resp.Body, maxResponseBytes)) - _ = json.Unmarshal(data, &out) + _ = json.Unmarshal(data, &out, jsonx.V1()) return out.ValidationResponse == code, nil } diff --git a/services/azureeventhubs/http_routes.go b/services/azureeventhubs/http_routes.go index c4aedbec97..8ffe0d8260 100644 --- a/services/azureeventhubs/http_routes.go +++ b/services/azureeventhubs/http_routes.go @@ -1,7 +1,7 @@ package azureeventhubs import ( - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/amqp1" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -151,7 +152,7 @@ func (h *Handler) authorized(r *http.Request, right string) bool { } func writeError(c *echo.Context, status int, msg string) error { - body, err := json.Marshal(map[string]string{"Code": http.StatusText(status), "Message": msg}) + body, err := json.Marshal(map[string]string{"Code": http.StatusText(status), "Message": msg}, jsonx.V1()) if err != nil { return c.String(http.StatusInternalServerError, "internal error") } @@ -219,7 +220,7 @@ func restEvents(r *http.Request, body []byte) ([]NewEvent, error) { if bp := r.Header.Get("Brokerproperties"); bp != "" { var props brokerProperties - if err := json.Unmarshal([]byte(bp), &props); err != nil { + if err := json.Unmarshal([]byte(bp), &props, jsonx.V1()); err != nil { return nil, err } @@ -233,9 +234,9 @@ func restEvents(r *http.Request, body []byte) ([]NewEvent, error) { func batchEvents(body []byte) ([]NewEvent, error) { var items []jsonEvent - if err := json.Unmarshal(body, &items); err != nil { + if err := json.Unmarshal(body, &items, jsonx.V1()); err != nil { var one jsonEvent - if err2 := json.Unmarshal(body, &one); err2 != nil { + if err2 := json.Unmarshal(body, &one, jsonx.V1()); err2 != nil { return nil, err } diff --git a/services/azurefile/ops_share.go b/services/azurefile/ops_share.go index 014a657a86..032a236cc2 100644 --- a/services/azurefile/ops_share.go +++ b/services/azurefile/ops_share.go @@ -1,7 +1,7 @@ package azurefile import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "math" "net/http" @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -751,7 +752,12 @@ func (h *Handler) createPermission(rc *reqCtx) (*resp, *apiError) { } var in permissionJSON - if err := json.Unmarshal(data, &in); err != nil || in.Permission == "" || len(in.Permission) > maxSDDLLen { + if err := json.Unmarshal( + data, + &in, + jsonx.V1(), + ); err != nil || in.Permission == "" || + len(in.Permission) > maxSDDLLen { return nil, errInvalidHeader("x-ms-file-permission") } @@ -792,7 +798,7 @@ func (h *Handler) getPermission(rc *reqCtx) (*resp, *apiError) { return nil, errInvalidHeader("x-ms-file-permission-key") } - body, err := json.Marshal(permissionJSON{Permission: sddl, Format: "Sddl"}) + body, err := json.Marshal(permissionJSON{Permission: sddl, Format: "Sddl"}, jsonx.V1()) if err != nil { return nil, newErr(http.StatusInternalServerError, "InternalError", "Failed to marshal response.") } diff --git a/services/azurefunctions/functions.go b/services/azurefunctions/functions.go index 7ebac1bc80..6c64a3e601 100644 --- a/services/azurefunctions/functions.go +++ b/services/azurefunctions/functions.go @@ -2,7 +2,7 @@ package azurefunctions import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -13,6 +13,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -107,7 +108,7 @@ func (b *InMemoryBackend) adminFunctions(ctx context.Context, name string) ([]ad } var fns []adminFunction - if err = json.Unmarshal(body, &fns); err != nil { + if err = json.Unmarshal(body, &fns, jsonx.V1()); err != nil { return nil, fmt.Errorf("decode host functions: %w", err) } diff --git a/services/azurefunctions/keys.go b/services/azurefunctions/keys.go index d7047a58a2..be8684c3f3 100644 --- a/services/azurefunctions/keys.go +++ b/services/azurefunctions/keys.go @@ -3,12 +3,14 @@ package azurefunctions import ( "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "os" "path/filepath" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -105,7 +107,7 @@ func writeSecrets(dir string, st *appState) error { Source: "runtime", } - raw, err := json.Marshal(host) + raw, err := json.Marshal(host, jsonx.V1()) if err != nil { return fmt.Errorf("encode host secrets: %w", err) } @@ -117,7 +119,7 @@ func writeSecrets(dir string, st *appState) error { want := map[string]bool{"host.json": true} for fn, keys := range st.FunctionKeys { - raw, err = json.Marshal(functionSecretsFile{Source: "runtime", Keys: sortedKeys(keys)}) + raw, err = json.Marshal(functionSecretsFile{Source: "runtime", Keys: sortedKeys(keys)}, jsonx.V1()) if err != nil { return fmt.Errorf("encode function secrets: %w", err) } diff --git a/services/azurefunctions/keys_test.go b/services/azurefunctions/keys_test.go index ae12992030..648dbb4efa 100644 --- a/services/azurefunctions/keys_test.go +++ b/services/azurefunctions/keys_test.go @@ -1,7 +1,7 @@ package azurefunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "path/filepath" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurefunctions" ) @@ -33,7 +34,7 @@ func loadSecrets(t *testing.T, dir, file string) secretsFile { require.NoError(t, err) var s secretsFile - require.NoError(t, json.Unmarshal([]byte(raw), &s)) + require.NoError(t, json.Unmarshal([]byte(raw), &s, jsonx.V1())) return s } diff --git a/services/azurefunctions/kudu.go b/services/azurefunctions/kudu.go index ff4fbdcfc7..8b64e1da45 100644 --- a/services/azurefunctions/kudu.go +++ b/services/azurefunctions/kudu.go @@ -2,13 +2,15 @@ package azurefunctions import ( "crypto/subtle" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -74,7 +76,7 @@ func toDeploymentJSON(r *http.Request, name string, d Deployment, active bool) d } func writeJSON(w http.ResponseWriter, status int, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -83,7 +85,7 @@ func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) - _, _ = w.Write(raw) + _, _ = w.Write(raw) // #nosec G705 } // authorized accepts the app's publishing credentials as Basic auth, or any Bearer token (Kudu trusts the diff --git a/services/azurefunctions/kudu_test.go b/services/azurefunctions/kudu_test.go index 924827ba6d..93b17a6c65 100644 --- a/services/azurefunctions/kudu_test.go +++ b/services/azurefunctions/kudu_test.go @@ -1,13 +1,14 @@ package azurefunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azurefunctions" ) @@ -133,7 +134,7 @@ func TestKudu_DeploymentsAndDownload(t *testing.T) { _, body = e.do(t, http.MethodPost, scm, "/api/zipdeploy", pkg, auth) var first map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &first)) + require.NoError(t, json.Unmarshal([]byte(body), &first, jsonx.V1())) assert.InDelta(t, 4, first["status"], 0) assert.Equal(t, true, first["complete"]) assert.Equal(t, "ZipDeploy", first["deployer"]) @@ -143,7 +144,7 @@ func TestKudu_DeploymentsAndDownload(t *testing.T) { _, body = e.do(t, http.MethodGet, scm, "/api/deployments", nil, auth) var list []map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &list)) + require.NoError(t, json.Unmarshal([]byte(body), &list, jsonx.V1())) require.Len(t, list, 2) assert.Equal(t, "OneDeploy", list[0]["deployer"], "newest first") assert.Equal(t, true, list[0]["active"]) diff --git a/services/azurefunctions/webapp.go b/services/azurefunctions/webapp.go index 1ecd3d2eff..1d30d423b4 100644 --- a/services/azurefunctions/webapp.go +++ b/services/azurefunctions/webapp.go @@ -2,7 +2,7 @@ package azurefunctions import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "os" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -193,7 +194,7 @@ func hasStartScript(dir string) bool { Scripts map[string]string `json:"scripts"` } - return json.Unmarshal(raw, &pkg) == nil && pkg.Scripts["start"] != "" + return json.Unmarshal(raw, &pkg, jsonx.V1()) == nil && pkg.Scripts["start"] != "" } func dotnetEntry(dir string) string { diff --git a/services/azurefunctions/zip.go b/services/azurefunctions/zip.go index 2ce0fed56a..dd194f858b 100644 --- a/services/azurefunctions/zip.go +++ b/services/azurefunctions/zip.go @@ -3,7 +3,7 @@ package azurefunctions import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "io" "os" @@ -12,6 +12,8 @@ import ( "regexp" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -143,7 +145,7 @@ func scanFunctions(data []byte) []FunctionDef { _ = rc.Close() var cfg map[string]any - if readErr != nil || json.Unmarshal(bytes.TrimPrefix(raw, []byte("\xef\xbb\xbf")), &cfg) != nil { + if readErr != nil || json.Unmarshal(bytes.TrimPrefix(raw, []byte("\xef\xbb\xbf")), &cfg, jsonx.V1()) != nil { continue } diff --git a/services/azuregraph/apps.go b/services/azuregraph/apps.go index 2e4d9e980f..35106ba886 100644 --- a/services/azuregraph/apps.go +++ b/services/azuregraph/apps.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "net/url" "slices" "strings" @@ -107,7 +107,7 @@ func validateAppRoles(roles []AppRole, scopes []PermissionScope) error { } // CreateApplication registers an application. -func (b *Backend) CreateApplication(body map[string]json.RawMessage) (Application, error) { +func (b *Backend) CreateApplication(body map[string]jsontext.Value) (Application, error) { b.mu.Lock("CreateApplication") defer b.mu.Unlock() @@ -159,7 +159,7 @@ func (b *Backend) ListApplications() []Application { } // UpdateApplication applies a PATCH body. -func (b *Backend) UpdateApplication(r Ref, body map[string]json.RawMessage) error { +func (b *Backend) UpdateApplication(r Ref, body map[string]jsontext.Value) error { b.mu.Lock("UpdateApplication") defer b.mu.Unlock() diff --git a/services/azuregraph/assign.go b/services/azuregraph/assign.go index 34d148f992..5efc42c6af 100644 --- a/services/azuregraph/assign.go +++ b/services/azuregraph/assign.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "slices" "strings" ) @@ -24,7 +24,7 @@ func principalType(kind string) string { // CreateAppRoleAssignment grants an app role. ownerID is the path service principal, user or group; // asResource selects appRoleAssignedTo (owner is the resource) over appRoleAssignments (owner is the principal). func (b *Backend) CreateAppRoleAssignment( - ownerID string, asResource bool, body map[string]json.RawMessage, + ownerID string, asResource bool, body map[string]jsontext.Value, ) (AppRoleAssignment, error) { if err := requireProps("AppRoleAssignment", body, "principalId", "resourceId", "appRoleId"); err != nil { return AppRoleAssignment{}, err @@ -135,7 +135,7 @@ func (b *Backend) DeleteAppRoleAssignment(id, assignmentID string, asResource bo } // CreateGrant records a delegated permission grant. -func (b *Backend) CreateGrant(body map[string]json.RawMessage) (OAuth2PermissionGrant, error) { +func (b *Backend) CreateGrant(body map[string]jsontext.Value) (OAuth2PermissionGrant, error) { if err := requireProps("OAuth2PermissionGrant", body, "clientId", "consentType", "resourceId"); err != nil { return OAuth2PermissionGrant{}, err } @@ -181,7 +181,7 @@ func (b *Backend) ListGrants() []OAuth2PermissionGrant { } // UpdateGrant changes the scope of a grant. -func (b *Backend) UpdateGrant(id string, body map[string]json.RawMessage) error { +func (b *Backend) UpdateGrant(id string, body map[string]jsontext.Value) error { b.mu.Lock("UpdateGrant") defer b.mu.Unlock() diff --git a/services/azuregraph/collections.go b/services/azuregraph/collections.go index c85233dfae..d54f58ad7f 100644 --- a/services/azuregraph/collections.go +++ b/services/azuregraph/collections.go @@ -1,10 +1,13 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "reflect" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // collection adapts one directory object type to the generic CRUD routes. @@ -12,8 +15,8 @@ type collection struct { fields map[string]bool list func(*Backend) []map[string]any get func(*Backend, Ref) (map[string]any, error) - create func(*Backend, map[string]json.RawMessage) (map[string]any, error) - patch func(*Backend, Ref, map[string]json.RawMessage) error + create func(*Backend, map[string]jsontext.Value) (map[string]any, error) + patch func(*Backend, Ref, map[string]jsontext.Value) error del func(*Backend, Ref) error name string resource string @@ -33,13 +36,13 @@ func splitKey(seg string) (string, Ref) { // toMap renders an entity as its generic JSON object. func toMap(v any) map[string]any { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return map[string]any{} } out := map[string]any{} - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) if t := reflect.TypeOf(v); t.Kind() == reflect.Struct { for name := range jsonFields(t) { @@ -84,15 +87,15 @@ func newCollection[T any]( name, resource, kind string, list func(*Backend) []T, get func(*Backend, Ref) (T, error), - create func(*Backend, map[string]json.RawMessage) (T, error), - patch func(*Backend, Ref, map[string]json.RawMessage) error, + create func(*Backend, map[string]jsontext.Value) (T, error), + patch func(*Backend, Ref, map[string]jsontext.Value) error, del func(*Backend, Ref) error, ) *collection { return &collection{ name: name, resource: resource, kind: kind, fields: fieldsOf[T](), patch: patch, del: del, list: func(b *Backend) []map[string]any { return mapAll(list(b)) }, get: func(b *Backend, r Ref) (map[string]any, error) { return one(get(b, r)) }, - create: func(b *Backend, m map[string]json.RawMessage) (map[string]any, error) { return one(create(b, m)) }, + create: func(b *Backend, m map[string]jsontext.Value) (map[string]any, error) { return one(create(b, m)) }, } } diff --git a/services/azuregraph/groups.go b/services/azuregraph/groups.go index a6dbc59768..e08edb32bc 100644 --- a/services/azuregraph/groups.go +++ b/services/azuregraph/groups.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "slices" "strings" ) @@ -55,7 +55,7 @@ func validateGroup(g *Group) error { } // CreateGroup creates a group; members@odata.bind and owners@odata.bind add references atomically. -func (b *Backend) CreateGroup(body map[string]json.RawMessage) (Group, error) { +func (b *Backend) CreateGroup(body map[string]jsontext.Value) (Group, error) { if err := requireProps("Group", body, "displayName", "mailEnabled", "mailNickname", "securityEnabled"); err != nil { return Group{}, err } @@ -119,7 +119,7 @@ func (b *Backend) ListGroups() []Group { } // UpdateGroup applies a PATCH body. isAssignableToRole can only be set at creation. -func (b *Backend) UpdateGroup(r Ref, body map[string]json.RawMessage) error { +func (b *Backend) UpdateGroup(r Ref, body map[string]jsontext.Value) error { b.mu.Lock("UpdateGroup") defer b.mu.Unlock() diff --git a/services/azuregraph/helpers_test.go b/services/azuregraph/helpers_test.go index 502485101b..0ffaf5be0d 100644 --- a/services/azuregraph/helpers_test.go +++ b/services/azuregraph/helpers_test.go @@ -2,7 +2,7 @@ package azuregraph_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/aadauth" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuregraph" ) @@ -98,7 +99,7 @@ func (e *env) req(t *testing.T, method, path string, body any, hdr ...string) re case string: rdr = strings.NewReader(b) default: - raw, err := json.Marshal(b) + raw, err := json.Marshal(b, jsonx.V1()) require.NoError(t, err) rdr = bytes.NewReader(raw) @@ -126,7 +127,7 @@ func (e *env) req(t *testing.T, method, path string, body any, hdr ...string) re require.NoError(t, err) out := resp{status: res.StatusCode, header: res.Header, raw: string(raw)} - _ = json.Unmarshal(raw, &out.body) + _ = json.Unmarshal(raw, &out.body, jsonx.V1()) return out } diff --git a/services/azuregraph/model.go b/services/azuregraph/model.go index bacadfe11b..c757302e8f 100644 --- a/services/azuregraph/model.go +++ b/services/azuregraph/model.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -124,15 +124,15 @@ type Application struct { DisplayName string `json:"displayName"` Description string `json:"description,omitempty"` API APIApplication `json:"api"` - Certification json.RawMessage `json:"certification,omitempty"` - VerifiedPublisher json.RawMessage `json:"verifiedPublisher,omitempty"` + Certification jsontext.Value `json:"certification,omitempty"` + VerifiedPublisher jsontext.Value `json:"verifiedPublisher,omitempty"` SPA RedirectFacet `json:"spa"` - DisabledByMicrosoftStatus json.RawMessage `json:"disabledByMicrosoftStatus,omitempty"` - AddIns json.RawMessage `json:"addIns,omitempty"` - ParentalControlSettings json.RawMessage `json:"parentalControlSettings,omitempty"` - Info json.RawMessage `json:"info,omitempty"` - OptionalClaims json.RawMessage `json:"optionalClaims,omitempty"` - AuthenticationBehaviors json.RawMessage `json:"authenticationBehaviors,omitempty"` + DisabledByMicrosoftStatus jsontext.Value `json:"disabledByMicrosoftStatus,omitempty"` + AddIns jsontext.Value `json:"addIns,omitempty"` + ParentalControlSettings jsontext.Value `json:"parentalControlSettings,omitempty"` + Info jsontext.Value `json:"info,omitempty"` + OptionalClaims jsontext.Value `json:"optionalClaims,omitempty"` + AuthenticationBehaviors jsontext.Value `json:"authenticationBehaviors,omitempty"` PublicClient RedirectFacet `json:"publicClient"` IdentifierURIs []string `json:"identifierUris"` Tags []string `json:"tags"` @@ -147,18 +147,18 @@ type Application struct { // ServicePrincipal is the local instance of an application in the tenant. type ServicePrincipal struct { - AddIns json.RawMessage `json:"addIns,omitempty"` - DisabledByMicrosoftStatus json.RawMessage `json:"disabledByMicrosoftStatus,omitempty"` - ResourceSpecificPermissions json.RawMessage `json:"resourceSpecificApplicationPermissions,omitempty"` - PasswordSSOSettings json.RawMessage `json:"passwordSingleSignOnSettings,omitempty"` - PreferredKeyEndDateTime json.RawMessage `json:"preferredTokenSigningKeyEndDateTime,omitempty"` - CustomSecurityAttributes json.RawMessage `json:"customSecurityAttributes,omitempty"` + AddIns jsontext.Value `json:"addIns,omitempty"` + DisabledByMicrosoftStatus jsontext.Value `json:"disabledByMicrosoftStatus,omitempty"` + ResourceSpecificPermissions jsontext.Value `json:"resourceSpecificApplicationPermissions,omitempty"` + PasswordSSOSettings jsontext.Value `json:"passwordSingleSignOnSettings,omitempty"` + PreferredKeyEndDateTime jsontext.Value `json:"preferredTokenSigningKeyEndDateTime,omitempty"` + CustomSecurityAttributes jsontext.Value `json:"customSecurityAttributes,omitempty"` SAMLMetadataURL string `json:"samlMetadataUrl,omitempty"` ApplicationTemplateID string `json:"applicationTemplateId,omitempty"` CreatedDateTime time.Time `json:"createdDateTime"` - Info json.RawMessage `json:"info,omitempty"` - VerifiedPublisher json.RawMessage `json:"verifiedPublisher,omitempty"` - SAMLSingleSignOnSettings json.RawMessage `json:"samlSingleSignOnSettings,omitempty"` + Info jsontext.Value `json:"info,omitempty"` + VerifiedPublisher jsontext.Value `json:"verifiedPublisher,omitempty"` + SAMLSingleSignOnSettings jsontext.Value `json:"samlSingleSignOnSettings,omitempty"` ID string `json:"id"` AppID string `json:"appId"` DisplayName string `json:"displayName"` diff --git a/services/azuregraph/patch.go b/services/azuregraph/patch.go index 7feb1372fb..14c47ec55b 100644 --- a/services/azuregraph/patch.go +++ b/services/azuregraph/patch.go @@ -2,10 +2,13 @@ package azuregraph import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "reflect" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type normalizer[T any] interface { @@ -28,7 +31,7 @@ func jsonFields(t reflect.Type) map[string]bool { } // mergeJSON overlays patch onto base: objects merge recursively, null deletes, anything else replaces. -func mergeJSON(base, patch map[string]json.RawMessage) { +func mergeJSON(base, patch map[string]jsontext.Value) { for k, v := range patch { if bytes.Equal(bytes.TrimSpace(v), []byte(jsonNull)) { delete(base, k) @@ -36,11 +39,12 @@ func mergeJSON(base, patch map[string]json.RawMessage) { continue } - var pm, bm map[string]json.RawMessage - if json.Unmarshal(v, &pm) == nil && pm != nil && json.Unmarshal(base[k], &bm) == nil && bm != nil { + var pm, bm map[string]jsontext.Value + if json.Unmarshal(v, &pm, jsonx.V1()) == nil && pm != nil && json.Unmarshal(base[k], &bm, jsonx.V1()) == nil && + bm != nil { mergeJSON(bm, pm) - if merged, err := json.Marshal(bm); err == nil { + if merged, err := json.Marshal(bm, jsonx.V1()); err == nil { base[k] = merged continue @@ -53,9 +57,9 @@ func mergeJSON(base, patch map[string]json.RawMessage) { // applyBody merges a create or PATCH body onto cur and returns the resulting object. // Unknown or read-only properties are rejected the way Graph does. -func applyBody[T any, P normalizer[T]](cur *T, body map[string]json.RawMessage, readOnly ...string) (*T, error) { +func applyBody[T any, P normalizer[T]](cur *T, body map[string]jsontext.Value, readOnly ...string) (*T, error) { known := jsonFields(reflect.TypeFor[T]()) - patch := make(map[string]json.RawMessage, len(body)) + patch := make(map[string]jsontext.Value, len(body)) for k, v := range body { if strings.Contains(k, "@") { @@ -73,25 +77,25 @@ func applyBody[T any, P normalizer[T]](cur *T, body map[string]json.RawMessage, patch[k] = v } - raw, err := json.Marshal(cur) + raw, err := json.Marshal(cur, jsonx.V1()) if err != nil { return nil, err //nolint:wrapcheck // marshal of an owned struct cannot fail on input } - base := map[string]json.RawMessage{} - if err = json.Unmarshal(raw, &base); err != nil { + base := map[string]jsontext.Value{} + if err = json.Unmarshal(raw, &base, jsonx.V1()); err != nil { return nil, err //nolint:wrapcheck // as above } mergeJSON(base, patch) - merged, err := json.Marshal(base) + merged, err := json.Marshal(base, jsonx.V1()) if err != nil { return nil, err //nolint:wrapcheck // as above } out := new(T) - if err = json.Unmarshal(merged, out); err != nil { + if err = json.Unmarshal(merged, out, jsonx.V1()); err != nil { return nil, badRequest( "Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.", ) diff --git a/services/azuregraph/relations.go b/services/azuregraph/relations.go index 21d446c79d..5b56da07ec 100644 --- a/services/azuregraph/relations.go +++ b/services/azuregraph/relations.go @@ -1,9 +1,12 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Object is a directory object of any kind with its entity (Application, ServicePrincipal, User or Group). @@ -55,13 +58,13 @@ func idFromRef(s string) string { } // bindTargets resolves a list of @odata.bind references, failing if any target does not exist. -func (b *Backend) bindTargets(raw json.RawMessage) ([]string, error) { +func (b *Backend) bindTargets(raw jsontext.Value) ([]string, error) { if len(raw) == 0 { return []string{}, nil } var refs []string - if err := json.Unmarshal(raw, &refs); err != nil { + if err := json.Unmarshal(raw, &refs, jsonx.V1()); err != nil { return nil, badRequest("Invalid value specified for an @odata.bind property.") } @@ -82,7 +85,7 @@ func (b *Backend) bindTargets(raw json.RawMessage) ([]string, error) { } // ownerBinds resolves owners@odata.bind; owners must be users or service principals. -func (b *Backend) ownerBinds(body map[string]json.RawMessage) ([]string, error) { +func (b *Backend) ownerBinds(body map[string]jsontext.Value) ([]string, error) { ids, err := b.bindTargets(body["owners@odata.bind"]) if err != nil { return nil, err diff --git a/services/azuregraph/routes.go b/services/azuregraph/routes.go index d03b1e07b5..7ad43b2a2b 100644 --- a/services/azuregraph/routes.go +++ b/services/azuregraph/routes.go @@ -1,7 +1,8 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "maps" "net/http" @@ -10,6 +11,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxGetByIDs = 1000 @@ -27,11 +30,11 @@ func odataBase(r *http.Request) string { return "https://" + r.Host + "/" + version } -func readJSON(c *echo.Context) (map[string]json.RawMessage, error) { +func readJSON(c *echo.Context) (map[string]jsontext.Value, error) { data, err := io.ReadAll(http.MaxBytesReader(c.Response(), c.Request().Body, maxBodyBytes)) - out := map[string]json.RawMessage{} + out := map[string]jsontext.Value{} - if err != nil || json.Unmarshal(data, &out) != nil || out == nil { + if err != nil || json.Unmarshal(data, &out, jsonx.V1()) != nil || out == nil { return nil, badRequest( "Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.", ) @@ -314,7 +317,7 @@ func (h *Handler) getByIDs(c *echo.Context) error { } data, _ := io.ReadAll(http.MaxBytesReader(c.Response(), r.Body, maxBodyBytes)) - if err := json.Unmarshal(data, &in); err != nil || len(in.IDs) == 0 || len(in.IDs) > maxGetByIDs { + if err := json.Unmarshal(data, &in, jsonx.V1()); err != nil || len(in.IDs) == 0 || len(in.IDs) > maxGetByIDs { return h.writeErr( c, badRequest("The 'ids' collection must contain between 1 and %d object identifiers.", maxGetByIDs), diff --git a/services/azuregraph/routes_sub.go b/services/azuregraph/routes_sub.go index 553cd9f49c..eb4f8fe26d 100644 --- a/services/azuregraph/routes_sub.go +++ b/services/azuregraph/routes_sub.go @@ -1,12 +1,15 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func supports(kind string, allowed ...string) bool { return slices.Contains(allowed, kind) } @@ -113,7 +116,7 @@ func (h *Handler) addPassword(c *echo.Context, id string) error { var in PasswordCredential if raw := body["passwordCredential"]; len(raw) > 0 && string(raw) != jsonNull { - if err = json.Unmarshal(raw, &in); err != nil { + if err = json.Unmarshal(raw, &in, jsonx.V1()); err != nil { return h.writeErr(c, badRequest("Invalid value specified for property 'passwordCredential'.")) } } @@ -143,7 +146,7 @@ func (h *Handler) addKey(c *echo.Context, id string) error { } var in KeyCredential - if err = json.Unmarshal(body["keyCredential"], &in); err != nil { + if err = json.Unmarshal(body["keyCredential"], &in, jsonx.V1()); err != nil { return h.writeErr(c, badRequest("A value is required for property 'keyCredential' of resource 'Application'.")) } @@ -166,7 +169,7 @@ func (h *Handler) removeCred(c *echo.Context, id string, remove func(id, keyID s } var keyID string - if json.Unmarshal(body["keyId"], &keyID) != nil || keyID == "" { + if json.Unmarshal(body["keyId"], &keyID, jsonx.V1()) != nil || keyID == "" { return h.writeErr(c, badRequest("A value is required for property 'keyId'.")) } @@ -190,9 +193,9 @@ func (h *Handler) relation(c *echo.Context, load func() ([]Object, error)) error return h.list(c, "directoryObjects", "DirectoryObject", nil, typedObjects(objs)) } -func odataRefTarget(body map[string]json.RawMessage) (string, bool) { +func odataRefTarget(body map[string]jsontext.Value) (string, bool) { var ref string - if json.Unmarshal(body["@odata.id"], &ref) != nil || ref == "" { + if json.Unmarshal(body["@odata.id"], &ref, jsonx.V1()) != nil || ref == "" { return "", false } diff --git a/services/azuregraph/sps.go b/services/azuregraph/sps.go index 95a65d0d8e..ec14f0e1e6 100644 --- a/services/azuregraph/sps.go +++ b/services/azuregraph/sps.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "strings" ) @@ -31,7 +31,7 @@ func (b *Backend) findSP(r Ref) (*ServicePrincipal, error) { } // CreateServicePrincipal instantiates an application in the tenant; body.appId must name a registered application. -func (b *Backend) CreateServicePrincipal(body map[string]json.RawMessage) (ServicePrincipal, error) { +func (b *Backend) CreateServicePrincipal(body map[string]jsontext.Value) (ServicePrincipal, error) { b.mu.Lock("CreateServicePrincipal") defer b.mu.Unlock() @@ -111,7 +111,7 @@ func (b *Backend) ListServicePrincipals() []ServicePrincipal { } // UpdateServicePrincipal applies a PATCH body. -func (b *Backend) UpdateServicePrincipal(r Ref, body map[string]json.RawMessage) error { +func (b *Backend) UpdateServicePrincipal(r Ref, body map[string]jsontext.Value) error { b.mu.Lock("UpdateServicePrincipal") defer b.mu.Unlock() diff --git a/services/azuregraph/users.go b/services/azuregraph/users.go index a2b2bc4aae..b2f4328730 100644 --- a/services/azuregraph/users.go +++ b/services/azuregraph/users.go @@ -1,7 +1,7 @@ package azuregraph import ( - "encoding/json" + "encoding/json/jsontext" "strings" "unicode" ) @@ -80,7 +80,7 @@ func (b *Backend) validateUser(u *User, self string) error { return nil } -func requireProps(kind string, body map[string]json.RawMessage, props ...string) error { +func requireProps(kind string, body map[string]jsontext.Value, props ...string) error { for _, p := range props { if v, ok := body[p]; !ok || string(v) == jsonNull { return badRequest("A value is required for property '%s' of resource '%s'.", p, kind) @@ -91,7 +91,7 @@ func requireProps(kind string, body map[string]json.RawMessage, props ...string) } // CreateUser creates a user. passwordProfile is validated and discarded. -func (b *Backend) CreateUser(body map[string]json.RawMessage) (User, error) { +func (b *Backend) CreateUser(body map[string]jsontext.Value) (User, error) { if err := requireProps(typeUser, body, "accountEnabled", "displayName", "mailNickname", "passwordProfile", "userPrincipalName"); err != nil { return User{}, err @@ -147,7 +147,7 @@ func (b *Backend) ListUsers() []User { } // UpdateUser applies a PATCH body. -func (b *Backend) UpdateUser(r Ref, body map[string]json.RawMessage) error { +func (b *Backend) UpdateUser(r Ref, body map[string]jsontext.Value) error { b.mu.Lock("UpdateUser") defer b.mu.Unlock() diff --git a/services/azurekeyvault/backup.go b/services/azurekeyvault/backup.go index 92cc8a5f8a..9dabc0f03f 100644 --- a/services/azurekeyvault/backup.go +++ b/services/azurekeyvault/backup.go @@ -1,8 +1,10 @@ package azurekeyvault import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const backupPrefix = "gopherstack-keyvault-backup:" @@ -25,7 +27,7 @@ type RestoreRequest struct { } func encodeBackup(bl *backupBlob) (BackupResult, error) { - raw, err := json.Marshal(bl) + raw, err := json.Marshal(bl, jsonx.V1()) if err != nil { return BackupResult{}, err } @@ -40,7 +42,7 @@ func decodeBackup(value, kind string) (*backupBlob, error) { } var bl backupBlob - if err = json.Unmarshal(raw[len(backupPrefix):], &bl); err != nil || bl.Kind != kind { + if err = json.Unmarshal(raw[len(backupPrefix):], &bl, jsonx.V1()); err != nil || bl.Kind != kind { return nil, errBadParameter("The backup blob is not a %s backup.", lowerKind(kind)) } diff --git a/services/azurekeyvault/handler.go b/services/azurekeyvault/handler.go index 4bd729ff1d..11055dc85a 100644 --- a/services/azurekeyvault/handler.go +++ b/services/azurekeyvault/handler.go @@ -3,7 +3,7 @@ package azurekeyvault import ( "context" "crypto/tls" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -18,6 +18,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/devtls" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -270,7 +271,7 @@ func decodeBody[T any](rc *reqCtx) (*T, error) { return &in, nil } - if err := json.Unmarshal(rc.body, &in); err != nil { + if err := json.Unmarshal(rc.body, &in, jsonx.V1()); err != nil { return nil, errBadParameter("The request body is not valid JSON: %s", err.Error()) } diff --git a/services/azuremonitor/backend.go b/services/azuremonitor/backend.go index a0d16cb026..a5a2a4bb59 100644 --- a/services/azuremonitor/backend.go +++ b/services/azuremonitor/backend.go @@ -3,13 +3,14 @@ package azuremonitor import ( "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "regexp" "slices" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -97,14 +98,14 @@ func cloneJSON(m map[string]any) map[string]any { return nil } - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil } var out map[string]any - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) return out } diff --git a/services/azuremonitor/handler.go b/services/azuremonitor/handler.go index 394d49671e..e3c2b5a669 100644 --- a/services/azuremonitor/handler.go +++ b/services/azuremonitor/handler.go @@ -5,7 +5,8 @@ import ( "compress/gzip" "context" "crypto/tls" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -22,6 +23,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/aadauth" "github.com/blackbirdworks/gopherstack/pkgs/devtls" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -347,11 +349,10 @@ func decodeContent(encoding string, data []byte) ([]byte, *apiError) { } func parseRecords(data []byte) ([]map[string]any, *apiError) { - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(data)) var records []map[string]any - if err := dec.Decode(&records); err != nil { + if err := json.UnmarshalDecode(dec, &records, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, newErr(http.StatusBadRequest, "InvalidPayload", "The request body must be a JSON array of objects: %v", err) } @@ -398,7 +399,7 @@ func parseQueryRequest(r *http.Request) (QueryRequest, bool, *apiError) { return req, false, newErr(http.StatusBadRequest, "BadArgumentError", "The request body could not be read.") } - if err = json.Unmarshal(data, &req); err != nil { + if err = json.Unmarshal(data, &req, jsonx.V1()); err != nil { return req, false, newErr(http.StatusBadRequest, "BadArgumentError", "The request body is not valid JSON: %v", err) } @@ -440,7 +441,7 @@ func (h *Handler) handleBatch(c *echo.Context) error { } var req batchRequest - if err = json.Unmarshal(data, &req); err != nil || len(req.Requests) == 0 { + if err = json.Unmarshal(data, &req, jsonx.V1()); err != nil || len(req.Requests) == 0 { return writeAPIError(c, newErr(http.StatusBadRequest, "BadArgumentError", "The batch must contain a non-empty 'requests' array.")) } diff --git a/services/azuremonitor/ingest.go b/services/azuremonitor/ingest.go index 44ba843586..66fe9b78c1 100644 --- a/services/azuremonitor/ingest.go +++ b/services/azuremonitor/ingest.go @@ -1,7 +1,6 @@ package azuremonitor import ( - "encoding/json" "errors" "fmt" "math" @@ -9,6 +8,7 @@ import ( "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuremonitor/kql" ) @@ -69,7 +69,7 @@ func convertDatetime(v any) (any, bool) { func numberToInt(v any) (int64, bool) { switch x := v.(type) { - case json.Number: + case jsonx.Number: n, err := x.Int64() return n, err == nil @@ -86,7 +86,7 @@ func numberToInt(v any) (int64, bool) { func numberToFloat(v any) (float64, bool) { switch x := v.(type) { - case json.Number: + case jsonx.Number: f, err := x.Float64() return f, err == nil @@ -103,7 +103,7 @@ func normalizeDynamic(v any) any { switch x := v.(type) { case float64: return kql.NormalizeJSON(x) - case json.Number: + case jsonx.Number: if n, err := x.Int64(); err == nil { return n } diff --git a/services/azuremonitor/kql/funcs.go b/services/azuremonitor/kql/funcs.go index 02edf9a6a3..ac26cf2176 100644 --- a/services/azuremonitor/kql/funcs.go +++ b/services/azuremonitor/kql/funcs.go @@ -2,7 +2,8 @@ package kql import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "math" "net/url" @@ -11,6 +12,8 @@ import ( "strings" "time" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -45,10 +48,9 @@ func sameAsFirst(a []Type) (Type, error) { return a[0], nil } func parseJSONValue(s string) (any, error) { var v any - dec := json.NewDecoder(strings.NewReader(s)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(s)) - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, err } diff --git a/services/azuremonitor/kql/golden_test.go b/services/azuremonitor/kql/golden_test.go index 532e10044b..f6a7fe4dc9 100644 --- a/services/azuremonitor/kql/golden_test.go +++ b/services/azuremonitor/kql/golden_test.go @@ -1,7 +1,7 @@ package kql_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuremonitor/kql" ) @@ -78,7 +79,7 @@ func render(rel *kql.Rel) (string, string) { } } - raw, _ := json.Marshal(rows) + raw, _ := json.Marshal(rows, jsonx.V1()) return cols, string(raw) } diff --git a/services/azuremonitor/kql/parser.go b/services/azuremonitor/kql/parser.go index 15a6d826b1..5d31f2aea4 100644 --- a/services/azuremonitor/kql/parser.go +++ b/services/azuremonitor/kql/parser.go @@ -1,11 +1,13 @@ package kql import ( - "encoding/json" + "encoding/json/v2" "math" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type parser struct { @@ -1385,9 +1387,9 @@ func dynamicLiteral(raw string) (node, error) { var v any - if err := json.Unmarshal([]byte(raw), &v); err != nil { + if err := json.Unmarshal([]byte(raw), &v, jsonx.V1()); err != nil { alt := strings.ReplaceAll(raw, "'", `"`) - if err2 := json.Unmarshal([]byte(alt), &v); err2 != nil { + if err2 := json.Unmarshal([]byte(alt), &v, jsonx.V1()); err2 != nil { return nil, syntaxErr("invalid dynamic literal") } } diff --git a/services/azuremonitor/kql/value.go b/services/azuremonitor/kql/value.go index e7d13fe3c7..59d914d551 100644 --- a/services/azuremonitor/kql/value.go +++ b/services/azuremonitor/kql/value.go @@ -3,11 +3,13 @@ package kql import ( - "encoding/json" + "encoding/json/v2" "math" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Type is a KQL column type name as it appears in query results. @@ -183,7 +185,7 @@ func groupKey(v any) string { return "d" + strconv.FormatInt(int64(x), 10) } - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) return "j" + string(raw) } @@ -273,7 +275,7 @@ func formatReal(f float64) string { } func jsonString(v any) string { - raw, err := json.Marshal(jsonable(v)) + raw, err := json.Marshal(jsonable(v), jsonx.V1()) if err != nil { return "" } @@ -342,7 +344,7 @@ func NormalizeJSON(v any) any { } return x - case json.Number: + case jsonx.Number: if i, err := x.Int64(); err == nil { return i } diff --git a/services/azuremonitor/sdk_test.go b/services/azuremonitor/sdk_test.go index e5c06fce97..5971a914c5 100644 --- a/services/azuremonitor/sdk_test.go +++ b/services/azuremonitor/sdk_test.go @@ -3,7 +3,7 @@ package azuremonitor_test import ( "bytes" "compress/gzip" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuremonitor" ) @@ -111,7 +112,7 @@ func TestIngestThenQuery(t *testing.T) { assert.Equal(t, tt.wantCols, cols) - raw, err := json.Marshal(res.Tables[0].Rows) + raw, err := json.Marshal(res.Tables[0].Rows, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.wantRows, string(raw)) }) @@ -461,7 +462,7 @@ func TestSnapshotRestoreKeepsRows(t *testing.T) { require.NoError(t, e2.handler.Restore(t.Context(), snap)) res := e2.run(t, s, "AppEvents_CL | order by Count asc | project Count, Value, Ok, Extra") - raw, err := json.Marshal(res.Tables[0].Rows) + raw, err := json.Marshal(res.Tables[0].Rows, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, `[[1,1.5,true,"{\"a\":1}"],[2,4,false,"[1,2]"],[3,2.5,true,null]]`, string(raw)) assert.Equal(t, queryazlogs.ColumnTypeLong, *res.Tables[0].Columns[0].Type) diff --git a/services/azureservicebus/handler.go b/services/azureservicebus/handler.go index ff633d251d..dfdcc682e6 100644 --- a/services/azureservicebus/handler.go +++ b/services/azureservicebus/handler.go @@ -4,7 +4,7 @@ import ( "context" "crypto/rand" "crypto/tls" - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -17,6 +17,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -849,7 +850,7 @@ func brokerPropertiesFromHeader(r *http.Request) brokerProperties { var bp brokerProperties - _ = json.Unmarshal([]byte(raw), &bp) + _ = json.Unmarshal([]byte(raw), &bp, jsonx.V1()) return bp } @@ -979,7 +980,7 @@ func (h *Handler) writeLockedMessage(c *echo.Context, req parsedRequest, info Me bp.LockToken = info.LockToken bp.LockedUntilUtc = info.LockedUntil.UTC().Format(http.TimeFormat) - bpJSON, err := json.Marshal(bp) + bpJSON, err := json.Marshal(bp, jsonx.V1()) if err != nil { return h.writeError(c, http.StatusInternalServerError, "InternalError", "Failed to marshal BrokerProperties.") } @@ -1199,7 +1200,7 @@ func (h *Handler) writeBodyReadError(c *echo.Context, err error) error { } func (h *Handler) writeError(c *echo.Context, status int, code, message string) error { - body, err := json.Marshal(azureError{Code: code, Message: message}) + body, err := json.Marshal(azureError{Code: code, Message: message}, jsonx.V1()) if err != nil { return c.String(http.StatusInternalServerError, "internal error") } diff --git a/services/azureservicebus/handler_test.go b/services/azureservicebus/handler_test.go index ba8728e9ef..97c3fa1e57 100644 --- a/services/azureservicebus/handler_test.go +++ b/services/azureservicebus/handler_test.go @@ -2,7 +2,7 @@ package azureservicebus_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureservicebus" ) @@ -146,7 +147,7 @@ func TestHandler_MessageLifecycle_Queue(t *testing.T) { var bp map[string]any - require.NoError(t, json.Unmarshal([]byte(bpHeader), &bp)) + require.NoError(t, json.Unmarshal([]byte(bpHeader), &bp, jsonx.V1())) messageID, _ := bp["MessageId"].(string) lockToken, _ := bp["LockToken"].(string) require.NotEmpty(t, messageID) diff --git a/services/azureservicebus/receive_handlers.go b/services/azureservicebus/receive_handlers.go index 894cb4d081..2435d7731c 100644 --- a/services/azureservicebus/receive_handlers.go +++ b/services/azureservicebus/receive_handlers.go @@ -1,7 +1,7 @@ package azureservicebus import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/textproto" @@ -9,6 +9,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Auto dead-letter reasons real Service Bus stamps on system dead-lettering. @@ -124,7 +126,7 @@ func (h *Handler) handleReceiveAndDelete(c *echo.Context, req parsedRequest) err } func (h *Handler) writeReceivedMessage(c *echo.Context, info MessageInfo) error { - bpJSON, err := json.Marshal(brokerPropertiesFor(info)) + bpJSON, err := json.Marshal(brokerPropertiesFor(info), jsonx.V1()) if err != nil { return h.writeError(c, http.StatusInternalServerError, "InternalError", "Failed to marshal BrokerProperties.") } diff --git a/services/azureservicebus/receive_test.go b/services/azureservicebus/receive_test.go index 3a46b51012..07c3701339 100644 --- a/services/azureservicebus/receive_test.go +++ b/services/azureservicebus/receive_test.go @@ -1,7 +1,7 @@ package azureservicebus_test import ( - "encoding/json" + "encoding/json/v2" "math" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azureservicebus" ) @@ -42,7 +43,7 @@ func peek(t *testing.T, h *azureservicebus.Handler, path string) []peekResp { require.Equal(t, http.StatusOK, rec.Code) var out []peekResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/azuretable/batch.go b/services/azuretable/batch.go index 0a9dd6b331..02988f5f0f 100644 --- a/services/azuretable/batch.go +++ b/services/azuretable/batch.go @@ -1,13 +1,14 @@ package azuretable import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/odatatable" ) @@ -102,7 +103,7 @@ func (h *Handler) batchSuccessPart( level := odataLevelFromAccept(o.Headers.Get("Accept")) - payload, err := json.Marshal(h.encodeEntity(info, o.Table, level, "")) + payload, err := json.Marshal(h.encodeEntity(info, o.Table, level, ""), jsonx.V1()) if err != nil { return part, err } diff --git a/services/azuretable/entity_ops_test.go b/services/azuretable/entity_ops_test.go index 1d8fcc5005..b04a6dc106 100644 --- a/services/azuretable/entity_ops_test.go +++ b/services/azuretable/entity_ops_test.go @@ -2,7 +2,7 @@ package azuretable_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuretable" ) @@ -31,7 +32,7 @@ func TestInsertEntity(t *testing.T) { assert.NotEmpty(t, rec.Header().Get("ETag")) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "p", got["PartitionKey"]) assert.Equal(t, "r", got["RowKey"]) assert.Equal(t, "hi", got["Name"]) @@ -173,7 +174,7 @@ func TestEntity_EDMTypeRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantValue, got["Prop"], tt.name) if tt.annPresent { @@ -221,7 +222,7 @@ func TestGetEntity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "x", got["A"]) assert.NotContains(t, got, "B") assert.Contains(t, got, "PartitionKey") @@ -255,7 +256,7 @@ func TestQueryEntities(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Value, 2) assert.Equal(t, "p1", body.Value[0]["PartitionKey"]) assert.Equal(t, "p2", body.Value[1]["PartitionKey"]) @@ -272,7 +273,7 @@ func TestQueryEntities(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Value, 1) assert.Equal(t, "p2", body.Value[0]["PartitionKey"]) }) @@ -297,7 +298,7 @@ func TestQueryEntities(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body.Value, 1) }) @@ -385,7 +386,7 @@ func TestReplaceEntity(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/"+testAccount+"/mytable(PartitionKey='p',RowKey='r')", nil) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "z", got["A"]) assert.NotContains(t, got, "B") }) @@ -412,7 +413,7 @@ func TestMergeEntity(t *testing.T) { rec2 := doRequest(t, h, http.MethodGet, "/"+testAccount+"/mytable(PartitionKey='p',RowKey='r')", nil) var got map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "z", got["A"]) assert.Equal(t, "y", got["B"]) }) @@ -469,7 +470,7 @@ func TestMergeEntity(t *testing.T) { // Merge semantics, not replace: B must survive. rec2 := doRequest(t, h, http.MethodGet, "/"+testAccount+"/mytable(PartitionKey='p',RowKey='r')", nil) var got map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "z", got["A"]) assert.Equal(t, "y", got["B"]) @@ -496,7 +497,7 @@ func TestMergeEntity(t *testing.T) { rec2 := doRequest(t, h, http.MethodGet, "/"+testAccount+"/mytable(PartitionKey='p',RowKey='r')", nil) var got map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "z", got["A"]) assert.Equal(t, "y", got["B"], "merge semantics must apply, not PUT's own replace semantics") }) diff --git a/services/azuretable/handler.go b/services/azuretable/handler.go index ecaa01c3ee..1ebca943db 100644 --- a/services/azuretable/handler.go +++ b/services/azuretable/handler.go @@ -3,7 +3,7 @@ package azuretable import ( "context" "crypto/rand" - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/azureauth" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/odatatable" @@ -626,7 +627,7 @@ func odataLevelFromAccept(accept string) string { func (h *Handler) writeJSON(c *echo.Context, status int, v any) error { level := odataLevelFromAccept(c.Request().Header.Get("Accept")) - body, err := json.Marshal(v) + body, err := json.Marshal(v, jsonx.V1()) if err != nil { return h.writeErrorNoRecurse(c, http.StatusInternalServerError, "InternalError", "Failed to marshal response.") } @@ -670,7 +671,7 @@ func (h *Handler) writeErrorNoRecurse(c *echo.Context, status int, code, message c.Response().Header().Set("X-Ms-Error-Code", code) body, _ := json.Marshal(odataErrorEnvelope{ Error: odataErrorDetail{Code: code, Message: odataErrorMessage{Lang: "en-US", Value: message}}, - }) + }, jsonx.V1()) return c.Blob(status, "application/json;odata=minimalmetadata;streaming=true;charset=utf-8", body) } diff --git a/services/azuretable/models_test.go b/services/azuretable/models_test.go index 4e89b9fdca..fc1c55cdde 100644 --- a/services/azuretable/models_test.go +++ b/services/azuretable/models_test.go @@ -1,7 +1,7 @@ package azuretable_test import ( - "encoding/json" + "encoding/json/v2" "math" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuretable" ) @@ -60,11 +61,11 @@ func TestEntityProperty_JSONRoundTrip(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - data, err := json.Marshal(tt.prop) + data, err := json.Marshal(tt.prop, jsonx.V1()) require.NoError(t, err, tt.name) var got azuretable.EntityProperty - require.NoError(t, json.Unmarshal(data, &got), tt.name) + require.NoError(t, json.Unmarshal(data, &got, jsonx.V1()), tt.name) assert.Equal(t, tt.prop.Type, got.Type, tt.name) @@ -113,7 +114,7 @@ func TestEntityProperty_MarshalJSON_TypeMismatchErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, err := json.Marshal(tt.prop) + _, err := json.Marshal(tt.prop, jsonx.V1()) require.Error(t, err, tt.name) }) } @@ -147,7 +148,7 @@ func TestEntityProperty_UnmarshalJSON_MalformedValueErrors(t *testing.T) { t.Parallel() var p azuretable.EntityProperty - err := json.Unmarshal([]byte(tt.json), &p) + err := json.Unmarshal([]byte(tt.json), &p, jsonx.V1()) require.Error(t, err, tt.name) }) } diff --git a/services/azuretable/sdk_wire_test.go b/services/azuretable/sdk_wire_test.go index a8e2a8dc77..dae611b2ca 100644 --- a/services/azuretable/sdk_wire_test.go +++ b/services/azuretable/sdk_wire_test.go @@ -1,7 +1,7 @@ package azuretable_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/azuretable" ) @@ -42,7 +43,7 @@ func sdkEntity(t *testing.T, pk, rk string, extra map[string]any) []byte { m := map[string]any{"PartitionKey": pk, "RowKey": rk} maps.Copy(m, extra) - b, err := json.Marshal(m) + b, err := json.Marshal(m, jsonx.V1()) require.NoError(t, err) return b @@ -224,7 +225,7 @@ func TestEdmDoubleRoundTripSDK(t *testing.T) { PartitionKey: "p", RowKey: "r", Properties: map[string]any{"D": 5.0}, } - raw, err := json.Marshal(ent) + raw, err := json.Marshal(ent, jsonx.V1()) require.NoError(t, err) _, err = client.AddEntity(ctx, raw, nil) require.NoError(t, err) @@ -233,7 +234,7 @@ func TestEdmDoubleRoundTripSDK(t *testing.T) { require.NoError(t, err) var back aztables.EDMEntity - require.NoError(t, json.Unmarshal(got.Value, &back)) + require.NoError(t, json.Unmarshal(got.Value, &back, jsonx.V1())) assert.InDelta(t, 5.0, back.Properties["D"], 0) } diff --git a/services/azuretable/table_ops.go b/services/azuretable/table_ops.go index 59a6074641..c9e930d37d 100644 --- a/services/azuretable/table_ops.go +++ b/services/azuretable/table_ops.go @@ -1,13 +1,14 @@ package azuretable import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/odatatable" ) @@ -32,7 +33,7 @@ func (h *Handler) createTable(c *echo.Context) error { } var req createTableBody - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInput", "The input is not valid.") } diff --git a/services/azuretable/table_ops_test.go b/services/azuretable/table_ops_test.go index 283f1b6464..0a97e5c938 100644 --- a/services/azuretable/table_ops_test.go +++ b/services/azuretable/table_ops_test.go @@ -1,7 +1,7 @@ package azuretable_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateTable(t *testing.T) { @@ -24,7 +26,7 @@ func TestCreateTable(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "mytable", body["TableName"]) assert.Contains(t, body, "odata.metadata") }) @@ -94,7 +96,7 @@ func TestListTables(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Value, 2) assert.Equal(t, "tablea", body.Value[0]["TableName"]) assert.Equal(t, "tableb", body.Value[1]["TableName"]) @@ -117,7 +119,7 @@ func TestListTables_NoMetadataLevel(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Value, 1) assert.NotContains(t, body.Value[0], "odata.metadata") assert.Contains(t, rec.Header().Get("Content-Type"), "odata=nometadata") @@ -140,7 +142,7 @@ func TestListTables_FullMetadataLevel(t *testing.T) { var body struct { Value []map[string]any `json:"value"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Value, 1) assert.Contains(t, body.Value[0], "odata.type") assert.Contains(t, body.Value[0], "odata.id") @@ -200,7 +202,7 @@ func TestGetTable(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "m8table", body["TableName"]) }) diff --git a/services/backup/async_jobs.go b/services/backup/async_jobs.go index 0022da64d2..371e3ae2e2 100644 --- a/services/backup/async_jobs.go +++ b/services/backup/async_jobs.go @@ -2,7 +2,7 @@ package backup import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" "time" @@ -97,7 +97,7 @@ func (b *InMemoryBackend) restoreResourceLocked(job *RestoreJob) (string, error) } // captureSnapshotLocked snapshots the backed-up resource when an engine is wired for its type. -func (b *InMemoryBackend) captureSnapshotLocked(job *Job) (json.RawMessage, error) { +func (b *InMemoryBackend) captureSnapshotLocked(job *Job) (jsontext.Value, error) { engine := b.engines[job.ResourceType] if engine == nil { return nil, nil diff --git a/services/backup/backup_plans.go b/services/backup/backup_plans.go index aa9013be8d..745a3d63b3 100644 --- a/services/backup/backup_plans.go +++ b/services/backup/backup_plans.go @@ -1,7 +1,7 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -211,7 +212,7 @@ func (b *InMemoryBackend) ExportBackupPlanTemplate(planID string) (string, error doc.AdvancedBackupSettings = advancedSettingsToJSON(plan.AdvancedBackupSettings) } - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return "", err } diff --git a/services/backup/copy_jobs.go b/services/backup/copy_jobs.go index 434401061b..598704cf85 100644 --- a/services/backup/copy_jobs.go +++ b/services/backup/copy_jobs.go @@ -1,7 +1,7 @@ package backup import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "strings" @@ -192,7 +192,7 @@ func (b *InMemoryBackend) settleCopyJobLocked(job *CopyJob, now time.Time) { return } - var snapshot json.RawMessage + var snapshot jsontext.Value if srcName, ok := b.vaultARNIndex[job.SourceBackupVaultArn]; ok { if srcRP, rpFound := b.recoveryPoints.Get(recoveryPointKey(srcName, job.SourceRecoveryPointArn)); rpFound { snapshot = srcRP.Snapshot diff --git a/services/backup/ddb_item_codec.go b/services/backup/ddb_item_codec.go index 88efbdd7ee..cfd6bda358 100644 --- a/services/backup/ddb_item_codec.go +++ b/services/backup/ddb_item_codec.go @@ -2,20 +2,23 @@ package backup import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" ddbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) -func encodeDDBItem(item map[string]ddbtypes.AttributeValue) (map[string]json.RawMessage, error) { - out := make(map[string]json.RawMessage, len(item)) +func encodeDDBItem(item map[string]ddbtypes.AttributeValue) (map[string]jsontext.Value, error) { + out := make(map[string]jsontext.Value, len(item)) for k, v := range item { w, err := ddbAttrToWire(v) if err != nil { return nil, err } - raw, err := json.Marshal(w) + raw, err := json.Marshal(w, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode attribute %s: %w", k, err) } @@ -25,11 +28,11 @@ func encodeDDBItem(item map[string]ddbtypes.AttributeValue) (map[string]json.Raw return out, nil } -func decodeDDBItem(raw map[string]json.RawMessage) (map[string]ddbtypes.AttributeValue, error) { +func decodeDDBItem(raw map[string]jsontext.Value) (map[string]ddbtypes.AttributeValue, error) { out := make(map[string]ddbtypes.AttributeValue, len(raw)) for k, r := range raw { var w map[string]any - if err := json.Unmarshal(r, &w); err != nil { + if err := json.Unmarshal(r, &w, jsonx.V1()); err != nil { return nil, fmt.Errorf("decode attribute %s: %w", k, err) } av, err := ddbWireToAttr(w) diff --git a/services/backup/handler_backup_access_points.go b/services/backup/handler_backup_access_points.go index f530564315..c823829225 100644 --- a/services/backup/handler_backup_access_points.go +++ b/services/backup/handler_backup_access_points.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createBackupAccessPointBody struct { @@ -18,7 +20,7 @@ type createBackupAccessPointBody struct { func (h *Handler) handleCreateBackupAccessPoint(c *echo.Context, body []byte) error { var in createBackupAccessPointBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_backup_jobs.go b/services/backup/handler_backup_jobs.go index 99a76d29ad..e20b9e21b2 100644 --- a/services/backup/handler_backup_jobs.go +++ b/services/backup/handler_backup_jobs.go @@ -1,11 +1,13 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type startBackupJobBody struct { @@ -22,7 +24,7 @@ type startBackupJobBody struct { func (h *Handler) handleStartBackupJob(c *echo.Context, body []byte) error { var in startBackupJobBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_backup_jobs_test.go b/services/backup/handler_backup_jobs_test.go index 3e4cd86d2d..0fc3189eae 100644 --- a/services/backup/handler_backup_jobs_test.go +++ b/services/backup/handler_backup_jobs_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -51,14 +52,14 @@ func TestDescribeBackupJobProgressFields(t *testing.T) { require.Equal(t, http.StatusOK, startResp.Code) var startData map[string]any - require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startData)) + require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startData, jsonx.V1())) jobID := startData["BackupJobId"].(string) descResp := doRequest(t, h, http.MethodGet, "/backup-jobs/"+jobID, "") require.Equal(t, http.StatusOK, descResp.Code) var descData map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descData)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descData, jsonx.V1())) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/services/backup/handler_backup_plans.go b/services/backup/handler_backup_plans.go index deb1ec9700..3177a22427 100644 --- a/services/backup/handler_backup_plans.go +++ b/services/backup/handler_backup_plans.go @@ -1,12 +1,14 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createBackupPlanBody struct { @@ -16,7 +18,7 @@ type createBackupPlanBody struct { func (h *Handler) handleCreateBackupPlan(c *echo.Context, body []byte) error { var in createBackupPlanBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -146,7 +148,7 @@ type updateBackupPlanBody struct { func (h *Handler) handleUpdateBackupPlan(c *echo.Context, id string, body []byte) error { var in updateBackupPlanBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -305,7 +307,7 @@ func (h *Handler) dispatchPlanTemplateCatalogOps( var reqBody struct { BackupPlanTemplateJSON string `json:"BackupPlanTemplateJson"` } - if err := json.Unmarshal(body, &reqBody); err != nil { + if err := json.Unmarshal(body, &reqBody, jsonx.V1()); err != nil { return true, c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -313,7 +315,7 @@ func (h *Handler) dispatchPlanTemplateCatalogOps( } var doc backupPlanBodyDoc - if err := json.Unmarshal([]byte(reqBody.BackupPlanTemplateJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(reqBody.BackupPlanTemplateJSON), &doc, jsonx.V1()); err != nil { return true, c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid BackupPlanTemplateJson"), diff --git a/services/backup/handler_backup_plans_test.go b/services/backup/handler_backup_plans_test.go index b9253064df..81c79c52a1 100644 --- a/services/backup/handler_backup_plans_test.go +++ b/services/backup/handler_backup_plans_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -33,7 +34,7 @@ func TestUpdateBackupPlanUpdateDate(t *testing.T) { }`) require.Equal(t, http.StatusOK, createResp.Code) var createData map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createData)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createData, jsonx.V1())) planID := createData["BackupPlanId"].(string) updateResp := doRequest(t, h, http.MethodPost, "/backup/plans/"+planID, `{ @@ -45,7 +46,7 @@ func TestUpdateBackupPlanUpdateDate(t *testing.T) { require.Equal(t, http.StatusOK, updateResp.Code) var updateData map[string]any - require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &updateData)) + require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &updateData, jsonx.V1())) assert.NotContains(t, updateData, "UpdateDate", "UpdateBackupPlanOutput has no UpdateDate member") @@ -105,7 +106,7 @@ func TestCreateBackupPlanAdvancedBackupSettings(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) if tc.wantInResponse { settings, ok := data["AdvancedBackupSettings"].([]any) @@ -255,14 +256,14 @@ func TestRuleLifecycle(t *testing.T) { "BackupPlanName": "test-plan", "Rules": tt.rules, }, - }) + }, jsonx.V1()) require.NoError(t, err) resp := doRequest(t, h, http.MethodPut, "/backup/plans", string(body)) require.Equal(t, http.StatusOK, resp.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createResp, jsonx.V1())) planID := createResp["BackupPlanId"].(string) // Retrieve and verify. @@ -271,7 +272,7 @@ func TestRuleLifecycle(t *testing.T) { _ = b var getPlan map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getPlan)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getPlan, jsonx.V1())) backupPlan := getPlan["BackupPlan"].(map[string]any) rules := backupPlan["Rules"].([]any) tt.wantInRules(t, rules) @@ -335,21 +336,21 @@ func TestAdvancedBackupSettings(t *testing.T) { planBody["BackupPlan"].(map[string]any)["AdvancedBackupSettings"] = tt.advancedSettings } - body, err := json.Marshal(planBody) + body, err := json.Marshal(planBody, jsonx.V1()) require.NoError(t, err) resp := doRequest(t, h, http.MethodPut, "/backup/plans", string(body)) require.Equal(t, http.StatusOK, resp.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createResp, jsonx.V1())) planID := createResp["BackupPlanId"].(string) getResp := doRequest(t, h, http.MethodGet, "/backup/plans/"+planID, "") require.Equal(t, http.StatusOK, getResp.Code) var getPlan map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getPlan)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getPlan, jsonx.V1())) backupPlan := getPlan["BackupPlan"].(map[string]any) if tt.advancedSettings != nil { diff --git a/services/backup/handler_copy_jobs.go b/services/backup/handler_copy_jobs.go index d27fb51889..8ac655223d 100644 --- a/services/backup/handler_copy_jobs.go +++ b/services/backup/handler_copy_jobs.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListCopyJobs(c *echo.Context) error { @@ -100,7 +102,7 @@ func (h *Handler) dispatchCopyJobExtraOps(c *echo.Context, route backupRoute, bo DestinationBackupVaultArn string `json:"DestinationBackupVaultArn"` IamRoleArn string `json:"IamRoleArn"` } - if err := json.Unmarshal(body, ©JobReq); err != nil { + if err := json.Unmarshal(body, ©JobReq, jsonx.V1()); err != nil { return true, c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_copy_jobs_test.go b/services/backup/handler_copy_jobs_test.go index 49e8367626..e3ee7958c8 100644 --- a/services/backup/handler_copy_jobs_test.go +++ b/services/backup/handler_copy_jobs_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -35,7 +36,7 @@ func TestStartCopyJobCreationDateEpoch(t *testing.T) { }`) require.Equal(t, http.StatusOK, resp.Code) var startData map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &startData)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &startData, jsonx.V1())) jobID := startData["BackupJobId"].(string) rpArn := "arn:aws:backup:us-east-1:000000000000:recovery-point:rp-copy-1" @@ -49,7 +50,7 @@ func TestStartCopyJobCreationDateEpoch(t *testing.T) { _ = jobID var copyData map[string]any - require.NoError(t, json.Unmarshal(copyResp.Body.Bytes(), ©Data)) + require.NoError(t, json.Unmarshal(copyResp.Body.Bytes(), ©Data, jsonx.V1())) assert.NotEmpty(t, copyData["CopyJobId"], "CopyJobId must be present") creationDate, exists := copyData["CreationDate"] diff --git a/services/backup/handler_dispatch.go b/services/backup/handler_dispatch.go index 68b3b18eec..88fd9f0295 100644 --- a/services/backup/handler_dispatch.go +++ b/services/backup/handler_dispatch.go @@ -2,7 +2,8 @@ package backup import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "regexp" @@ -14,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -184,9 +186,9 @@ func (h *Handler) Handler() echo.HandlerFunc { var body []byte if c.Request().Body != nil { - decoder := json.NewDecoder(c.Request().Body) - var raw json.RawMessage - if err := decoder.Decode(&raw); err == nil { + decoder := jsontext.NewDecoder(c.Request().Body) + var raw jsontext.Value + if err := json.UnmarshalDecode(decoder, &raw, jsonx.V1()); err == nil { body = raw } } diff --git a/services/backup/handler_frameworks.go b/services/backup/handler_frameworks.go index 8f0ed1a884..8e2d996d77 100644 --- a/services/backup/handler_frameworks.go +++ b/services/backup/handler_frameworks.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // controlInputParameterJSON is ControlInputParameter's wire shape: a single @@ -98,7 +100,7 @@ type createFrameworkBody struct { func (h *Handler) handleCreateFramework(c *echo.Context, body []byte) error { var in createFrameworkBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -193,7 +195,7 @@ func (h *Handler) handleUpdateFramework(c *echo.Context, name string, body []byt var in updateFrameworkBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_frameworks_test.go b/services/backup/handler_frameworks_test.go index e6c251356c..2f2d567dad 100644 --- a/services/backup/handler_frameworks_test.go +++ b/services/backup/handler_frameworks_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -70,7 +71,7 @@ func TestFrameworkControls(t *testing.T) { require.Equal(t, tt.wantStatus, resp.Code) var createData map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createData)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createData, jsonx.V1())) fwName := createData["FrameworkName"].(string) // Describe and verify controls. @@ -78,7 +79,7 @@ func TestFrameworkControls(t *testing.T) { require.Equal(t, http.StatusOK, getResp.Code) var fwData map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &fwData)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &fwData, jsonx.V1())) assert.Equal(t, tt.wantStatus2, fwData["FrameworkStatus"]) assert.Equal(t, "COMPLETED", fwData["DeploymentStatus"]) if tt.wantControlLen > 0 { diff --git a/services/backup/handler_legal_holds.go b/services/backup/handler_legal_holds.go index 0622fddc5d..9d370a66dd 100644 --- a/services/backup/handler_legal_holds.go +++ b/services/backup/handler_legal_holds.go @@ -1,11 +1,13 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCancelLegalHold(c *echo.Context, legalHoldID string) error { @@ -70,7 +72,7 @@ type createLegalHoldBody struct { func (h *Handler) handleCreateLegalHold(c *echo.Context, body []byte) error { var in createLegalHoldBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_recovery_points.go b/services/backup/handler_recovery_points.go index ef7d8fef80..82a839df16 100644 --- a/services/backup/handler_recovery_points.go +++ b/services/backup/handler_recovery_points.go @@ -1,12 +1,14 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // splitVaultRP splits a "vaultName|recoveryPointArn" resource string. @@ -362,7 +364,7 @@ func (h *Handler) handleUpdateRecoveryPointIndexSettings( var reqBody struct { Index string `json:"Index"` } - _ = json.Unmarshal(body, &reqBody) + _ = json.Unmarshal(body, &reqBody, jsonx.V1()) if err := h.Backend.UpdateRecoveryPointIndexSettings(vaultName, rpArn, reqBody.Index); err != nil { return h.handleError(c, err) @@ -388,7 +390,7 @@ func (h *Handler) handleUpdateRecoveryPointLifecycle( DeleteAfterDays int64 `json:"DeleteAfterDays"` } `json:"Lifecycle"` } - _ = json.Unmarshal(body, &reqBody) + _ = json.Unmarshal(body, &reqBody, jsonx.V1()) if err := h.Backend.UpdateRecoveryPointLifecycle(vaultName, rpArn, reqBody.Lifecycle.MoveToColdStorageAfterDays, reqBody.Lifecycle.DeleteAfterDays); err != nil { diff --git a/services/backup/handler_recovery_points_test.go b/services/backup/handler_recovery_points_test.go index d1a2bc7969..c5e2c6102e 100644 --- a/services/backup/handler_recovery_points_test.go +++ b/services/backup/handler_recovery_points_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -111,7 +112,7 @@ func TestRecoveryPointNewFields(t *testing.T) { require.Equal(t, http.StatusOK, descResp.Code) var respData map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &respData)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &respData, jsonx.V1())) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/services/backup/handler_report_plans.go b/services/backup/handler_report_plans.go index 62e76ee4c9..6df6e35630 100644 --- a/services/backup/handler_report_plans.go +++ b/services/backup/handler_report_plans.go @@ -1,12 +1,14 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ScanJobsFilterFromQuery builds a ListScanJobsFilter from ListScanJobs @@ -140,7 +142,7 @@ type createReportPlanBody struct { func (h *Handler) handleCreateReportPlan(c *echo.Context, body []byte) error { var in createReportPlanBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -239,7 +241,7 @@ func (h *Handler) handleUpdateReportPlan(c *echo.Context, name string, body []by var in updateReportPlanBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -413,7 +415,7 @@ func (h *Handler) handleStartScanJob(c *echo.Context, body []byte) error { IdempotencyToken string `json:"IdempotencyToken"` ScanBaseRecoveryPointArn string `json:"ScanBaseRecoveryPointArn"` } - if err := json.Unmarshal(body, &reqBody); err != nil { + if err := json.Unmarshal(body, &reqBody, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_report_plans_test.go b/services/backup/handler_report_plans_test.go index 532823e84f..c1c12cf050 100644 --- a/services/backup/handler_report_plans_test.go +++ b/services/backup/handler_report_plans_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -76,7 +77,7 @@ func TestReportPlanSubFields(t *testing.T) { require.Equal(t, tt.wantStatus, resp.Code) var createData map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createData)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &createData, jsonx.V1())) rpName := createData["ReportPlanName"].(string) // Describe and verify sub-fields. @@ -84,7 +85,7 @@ func TestReportPlanSubFields(t *testing.T) { require.Equal(t, http.StatusOK, getResp.Code) var getData map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData, jsonx.V1())) rpDoc := getData["ReportPlan"].(map[string]any) if tt.wantDelivery { @@ -142,7 +143,7 @@ func TestReportPlanFullShape(t *testing.T) { getResp := doRequest(t, h, http.MethodGet, "/audit/report-plans/rp-full-shape", "") require.Equal(t, http.StatusOK, getResp.Code) var getData map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData, jsonx.V1())) rpDoc := getData["ReportPlan"].(map[string]any) ch := rpDoc["ReportDeliveryChannel"].(map[string]any) @@ -169,7 +170,7 @@ func TestReportPlanFullShape(t *testing.T) { getResp2 := doRequest(t, h, http.MethodGet, "/audit/report-plans/rp-full-shape", "") var getData2 map[string]any - require.NoError(t, json.Unmarshal(getResp2.Body.Bytes(), &getData2)) + require.NoError(t, json.Unmarshal(getResp2.Body.Bytes(), &getData2, jsonx.V1())) rpDoc2 := getData2["ReportPlan"].(map[string]any) assert.Equal(t, "updated", rpDoc2["ReportPlanDescription"]) ch2 := rpDoc2["ReportDeliveryChannel"].(map[string]any) diff --git a/services/backup/handler_restore_jobs.go b/services/backup/handler_restore_jobs.go index 2f8287ebb5..40a1042b13 100644 --- a/services/backup/handler_restore_jobs.go +++ b/services/backup/handler_restore_jobs.go @@ -1,11 +1,13 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // RestoreJobsFilterFromQuery builds a ListRestoreJobsFilter from ListRestoreJobs @@ -92,7 +94,7 @@ func (h *Handler) handleStartRestoreJob(c *echo.Context, defaultRecoveryPointArn ResourceType string `json:"ResourceType"` IdempotencyToken string `json:"IdempotencyToken"` } - if err := json.Unmarshal(body, &reqBody); err != nil { + if err := json.Unmarshal(body, &reqBody, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } if reqBody.RecoveryPointArn == "" { @@ -126,7 +128,7 @@ func (h *Handler) handlePutRestoreValidationResult(c *echo.Context, restoreJobID ValidationStatus string `json:"ValidationStatus"` ValidationStatusMessage string `json:"ValidationStatusMessage"` } - if err := json.Unmarshal(body, &reqBody); err != nil { + if err := json.Unmarshal(body, &reqBody, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_restore_testing.go b/services/backup/handler_restore_testing.go index 3c44f6069b..745b62bf26 100644 --- a/services/backup/handler_restore_testing.go +++ b/services/backup/handler_restore_testing.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // restoreTestingRecoveryPointSelectionJSON is the wire shape of @@ -71,7 +73,7 @@ type createRestoreTestingPlanBody struct { func (h *Handler) handleCreateRestoreTestingPlan(c *echo.Context, body []byte) error { var in createRestoreTestingPlanBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -229,7 +231,7 @@ func (h *Handler) handleCreateRestoreTestingSelection( } var in createRestoreTestingSelectionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -331,7 +333,7 @@ func (h *Handler) handleUpdateRestoreTestingPlan( var in updateRestoreTestingPlanBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -445,7 +447,7 @@ func (h *Handler) handleUpdateRestoreTestingSelection( var in updateRestoreTestingSelectionBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_selections.go b/services/backup/handler_selections.go index d30b133e5d..99fa684980 100644 --- a/services/backup/handler_selections.go +++ b/services/backup/handler_selections.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type backupSelectionDoc struct { @@ -30,7 +32,7 @@ func (h *Handler) handleCreateBackupSelection(c *echo.Context, planID string, bo } var in createBackupSelectionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_selections_test.go b/services/backup/handler_selections_test.go index 670fa1a696..61d1b49470 100644 --- a/services/backup/handler_selections_test.go +++ b/services/backup/handler_selections_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -109,18 +110,18 @@ func TestBackupSelectionResourceFields(t *testing.T) { `{"BackupPlan":{"BackupPlanName":"p","Rules":[{"RuleName":"r","TargetBackupVaultName":"v"}]}}`) require.Equal(t, http.StatusOK, planResp.Code) var planData map[string]any - require.NoError(t, json.Unmarshal(planResp.Body.Bytes(), &planData)) + require.NoError(t, json.Unmarshal(planResp.Body.Bytes(), &planData, jsonx.V1())) planID := planData["BackupPlanId"].(string) // Create selection. - body, err := json.Marshal(map[string]any{"BackupSelection": tt.selection}) + body, err := json.Marshal(map[string]any{"BackupSelection": tt.selection}, jsonx.V1()) require.NoError(t, err) selResp := doRequest(t, h, http.MethodPut, "/backup/plans/"+planID+"/selections", string(body)) require.Equal(t, http.StatusOK, selResp.Code) var selData map[string]any - require.NoError(t, json.Unmarshal(selResp.Body.Bytes(), &selData)) + require.NoError(t, json.Unmarshal(selResp.Body.Bytes(), &selData, jsonx.V1())) selID := selData["SelectionId"].(string) // Get selection and verify fields. @@ -129,7 +130,7 @@ func TestBackupSelectionResourceFields(t *testing.T) { require.Equal(t, http.StatusOK, getResp.Code) var getData map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getData, jsonx.V1())) selDoc := getData["BackupSelection"].(map[string]any) tt.wantSel(t, selDoc) }) diff --git a/services/backup/handler_settings.go b/services/backup/handler_settings.go index f74adb5b5f..ff43bc0227 100644 --- a/services/backup/handler_settings.go +++ b/services/backup/handler_settings.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchSettingsOps handles the account-level global/region settings and @@ -26,7 +28,7 @@ func (h *Handler) dispatchSettingsOps( var reqGSBody struct { GlobalSettings map[string]string `json:"GlobalSettings"` } - if err := json.Unmarshal(body, &reqGSBody); err == nil && + if err := json.Unmarshal(body, &reqGSBody, jsonx.V1()); err == nil && reqGSBody.GlobalSettings != nil { h.Backend.UpdateGlobalSettings(reqGSBody.GlobalSettings) } @@ -44,7 +46,7 @@ func (h *Handler) dispatchSettingsOps( ResourceTypeManagementPreference map[string]bool `json:"ResourceTypeManagementPreference"` ResourceTypeOptInPreference map[string]bool `json:"ResourceTypeOptInPreference"` } - if err := json.Unmarshal(body, &reqRSBody); err == nil { + if err := json.Unmarshal(body, &reqRSBody, jsonx.V1()); err == nil { h.Backend.UpdateRegionSettings( reqRSBody.ResourceTypeManagementPreference, reqRSBody.ResourceTypeOptInPreference, diff --git a/services/backup/handler_tags.go b/services/backup/handler_tags.go index 667411460b..451d0dbd07 100644 --- a/services/backup/handler_tags.go +++ b/services/backup/handler_tags.go @@ -1,12 +1,14 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "slices" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceBody struct { @@ -15,7 +17,7 @@ type tagResourceBody struct { func (h *Handler) handleTagResource(c *echo.Context, resourceArn string, body []byte) error { var in tagResourceBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -61,7 +63,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, resourceArn string, body var in untagResourceBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_templates_test.go b/services/backup/handler_templates_test.go index 139c14a672..b3beee0685 100644 --- a/services/backup/handler_templates_test.go +++ b/services/backup/handler_templates_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -41,19 +42,19 @@ func TestExportBackupPlanTemplate_RealRules(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) planID := created["BackupPlanId"].(string) resp := doRequest(t, h, http.MethodGet, "/backup/plans/"+planID+"/toTemplate", "") require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) tmplJSON, ok := out["BackupPlanTemplateJson"].(string) require.True(t, ok) var tmpl map[string]any - require.NoError(t, json.Unmarshal([]byte(tmplJSON), &tmpl)) + require.NoError(t, json.Unmarshal([]byte(tmplJSON), &tmpl, jsonx.V1())) assert.Equal(t, "export-plan", tmpl["BackupPlanName"]) rules, ok := tmpl["Rules"].([]any) require.True(t, ok) @@ -75,7 +76,7 @@ func TestGetBackupPlanFromJSON_BuildsFromParsedBody(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) plan, ok := out["BackupPlan"].(map[string]any) require.True(t, ok) assert.Equal(t, "json-plan", plan["BackupPlanName"]) @@ -107,7 +108,7 @@ func TestGetBackupPlanFromTemplate_ResolvesBuiltinTemplate(t *testing.T) { require.Equal(t, http.StatusOK, listResp.Code) var list map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &list, jsonx.V1())) tmpls, ok := list["BackupPlanTemplatesList"].([]any) require.True(t, ok) require.NotEmpty(t, tmpls) @@ -120,7 +121,7 @@ func TestGetBackupPlanFromTemplate_ResolvesBuiltinTemplate(t *testing.T) { require.Equal(t, http.StatusOK, getResp.Code) var doc map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &doc, jsonx.V1())) planDoc, ok := doc["BackupPlanDocument"].(map[string]any) require.True(t, ok) assert.Equal(t, tmplName, planDoc["BackupPlanName"]) @@ -148,7 +149,7 @@ func TestListBackupPlanTemplates_ReturnsBuiltinCatalog(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) items, ok := out["BackupPlanTemplatesList"].([]any) require.True(t, ok) require.NotEmpty(t, items) diff --git a/services/backup/handler_tiering.go b/services/backup/handler_tiering.go index 7e450eecc4..b63ec786e4 100644 --- a/services/backup/handler_tiering.go +++ b/services/backup/handler_tiering.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -103,7 +105,7 @@ func (h *Handler) dispatchTieringOps( func (h *Handler) handleCreateTieringConfiguration(c *echo.Context, body []byte) error { var in createTieringConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } @@ -167,7 +169,7 @@ func (h *Handler) handleListTieringConfigurations(c *echo.Context) error { func (h *Handler) handleUpdateTieringConfiguration(c *echo.Context, name string, body []byte) error { var in updateTieringConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_vault_policies.go b/services/backup/handler_vault_policies.go index 39a10bf864..f92566be31 100644 --- a/services/backup/handler_vault_policies.go +++ b/services/backup/handler_vault_policies.go @@ -1,10 +1,12 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putVaultAccessPolicyBody struct { @@ -25,7 +27,7 @@ func (h *Handler) handlePutBackupVaultAccessPolicy( var in putVaultAccessPolicyBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -100,7 +102,7 @@ func (h *Handler) handlePutBackupVaultLockConfiguration( var in putVaultLockConfigBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -158,7 +160,7 @@ func (h *Handler) handlePutBackupVaultNotifications( var in putVaultNotificationsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), diff --git a/services/backup/handler_vault_policies_test.go b/services/backup/handler_vault_policies_test.go index 81874b9396..8c72d331d3 100644 --- a/services/backup/handler_vault_policies_test.go +++ b/services/backup/handler_vault_policies_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -59,7 +60,7 @@ func TestDescribeBackupVaultLockedField(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) locked, exists := data["Locked"] require.True(t, exists, "Locked must be present in DescribeBackupVault response") diff --git a/services/backup/handler_vaults.go b/services/backup/handler_vaults.go index 0f0236a652..5eefb43655 100644 --- a/services/backup/handler_vaults.go +++ b/services/backup/handler_vaults.go @@ -1,11 +1,13 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createBackupVaultBody struct { @@ -24,7 +26,7 @@ func (h *Handler) handleCreateBackupVault(c *echo.Context, name string, body []b var in createBackupVaultBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -192,7 +194,7 @@ func (h *Handler) handleAssociateBackupVaultMpaApprovalTeam( var in associateMpaApprovalTeamBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -230,7 +232,7 @@ func (h *Handler) handleCreateLogicallyAirGappedBackupVault( var in createLogicallyAirGappedBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body"), @@ -264,7 +266,7 @@ type createRestoreAccessVaultBody struct { func (h *Handler) handleCreateRestoreAccessBackupVault(c *echo.Context, body []byte) error { var in createRestoreAccessVaultBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterValueException", "invalid request body")) } diff --git a/services/backup/handler_vaults_test.go b/services/backup/handler_vaults_test.go index fba8592d38..1da6aea43d 100644 --- a/services/backup/handler_vaults_test.go +++ b/services/backup/handler_vaults_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -33,7 +34,7 @@ func TestDescribeBackupVaultVaultState(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) state, exists := data["VaultState"] require.True(t, exists, "VaultState must be present in DescribeBackupVault response") @@ -67,7 +68,7 @@ func TestListBackupVaultsVaultState(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) list, ok := data["BackupVaultList"].([]any) require.True(t, ok, "BackupVaultList must be present") @@ -184,14 +185,14 @@ func TestCreateVaultCreatorRequestIdIdempotency(t *testing.T) { r1 := doRequest(t, h, http.MethodPut, "/backup-vaults/idem-vault", tt.firstRequest) require.Equal(t, http.StatusOK, r1.Code) var d1 map[string]any - require.NoError(t, json.Unmarshal(r1.Body.Bytes(), &d1)) + require.NoError(t, json.Unmarshal(r1.Body.Bytes(), &d1, jsonx.V1())) arn1 := d1["BackupVaultArn"].(string) r2 := doRequest(t, h, http.MethodPut, "/backup-vaults/idem-vault", tt.secondRequest) assert.Equal(t, tt.wantSecondCode, r2.Code) if tt.wantSameArn { var d2 map[string]any - require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &d2)) + require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &d2, jsonx.V1())) assert.Equal(t, arn1, d2["BackupVaultArn"].(string)) } }) diff --git a/services/backup/helpers_test.go b/services/backup/helpers_test.go index 19c61685f9..cd53f98ad0 100644 --- a/services/backup/helpers_test.go +++ b/services/backup/helpers_test.go @@ -2,7 +2,7 @@ package backup_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -131,7 +132,7 @@ func doREST( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -151,7 +152,7 @@ func parseResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/backup/models.go b/services/backup/models.go index bc78d665c9..ad7e7906dd 100644 --- a/services/backup/models.go +++ b/services/backup/models.go @@ -1,7 +1,7 @@ package backup import ( - "encoding/json" + "encoding/json/jsontext" "time" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -401,7 +401,7 @@ type RecoveryPoint struct { SourceBackupVaultArn string `json:"sourceBackupVaultArn,omitempty"` ParentRecoveryPointArn string `json:"parentRecoveryPointArn,omitempty"` CompositeMemberIdentifier string `json:"compositeMemberIdentifier,omitempty"` - Snapshot json.RawMessage `json:"snapshot,omitempty"` + Snapshot jsontext.Value `json:"snapshot,omitempty"` BackupSizeInBytes int64 `json:"backupSizeInBytes,omitempty"` IsEncrypted bool `json:"isEncrypted,omitempty"` } diff --git a/services/backup/multi_region_test.go b/services/backup/multi_region_test.go index 79b3fe3527..288465938a 100644 --- a/services/backup/multi_region_test.go +++ b/services/backup/multi_region_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -38,7 +39,7 @@ func regionCall(t *testing.T, h *backup.Handler, region, method, path, body stri require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/backup/persistence.go b/services/backup/persistence.go index da564feeeb..467e6710fe 100644 --- a/services/backup/persistence.go +++ b/services/backup/persistence.go @@ -2,7 +2,7 @@ package backup import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -33,15 +33,15 @@ const backupSnapshotVersion = 1 // indexes (vaultARNIndex etc.) are NOT included here; they are rebuilt from // the restored tables by rebuildARNIndexes instead of persisted. type backendSnapshot struct { - GlobalSettingsLastUpdate time.Time `json:"globalSettingsLastUpdate,omitzero"` - Tables map[string]json.RawMessage `json:"tables"` - MpaApprovals map[string]string `json:"mpaApprovals,omitempty"` - GlobalSettings map[string]string `json:"globalSettings,omitempty"` - RecoveryPointIndexStatus map[string]string `json:"recoveryPointIndexStatus,omitempty"` - RegionSettings *RegionSettings `json:"regionSettings,omitempty"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + GlobalSettingsLastUpdate time.Time `json:"globalSettingsLastUpdate,omitzero"` + Tables map[string]jsontext.Value `json:"tables"` + MpaApprovals map[string]string `json:"mpaApprovals,omitempty"` + GlobalSettings map[string]string `json:"globalSettings,omitempty"` + RecoveryPointIndexStatus map[string]string `json:"recoveryPointIndexStatus,omitempty"` + RegionSettings *RegionSettings `json:"regionSettings,omitempty"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/backup/protected_resources_test.go b/services/backup/protected_resources_test.go index 08d1c466dd..e2d5c85813 100644 --- a/services/backup/protected_resources_test.go +++ b/services/backup/protected_resources_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -46,7 +47,7 @@ func TestProtectedResourceLastBackupTimeEpoch(t *testing.T) { "IamRoleArn": "arn:aws:iam::000000000000:role/backup-role" }`) var startData map[string]any - require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startData)) + require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startData, jsonx.V1())) // Real AWS Backup jobs complete asynchronously; the emulator // models that via the janitor. Complete it synchronously here // so the resource is recorded as protected. @@ -66,7 +67,7 @@ func TestProtectedResourceLastBackupTimeEpoch(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) lastBackup, exists := data["LastBackupTime"] require.True(t, exists, "LastBackupTime must be present") diff --git a/services/backup/recovery_point_by_vault_storage_class_test.go b/services/backup/recovery_point_by_vault_storage_class_test.go index 02ed5e6e58..0c4e407a21 100644 --- a/services/backup/recovery_point_by_vault_storage_class_test.go +++ b/services/backup/recovery_point_by_vault_storage_class_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -42,7 +43,7 @@ func TestListRecoveryPointsByBackupVault_NoStorageClassLeak(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, ok := listResp["RecoveryPoints"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -58,6 +59,6 @@ func TestListRecoveryPointsByBackupVault_NoStorageClassLeak(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "WARM", descResp["StorageClass"], "DescribeRecoveryPoint genuinely has StorageClass") } diff --git a/services/backup/recovery_points_test.go b/services/backup/recovery_points_test.go index 00ca5aae3c..db140880d9 100644 --- a/services/backup/recovery_points_test.go +++ b/services/backup/recovery_points_test.go @@ -1,7 +1,7 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -246,7 +247,7 @@ func TestDeleteRecoveryPointVaultLock(t *testing.T) { var probe struct { RecoveryPointIndexStatus map[string]string `json:"recoveryPointIndexStatus"` } - require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &probe)) + require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &probe, jsonx.V1())) assert.NotContains(t, probe.RecoveryPointIndexStatus, "idx-vault:arn:aws:backup:::rp/1") }) } diff --git a/services/backup/report_delivery.go b/services/backup/report_delivery.go index ac27853e3d..3dd82e1c82 100644 --- a/services/backup/report_delivery.go +++ b/services/backup/report_delivery.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/csv" - "encoding/json" + "encoding/json/v2" "fmt" "path" "sort" @@ -14,6 +14,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -89,7 +91,7 @@ func renderReport(format string, t reportTable) ([]byte, string, error) { recs = append(recs, rec) } - body, err := json.Marshal(recs) + body, err := json.Marshal(recs, jsonx.V1()) if err != nil { return nil, "", fmt.Errorf("encode JSON report: %w", err) } diff --git a/services/backup/resource_engines.go b/services/backup/resource_engines.go index 2a1650fbf7..4617893f99 100644 --- a/services/backup/resource_engines.go +++ b/services/backup/resource_engines.go @@ -3,7 +3,8 @@ package backup import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" @@ -13,6 +14,7 @@ import ( sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -24,8 +26,8 @@ const ( // resourceEngine snapshots a live resource at backup time and recreates it from that snapshot on restore. type resourceEngine interface { // capture returns nil data when the resource does not exist. - capture(ctx context.Context, resourceArn string) (json.RawMessage, error) - restore(ctx context.Context, data json.RawMessage, metadata map[string]string) (string, error) + capture(ctx context.Context, resourceArn string) (jsontext.Value, error) + restore(ctx context.Context, data jsontext.Value, metadata map[string]string) (string, error) } // DynamoDBBackend is the DynamoDB surface used to snapshot and restore tables. @@ -94,10 +96,10 @@ type ddbSnapshot struct { Attributes []ddbtypes.AttributeDefinition `json:"attributes"` GSIs []ddbtypes.GlobalSecondaryIndex `json:"gsis,omitempty"` LSIs []ddbtypes.LocalSecondaryIndex `json:"lsis,omitempty"` - Items []map[string]json.RawMessage `json:"items"` + Items []map[string]jsontext.Value `json:"items"` } -func (e *ddbEngine) capture(ctx context.Context, resourceArn string) (json.RawMessage, error) { +func (e *ddbEngine) capture(ctx context.Context, resourceArn string) (jsontext.Value, error) { name := resourceNameFromARN(resourceArn) desc, err := e.ddb.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: aws.String(name)}) @@ -139,12 +141,12 @@ func (e *ddbEngine) capture(ctx context.Context, resourceArn string) (json.RawMe return nil, err } - return json.Marshal(snap) + return json.Marshal(snap, jsonx.V1()) } -func (e *ddbEngine) scanAll(ctx context.Context, table string) ([]map[string]json.RawMessage, error) { +func (e *ddbEngine) scanAll(ctx context.Context, table string) ([]map[string]jsontext.Value, error) { var ( - items []map[string]json.RawMessage + items []map[string]jsontext.Value start map[string]ddbtypes.AttributeValue ) @@ -169,9 +171,9 @@ func (e *ddbEngine) scanAll(ctx context.Context, table string) ([]map[string]jso } } -func (e *ddbEngine) restore(ctx context.Context, data json.RawMessage, md map[string]string) (string, error) { +func (e *ddbEngine) restore(ctx context.Context, data jsontext.Value, md map[string]string) (string, error) { var snap ddbSnapshot - if err := json.Unmarshal(data, &snap); err != nil { + if err := json.Unmarshal(data, &snap, jsonx.V1()); err != nil { return "", fmt.Errorf("decode DynamoDB snapshot: %w", err) } @@ -215,7 +217,7 @@ type s3SnapObject struct { Body []byte `json:"body"` } -func (e *s3Engine) capture(ctx context.Context, resourceArn string) (json.RawMessage, error) { +func (e *s3Engine) capture(ctx context.Context, resourceArn string) (jsontext.Value, error) { bucket, ok := s3BucketFromARN(resourceArn) if !ok { return nil, nil @@ -249,7 +251,7 @@ func (e *s3Engine) capture(ctx context.Context, resourceArn string) (json.RawMes token = page.NextContinuationToken } - return json.Marshal(snap) + return json.Marshal(snap, jsonx.V1()) } func (e *s3Engine) readObject(ctx context.Context, bucket, key string) (s3SnapObject, error) { @@ -267,9 +269,9 @@ func (e *s3Engine) readObject(ctx context.Context, bucket, key string) (s3SnapOb return s3SnapObject{Key: key, ContentType: aws.ToString(out.ContentType), Body: body}, nil } -func (e *s3Engine) restore(ctx context.Context, data json.RawMessage, md map[string]string) (string, error) { +func (e *s3Engine) restore(ctx context.Context, data jsontext.Value, md map[string]string) (string, error) { var snap s3Snapshot - if err := json.Unmarshal(data, &snap); err != nil { + if err := json.Unmarshal(data, &snap, jsonx.V1()); err != nil { return "", fmt.Errorf("decode S3 snapshot: %w", err) } @@ -303,18 +305,18 @@ type ebsEngine struct { accountID string } -func (e *ebsEngine) capture(_ context.Context, resourceArn string) (json.RawMessage, error) { +func (e *ebsEngine) capture(_ context.Context, resourceArn string) (jsontext.Value, error) { info, ok := e.vols.DescribeVolume(resourceNameFromARN(resourceArn)) if !ok { return nil, nil } - return json.Marshal(info) + return json.Marshal(info, jsonx.V1()) } -func (e *ebsEngine) restore(_ context.Context, data json.RawMessage, md map[string]string) (string, error) { +func (e *ebsEngine) restore(_ context.Context, data jsontext.Value, md map[string]string) (string, error) { var info VolumeInfo - if err := json.Unmarshal(data, &info); err != nil { + if err := json.Unmarshal(data, &info, jsonx.V1()); err != nil { return "", fmt.Errorf("decode EBS snapshot: %w", err) } if v := md["availabilityZone"]; v != "" { diff --git a/services/backup/resource_restore_test.go b/services/backup/resource_restore_test.go index b9768aeee0..893fa51e8b 100644 --- a/services/backup/resource_restore_test.go +++ b/services/backup/resource_restore_test.go @@ -3,7 +3,7 @@ package backup_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "sort" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -359,7 +360,7 @@ func TestReportJob_JSONContent(t *testing.T) { require.Len(t, got.ReportDestination.S3Keys, 1) var rows []map[string]string - require.NoError(t, json.Unmarshal(store.objects["rep/"+got.ReportDestination.S3Keys[0]], &rows)) + require.NoError(t, json.Unmarshal(store.objects["rep/"+got.ReportDestination.S3Keys[0]], &rows, jsonx.V1())) require.Len(t, rows, 1) assert.Equal(t, testVolumeArn, rows[0]["Resource ARN"]) assert.Equal(t, "COMPLETED", rows[0]["Job Status"]) diff --git a/services/backup/settings_test.go b/services/backup/settings_test.go index dbbab38d19..66a63872f0 100644 --- a/services/backup/settings_test.go +++ b/services/backup/settings_test.go @@ -1,13 +1,14 @@ package backup_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/backup" ) @@ -44,7 +45,7 @@ func TestGlobalSettingsLastUpdateTimeEpoch(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var data map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &data, jsonx.V1())) lastUpdate, exists := data["LastUpdateTime"] require.True(t, exists, "LastUpdateTime must be present") diff --git a/services/backup/vault_policies.go b/services/backup/vault_policies.go index 1f1ee7e792..87adb6f98b 100644 --- a/services/backup/vault_policies.go +++ b/services/backup/vault_policies.go @@ -1,11 +1,13 @@ package backup import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" sdktypes "github.com/aws/aws-sdk-go-v2/service/backup/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // isValidVaultEvent derives its answer from types.BackupVaultEvent.Values() @@ -35,7 +37,7 @@ func (b *InMemoryBackend) PutBackupVaultAccessPolicy(vaultName, policy string) e if policy != "" { var doc map[string]any - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return fmt.Errorf( "%w: Policy must be a valid JSON document: %s", ErrValidation, diff --git a/services/batch/exec_test.go b/services/batch/exec_test.go index b38992a268..24e6395db5 100644 --- a/services/batch/exec_test.go +++ b/services/batch/exec_test.go @@ -2,7 +2,7 @@ package batch_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "log/slog" "strings" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -300,7 +301,7 @@ func TestExecJobStateAndExitCode(t *testing.T) { assert.Equal(t, done.Container.LogStreamName, done.Attempts[0].Container.LogStreamName) assert.NotNil(t, done.Attempts[0].StoppedAt) - raw, err := json.Marshal(done.Attempts[0].Container) + raw, err := json.Marshal(done.Attempts[0].Container, jsonx.V1()) require.NoError(t, err) assert.Contains(t, string(raw), `"exitCode"`) diff --git a/services/batch/handler.go b/services/batch/handler.go index 26acb3482f..917ad42c2b 100644 --- a/services/batch/handler.go +++ b/services/batch/handler.go @@ -2,7 +2,7 @@ package batch import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "log/slog" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -331,7 +332,7 @@ func (h *Handler) Handler() echo.HandlerFunc { return h.writeError(c, opErr) } - out, marshalErr := json.Marshal(result) + out, marshalErr := json.Marshal(result, jsonx.V1()) if marshalErr != nil { log.ErrorContext(ctx, "batch: failed to marshal response", "error", marshalErr) diff --git a/services/batch/handler_compute_environments_test.go b/services/batch/handler_compute_environments_test.go index 31c4878362..101423e7aa 100644 --- a/services/batch/handler_compute_environments_test.go +++ b/services/batch/handler_compute_environments_test.go @@ -1,13 +1,15 @@ package batch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -293,7 +295,7 @@ func TestDescribeComputeEnvironments_TagsPresentNoTags(t *testing.T) { items := out["computeEnvironments"].([]any) require.Len(t, items, 1) - itemBytes, err := json.Marshal(items[0]) + itemBytes, err := json.Marshal(items[0], jsonx.V1()) require.NoError(t, err) assertTagsPresent(t, itemBytes) } @@ -307,8 +309,8 @@ func TestDescribeComputeEnvironments_EmptyList(t *testing.T) { rec := post(t, h, "/v1/describecomputeenvironments", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap, jsonx.V1())) raw, ok := rawMap["computeEnvironments"] require.True(t, ok, "computeEnvironments key must be present") assert.Equal(t, "[]", string(raw), "computeEnvironments must be [] not null when empty") diff --git a/services/batch/handler_job_definitions_test.go b/services/batch/handler_job_definitions_test.go index a5a640529e..8b96b2337f 100644 --- a/services/batch/handler_job_definitions_test.go +++ b/services/batch/handler_job_definitions_test.go @@ -1,12 +1,15 @@ package batch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_JobDefinition_CRUD(t *testing.T) { @@ -194,7 +197,7 @@ func TestDescribeJobDefinitions_TagsPresentNoTags(t *testing.T) { items := out["jobDefinitions"].([]any) require.Len(t, items, 1) - itemBytes, err := json.Marshal(items[0]) + itemBytes, err := json.Marshal(items[0], jsonx.V1()) require.NoError(t, err) assertTagsPresent(t, itemBytes) } @@ -208,8 +211,8 @@ func TestDescribeJobDefinitions_EmptyList(t *testing.T) { rec := post(t, h, "/v1/describejobdefinitions", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap, jsonx.V1())) raw, ok := rawMap["jobDefinitions"] require.True(t, ok, "jobDefinitions key must be present") assert.Equal(t, "[]", string(raw), "jobDefinitions must be [] not null when empty") diff --git a/services/batch/handler_job_queues_test.go b/services/batch/handler_job_queues_test.go index c4e5996240..3f33061bbb 100644 --- a/services/batch/handler_job_queues_test.go +++ b/services/batch/handler_job_queues_test.go @@ -1,13 +1,15 @@ package batch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -378,7 +380,7 @@ func TestHandler_DeleteJobQueue_TerminatesJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var submitResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitResp, jsonx.V1())) jobID := submitResp["jobId"].(string) // Delete the queue — associated jobs must be terminated (FAILED), not @@ -402,7 +404,7 @@ func TestHandler_DeleteJobQueue_TerminatesJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) jobs := descResp["jobs"].([]any) require.Len(t, jobs, 1, "job history must survive job queue deletion") job, _ := jobs[0].(map[string]any) @@ -524,21 +526,21 @@ func TestHandler_GetJobQueueSnapshot_WireShape(t *testing.T) { rec = post(t, h, "/v1/getjobqueuesnapshot", map[string]any{"jobQueue": "q-snap-wire"}) require.Equal(t, http.StatusOK, rec.Code) - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap, jsonx.V1())) - var frontOfQueue map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rawMap["frontOfQueue"], &frontOfQueue)) + var frontOfQueue map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rawMap["frontOfQueue"], &frontOfQueue, jsonx.V1())) _, hasTimestamp := frontOfQueue["timestamp"] assert.False(t, hasTimestamp, "wire shape must not have a legacy \"timestamp\" field") var lastUpdatedAt int64 - require.NoError(t, json.Unmarshal(frontOfQueue["lastUpdatedAt"], &lastUpdatedAt)) + require.NoError(t, json.Unmarshal(frontOfQueue["lastUpdatedAt"], &lastUpdatedAt, jsonx.V1())) assert.Positive(t, lastUpdatedAt, "lastUpdatedAt should be a positive epoch-millisecond value") var jobs []map[string]any - require.NoError(t, json.Unmarshal(frontOfQueue["jobs"], &jobs)) + require.NoError(t, json.Unmarshal(frontOfQueue["jobs"], &jobs, jsonx.V1())) require.Len(t, jobs, 1) assert.Contains(t, jobs[0]["jobArn"], "job/") @@ -583,7 +585,7 @@ func TestDescribeJobQueues_TagsPresentNoTags(t *testing.T) { items := out["jobQueues"].([]any) require.Len(t, items, 1) - itemBytes, err := json.Marshal(items[0]) + itemBytes, err := json.Marshal(items[0], jsonx.V1()) require.NoError(t, err) assertTagsPresent(t, itemBytes) } @@ -597,8 +599,8 @@ func TestDescribeJobQueues_EmptyList(t *testing.T) { rec := post(t, h, "/v1/describejobqueues", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap, jsonx.V1())) raw, ok := rawMap["jobQueues"] require.True(t, ok, "jobQueues key must be present") assert.Equal(t, "[]", string(raw), "jobQueues must be [] not null when empty") diff --git a/services/batch/handler_jobs_test.go b/services/batch/handler_jobs_test.go index 45284792ff..6c64ac9464 100644 --- a/services/batch/handler_jobs_test.go +++ b/services/batch/handler_jobs_test.go @@ -1,7 +1,8 @@ package batch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -198,7 +200,7 @@ func TestHandler_SubmitJob_JobARNPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var submitResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitResp, jsonx.V1())) jobID := submitResp["jobId"].(string) assert.NotEmpty(t, submitResp["jobArn"], "SubmitJob response should include jobArn") @@ -209,7 +211,7 @@ func TestHandler_SubmitJob_JobARNPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) jobs := descResp["jobs"].([]any) require.Len(t, jobs, 1) @@ -621,7 +623,7 @@ func TestDescribeJobs_TagsPresentNoTags(t *testing.T) { jobs := out["jobs"].([]any) require.Len(t, jobs, 1) - itemBytes, err := json.Marshal(jobs[0]) + itemBytes, err := json.Marshal(jobs[0], jsonx.V1()) require.NoError(t, err) assertTagsPresent(t, itemBytes) } @@ -668,8 +670,8 @@ func TestDescribeJobs_EmptyList(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rawMap, jsonx.V1())) raw, ok := rawMap["jobs"] require.True(t, ok, "jobs key must be present") assert.Equal(t, "[]", string(raw), "jobs must be [] not null when no matching jobs found") @@ -695,7 +697,7 @@ func TestListJobs_SummaryIncludesJobArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, _ := out["jobSummaryList"].([]any) require.Len(t, summaries, 1) @@ -720,7 +722,7 @@ func TestListJobs_SummaryIncludesTimestamps(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var submitOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &submitOut, jsonx.V1())) jobID := submitOut["jobId"] rec = post(t, h, "/v1/terminatejob", map[string]any{ @@ -736,7 +738,7 @@ func TestListJobs_SummaryIncludesTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, _ := out["jobSummaryList"].([]any) require.Len(t, summaries, 1) diff --git a/services/batch/handler_tags.go b/services/batch/handler_tags.go index f0b1dbe2d8..f163134652 100644 --- a/services/batch/handler_tags.go +++ b/services/batch/handler_tags.go @@ -2,11 +2,13 @@ package batch import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Tags handlers --- @@ -35,7 +37,7 @@ type tagResourceInput struct { func (h *Handler) handleTagResource(ctx context.Context, c *echo.Context, resourceARN string, body []byte) error { var in tagResourceInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid request body")) } } diff --git a/services/batch/handler_tags_test.go b/services/batch/handler_tags_test.go index dea1e9abc7..e02d46e958 100644 --- a/services/batch/handler_tags_test.go +++ b/services/batch/handler_tags_test.go @@ -2,7 +2,7 @@ package batch_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -259,7 +260,7 @@ func TestBatch_TagsOnNewResourceTypes(t *testing.T) { e := echo.New() tagPath := "/v1/tags/" + crARN tagBody := map[string]any{"tags": map[string]string{"env": "prod"}} - tagBytes, err := json.Marshal(tagBody) + tagBytes, err := json.Marshal(tagBody, jsonx.V1()) require.NoError(t, err) tagReq := httptest.NewRequest(http.MethodPost, tagPath, bytes.NewReader(tagBytes)) @@ -277,7 +278,7 @@ func TestBatch_TagsOnNewResourceTypes(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var tagsOut map[string]map[string]string - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) assert.Equal(t, "prod", tagsOut["tags"]["env"]) } diff --git a/services/batch/handler_test.go b/services/batch/handler_test.go index 7da8a819dd..58b97d1abe 100644 --- a/services/batch/handler_test.go +++ b/services/batch/handler_test.go @@ -2,7 +2,8 @@ package batch_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -33,7 +35,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -58,7 +60,7 @@ func post(t *testing.T, h *batch.Handler, path string, body any) *httptest.Respo func mustUnmarshal(t *testing.T, rec *httptest.ResponseRecorder, v any) { t.Helper() - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1())) } // --- Handler metadata tests --- @@ -418,15 +420,15 @@ func TestBatch_RequiredFieldValidation(t *testing.T) { func assertTagsPresent(t *testing.T, raw []byte) { t.Helper() - var rawMap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(raw, &rawMap)) + var rawMap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(raw, &rawMap, jsonx.V1())) tagBytes, hasTags := rawMap["tags"] assert.True(t, hasTags, "tags key must be present in describe response") if hasTags { var tags map[string]string - require.NoError(t, json.Unmarshal(tagBytes, &tags), "tags must be a JSON object, not null") + require.NoError(t, json.Unmarshal(tagBytes, &tags, jsonx.V1()), "tags must be a JSON object, not null") assert.NotNil(t, tags, "tags must be {} not null") } } diff --git a/services/batch/job_overrides.go b/services/batch/job_overrides.go index f7d5cf3f51..f09538a781 100644 --- a/services/batch/job_overrides.go +++ b/services/batch/job_overrides.go @@ -1,11 +1,13 @@ package batch import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const reservedEnvPrefix = "AWS_BATCH" @@ -51,12 +53,12 @@ type EksPropertiesOverride struct { func deepCopy[T any](v T) T { var out T - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } - if err = json.Unmarshal(raw, &out); err != nil { + if err = json.Unmarshal(raw, &out, jsonx.V1()); err != nil { return v } diff --git a/services/batch/models.go b/services/batch/models.go index dcd8309ffb..3e06cd167a 100644 --- a/services/batch/models.go +++ b/services/batch/models.go @@ -1,8 +1,10 @@ package batch import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- ComputeResources sub-types --- @@ -320,7 +322,7 @@ func (n *NodeRangeProperty) UnmarshalJSON(data []byte) error { Legacy *ContainerProperties `json:"containerProperties"` }{plain: (*plain)(n)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/batch/node_range_property_json_test.go b/services/batch/node_range_property_json_test.go index 30c2522330..20a8a292fa 100644 --- a/services/batch/node_range_property_json_test.go +++ b/services/batch/node_range_property_json_test.go @@ -1,12 +1,13 @@ package batch_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/batch" ) @@ -26,11 +27,11 @@ func TestNodeRangeProperty_JSONKeys(t *testing.T) { t.Parallel() var r batch.NodeRangeProperty - require.NoError(t, json.Unmarshal([]byte(tt.in), &r)) + require.NoError(t, json.Unmarshal([]byte(tt.in), &r, jsonx.V1())) require.NotNil(t, r.ContainerProperties) assert.Equal(t, "busybox", r.ContainerProperties.Image) - out, err := json.Marshal(r) + out, err := json.Marshal(r, jsonx.V1()) require.NoError(t, err) assert.Contains(t, string(out), `"container":`) assert.NotContains(t, string(out), `"containerProperties"`) diff --git a/services/batch/persistence.go b/services/batch/persistence.go index 1e7c5f8101..948a9b37b0 100644 --- a/services/batch/persistence.go +++ b/services/batch/persistence.go @@ -2,7 +2,7 @@ package batch import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/google/uuid" @@ -55,7 +55,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` JobDefRevisions map[string]map[string]int32 `json:"jobDefRevisions"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -271,7 +271,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // table (byRegion, byARN, byQueue, byName -- see store_setup.go), so unlike // the pre-refactor Restore this needs no separate index-rebuild step. // Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/batch/validation.go b/services/batch/validation.go index ae3cbb0539..862cc5aa14 100644 --- a/services/batch/validation.go +++ b/services/batch/validation.go @@ -1,10 +1,11 @@ package batch import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -40,7 +41,7 @@ func checkNextToken(body []byte) error { NextToken *string `json:"nextToken"` } - if json.Unmarshal(body, &in) == nil && in.NextToken != nil && *in.NextToken != "" && + if json.Unmarshal(body, &in, jsonx.V1()) == nil && in.NextToken != nil && *in.NextToken != "" && page.DecodeHMACToken(*in.NextToken, paginationSecret) <= 0 { return fmt.Errorf("%w: invalid nextToken", ErrValidation) } diff --git a/services/bcmdataexports/errors.go b/services/bcmdataexports/errors.go index 3dee148399..c70937e422 100644 --- a/services/bcmdataexports/errors.go +++ b/services/bcmdataexports/errors.go @@ -1,7 +1,7 @@ package bcmdataexports import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "hash/crc32" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Sentinels, one per exception in the pinned SDK's error set. @@ -120,7 +121,7 @@ func writeErrorBody(c *echo.Context, e awserr.APIError, d detail) error { body, _ := json.Marshal(errorBody{ Type: e.Code, Message: e.Message, ResourceID: d.resourceID, ResourceType: d.resourceType, Reason: d.reason, Fields: d.fields, - }) + }, jsonx.V1()) c.Response().Header().Set("X-Amz-Crc32", strconv.FormatUint(uint64(crc32.ChecksumIEEE(body)), 10)) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.1") diff --git a/services/bcmdataexports/executions.go b/services/bcmdataexports/executions.go index 9578f127df..152f13cbfb 100644 --- a/services/bcmdataexports/executions.go +++ b/services/bcmdataexports/executions.go @@ -2,13 +2,14 @@ package bcmdataexports import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "path" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/billingexport" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -239,7 +240,7 @@ func writeResult( man.DataFileKeys = []string{dataKey} - mb, err := json.Marshal(&man) + mb, err := json.Marshal(&man, jsonx.V1()) if err != nil { return fmt.Errorf("encode manifest: %w", err) } diff --git a/services/bcmdataexports/handler.go b/services/bcmdataexports/handler.go index 2400b09779..7bf17bd94b 100644 --- a/services/bcmdataexports/handler.go +++ b/services/bcmdataexports/handler.go @@ -2,7 +2,7 @@ package bcmdataexports import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/worker" @@ -39,7 +40,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, failReason(reasonCannotParse, "malformed request body: %v", err) } } diff --git a/services/bcmdataexports/persistence.go b/services/bcmdataexports/persistence.go index c924c24a85..aa1f2efcd5 100644 --- a/services/bcmdataexports/persistence.go +++ b/services/bcmdataexports/persistence.go @@ -2,7 +2,7 @@ package bcmdataexports import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/bcmdataexports/rows.go b/services/bcmdataexports/rows.go index 8dcd6663e1..2576e2faad 100644 --- a/services/bcmdataexports/rows.go +++ b/services/bcmdataexports/rows.go @@ -2,12 +2,13 @@ package bcmdataexports import ( "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "hash/fnv" "time" "github.com/blackbirdworks/gopherstack/pkgs/billingexport" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -35,7 +36,7 @@ func jsonMap(kv ...string) string { m[kv[i]] = kv[i+1] } - b, _ := json.Marshal(m) + b, _ := json.Marshal(m, jsonx.V1()) return string(b) } diff --git a/services/bedrock/automated_reasoning_policies.go b/services/bedrock/automated_reasoning_policies.go index 76c3ee6825..1af2f3c2fe 100644 --- a/services/bedrock/automated_reasoning_policies.go +++ b/services/bedrock/automated_reasoning_policies.go @@ -1,7 +1,7 @@ package bedrock import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "sort" @@ -56,7 +56,7 @@ func (b *InMemoryBackend) CreateAutomatedReasoningPolicy( // ARPCreateOptions carries CreateAutomatedReasoningPolicy's optional policyDefinition and kmsKeyId. type ARPCreateOptions struct { KmsKeyID string - PolicyDefinition json.RawMessage + PolicyDefinition jsontext.Value } // CreateAutomatedReasoningPolicyWithOptions is CreateAutomatedReasoningPolicy with ARPCreateOptions. @@ -317,7 +317,7 @@ func (b *InMemoryBackend) arpVersionSummariesLocked(policyARN string) []*Automat // would orphan that index instead of leaving the name unchanged. func (b *InMemoryBackend) UpdateAutomatedReasoningPolicy( policyARN, name, description string, - policyDefinition json.RawMessage, + policyDefinition jsontext.Value, ) (*AutomatedReasoningPolicy, error) { if len(policyDefinition) == 0 { return nil, fmt.Errorf("%w: policyDefinition is required", ErrValidation) @@ -451,7 +451,7 @@ func (b *InMemoryBackend) DeleteAutomatedReasoningPolicy(policyARN string, force // request body (serializers.go:8008-8058). func (b *InMemoryBackend) StartAutomatedReasoningPolicyBuildWorkflow( policyARN, buildWorkflowType string, - sourceContent json.RawMessage, + sourceContent jsontext.Value, ) (*AutomatedReasoningPolicyBuildWorkflow, error) { if buildWorkflowType == "" { return nil, fmt.Errorf("%w: buildWorkflowType is required", ErrValidation) diff --git a/services/bedrock/export_test.go b/services/bedrock/export_test.go index 3b39030b18..95752a90c3 100644 --- a/services/bedrock/export_test.go +++ b/services/bedrock/export_test.go @@ -1,7 +1,7 @@ package bedrock import ( - "encoding/json" + "encoding/json/jsontext" "strconv" ) @@ -112,7 +112,7 @@ func (b *InMemoryBackend) AddBuildWorkflowForTest(policyARN string) *AutomatedRe // conversion's registry mechanics directly, independent of the production // Snapshot/Restore pair added in persistence.go (see persistence_test.go for // black-box tests of that production path). -func (b *InMemoryBackend) SnapshotTablesForTest() (map[string]json.RawMessage, error) { +func (b *InMemoryBackend) SnapshotTablesForTest() (map[string]jsontext.Value, error) { b.mu.RLock("SnapshotTablesForTest") defer b.mu.RUnlock() @@ -121,7 +121,7 @@ func (b *InMemoryBackend) SnapshotTablesForTest() (map[string]json.RawMessage, e // RestoreTablesForTest replaces every store.Table registered on b.registry // with the contents of data, as produced by SnapshotTablesForTest. -func (b *InMemoryBackend) RestoreTablesForTest(data map[string]json.RawMessage) error { +func (b *InMemoryBackend) RestoreTablesForTest(data map[string]jsontext.Value) error { b.mu.Lock("RestoreTablesForTest") defer b.mu.Unlock() diff --git a/services/bedrock/ghost_row_jkiu_test.go b/services/bedrock/ghost_row_jkiu_test.go index 8a3d83869e..79f567e7f4 100644 --- a/services/bedrock/ghost_row_jkiu_test.go +++ b/services/bedrock/ghost_row_jkiu_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -24,7 +24,7 @@ func TestDeleteAutomatedReasoningPolicy_PrunesVersionCounterAndAnnotations(t *te wf, err := b.StartAutomatedReasoningPolicyBuildWorkflow( policy.PolicyArn, "INGEST_CONTENT", - json.RawMessage(`{}`), + jsontext.Value(`{}`), ) require.NoError(t, err) @@ -68,7 +68,7 @@ func TestDeleteAutomatedReasoningPolicyBuildWorkflow_PrunesAnnotations(t *testin wf, err := b.StartAutomatedReasoningPolicyBuildWorkflow( policy.PolicyArn, "INGEST_CONTENT", - json.RawMessage(`{}`), + jsontext.Value(`{}`), ) require.NoError(t, err) diff --git a/services/bedrock/handler.go b/services/bedrock/handler.go index 5a129d7615..f559a0b97d 100644 --- a/services/bedrock/handler.go +++ b/services/bedrock/handler.go @@ -2,7 +2,7 @@ package bedrock import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -140,7 +141,7 @@ type isoTime struct { } func (t isoTime) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Time.Format(time.RFC3339)) + return json.Marshal(t.Time.Format(time.RFC3339), jsonx.V1()) } // Handler is the Echo HTTP handler for Amazon Bedrock operations. @@ -704,7 +705,7 @@ func parseBody[T any](body []byte) (*T, error) { return &v, nil } - if err := json.Unmarshal(body, &v); err != nil { + if err := json.Unmarshal(body, &v, jsonx.V1()); err != nil { return nil, err } diff --git a/services/bedrock/handler_automated_reasoning_policies.go b/services/bedrock/handler_automated_reasoning_policies.go index 2938662c1c..0973ab25ce 100644 --- a/services/bedrock/handler_automated_reasoning_policies.go +++ b/services/bedrock/handler_automated_reasoning_policies.go @@ -1,12 +1,14 @@ package bedrock import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "strconv" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractARPOperation mirrors routeARP's dispatch order exactly (same @@ -347,12 +349,12 @@ func (h *Handler) routeARPSingleItem( } type createAutomatedReasoningPolicyInput struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - KmsKeyID string `json:"kmsKeyId,omitempty"` - ClientRequestToken string `json:"clientRequestToken,omitempty"` - PolicyDefinition json.RawMessage `json:"policyDefinition,omitempty"` - Tags []Tag `json:"tags,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + KmsKeyID string `json:"kmsKeyId,omitempty"` + ClientRequestToken string `json:"clientRequestToken,omitempty"` + PolicyDefinition jsontext.Value `json:"policyDefinition,omitempty"` + Tags []Tag `json:"tags,omitempty"` } type createAutomatedReasoningPolicyOutput struct { @@ -809,9 +811,9 @@ func (h *Handler) handleListAutomatedReasoningPolicies(c *echo.Context) error { } type updateARPInput struct { - Description string `json:"description,omitempty"` - Name string `json:"name,omitempty"` - PolicyDefinition json.RawMessage `json:"policyDefinition"` + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + PolicyDefinition jsontext.Value `json:"policyDefinition"` } func (h *Handler) handleUpdateAutomatedReasoningPolicy(c *echo.Context, policyARN string, body []byte) error { @@ -858,7 +860,7 @@ func (h *Handler) handleDeleteAutomatedReasoningPolicy(c *echo.Context, policyAR func (h *Handler) handleStartARPBuildWorkflow(c *echo.Context, path string, body []byte) error { policyARN, buildWorkflowType := extractARPBuildWorkflowStart(path) - if !json.Valid(body) { + if !jsontext.Value(body).IsValid(jsonx.V1()) { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid request body")) } @@ -873,7 +875,7 @@ func (h *Handler) handleStartARPBuildWorkflow(c *echo.Context, path string, body return h.Backend.StartAutomatedReasoningPolicyBuildWorkflow( policyARN, buildWorkflowType, - json.RawMessage(body), + jsontext.Value(body), ) }, ) diff --git a/services/bedrock/handler_automated_reasoning_policies_test.go b/services/bedrock/handler_automated_reasoning_policies_test.go index d93cdbe4ad..4abfd0af58 100644 --- a/services/bedrock/handler_automated_reasoning_policies_test.go +++ b/services/bedrock/handler_automated_reasoning_policies_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -362,7 +363,7 @@ func TestAccuracy_ARPVersion_ViaHTTP(t *testing.T) { "/automated-reasoning-policies/"+url.PathEscape(policy.PolicyArn)+"/versions", map[string]any{"notes": fmt.Sprintf("version %d", i+1)}) require.Equal(t, http.StatusCreated, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lastVersion)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lastVersion, jsonx.V1())) } assert.Equal(t, tt.wantLastVersion, lastVersion["version"]) @@ -384,14 +385,14 @@ func TestAccuracy_ARP_DescriptionPreservedOnCreate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/automated-reasoning-policies/"+policyARN, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, desc, getOut["description"]) assert.Equal(t, "finance-arp", getOut["name"]) } @@ -406,7 +407,7 @@ func TestAccuracy_ARP_UpdateDescriptionReflected(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) // Update description (real AWS: UpdateAutomatedReasoningPolicy uses PATCH) @@ -424,7 +425,7 @@ func TestAccuracy_ARP_UpdateDescriptionReflected(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "updated description", getOut["description"]) } @@ -456,7 +457,7 @@ func TestAccuracy_ARP_VersionNumberingIsolatedPerPolicy(t *testing.T) { // Verify second version for policy0 has version "2" var verOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verOut, jsonx.V1())) // policy1's first version is "1" (isolated counter) assert.Equal(t, "1", verOut["version"], "policy1 version counter starts from 1 independent of policy0") @@ -475,7 +476,7 @@ func TestAccuracy_ARP_BuildWorkflowStatusTransitions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) // Add a build workflow via test helper @@ -488,7 +489,7 @@ func TestAccuracy_ARP_BuildWorkflowStatusTransitions(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "BUILDING", getOut["status"]) } @@ -504,7 +505,7 @@ func TestAccuracy_ARP_TestCaseRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) // Create test case (needs URL-encoded ARN in path) @@ -516,7 +517,7 @@ func TestAccuracy_ARP_TestCaseRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, tcRec.Code) var tcOut map[string]any - require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tcOut)) + require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tcOut, jsonx.V1())) tcID := tcOut["testCaseId"].(string) assert.NotEmpty(t, tcID) @@ -526,7 +527,7 @@ func TestAccuracy_ARP_TestCaseRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getTestRec.Code) var getTestOut map[string]any - require.NoError(t, json.Unmarshal(getTestRec.Body.Bytes(), &getTestOut)) + require.NoError(t, json.Unmarshal(getTestRec.Body.Bytes(), &getTestOut, jsonx.V1())) assert.Equal(t, policyARN, getTestOut["policyArn"]) testCase, ok := getTestOut["testCase"].(map[string]any) require.True(t, ok, "response must wrap the test case under the required testCase key") @@ -545,7 +546,7 @@ func TestAccuracy_ARP_AnnotationsGetAfterUpdate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) wf := h.Backend.AddBuildWorkflowForTest(policyARN) @@ -560,7 +561,7 @@ func TestAccuracy_ARP_AnnotationsGetAfterUpdate(t *testing.T) { require.Equal(t, http.StatusOK, preRec.Code) var preOut map[string]any - require.NoError(t, json.Unmarshal(preRec.Body.Bytes(), &preOut)) + require.NoError(t, json.Unmarshal(preRec.Body.Bytes(), &preOut, jsonx.V1())) hash, ok := preOut["annotationSetHash"].(string) require.True(t, ok) require.NotEmpty(t, hash) @@ -578,7 +579,7 @@ func TestAccuracy_ARP_AnnotationsGetAfterUpdate(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) assert.Equal(t, policyARN, updateOut["policyArn"]) assert.Equal(t, wf.BuildWorkflowID, updateOut["buildWorkflowId"]) assert.NotEmpty(t, updateOut["annotationSetHash"]) @@ -589,7 +590,7 @@ func TestAccuracy_ARP_AnnotationsGetAfterUpdate(t *testing.T) { require.Equal(t, http.StatusOK, getAnnRec.Code) var annOut map[string]any - require.NoError(t, json.Unmarshal(getAnnRec.Body.Bytes(), &annOut)) + require.NoError(t, json.Unmarshal(getAnnRec.Body.Bytes(), &annOut, jsonx.V1())) anns, ok := annOut["annotations"].([]any) require.True(t, ok) require.Len(t, anns, 1) @@ -1021,7 +1022,7 @@ func TestAccuracy_ARP_UpdateOpsRejectOldPUTMethod(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policyARN := out["policyArn"].(string) tcRec := doRequest(t, h, http.MethodPost, @@ -1029,7 +1030,7 @@ func TestAccuracy_ARP_UpdateOpsRejectOldPUTMethod(t *testing.T) { require.Equal(t, http.StatusCreated, tcRec.Code) var tcOut map[string]any - require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tcOut)) + require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tcOut, jsonx.V1())) tcID := tcOut["testCaseId"].(string) wf := h.Backend.AddBuildWorkflowForTest(policyARN) @@ -1131,7 +1132,7 @@ func TestAccuracy_ARP_BuildWorkflowScopedSubResources(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) policyARN := created["policyArn"].(string) wf := h.Backend.AddBuildWorkflowForTest(policyARN) @@ -1141,7 +1142,7 @@ func TestAccuracy_ARP_BuildWorkflowScopedSubResources(t *testing.T) { require.Equal(t, http.StatusCreated, tcRec.Code) var tc map[string]any - require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tc)) + require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tc, jsonx.V1())) tcID := tc["testCaseId"].(string) path := "/automated-reasoning-policies/" + url.PathEscape(policyARN) + @@ -1165,14 +1166,14 @@ func TestAccuracy_ARP_BuildWorkflowSubResourcesRejectWrongPolicy(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &p1, jsonx.V1())) policy1ARN := p1["policyArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/automated-reasoning-policies", map[string]any{"name": "wrong-policy-2"}) require.Equal(t, http.StatusCreated, rec2.Code) var p2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &p2, jsonx.V1())) policy2ARN := p2["policyArn"].(string) wf := h.Backend.AddBuildWorkflowForTest(policy1ARN) @@ -1214,7 +1215,7 @@ func TestAccuracy_ARP_ExportVersion(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) policyARN := created["policyArn"].(string) verRec := doRequest(t, h, http.MethodPost, @@ -1223,7 +1224,7 @@ func TestAccuracy_ARP_ExportVersion(t *testing.T) { require.Equal(t, http.StatusCreated, verRec.Code) var ver map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &ver)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &ver, jsonx.V1())) versionedARN := ver["policyArn"].(string) exportRec := doRequest(t, h, http.MethodGet, @@ -1231,7 +1232,7 @@ func TestAccuracy_ARP_ExportVersion(t *testing.T) { require.Equal(t, http.StatusOK, exportRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(exportRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(exportRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "hash-export", out["definitionHash"]) } @@ -1249,7 +1250,7 @@ func TestAccuracy_ARP_OldInventedSubResourcePathsGone(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) policyARN := created["policyArn"].(string) tcRec := doRequest(t, h, http.MethodPost, @@ -1257,7 +1258,7 @@ func TestAccuracy_ARP_OldInventedSubResourcePathsGone(t *testing.T) { require.Equal(t, http.StatusCreated, tcRec.Code) var tc map[string]any - require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tc)) + require.NoError(t, json.Unmarshal(tcRec.Body.Bytes(), &tc, jsonx.V1())) tcID := tc["testCaseId"].(string) verRec := doRequest(t, h, http.MethodPost, @@ -1266,7 +1267,7 @@ func TestAccuracy_ARP_OldInventedSubResourcePathsGone(t *testing.T) { require.Equal(t, http.StatusCreated, verRec.Code) var ver map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &ver)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &ver, jsonx.V1())) version := ver["version"].(string) base := "/automated-reasoning-policies/" + url.PathEscape(policyARN) diff --git a/services/bedrock/handler_custom_model_deployments_test.go b/services/bedrock/handler_custom_model_deployments_test.go index c2e70dc8b6..6f7a6be407 100644 --- a/services/bedrock/handler_custom_model_deployments_test.go +++ b/services/bedrock/handler_custom_model_deployments_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -129,7 +130,7 @@ func TestAccuracy_CustomModelDeployment_StatusIsActive(t *testing.T) { require.Equal(t, http.StatusCreated, modelRec.Code, modelRec.Body.String()) var modelOut map[string]any - require.NoError(t, json.Unmarshal(modelRec.Body.Bytes(), &modelOut)) + require.NoError(t, json.Unmarshal(modelRec.Body.Bytes(), &modelOut, jsonx.V1())) modelARN := modelOut["modelArn"].(string) // Create deployment for the model @@ -140,7 +141,7 @@ func TestAccuracy_CustomModelDeployment_StatusIsActive(t *testing.T) { require.Equal(t, http.StatusCreated, depRec.Code) var depOut map[string]any - require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &depOut)) + require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &depOut, jsonx.V1())) depARN := depOut["customModelDeploymentArn"].(string) assert.NotEmpty(t, depARN) @@ -148,7 +149,7 @@ func TestAccuracy_CustomModelDeployment_StatusIsActive(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "my-deployment", getOut["modelDeploymentName"]) // Creating is right immediately after create, but this test's own name // promises "StatusIsActive" -- an assertion that only checks the first @@ -162,7 +163,7 @@ func TestAccuracy_CustomModelDeployment_StatusIsActive(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var getOut2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2, jsonx.V1())) assert.Equal(t, "Active", getOut2["status"]) } @@ -184,7 +185,7 @@ func TestAccuracy_CustomModelDeployment_ListAfterMultipleCreates(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) deployments := listOut["modelDeploymentSummaries"].([]any) assert.GreaterOrEqual(t, len(deployments), 3) } @@ -203,7 +204,7 @@ func TestAccuracy_CustomModelDeployment_DeleteRemovesFromList(t *testing.T) { require.Equal(t, http.StatusCreated, depRec.Code) var depOut map[string]any - require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &depOut)) + require.NoError(t, json.Unmarshal(depRec.Body.Bytes(), &depOut, jsonx.V1())) depARN := depOut["customModelDeploymentArn"].(string) // Delete it diff --git a/services/bedrock/handler_custom_models_test.go b/services/bedrock/handler_custom_models_test.go index 49a8cdabcc..cfb2862f1f 100644 --- a/services/bedrock/handler_custom_models_test.go +++ b/services/bedrock/handler_custom_models_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -111,7 +112,7 @@ func TestAccuracy_CustomModel_GetViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, m.ModelArn, out["modelArn"]) assert.Equal(t, tt.modelName, out["modelName"]) assert.NotEmpty(t, out["creationTime"]) @@ -156,7 +157,7 @@ func TestAccuracy_CustomModel_ListViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) models := out["modelSummaries"].([]any) assert.Len(t, models, tt.wantLen) }) @@ -200,7 +201,7 @@ func TestAccuracy_CustomModel_ListFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) models := out["modelSummaries"].([]any) gotNames := make([]string, 0, len(models)) @@ -266,7 +267,7 @@ func TestHandler_CustomModelLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) modelARN := createOut["modelArn"].(string) // List custom models. @@ -274,7 +275,7 @@ func TestHandler_CustomModelLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut, jsonx.V1())) assert.NotEmpty(t, listOut["modelSummaries"]) // Get custom model by ARN. diff --git a/services/bedrock/handler_evaluation_jobs_test.go b/services/bedrock/handler_evaluation_jobs_test.go index 7cbe675512..a9655f3638 100644 --- a/services/bedrock/handler_evaluation_jobs_test.go +++ b/services/bedrock/handler_evaluation_jobs_test.go @@ -1,7 +1,8 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "strconv" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -290,7 +292,7 @@ func TestBatch1_EvaluationJob_RAGConfig_RoundTrip(t *testing.T) { }, InferenceConfig: &bedrock.EvaluationInferenceConfig{ RagConfigs: []bedrock.RAGConfig{ - {KnowledgeBaseConfig: json.RawMessage(`{"retrieveConfig":{"knowledgeBaseId":"kb-0001"}}`)}, + {KnowledgeBaseConfig: jsontext.Value(`{"retrieveConfig":{"knowledgeBaseId":"kb-0001"}}`)}, }, }, } @@ -508,7 +510,7 @@ func TestAccuracy_EvaluationJob_StopTransitionsStatus(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "Stopping", out["status"]) }) } @@ -542,7 +544,7 @@ func TestAccuracy_EvaluationJob_ListResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["jobSummaries"].([]any) assert.Len(t, summaries, tt.wantLen) @@ -595,14 +597,14 @@ func TestAccuracy_EvaluationJob_EvaluatorModelConfigPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/evaluation-jobs/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) automated := getOut["evaluationConfig"].(map[string]any)["automated"].(map[string]any) evaluatorModelConfig := automated["evaluatorModelConfig"].(map[string]any) @@ -631,14 +633,14 @@ func TestAccuracy_EvaluationJob_InferenceModelsPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/evaluation-jobs/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) inferCfg := getOut["inferenceConfig"].(map[string]any) models := inferCfg["models"].([]any) @@ -663,14 +665,14 @@ func TestAccuracy_EvaluationJob_RAGConfigPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/evaluation-jobs/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) inferCfg := getOut["inferenceConfig"].(map[string]any) ragConfigs := inferCfg["ragConfigs"].([]any) @@ -707,14 +709,14 @@ func TestAccuracy_EvaluationJob_EvalTaskConfigPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/evaluation-jobs/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) automated := getOut["evaluationConfig"].(map[string]any)["automated"].(map[string]any) datasetMetricConfigs := automated["datasetMetricConfigs"].([]any) @@ -738,14 +740,14 @@ func TestAccuracy_EvaluationJob_JobDescriptionPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/evaluation-jobs/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, desc, getOut["jobDescription"]) } @@ -983,7 +985,7 @@ func TestBatch2Ops_StopEvaluationJob_AlreadyStopped_Rejected(t *testing.T) { "stop of already-stopped job should return 400 ValidationException") var errBody map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ValidationException", errBody["__type"], "error type must be ValidationException") }) diff --git a/services/bedrock/handler_foundation_model_agreements_test.go b/services/bedrock/handler_foundation_model_agreements_test.go index 92e4e92e3d..08b2583576 100644 --- a/services/bedrock/handler_foundation_model_agreements_test.go +++ b/services/bedrock/handler_foundation_model_agreements_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -71,7 +72,7 @@ func TestAccuracy_FoundationModelAgreementOffers_ListRequiresModelID(t *testing. require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "amazon.titan-text-express-v1", out["modelId"]) offers, _ := out["offers"].([]any) diff --git a/services/bedrock/handler_foundation_models_test.go b/services/bedrock/handler_foundation_models_test.go index f5a2415973..bf9031b687 100644 --- a/services/bedrock/handler_foundation_models_test.go +++ b/services/bedrock/handler_foundation_models_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -216,14 +217,14 @@ func TestBatch1_FoundationModel_ResponseStreamingSupported_Bool(t *testing.T) { var out map[string]any mustUnmarshal(t, rec, &out) details := out["modelDetails"].(map[string]any) - rawBytes, err := json.Marshal(details) + rawBytes, err := json.Marshal(details, jsonx.V1()) require.NoError(t, err) var typed struct { InferenceTypesSupported []string `json:"inferenceTypesSupported"` ResponseStreamingSupported bool `json:"responseStreamingSupported"` } - require.NoError(t, json.Unmarshal(rawBytes, &typed)) + require.NoError(t, json.Unmarshal(rawBytes, &typed, jsonx.V1())) assert.True(t, typed.ResponseStreamingSupported) assert.NotEmpty(t, typed.InferenceTypesSupported) @@ -234,13 +235,13 @@ func TestBatch1_FoundationModel_ResponseStreamingSupported_Bool(t *testing.T) { var out2 map[string]any mustUnmarshal(t, rec2, &out2) details2 := out2["modelDetails"].(map[string]any) - rawBytes2, err := json.Marshal(details2) + rawBytes2, err := json.Marshal(details2, jsonx.V1()) require.NoError(t, err) var typed2 struct { ResponseStreamingSupported bool `json:"responseStreamingSupported"` } - require.NoError(t, json.Unmarshal(rawBytes2, &typed2)) + require.NoError(t, json.Unmarshal(rawBytes2, &typed2, jsonx.V1())) assert.False(t, typed2.ResponseStreamingSupported) } @@ -270,7 +271,7 @@ func TestAccuracy_FoundationModel_ListIsNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["modelSummaries"].([]any) assert.NotEmpty(t, summaries, "foundation models must be seeded on init") }) @@ -294,7 +295,7 @@ func TestAccuracy_FoundationModel_FieldsPresentOnList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["modelSummaries"].([]any) require.NotEmpty(t, summaries) @@ -336,7 +337,7 @@ func TestParity_FoundationModel_ARNIncludesRegion(t *testing.T) { ModelID string `json:"modelId"` } `json:"modelDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp.ModelDetails.ModelArn assert.True(t, strings.HasPrefix(arn, "arn:aws:bedrock:us-east-1::foundation-model/"), @@ -363,7 +364,7 @@ func TestParity_ListFoundationModels_ARNFormat(t *testing.T) { ModelArn string `json:"modelArn"` } `json:"modelSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.ModelSummaries, "ListFoundationModels must return at least one model") for _, m := range resp.ModelSummaries { @@ -402,7 +403,7 @@ func TestParity_FoundationModel_ModelLifecyclePresent(t *testing.T) { } `json:"modelLifecycle"` } `json:"modelDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACTIVE", resp.ModelDetails.ModelLifecycle.Status, "modelLifecycle.status must be ACTIVE for available models") @@ -427,7 +428,7 @@ func TestParity_ListFoundationModels_ModelLifecyclePresent(t *testing.T) { ModelID string `json:"modelId"` } `json:"modelSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.ModelSummaries) for _, m := range resp.ModelSummaries { @@ -470,7 +471,7 @@ func TestParity_GetFoundationModel_ByARN(t *testing.T) { ModelArn string `json:"modelArn"` } `json:"modelDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.modelID, resp.ModelDetails.ModelID, "GetFoundationModel by ARN must return the correct model") assert.Equal(t, expectedARN, resp.ModelDetails.ModelArn, @@ -501,7 +502,7 @@ func TestParity_GetFoundationModel_NotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) errType, _ := resp["__type"].(string) assert.Contains(t, errType, "ResourceNotFoundException") }) diff --git a/services/bedrock/handler_guardrails_test.go b/services/bedrock/handler_guardrails_test.go index c38a5e3c6b..89be258aa9 100644 --- a/services/bedrock/handler_guardrails_test.go +++ b/services/bedrock/handler_guardrails_test.go @@ -2,7 +2,7 @@ package bedrock_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -1081,7 +1082,7 @@ func TestAccuracy_GuardrailVersion_HTTPCreate(t *testing.T) { "/guardrails/"+g.GuardrailID, map[string]any{"description": fmt.Sprintf("v%d", i+1)}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lastOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lastOut, jsonx.V1())) } assert.Equal(t, tt.wantVersion, lastOut["version"]) @@ -1137,7 +1138,7 @@ func TestAccuracy_Guardrail_UpdatePreservesAllFields(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.newName, out["name"]) assert.Equal(t, tt.newDesc, out["description"]) }) @@ -1178,7 +1179,7 @@ func TestAccuracy_Tags_OnGuardrailViaListTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) returnedTags := out["tags"].([]any) assert.Len(t, returnedTags, tt.tagCount) }) @@ -1220,7 +1221,7 @@ func TestAccuracy_Tags_TagGuardrailAfterCreate(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) returnedTags := out["tags"].([]any) assert.Len(t, returnedTags, 1) assert.Equal(t, tt.key, returnedTags[0].(map[string]any)["key"]) @@ -1246,7 +1247,7 @@ func TestAccuracy_GuardrailVersion_PoliciesPreservedInVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) guardrailID := out["guardrailId"].(string) // Create a version @@ -1255,7 +1256,7 @@ func TestAccuracy_GuardrailVersion_PoliciesPreservedInVersion(t *testing.T) { require.Equal(t, http.StatusOK, versionRec.Code) var verOut map[string]any - require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &verOut)) + require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &verOut, jsonx.V1())) version := verOut["version"].(string) assert.NotEqual(t, "DRAFT", version) @@ -1264,6 +1265,6 @@ func TestAccuracy_GuardrailVersion_PoliciesPreservedInVersion(t *testing.T) { require.Equal(t, http.StatusOK, getVerRec.Code) var verDetailOut map[string]any - require.NoError(t, json.Unmarshal(getVerRec.Body.Bytes(), &verDetailOut)) + require.NoError(t, json.Unmarshal(getVerRec.Body.Bytes(), &verDetailOut, jsonx.V1())) assert.NotNil(t, verDetailOut["contentPolicy"], "version should preserve policies") } diff --git a/services/bedrock/handler_inference_profiles_test.go b/services/bedrock/handler_inference_profiles_test.go index 394293d713..0a17521cf9 100644 --- a/services/bedrock/handler_inference_profiles_test.go +++ b/services/bedrock/handler_inference_profiles_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -49,7 +50,7 @@ func TestAccuracy_InferenceProfile_CreateResponseShape(t *testing.T) { if tt.wantARN { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["inferenceProfileArn"]) assert.Equal(t, tt.wantProfileStatus, out["status"]) } @@ -141,7 +142,7 @@ func TestAccuracy_InferenceProfile_GetReturnsAllFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, p.InferenceProfileArn, out["inferenceProfileArn"]) assert.Equal(t, p.InferenceProfileID, out["inferenceProfileId"]) @@ -198,7 +199,7 @@ func TestAccuracy_InferenceProfile_ListContainsCreated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["inferenceProfileSummaries"].([]any) assert.Len(t, summaries, len(tt.profileNames)) @@ -246,7 +247,7 @@ func TestAccuracy_InferenceProfile_ListTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["inferenceProfileSummaries"].([]any) assert.Len(t, summaries, tt.wantLen) }) @@ -270,7 +271,7 @@ func TestAccuracy_InferenceProfile_DeleteRemovesFromList(t *testing.T) { recList := doRequest(t, h, http.MethodGet, "/inference-profiles", nil) require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut["inferenceProfileSummaries"].([]any)) } @@ -344,7 +345,7 @@ func TestHandler_InferenceProfileLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) profileARN := createOut["inferenceProfileArn"].(string) // List profiles. @@ -352,7 +353,7 @@ func TestHandler_InferenceProfileLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut, jsonx.V1())) assert.NotEmpty(t, listOut["inferenceProfileSummaries"]) // Get profile. diff --git a/services/bedrock/handler_marketplace_model_endpoints_test.go b/services/bedrock/handler_marketplace_model_endpoints_test.go index 68ade9382b..de7f1d52e5 100644 --- a/services/bedrock/handler_marketplace_model_endpoints_test.go +++ b/services/bedrock/handler_marketplace_model_endpoints_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -50,7 +51,7 @@ func TestAccuracy_MarketplaceEndpoint_CreateStartsAsCreating(t *testing.T) { if tt.wantStatus == http.StatusCreated { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ep := out["marketplaceModelEndpoint"].(map[string]any) assert.Equal(t, "REGISTERED", ep["status"]) assert.Equal(t, "Creating", ep["endpointStatus"]) @@ -185,7 +186,7 @@ func TestAccuracy_MarketplaceEndpoint_ListResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) endpoints := out["marketplaceModelEndpoints"].([]any) assert.Len(t, endpoints, 3) @@ -243,7 +244,7 @@ func TestAccuracy_MarketplaceEndpoint_ListModelSourceFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) endpoints := out["marketplaceModelEndpoints"].([]any) gotSources := make([]string, 0, len(endpoints)) @@ -277,7 +278,7 @@ func TestAccuracy_MarketplaceEndpoint_DeleteRemovesFromList(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["marketplaceModelEndpoints"].([]any)) } @@ -299,7 +300,7 @@ func TestAccuracy_MarketplaceEndpoint_UpdateReturnsEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) updated := out["marketplaceModelEndpoint"].(map[string]any) assert.Equal(t, ep.EndpointArn, updated["endpointArn"]) assert.NotEmpty(t, updated["updatedAt"]) @@ -428,7 +429,7 @@ func TestHandler_MarketplaceModelEndpointLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) endpointARN := createOut["marketplaceModelEndpoint"].(map[string]any)["endpointArn"].(string) // List endpoints. diff --git a/services/bedrock/handler_model_copy_jobs_test.go b/services/bedrock/handler_model_copy_jobs_test.go index a736c78647..44e84b7242 100644 --- a/services/bedrock/handler_model_copy_jobs_test.go +++ b/services/bedrock/handler_model_copy_jobs_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -156,7 +157,7 @@ func TestParity_ValidModelCopyJob_Returns201(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "jobArn", "successful response must include jobArn") } diff --git a/services/bedrock/handler_model_customization_jobs_test.go b/services/bedrock/handler_model_customization_jobs_test.go index 7d2cf485fa..d3b78e175f 100644 --- a/services/bedrock/handler_model_customization_jobs_test.go +++ b/services/bedrock/handler_model_customization_jobs_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -239,7 +240,7 @@ func TestAccuracy_CustomizationJob_StopTransitionsStatus(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "Stopping", out["status"]) }) } @@ -284,7 +285,7 @@ func TestAccuracy_CustomizationJob_ListViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["modelCustomizationJobSummaries"].([]any) assert.Len(t, summaries, tt.wantLen) @@ -347,7 +348,7 @@ func TestAccuracy_CustomizationJob_ListFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["modelCustomizationJobSummaries"].([]any) gotNames := make([]string, 0, len(summaries)) @@ -447,7 +448,7 @@ func TestAccuracy_CustomizationJob_CustomizationTypePreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) jobARN := createOut["jobArn"].(string) recGet := doRequest( @@ -460,7 +461,7 @@ func TestAccuracy_CustomizationJob_CustomizationTypePreserved(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.customizationType, out["customizationType"]) }) } @@ -481,7 +482,7 @@ func TestHandler_ModelCustomizationJobLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) jobARN := createOut["jobArn"].(string) assert.NotEmpty(t, jobARN) @@ -490,7 +491,7 @@ func TestHandler_ModelCustomizationJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut, jsonx.V1())) assert.NotEmpty(t, listOut["modelCustomizationJobSummaries"]) // Get job. diff --git a/services/bedrock/handler_model_import_jobs_test.go b/services/bedrock/handler_model_import_jobs_test.go index cf2cb88982..8fe2907434 100644 --- a/services/bedrock/handler_model_import_jobs_test.go +++ b/services/bedrock/handler_model_import_jobs_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -185,7 +186,7 @@ func TestAccuracy_ImportedModel_VisibleAfterImportJob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) models := out["modelSummaries"].([]any) assert.Len(t, models, 1) @@ -195,7 +196,7 @@ func TestAccuracy_ImportedModel_VisibleAfterImportJob(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var modelOut map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &modelOut)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &modelOut, jsonx.V1())) assert.Equal(t, job.ImportedModelArn, modelOut["modelArn"]) assert.Equal(t, tt.jobName+"-model", modelOut["modelName"]) assert.Equal(t, job.JobArn, modelOut["jobArn"]) @@ -223,7 +224,7 @@ func TestAccuracy_ImportedModel_DeleteRemovesFromList(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["modelSummaries"].([]any)) } @@ -240,7 +241,7 @@ func TestAccuracy_ModelImportJob_EndTimeAbsentWhileInProgress(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "InProgress", out["status"]) _, hasEndTime := out["endTime"] assert.False(t, hasEndTime, "endTime must not be present while InProgress") @@ -282,14 +283,14 @@ func TestAccuracy_ModelImportJob_ImportedModelStatus(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobARN := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/model-import-jobs/"+jobARN, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) // Status should be one of the valid statuses assert.Contains(t, []any{"InProgress", "Completed", "Failed"}, getOut["status"]) @@ -307,14 +308,14 @@ func TestAccuracy_ModelImportJob_TagsPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobARN := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/model-import-jobs/"+jobARN, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "tagged-import", getOut["jobName"]) } @@ -330,7 +331,7 @@ func TestAccuracy_ImportedModel_AvailableAfterJobComplete(t *testing.T) { require.Equal(t, http.StatusCreated, importRec.Code) var importOut map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut, jsonx.V1())) jobARN := importOut["jobArn"].(string) // Advance job status to complete (zero duration = immediate) @@ -341,7 +342,7 @@ func TestAccuracy_ImportedModel_AvailableAfterJobComplete(t *testing.T) { require.Equal(t, http.StatusOK, getJobRec.Code) var jobOut map[string]any - require.NoError(t, json.Unmarshal(getJobRec.Body.Bytes(), &jobOut)) + require.NoError(t, json.Unmarshal(getJobRec.Body.Bytes(), &jobOut, jsonx.V1())) importedModelARN := jobOut["importedModelArn"].(string) assert.NotEmpty(t, importedModelARN) @@ -350,7 +351,7 @@ func TestAccuracy_ImportedModel_AvailableAfterJobComplete(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) models := listOut["modelSummaries"].([]any) assert.NotEmpty(t, models) } diff --git a/services/bedrock/handler_model_invocation_jobs_test.go b/services/bedrock/handler_model_invocation_jobs_test.go index 123bae51e7..96dd0ea179 100644 --- a/services/bedrock/handler_model_invocation_jobs_test.go +++ b/services/bedrock/handler_model_invocation_jobs_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -43,7 +44,7 @@ func TestAccuracy_ModelInvocationJob_StopTransitionsStatus(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "Stopping", out["status"]) }) } @@ -77,7 +78,7 @@ func TestAccuracy_ModelInvocationJob_ListViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["invocationJobSummaries"].([]any) assert.Len(t, summaries, tt.wantLen) }) @@ -99,14 +100,14 @@ func TestAccuracy_ModelInvocationJob_JobNamePreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/model-invocation-job/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, jobName, getOut["jobName"]) }) } @@ -121,14 +122,14 @@ func TestAccuracy_ModelInvocationJob_StatusProgression(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/model-invocation-job/"+jobArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Contains(t, []any{"Submitted", "InProgress", "Completed", "Failed", "Stopped"}, getOut["status"], "status should be a valid model invocation job status") } @@ -142,7 +143,7 @@ func TestAccuracy_ModelInvocationJob_StopChangesStatus(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobArn := out["jobArn"].(string) stopRec := doRequest(t, h, http.MethodPost, "/model-invocation-job/"+jobArn+"/stop", nil) @@ -152,7 +153,7 @@ func TestAccuracy_ModelInvocationJob_StopChangesStatus(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "Stopping", getOut["status"]) } @@ -248,7 +249,7 @@ func TestBatch2Ops_StopModelInvocationJob_AlreadyStopped_Rejected(t *testing.T) "stop of already-stopped invocation job should return 400 ValidationException") var errBody map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ValidationException", errBody["__type"]) }) } diff --git a/services/bedrock/handler_model_invocation_logging_test.go b/services/bedrock/handler_model_invocation_logging_test.go index 141120b756..e77b2a6f95 100644 --- a/services/bedrock/handler_model_invocation_logging_test.go +++ b/services/bedrock/handler_model_invocation_logging_test.go @@ -1,12 +1,14 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAccuracy_LoggingConfig_RoundTrip proves the real @@ -36,7 +38,7 @@ func TestAccuracy_LoggingConfig_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) cfg, ok := out["loggingConfig"].(map[string]any) require.True(t, ok, "loggingConfig must be present after Put") @@ -73,7 +75,7 @@ func TestAccuracy_LoggingConfig_CloudWatchConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) cfg, ok := out["loggingConfig"].(map[string]any) require.True(t, ok) @@ -109,7 +111,7 @@ func TestAccuracy_LoggingConfig_DeleteClearsConfig(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Nil(t, out["loggingConfig"], "Delete must clear the config back to absent, not an empty object") } @@ -125,6 +127,6 @@ func TestAccuracy_LoggingConfig_GetBeforePutOmitsLoggingConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Nil(t, out["loggingConfig"]) } diff --git a/services/bedrock/handler_prompt_routers_test.go b/services/bedrock/handler_prompt_routers_test.go index 759e9301ad..c943fb865a 100644 --- a/services/bedrock/handler_prompt_routers_test.go +++ b/services/bedrock/handler_prompt_routers_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -78,7 +79,7 @@ func TestAccuracy_PromptRouter_CreateResponseShape(t *testing.T) { if tt.wantRouterID { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["promptRouterArn"]) } }) @@ -155,7 +156,7 @@ func TestAccuracy_PromptRouter_GetAndListAccuracy(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) routers := listOut["promptRouterSummaries"].([]any) assert.Len(t, routers, tt.wantLen) @@ -165,7 +166,7 @@ func TestAccuracy_PromptRouter_GetAndListAccuracy(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, lastARN, getOut["promptRouterArn"]) assert.Equal(t, "AVAILABLE", getOut["status"]) assert.Equal(t, "custom", getOut["type"]) @@ -232,7 +233,7 @@ func TestAccuracy_PromptRouter_NamePreservedInGetAndList(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) routerARN := out["promptRouterArn"].(string) assert.NotEmpty(t, routerARN) @@ -240,7 +241,7 @@ func TestAccuracy_PromptRouter_NamePreservedInGetAndList(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, tt.routerName, getOut["promptRouterName"]) assert.Equal(t, "AVAILABLE", getOut["status"]) }) @@ -260,7 +261,7 @@ func TestAccuracy_PromptRouter_TagsPreserved(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) routerARN := out["promptRouterArn"].(string) getRec := doRequest(t, h, http.MethodGet, "/prompt-routers/"+routerARN, nil) @@ -268,7 +269,7 @@ func TestAccuracy_PromptRouter_TagsPreserved(t *testing.T) { // Router ARN is returned and non-empty var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "AVAILABLE", getOut["status"]) } @@ -297,7 +298,7 @@ func TestAccuracy_PromptRouter_ListAfterDelete(t *testing.T) { require.Equal(t, http.StatusCreated, delRec.Code) var delOut map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delOut)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delOut, jsonx.V1())) delARN := delOut["promptRouterArn"].(string) // Delete it @@ -309,7 +310,7 @@ func TestAccuracy_PromptRouter_ListAfterDelete(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) routers := listOut["promptRouterSummaries"].([]any) for _, r := range routers { diff --git a/services/bedrock/handler_provisioned_throughput_test.go b/services/bedrock/handler_provisioned_throughput_test.go index 26dd18df1a..2775344e6b 100644 --- a/services/bedrock/handler_provisioned_throughput_test.go +++ b/services/bedrock/handler_provisioned_throughput_test.go @@ -2,7 +2,7 @@ package bedrock_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -533,7 +534,7 @@ func TestParity_ListProvisionedModelThroughputs_NameContainsFilter(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["provisionedModelSummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 1) diff --git a/services/bedrock/handler_test.go b/services/bedrock/handler_test.go index 55fecb2ea1..dd16f77b34 100644 --- a/services/bedrock/handler_test.go +++ b/services/bedrock/handler_test.go @@ -2,7 +2,7 @@ package bedrock_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -571,7 +572,7 @@ func TestHandler_RealSDKWireShapeRouteMatcher(t *testing.T) { //nolint:parallelt require.NoError(t, h.Handler()(c2)) var out map[string]string - _ = json.Unmarshal(rec2.Body.Bytes(), &out) + _ = json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1()) assert.NotEqual( t, "UnknownOperationException", out["__type"], "path/method must be dispatched to a real handler, not fall through as unrouted", @@ -714,7 +715,7 @@ func TestParity_ValidationException_Returns400(t *testing.T) { "ValidationException must return 400, not 500") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) errType, _ := resp["__type"].(string) assert.Contains(t, errType, "ValidationException", @@ -772,7 +773,7 @@ func TestParity_ValidationException_ErrorShape(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasType := resp["__type"] assert.True(t, hasType, "error response must have '__type' field") diff --git a/services/bedrock/list_summary_shapes_test.go b/services/bedrock/list_summary_shapes_test.go index c7be7b2e56..6ac44223c6 100644 --- a/services/bedrock/list_summary_shapes_test.go +++ b/services/bedrock/list_summary_shapes_test.go @@ -1,12 +1,15 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // listItems unmarshals a List response body and returns the array found @@ -14,14 +17,14 @@ import ( func listItems(t *testing.T, body []byte, key string) []map[string]any { t.Helper() - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(body, &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) raw, ok := resp[key] require.True(t, ok, "response missing key %q", key) var items []map[string]any - require.NoError(t, json.Unmarshal(raw, &items)) + require.NoError(t, json.Unmarshal(raw, &items, jsonx.V1())) return items } diff --git a/services/bedrock/models.go b/services/bedrock/models.go index 49ed6f555d..8ab9d95063 100644 --- a/services/bedrock/models.go +++ b/services/bedrock/models.go @@ -1,7 +1,7 @@ package bedrock import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -252,7 +252,7 @@ type EvaluationDatasetMetricConfig struct { type AutomatedEvaluationConfig struct { EvaluatorModelConfig *EvaluatorModelConfig `json:"evaluatorModelConfig,omitempty"` DatasetMetricConfigs []EvaluationDatasetMetricConfig `json:"datasetMetricConfigs,omitempty"` - CustomMetricConfig json.RawMessage `json:"customMetricConfig,omitempty"` + CustomMetricConfig jsontext.Value `json:"customMetricConfig,omitempty"` } // HumanEvaluationCustomMetric mirrors types.HumanEvaluationCustomMetric @@ -366,8 +366,8 @@ func (c *EvaluationConfig) EvaluatorModelIdentifiers() []string { // than partially modeled, same rationale as AutomatedEvaluationConfig. // CustomMetricConfig above. type RAGConfig struct { - KnowledgeBaseConfig json.RawMessage `json:"knowledgeBaseConfig,omitempty"` - PrecomputedRagSourceConfig json.RawMessage `json:"precomputedRagSourceConfig,omitempty"` + KnowledgeBaseConfig jsontext.Value `json:"knowledgeBaseConfig,omitempty"` + PrecomputedRagSourceConfig jsontext.Value `json:"precomputedRagSourceConfig,omitempty"` } // EvaluationInferenceConfig models bedrock's inferenceConfig union @@ -408,13 +408,13 @@ type AutomatedReasoningPolicy struct { // api_op_UpdateAutomatedReasoningPolicy.go:37-63). Kept as raw JSON rather // than the real nested rules/types/variables union -- inert, never // interpreted, but genuinely the caller's own content, not fabricated. - PolicyDefinition json.RawMessage `json:"policyDefinition,omitempty"` - Description string `json:"description,omitempty"` - Status string `json:"status"` - DefinitionHash string `json:"definitionHash,omitempty"` - Version string `json:"version,omitempty"` - KmsKeyArn string `json:"kmsKeyArn,omitempty"` - Tags []Tag `json:"tags,omitempty"` + PolicyDefinition jsontext.Value `json:"policyDefinition,omitempty"` + Description string `json:"description,omitempty"` + Status string `json:"status"` + DefinitionHash string `json:"definitionHash,omitempty"` + Version string `json:"version,omitempty"` + KmsKeyArn string `json:"kmsKeyArn,omitempty"` + Tags []Tag `json:"tags,omitempty"` } // AutomatedReasoningPolicyBuildWorkflow represents a build workflow for a policy. @@ -430,8 +430,8 @@ type AutomatedReasoningPolicyBuildWorkflow struct { // label, sourceContent is the entire JSON payload). SourceContent is // stored verbatim and never interpreted -- same rationale as // AutomatedReasoningPolicy.PolicyDefinition above. - BuildWorkflowType string `json:"buildWorkflowType,omitempty"` - SourceContent json.RawMessage `json:"sourceContent,omitempty"` + BuildWorkflowType string `json:"buildWorkflowType,omitempty"` + SourceContent jsontext.Value `json:"sourceContent,omitempty"` } // AutomatedReasoningPolicyTestCase represents a test case for a policy. diff --git a/services/bedrock/persistence.go b/services/bedrock/persistence.go index 82dd1eb414..d24caa025a 100644 --- a/services/bedrock/persistence.go +++ b/services/bedrock/persistence.go @@ -7,7 +7,7 @@ package bedrock // [github.com/blackbirdworks/gopherstack/pkgs/persistence.Persistable]. import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -75,7 +75,7 @@ const bedrockSnapshotVersion = 3 // CreateGuardrailVersion call reissues -- a real data-corruption risk, not // just a cosmetic gap. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` LoggingConfig *ModelInvocationLoggingConfiguration `json:"loggingConfig,omitempty"` GuardrailsByName map[string]string `json:"guardrailsByName"` GuardrailsByARN map[string]string `json:"guardrailsByARN"` diff --git a/services/bedrock/persistence_test.go b/services/bedrock/persistence_test.go index 17c7ada266..2fcc73f0a1 100644 --- a/services/bedrock/persistence_test.go +++ b/services/bedrock/persistence_test.go @@ -2,7 +2,7 @@ package bedrock_test import ( "context" - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -200,7 +200,7 @@ func seedJobResources( require.NoError(t, err) wf, err := b.StartAutomatedReasoningPolicyBuildWorkflow( - arp.PolicyArn, "INGEST_CONTENT", json.RawMessage(`{}`), + arp.PolicyArn, "INGEST_CONTENT", jsontext.Value(`{}`), ) require.NoError(t, err) diff --git a/services/bedrock/request_realism_test.go b/services/bedrock/request_realism_test.go index dea9697560..2a1113fa33 100644 --- a/services/bedrock/request_realism_test.go +++ b/services/bedrock/request_realism_test.go @@ -1,7 +1,7 @@ package bedrock_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -33,7 +34,7 @@ func TestRequestRealism_ErrorMessageHasNoCodePrefix(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", out["__type"]) assert.NotContains(t, out["message"], "ResourceNotFoundException") }) diff --git a/services/bedrock/test_helpers_test.go b/services/bedrock/test_helpers_test.go index 413b211749..9a5b843e1d 100644 --- a/services/bedrock/test_helpers_test.go +++ b/services/bedrock/test_helpers_test.go @@ -2,13 +2,14 @@ package bedrock_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http/httptest" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -72,7 +73,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -91,7 +92,7 @@ func doRequest( func mustUnmarshal(t *testing.T, rec *httptest.ResponseRecorder, v any) { t.Helper() - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1())) } func typesAsStrings(raw []any) []string { diff --git a/services/bedrockagent/delete_output_shape_test.go b/services/bedrockagent/delete_output_shape_test.go index cf47f1fb17..4ac4b9d403 100644 --- a/services/bedrockagent/delete_output_shape_test.go +++ b/services/bedrockagent/delete_output_shape_test.go @@ -1,7 +1,7 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "sort" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -86,7 +87,7 @@ func TestDeleteOps_ExactWireKeySet(t *testing.T) { require.Equal(t, http.StatusOK, agRec.Code, agRec.Body.String()) var agOut map[string]map[string]any - require.NoError(t, json.Unmarshal(agRec.Body.Bytes(), &agOut)) + require.NoError(t, json.Unmarshal(agRec.Body.Bytes(), &agOut, jsonx.V1())) groupID, _ := agOut["agentActionGroup"]["actionGroupId"].(string) require.NotEmpty(t, groupID) @@ -121,7 +122,7 @@ func TestDeleteOps_ExactWireKeySet(t *testing.T) { require.Equal(t, http.StatusCreated, verRec.Code, verRec.Body.String()) var verBody map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verBody)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verBody, jsonx.V1())) version, _ := verBody["version"].(string) require.NotEmpty(t, version) @@ -143,7 +144,7 @@ func TestDeleteOps_ExactWireKeySet(t *testing.T) { require.Equal(t, http.StatusCreated, aliasRec.Code, aliasRec.Body.String()) var aliasBody map[string]any - require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &aliasBody)) + require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &aliasBody, jsonx.V1())) aliasID, _ := aliasBody["id"].(string) require.NotEmpty(t, aliasID) @@ -242,7 +243,7 @@ func TestDeleteOps_ExactWireKeySet(t *testing.T) { require.Equal(t, http.StatusCreated, verRec.Code, verRec.Body.String()) var verBody map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verBody)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verBody, jsonx.V1())) version, _ := verBody["version"].(string) require.NotEmpty(t, version) @@ -269,7 +270,7 @@ func TestDeleteOps_ExactWireKeySet(t *testing.T) { } var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) gotKeys := make([]string, 0, len(out)) for k := range out { @@ -296,7 +297,7 @@ func createTestAgentForDeleteSweep(t *testing.T, h *bedrockagent.Handler, e *ech require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) agentID, _ := out["agent"]["agentId"].(string) require.NotEmpty(t, agentID) @@ -318,7 +319,7 @@ func createTestAgentAliasForDeleteSweep( require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) aliasID, _ := out["agentAlias"]["agentAliasId"].(string) require.NotEmpty(t, aliasID) @@ -342,7 +343,7 @@ func createTestFlowForDeleteSweep(t *testing.T, h *bedrockagent.Handler, e *echo require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) flowID, _ := out["id"].(string) require.NotEmpty(t, flowID) @@ -361,7 +362,7 @@ func createTestKBForDeleteSweep(t *testing.T, h *bedrockagent.Handler, e *echo.E require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) kbID, _ := out["knowledgeBase"]["knowledgeBaseId"].(string) require.NotEmpty(t, kbID) @@ -378,7 +379,7 @@ func createTestDSForDeleteSweep(t *testing.T, h *bedrockagent.Handler, e *echo.E require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) dsID, _ := out["dataSource"]["dataSourceId"].(string) require.NotEmpty(t, dsID) @@ -398,7 +399,7 @@ func createTestPromptForDeleteSweep(t *testing.T, h *bedrockagent.Handler, e *ec require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) promptID, _ := out["id"].(string) require.NotEmpty(t, promptID) diff --git a/services/bedrockagent/delete_version_in_use_test.go b/services/bedrockagent/delete_version_in_use_test.go index 681b23de3c..3eb7b34528 100644 --- a/services/bedrockagent/delete_version_in_use_test.go +++ b/services/bedrockagent/delete_version_in_use_test.go @@ -2,7 +2,7 @@ package bedrockagent_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -150,7 +151,7 @@ func TestHandleDeleteAgentVersion_ConflictOverHTTP(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) agentWrap, _ := created["agent"].(map[string]any) agentID, _ := agentWrap["agentId"].(string) require.NotEmpty(t, agentID) @@ -161,7 +162,7 @@ func TestHandleDeleteAgentVersion_ConflictOverHTTP(t *testing.T) { require.Equal(t, http.StatusOK, aliasRec.Code, aliasRec.Body.String()) var aliasBody map[string]any - require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &aliasBody)) + require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &aliasBody, jsonx.V1())) aliasWrap, _ := aliasBody["agentAlias"].(map[string]any) routing, _ := aliasWrap["routingConfiguration"].([]any) require.NotEmpty(t, routing) diff --git a/services/bedrockagent/handler_agent_action_groups.go b/services/bedrockagent/handler_agent_action_groups.go index e25a1810b2..0603556b9d 100644 --- a/services/bedrockagent/handler_agent_action_groups.go +++ b/services/bedrockagent/handler_agent_action_groups.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -27,7 +29,7 @@ func (h *Handler) handleCreateAgentActionGroup( ParentActionGroupSignature string `json:"parentActionGroupSignature"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -76,7 +78,7 @@ func (h *Handler) handleUpdateAgentActionGroup( ParentActionGroupSignature string `json:"parentActionGroupSignature"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_agent_action_groups_test.go b/services/bedrockagent/handler_agent_action_groups_test.go index 0e38088d97..0ca51f2e3f 100644 --- a/services/bedrockagent/handler_agent_action_groups_test.go +++ b/services/bedrockagent/handler_agent_action_groups_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateAgentActionGroupVersion locks in real AWS's constraint on the @@ -31,7 +33,7 @@ func TestCreateAgentActionGroupVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) rec := doRequest(t, h, e, http.MethodPut, @@ -44,7 +46,7 @@ func TestCreateAgentActionGroupVersion(t *testing.T) { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -69,7 +71,7 @@ func TestCreateAgentActionGroupVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) rec := doRequest(t, h, e, http.MethodPut, diff --git a/services/bedrockagent/handler_agent_aliases.go b/services/bedrockagent/handler_agent_aliases.go index b8ab5fcb57..a45b20e99a 100644 --- a/services/bedrockagent/handler_agent_aliases.go +++ b/services/bedrockagent/handler_agent_aliases.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -24,7 +26,7 @@ func (h *Handler) handleCreateAgentAlias( RoutingConfiguration []AliasRouting `json:"routingConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -66,7 +68,7 @@ func (h *Handler) handleUpdateAgentAlias( RoutingConfiguration []AliasRouting `json:"routingConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_agent_collaborators.go b/services/bedrockagent/handler_agent_collaborators.go index 515e2969f7..27cd5076d1 100644 --- a/services/bedrockagent/handler_agent_collaborators.go +++ b/services/bedrockagent/handler_agent_collaborators.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -23,7 +25,7 @@ func (h *Handler) handleAssociateCollaborator( ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -62,7 +64,7 @@ func (h *Handler) handleUpdateCollaborator( RelayConversationHistory string `json:"relayConversationHistory"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_agent_collaborators_test.go b/services/bedrockagent/handler_agent_collaborators_test.go index 28479cf24e..2d7e97e9d3 100644 --- a/services/bedrockagent/handler_agent_collaborators_test.go +++ b/services/bedrockagent/handler_agent_collaborators_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAssociateAgentCollaboratorVersion locks in real AWS's constraint on @@ -38,7 +40,7 @@ func TestAssociateAgentCollaboratorVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) rec := doRequest(t, h, e, http.MethodPut, @@ -55,7 +57,7 @@ func TestAssociateAgentCollaboratorVersion(t *testing.T) { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -80,7 +82,7 @@ func TestAssociateAgentCollaboratorVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) rec := doRequest(t, h, e, http.MethodPut, diff --git a/services/bedrockagent/handler_agent_knowledge_bases.go b/services/bedrockagent/handler_agent_knowledge_bases.go index 0b3f10bcf7..4c3e0bd5a3 100644 --- a/services/bedrockagent/handler_agent_knowledge_bases.go +++ b/services/bedrockagent/handler_agent_knowledge_bases.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -21,7 +23,7 @@ func (h *Handler) handleAssociateAgentKB( KnowledgeBaseState string `json:"knowledgeBaseState"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -54,7 +56,7 @@ func (h *Handler) handleUpdateAgentKB( KnowledgeBaseState string `json:"knowledgeBaseState"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_agent_knowledge_bases_test.go b/services/bedrockagent/handler_agent_knowledge_bases_test.go index b77461b59a..cab336c911 100644 --- a/services/bedrockagent/handler_agent_knowledge_bases_test.go +++ b/services/bedrockagent/handler_agent_knowledge_bases_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAssociateAgentKnowledgeBaseVersion locks in real AWS's constraint on @@ -29,7 +31,7 @@ func TestAssociateAgentKnowledgeBaseVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) kbRec := doRequest(t, h, e, http.MethodPut, "/knowledgebases", map[string]any{ @@ -44,7 +46,7 @@ func TestAssociateAgentKnowledgeBaseVersion(t *testing.T) { var kbResp map[string]map[string]any - _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp) + _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp, jsonx.V1()) kbID, _ := kbResp["knowledgeBase"]["knowledgeBaseId"].(string) rec := doRequest(t, h, e, http.MethodPut, @@ -58,7 +60,7 @@ func TestAssociateAgentKnowledgeBaseVersion(t *testing.T) { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -83,7 +85,7 @@ func TestAssociateAgentKnowledgeBaseVersion(t *testing.T) { var agentResp map[string]map[string]any - _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp) + _ = json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1()) agentID, _ := agentResp["agent"]["agentId"].(string) kbRec := doRequest(t, h, e, http.MethodPut, "/knowledgebases", map[string]any{ @@ -98,7 +100,7 @@ func TestAssociateAgentKnowledgeBaseVersion(t *testing.T) { var kbResp map[string]map[string]any - _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp) + _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp, jsonx.V1()) kbID, _ := kbResp["knowledgeBase"]["knowledgeBaseId"].(string) rec := doRequest(t, h, e, http.MethodPut, diff --git a/services/bedrockagent/handler_agents.go b/services/bedrockagent/handler_agents.go index 292e681366..cf9d85d1ed 100644 --- a/services/bedrockagent/handler_agents.go +++ b/services/bedrockagent/handler_agents.go @@ -2,12 +2,14 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -33,7 +35,7 @@ func (h *Handler) handleCreateAgent(ctx context.Context, c *echo.Context, body [ IdleSessionTTLInSeconds int `json:"idleSessionTTLInSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -92,7 +94,7 @@ func (h *Handler) handleUpdateAgent( IdleSessionTTLInSeconds int `json:"idleSessionTTLInSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_agents_test.go b/services/bedrockagent/handler_agents_test.go index 89b26a4e26..ae9d82b70e 100644 --- a/services/bedrockagent/handler_agents_test.go +++ b/services/bedrockagent/handler_agents_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerAgentCRUD(t *testing.T) { @@ -31,7 +33,7 @@ func TestHandlerAgentCRUD(t *testing.T) { } var createResp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &createResp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal create response: %v", err) } @@ -74,7 +76,7 @@ func TestHandlerAgentCRUD(t *testing.T) { } var sr map[string]map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &sr) + _ = json.Unmarshal(r.Body.Bytes(), &sr, jsonx.V1()) aid := sr["agent"]["agentId"].(string) path := tc.path @@ -122,7 +124,7 @@ func TestHandlerAgentVersions(t *testing.T) { rec := doRequest(t, h, e, http.MethodPut, "/agents", createBody) var createResp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &createResp) + _ = json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()) agentID := createResp["agent"]["agentId"].(string) // Prepare first so we can create a version @@ -138,7 +140,7 @@ func TestHandlerAgentVersions(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPut, "/agents", createBody) var resp map[string]map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) aid := resp["agent"]["agentId"].(string) doRequest(t, h2, e2, http.MethodPost, "/agents/"+aid+"/prepare", nil) @@ -148,7 +150,7 @@ func TestHandlerAgentVersions(t *testing.T) { } var listResp map[string]any - _ = json.Unmarshal(rec2.Body.Bytes(), &listResp) + _ = json.Unmarshal(rec2.Body.Bytes(), &listResp, jsonx.V1()) if _, ok := listResp["agentVersionSummaries"]; !ok { t.Errorf("response missing agentVersionSummaries: %s", rec2.Body.String()) @@ -181,7 +183,7 @@ func TestCreateAgent_DefaultIdleSessionTTL(t *testing.T) { } var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -208,7 +210,7 @@ func TestCreateAgent_CustomIdleSessionTTL(t *testing.T) { } var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -230,7 +232,7 @@ func TestUpdateAgent_IdleSessionTTL(t *testing.T) { }) var createResp map[string]map[string]any - if err := json.Unmarshal(createRec.Body.Bytes(), &createResp); err != nil { + if err := json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -248,7 +250,7 @@ func TestUpdateAgent_IdleSessionTTL(t *testing.T) { } var updateResp map[string]map[string]any - if err := json.Unmarshal(updateRec.Body.Bytes(), &updateResp); err != nil { + if err := json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -277,7 +279,7 @@ func TestAgentVersionsPOST_IsListNotCreate(t *testing.T) { }) var createResp map[string]map[string]any - if err := json.Unmarshal(createRec.Body.Bytes(), &createResp); err != nil { + if err := json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -290,7 +292,7 @@ func TestAgentVersionsPOST_IsListNotCreate(t *testing.T) { } var resp map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -315,7 +317,7 @@ func TestCreateAgentAlias_NoRoutingConfig_AutoCreatesVersion(t *testing.T) { }) var createResp map[string]map[string]any - if err := json.Unmarshal(createRec.Body.Bytes(), &createResp); err != nil { + if err := json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -330,7 +332,7 @@ func TestCreateAgentAlias_NoRoutingConfig_AutoCreatesVersion(t *testing.T) { } var aliasResp map[string]map[string]any - if err := json.Unmarshal(aliasRec.Body.Bytes(), &aliasResp); err != nil { + if err := json.Unmarshal(aliasRec.Body.Bytes(), &aliasResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -342,7 +344,7 @@ func TestCreateAgentAlias_NoRoutingConfig_AutoCreatesVersion(t *testing.T) { listRec := doRequest(t, h, e, http.MethodGet, "/agents/"+agentID+"/agentversions", nil) var listResp map[string]any - if err := json.Unmarshal(listRec.Body.Bytes(), &listResp); err != nil { + if err := json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -364,7 +366,7 @@ func TestPrepareAgent_ResponseIncludesPreparedAt(t *testing.T) { }) var createResp map[string]map[string]any - if err := json.Unmarshal(createRec.Body.Bytes(), &createResp); err != nil { + if err := json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -377,7 +379,7 @@ func TestPrepareAgent_ResponseIncludesPreparedAt(t *testing.T) { } var resp map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } diff --git a/services/bedrockagent/handler_data_sources.go b/services/bedrockagent/handler_data_sources.go index fffef1344d..5d1979f2f7 100644 --- a/services/bedrockagent/handler_data_sources.go +++ b/services/bedrockagent/handler_data_sources.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -23,7 +25,7 @@ func (h *Handler) handleCreateDS(ctx context.Context, c *echo.Context, kbID stri DataDeletionPolicy string `json:"dataDeletionPolicy"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -66,7 +68,7 @@ func (h *Handler) handleUpdateDS( DataDeletionPolicy string `json:"dataDeletionPolicy"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_data_sources_test.go b/services/bedrockagent/handler_data_sources_test.go index bfacfbd9c4..70931ba468 100644 --- a/services/bedrockagent/handler_data_sources_test.go +++ b/services/bedrockagent/handler_data_sources_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerDataSourceAndIngestion(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandlerDataSourceAndIngestion(t *testing.T) { } var kbResp map[string]map[string]any - _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp) + _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp, jsonx.V1()) kbID := kbResp["knowledgeBase"]["knowledgeBaseId"].(string) dsBody := map[string]any{ @@ -38,7 +40,7 @@ func TestHandlerDataSourceAndIngestion(t *testing.T) { } var dsResp map[string]map[string]any - _ = json.Unmarshal(dsRec.Body.Bytes(), &dsResp) + _ = json.Unmarshal(dsRec.Body.Bytes(), &dsResp, jsonx.V1()) dsID := dsResp["dataSource"]["dataSourceId"].(string) t.Run("start ingestion job", func(t *testing.T) { diff --git a/services/bedrockagent/handler_flows.go b/services/bedrockagent/handler_flows.go index 2ca5a42d1b..066de3a2eb 100644 --- a/services/bedrockagent/handler_flows.go +++ b/services/bedrockagent/handler_flows.go @@ -2,12 +2,14 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -25,7 +27,7 @@ func (h *Handler) handleCreateFlow(ctx context.Context, c *echo.Context, body [] CustomerEncryptionKeyArn string `json:"customerEncryptionKeyArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -77,7 +79,7 @@ func (h *Handler) handleUpdateFlow( CustomerEncryptionKeyArn string `json:"customerEncryptionKeyArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -133,7 +135,7 @@ func (h *Handler) handleValidateFlowDef(ctx context.Context, c *echo.Context, bo Definition map[string]any `json:"definition"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) errs, err := h.Backend.ValidateFlowDefinition(ctx, req.Definition) if err != nil { @@ -155,7 +157,7 @@ func (h *Handler) handleCreateFlowVersion( ClientToken string `json:"clientToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) fv, err := h.Backend.CreateFlowVersion(ctx, flowID, VersionConfig{ Description: req.Description, ClientToken: req.ClientToken, @@ -228,7 +230,7 @@ func (h *Handler) handleCreateFlowAlias( RoutingConfiguration []FlowAliasRouting `json:"routingConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -270,7 +272,7 @@ func (h *Handler) handleUpdateFlowAlias( RoutingConfiguration []FlowAliasRouting `json:"routingConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_flows_test.go b/services/bedrockagent/handler_flows_test.go index 4f426c2b66..7d1c411357 100644 --- a/services/bedrockagent/handler_flows_test.go +++ b/services/bedrockagent/handler_flows_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerFlowCRUD(t *testing.T) { @@ -26,7 +28,7 @@ func TestHandlerFlowCRUD(t *testing.T) { } var createResp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &createResp) + _ = json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()) flowID, _ := createResp["id"].(string) if flowID == "" { @@ -40,7 +42,7 @@ func TestHandlerFlowCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPost, "/flows", createBody) var resp map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["id"].(string) rec2 := doRequest(t, h2, e2, http.MethodGet, "/flows/"+id, nil) @@ -65,7 +67,7 @@ func TestHandlerFlowCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPost, "/flows", createBody) var resp map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["id"].(string) // Real PrepareFlow POSTs to "/flows/{flowIdentifier}/" -- no diff --git a/services/bedrockagent/handler_helpers.go b/services/bedrockagent/handler_helpers.go index c74b8b50dc..462edaa5e7 100644 --- a/services/bedrockagent/handler_helpers.go +++ b/services/bedrockagent/handler_helpers.go @@ -2,7 +2,7 @@ package bedrockagent import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,13 +15,12 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var wireCodePrefix = regexp.MustCompile(`^(?:[A-Za-z]+Exception: )+`) func handleErr(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var code string var status int @@ -35,7 +34,7 @@ func handleErr(c *echo.Context, err error) error { case errors.Is(err, awserr.ErrInvalidParameter): status = http.StatusBadRequest code = "ValidationException" - case errors.As(err, &syntaxErr): + case jsonx.IsSyntaxError(err): status = http.StatusBadRequest code = "ValidationException" default: @@ -76,7 +75,7 @@ func bodyPageParams(body []byte) (int, string) { } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } maxResults := maxPageDefault @@ -199,7 +198,7 @@ func validatePageRequest(query url.Values, body []byte) error { } var fromBody pageFields - if len(body) > 0 && json.Unmarshal(body, &fromBody) == nil { + if len(body) > 0 && json.Unmarshal(body, &fromBody, jsonx.V1()) == nil { req.overlay(fromBody) } diff --git a/services/bedrockagent/handler_ingestion_jobs.go b/services/bedrockagent/handler_ingestion_jobs.go index 50fd2583d2..475859b58e 100644 --- a/services/bedrockagent/handler_ingestion_jobs.go +++ b/services/bedrockagent/handler_ingestion_jobs.go @@ -2,10 +2,12 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -20,7 +22,7 @@ func (h *Handler) handleStartIngestionJob( ClientToken string `json:"clientToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) job, err := h.Backend.StartIngestionJob(ctx, kbID, dsID, req.Description, req.ClientToken) if err != nil { @@ -70,7 +72,7 @@ func (h *Handler) handleListIngestionJobs( } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } } diff --git a/services/bedrockagent/handler_ingestion_jobs_test.go b/services/bedrockagent/handler_ingestion_jobs_test.go index c102d923c3..d50629d2fc 100644 --- a/services/bedrockagent/handler_ingestion_jobs_test.go +++ b/services/bedrockagent/handler_ingestion_jobs_test.go @@ -1,12 +1,13 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -39,7 +40,7 @@ func newIngestionFixture(t *testing.T) ingestionFixture { var kbResp map[string]map[string]any - _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp) + _ = json.Unmarshal(kbRec.Body.Bytes(), &kbResp, jsonx.V1()) kbID, _ := kbResp["knowledgeBase"]["knowledgeBaseId"].(string) dsBody := map[string]any{ @@ -54,7 +55,7 @@ func newIngestionFixture(t *testing.T) ingestionFixture { var dsResp map[string]map[string]any - _ = json.Unmarshal(dsRec.Body.Bytes(), &dsResp) + _ = json.Unmarshal(dsRec.Body.Bytes(), &dsResp, jsonx.V1()) dsID, _ := dsResp["dataSource"]["dataSourceId"].(string) return ingestionFixture{h: h, e: e, kbID: kbID, dsID: dsID} @@ -95,7 +96,7 @@ func (f ingestionFixture) startJob(t *testing.T) map[string]any { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -199,7 +200,7 @@ func checkGetIngestionJobReturnsPersistedStats(t *testing.T) { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -232,7 +233,7 @@ func checkListIngestionJobsSummariesIncludeStats(t *testing.T) { IngestionJobSummaries []map[string]any `json:"ingestionJobSummaries"` } - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -284,7 +285,7 @@ func TestKBDocumentsRealWireRouting(t *testing.T) { var ingestResp map[string]any - if err := json.Unmarshal(putRec.Body.Bytes(), &ingestResp); err != nil { + if err := json.Unmarshal(putRec.Body.Bytes(), &ingestResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal PUT response: %v", err) } @@ -308,7 +309,7 @@ func TestKBDocumentsRealWireRouting(t *testing.T) { DocumentDetails []map[string]any `json:"documentDetails"` } - if err := json.Unmarshal(postRec.Body.Bytes(), &listResp); err != nil { + if err := json.Unmarshal(postRec.Body.Bytes(), &listResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal POST response: %v", err) } @@ -335,7 +336,7 @@ func TestStopIngestionJob(t *testing.T) { var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } diff --git a/services/bedrockagent/handler_knowledge_bases.go b/services/bedrockagent/handler_knowledge_bases.go index ee3abe4aa4..facc16f3c2 100644 --- a/services/bedrockagent/handler_knowledge_bases.go +++ b/services/bedrockagent/handler_knowledge_bases.go @@ -2,11 +2,13 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -24,7 +26,7 @@ func (h *Handler) handleCreateKB(ctx context.Context, c *echo.Context, body []by ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -63,7 +65,7 @@ func (h *Handler) handleUpdateKB(ctx context.Context, c *echo.Context, kbID stri RoleARN string `json:"roleArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -167,7 +169,7 @@ func (h *Handler) handleIngestKBDocs( } `json:"documents"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -195,7 +197,7 @@ func (h *Handler) handleGetKBDocs( DocumentIdentifiers []KBDocumentIdentifier `json:"documentIdentifiers"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -215,7 +217,7 @@ func (h *Handler) handleDeleteKBDocs( DocumentIdentifiers []KBDocumentIdentifier `json:"documentIdentifiers"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_knowledge_bases_test.go b/services/bedrockagent/handler_knowledge_bases_test.go index 7a9ad76ee8..e5e227f4c8 100644 --- a/services/bedrockagent/handler_knowledge_bases_test.go +++ b/services/bedrockagent/handler_knowledge_bases_test.go @@ -1,7 +1,7 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -35,7 +36,7 @@ func TestHandlerKnowledgeBaseCRUD(t *testing.T) { } var createResp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &createResp) + _ = json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()) kbID := createResp["knowledgeBase"]["knowledgeBaseId"].(string) t.Run("get kb", func(t *testing.T) { @@ -45,7 +46,7 @@ func TestHandlerKnowledgeBaseCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPut, "/knowledgebases", createBody) var resp map[string]map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["knowledgeBase"]["knowledgeBaseId"].(string) rec2 := doRequest(t, h2, e2, http.MethodGet, "/knowledgebases/"+id, nil) @@ -70,7 +71,7 @@ func TestHandlerKnowledgeBaseCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPut, "/knowledgebases", createBody) var resp map[string]map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["knowledgeBase"]["knowledgeBaseId"].(string) rec2 := doRequest(t, h2, e2, http.MethodDelete, "/knowledgebases/"+id, nil) @@ -98,7 +99,7 @@ func createKBForResourcePolicyTest(t *testing.T, h *bedrockagent.Handler, e *ech } var resp map[string]map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal create kb response: %v", err) } @@ -123,7 +124,7 @@ func putKBResourcePolicy( } var resp map[string]string - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal put resource policy response: %v", err) } @@ -215,7 +216,7 @@ func TestHandlerResourcePolicyPut(t *testing.T) { t.Helper() var resp map[string]string - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -369,7 +370,7 @@ func TestHandlerResourcePolicyGet(t *testing.T) { t.Helper() var resp map[string]string - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -420,7 +421,7 @@ func TestHandlerResourcePolicyDelete(t *testing.T) { t.Helper() var resp map[string]string - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -528,7 +529,7 @@ func listKBDocsForIdentifierTest(t *testing.T, f ingestionFixture) []map[string] var resp struct { DocumentDetails []map[string]any `json:"documentDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp.DocumentDetails } @@ -554,7 +555,7 @@ func TestHandlerGetKBDocuments_RealWireIdentifier(t *testing.T) { var resp struct { DocumentDetails []map[string]any `json:"documentDetails"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.DocumentDetails, 1) assert.Equal(t, "INDEXED", resp.DocumentDetails[0]["status"]) @@ -588,7 +589,7 @@ func TestHandlerDeleteKBDocuments_RealWireIdentifier(t *testing.T) { var deleteResp struct { DocumentDetails []map[string]any `json:"documentDetails"` } - require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp, jsonx.V1())) require.Len(t, deleteResp.DocumentDetails, 1) assert.Equal(t, "DELETING", deleteResp.DocumentDetails[0]["status"]) diff --git a/services/bedrockagent/handler_prompts.go b/services/bedrockagent/handler_prompts.go index 5d873337c0..d59cfc9b3c 100644 --- a/services/bedrockagent/handler_prompts.go +++ b/services/bedrockagent/handler_prompts.go @@ -2,11 +2,13 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -24,7 +26,7 @@ func (h *Handler) handleCreatePrompt(ctx context.Context, c *echo.Context, body Variants []map[string]any `json:"variants"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -89,7 +91,7 @@ func (h *Handler) handleUpdatePrompt( Variants []map[string]any `json:"variants"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } @@ -150,7 +152,7 @@ func (h *Handler) handleCreatePromptVersion( ClientToken string `json:"clientToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) pv, err := h.Backend.CreatePromptVersion(ctx, promptID, VersionConfig{ Description: req.Description, ClientToken: req.ClientToken, Tags: req.Tags, diff --git a/services/bedrockagent/handler_prompts_test.go b/services/bedrockagent/handler_prompts_test.go index f343619833..f5d20a86d4 100644 --- a/services/bedrockagent/handler_prompts_test.go +++ b/services/bedrockagent/handler_prompts_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerPromptCRUD(t *testing.T) { @@ -28,7 +30,7 @@ func TestHandlerPromptCRUD(t *testing.T) { } var createResp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &createResp) + _ = json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()) promptID, _ := createResp["id"].(string) if promptID == "" { @@ -42,7 +44,7 @@ func TestHandlerPromptCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPost, "/prompts", createBody) var resp map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["id"].(string) rec2 := doRequest(t, h2, e2, http.MethodGet, "/prompts/"+id, nil) @@ -67,7 +69,7 @@ func TestHandlerPromptCRUD(t *testing.T) { r := doRequest(t, h2, e2, http.MethodPost, "/prompts", createBody) var resp map[string]any - _ = json.Unmarshal(r.Body.Bytes(), &resp) + _ = json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1()) id := resp["id"].(string) rec2 := doRequest(t, h2, e2, http.MethodPost, "/prompts/"+id+"/versions", map[string]any{ diff --git a/services/bedrockagent/handler_resource_policy.go b/services/bedrockagent/handler_resource_policy.go index b57796caa2..d282b4a377 100644 --- a/services/bedrockagent/handler_resource_policy.go +++ b/services/bedrockagent/handler_resource_policy.go @@ -2,11 +2,13 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resourcePolicyBase is bedrock-agent's real PutResourcePolicy/ @@ -85,7 +87,7 @@ func (h *Handler) handlePutResourcePolicy( ctx context.Context, c *echo.Context, resourceArn string, body []byte, ) error { var req putResourcePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_tags.go b/services/bedrockagent/handler_tags.go index d6d39d0af0..0cb99faee0 100644 --- a/services/bedrockagent/handler_tags.go +++ b/services/bedrockagent/handler_tags.go @@ -2,11 +2,13 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -31,7 +33,7 @@ func (h *Handler) handleTagResource( Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/handler_tags_test.go b/services/bedrockagent/handler_tags_test.go index 1a5aee225d..2c939ee0aa 100644 --- a/services/bedrockagent/handler_tags_test.go +++ b/services/bedrockagent/handler_tags_test.go @@ -1,9 +1,11 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerTagging(t *testing.T) { @@ -20,7 +22,7 @@ func TestHandlerTagging(t *testing.T) { rec := doRequest(t, h, e, http.MethodPut, "/agents", createBody) var createResp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &createResp) + _ = json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1()) arn := createResp["agent"]["agentArn"].(string) t.Run("tag resource", func(t *testing.T) { diff --git a/services/bedrockagent/handler_test.go b/services/bedrockagent/handler_test.go index 050d1be4d3..24111b9d90 100644 --- a/services/bedrockagent/handler_test.go +++ b/services/bedrockagent/handler_test.go @@ -3,13 +3,14 @@ package bedrockagent_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -36,7 +37,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) if err != nil { t.Fatalf("marshal body: %v", err) } diff --git a/services/bedrockagent/handler_vpc_configurations.go b/services/bedrockagent/handler_vpc_configurations.go index 480051047a..39a9c8daf6 100644 --- a/services/bedrockagent/handler_vpc_configurations.go +++ b/services/bedrockagent/handler_vpc_configurations.go @@ -2,13 +2,15 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -77,7 +79,7 @@ func (h *Handler) handleCreateVpcConfiguration( Port int32 `json:"port"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return handleErr(c, err) } diff --git a/services/bedrockagent/lifecycle_test.go b/services/bedrockagent/lifecycle_test.go index 9cf8297221..613cfb6ace 100644 --- a/services/bedrockagent/lifecycle_test.go +++ b/services/bedrockagent/lifecycle_test.go @@ -1,7 +1,7 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagent" ) @@ -117,7 +118,7 @@ func TestRequestRealism_ErrorsAndPaging(t *testing.T) { assert.Equal(t, tt.wantType, rec.Header().Get("X-Amzn-Errortype")) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out["message"], "Exception") }) } diff --git a/services/bedrockagent/models.go b/services/bedrockagent/models.go index 137a1c909e..be25ec1c1b 100644 --- a/services/bedrockagent/models.go +++ b/services/bedrockagent/models.go @@ -1,8 +1,10 @@ package bedrockagent import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // defaultIdleSessionTTLSeconds is the default agent idle session TTL (10 minutes), @@ -493,7 +495,7 @@ func (d *DataSource) UnmarshalJSON(data []byte) error { *dataSourceAlias }{dataSourceAlias: (*dataSourceAlias)(d)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/bedrockagent/persistence.go b/services/bedrockagent/persistence.go index a178925312..b203b4b90f 100644 --- a/services/bedrockagent/persistence.go +++ b/services/bedrockagent/persistence.go @@ -2,7 +2,7 @@ package bedrockagent import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -63,7 +63,7 @@ const bedrockagentSnapshotVersion = 3 // too, or a restored backend would start minting IDs that collide with // pre-restore ones. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` AgentsByName map[string]string `json:"agentsByName"` KBsByName map[string]string `json:"kbsByName"` FlowsByName map[string]string `json:"flowsByName"` diff --git a/services/bedrockagent/wire_field_fixes_test.go b/services/bedrockagent/wire_field_fixes_test.go index 0aacb27ac4..b730bea210 100644 --- a/services/bedrockagent/wire_field_fixes_test.go +++ b/services/bedrockagent/wire_field_fixes_test.go @@ -1,12 +1,14 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDeleteFlow_NoInventedStatusKey_RealClient guards against @@ -28,7 +30,7 @@ func TestDeleteFlow_NoInventedStatusKey_RealClient(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code, createRec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) flowID, _ := created["id"].(string) require.NotEmpty(t, flowID) @@ -58,7 +60,7 @@ func TestDeleteFlowVersion_NoInventedStatusKey_RealClient(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code, createRec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) flowID, _ := created["id"].(string) require.NotEmpty(t, flowID) @@ -66,7 +68,7 @@ func TestDeleteFlowVersion_NoInventedStatusKey_RealClient(t *testing.T) { require.Equal(t, http.StatusCreated, versionRec.Code, versionRec.Body.String()) var versionBody map[string]any - require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &versionBody)) + require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &versionBody, jsonx.V1())) version, _ := versionBody["version"].(string) require.NotEmpty(t, version) diff --git a/services/bedrockagent/wire_field_omissions_test.go b/services/bedrockagent/wire_field_omissions_test.go index c13b6215bc..72c8be6071 100644 --- a/services/bedrockagent/wire_field_omissions_test.go +++ b/services/bedrockagent/wire_field_omissions_test.go @@ -1,11 +1,13 @@ package bedrockagent_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestBedrockAgentLists_OmitGetOnlyFields asserts the raw decoded response @@ -30,7 +32,7 @@ func TestBedrockAgentLists_OmitGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, agentRec.Code, agentRec.Body.String()) var agentResp map[string]map[string]any - require.NoError(t, json.Unmarshal(agentRec.Body.Bytes(), &agentResp)) + require.NoError(t, json.Unmarshal(agentRec.Body.Bytes(), &agentResp, jsonx.V1())) agentID, _ := agentResp["agent"]["agentId"].(string) require.NotEmpty(t, agentID) @@ -43,7 +45,7 @@ func TestBedrockAgentLists_OmitGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, kbRec.Code, kbRec.Body.String()) var kbResp map[string]map[string]any - require.NoError(t, json.Unmarshal(kbRec.Body.Bytes(), &kbResp)) + require.NoError(t, json.Unmarshal(kbRec.Body.Bytes(), &kbResp, jsonx.V1())) kbID, _ := kbResp["knowledgeBase"]["knowledgeBaseId"].(string) require.NotEmpty(t, kbID) @@ -59,7 +61,7 @@ func TestBedrockAgentLists_OmitGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) summaries, ok := raw["agentKnowledgeBaseSummaries"].([]any) require.True(t, ok) @@ -91,7 +93,7 @@ func TestBedrockAgentLists_OmitGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) flowID, _ := createResp["id"].(string) require.NotEmpty(t, flowID) @@ -103,7 +105,7 @@ func TestBedrockAgentLists_OmitGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) summaries, ok := raw["flowVersionSummaries"].([]any) require.True(t, ok) diff --git a/services/bedrockagentcore/identity.go b/services/bedrockagentcore/identity.go index e3f68dadf6..e99853f220 100644 --- a/services/bedrockagentcore/identity.go +++ b/services/bedrockagentcore/identity.go @@ -3,11 +3,12 @@ package bedrockagentcore import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -96,7 +97,7 @@ func userFromJWT(in doc) (string, error) { Sub string `json:"sub"` } - if err = json.Unmarshal(raw, &claims); err != nil || claims.Sub == "" { + if err = json.Unmarshal(raw, &claims, jsonx.V1()); err != nil || claims.Sub == "" { return "", invalid("userToken must carry a sub claim") } diff --git a/services/bedrockagentcore/identity_test.go b/services/bedrockagentcore/identity_test.go index 99ca6afd5a..ac81cc5e42 100644 --- a/services/bedrockagentcore/identity_test.go +++ b/services/bedrockagentcore/identity_test.go @@ -2,7 +2,8 @@ package bedrockagentcore_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagentcore" ) @@ -32,7 +34,10 @@ func tokenServer(t *testing.T) *httptest.Server { return } - _ = json.NewEncoder(w).Encode(map[string]string{"access_token": "tok-for-" + r.FormValue("scope")}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"access_token": "tok-for-" + r.FormValue("scope")}, + ) })) t.Cleanup(srv.Close) @@ -81,7 +86,7 @@ func TestIdentity_WorkloadTokens(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, *plain.WorkloadAccessToken) - payload, _ := json.Marshal(map[string]string{"sub": "user-9"}) + payload, _ := json.Marshal(map[string]string{"sub": "user-9"}, jsonx.V1()) jwt := "e30." + base64.RawURLEncoding.EncodeToString(payload) + ".sig" viaJWT, err := f.client.GetWorkloadAccessTokenForJWT(t.Context(), &sdk.GetWorkloadAccessTokenForJWTInput{ diff --git a/services/bedrockagentcore/oauth.go b/services/bedrockagentcore/oauth.go index 00447788ea..e17cc0f6b8 100644 --- a/services/bedrockagentcore/oauth.go +++ b/services/bedrockagentcore/oauth.go @@ -2,12 +2,14 @@ package bedrockagentcore import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/url" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -101,7 +103,7 @@ func (b *InMemoryBackend) exchange(hreq *http.Request) (string, error) { AccessToken string `json:"access_token"` } - if err = json.Unmarshal(body, &out); err != nil || out.AccessToken == "" { + if err = json.Unmarshal(body, &out, jsonx.V1()); err != nil || out.AccessToken == "" { return "", invalid("the token endpoint did not return an access_token") } @@ -136,7 +138,11 @@ func (b *InMemoryBackend) tokenEndpoint(ctx context.Context, p ProviderInfo) (st TokenEndpoint string `json:"token_endpoint"` } - if err = json.NewDecoder(io.LimitReader(resp.Body, maxTokenResponse)).Decode(&meta); err != nil || + if err = json.UnmarshalDecode( + jsontext.NewDecoder(io.LimitReader(resp.Body, maxTokenResponse)), + &meta, + jsonx.V1(), + ); err != nil || meta.TokenEndpoint == "" { return "", invalid("the discovery document of provider %q has no token_endpoint", p.Name) } diff --git a/services/bedrockagentcore/persistence.go b/services/bedrockagentcore/persistence.go index 32b162b4d1..be7f0ae517 100644 --- a/services/bedrockagentcore/persistence.go +++ b/services/bedrockagentcore/persistence.go @@ -2,7 +2,7 @@ package bedrockagentcore import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. Containers and sandboxes are not part of it. diff --git a/services/bedrockagentcore/runtime_docker_test.go b/services/bedrockagentcore/runtime_docker_test.go index a52cad22a8..2af6cab527 100644 --- a/services/bedrockagentcore/runtime_docker_test.go +++ b/services/bedrockagentcore/runtime_docker_test.go @@ -2,7 +2,7 @@ package bedrockagentcore_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "os" "os/exec" @@ -18,6 +18,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockagentcore" ) @@ -160,7 +161,7 @@ func invokeRuntime( ) (map[string]any, *sdk.InvokeAgentRuntimeOutput) { t.Helper() - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) require.NoError(t, err) out, err := f.client.InvokeAgentRuntime(t.Context(), &sdk.InvokeAgentRuntimeInput{ @@ -179,7 +180,7 @@ func invokeRuntime( require.NoError(t, err) var got map[string]any - if json.Unmarshal(raw, &got) != nil { + if json.Unmarshal(raw, &got, jsonx.V1()) != nil { got = map[string]any{"raw": string(raw)} } diff --git a/services/bedrockagentcore/wire.go b/services/bedrockagentcore/wire.go index ba58c28172..47c7359f85 100644 --- a/services/bedrockagentcore/wire.go +++ b/services/bedrockagentcore/wire.go @@ -2,7 +2,7 @@ package bedrockagentcore import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -127,7 +128,7 @@ func sortedKeys[V any](m map[string]V) []string { } func mustJSON(v any) []byte { - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return b } @@ -147,7 +148,7 @@ func readJSONBody(r *http.Request) (doc, error) { return d, nil } - if err = json.Unmarshal(body, &d); err != nil { + if err = json.Unmarshal(body, &d, jsonx.V1()); err != nil { return nil, invalid("request body is not valid JSON") } diff --git a/services/bedrockagentcorecontrol/dataplane.go b/services/bedrockagentcorecontrol/dataplane.go index 0f69f344aa..d00aeb6c19 100644 --- a/services/bedrockagentcorecontrol/dataplane.go +++ b/services/bedrockagentcorecontrol/dataplane.go @@ -1,8 +1,9 @@ package bedrockagentcorecontrol import ( - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" dataplane "github.com/blackbirdworks/gopherstack/services/bedrockagentcore" ) @@ -178,7 +179,7 @@ func (d dataSeam) SecretValue(arn, jsonKey string) (string, bool) { } var fields map[string]any - if err := json.Unmarshal([]byte(raw), &fields); err != nil { + if err := json.Unmarshal([]byte(raw), &fields, jsonx.V1()); err != nil { return raw, true } @@ -193,7 +194,7 @@ func (d dataSeam) SecretValue(arn, jsonKey string) (string, bool) { } func mustMarshal(v any) []byte { - out, _ := json.Marshal(v) + out, _ := json.Marshal(v, jsonx.V1()) return out } diff --git a/services/bedrockagentcorecontrol/dataplane_test.go b/services/bedrockagentcorecontrol/dataplane_test.go index 3dfb588665..1cc556d4b1 100644 --- a/services/bedrockagentcorecontrol/dataplane_test.go +++ b/services/bedrockagentcorecontrol/dataplane_test.go @@ -1,7 +1,7 @@ package bedrockagentcorecontrol_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" dataplane "github.com/blackbirdworks/gopherstack/services/bedrockagentcore" ) @@ -98,7 +99,7 @@ func TestCredentialProviders_StoreRealSecrets(t *testing.T) { assert.Contains(t, arn, ":secret:bedrock-agentcore-identity-default/apikey/vendor/apiKey") var payload map[string]string - require.NoError(t, json.Unmarshal([]byte(store.values[arn]), &payload)) + require.NoError(t, json.Unmarshal([]byte(store.values[arn]), &payload, jsonx.V1())) assert.Equal(t, "sk-1", payload["apiKey"], "the key is stored, not discarded") info, ok := seam.Provider(dpScope(), dataplane.ProviderAPIKey, "vendor") diff --git a/services/bedrockagentcorecontrol/engine.go b/services/bedrockagentcorecontrol/engine.go index f9894ffb4c..7d7604a858 100644 --- a/services/bedrockagentcorecontrol/engine.go +++ b/services/bedrockagentcorecontrol/engine.go @@ -2,7 +2,7 @@ package bedrockagentcorecontrol import ( "cmp" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "slices" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -511,7 +512,7 @@ func (b *InMemoryBackend) matches(r *Resource, sp *opSpec, in args) bool { return false } case "targetResourceScope": - raw, _ := json.Marshal(r.Doc["definition"]) + raw, _ := json.Marshal(r.Doc["definition"], jsonx.V1()) if !strings.Contains(string(raw), want) { return false } diff --git a/services/bedrockagentcorecontrol/hooks_data.go b/services/bedrockagentcorecontrol/hooks_data.go index 8b435d6b24..5f340f4857 100644 --- a/services/bedrockagentcorecontrol/hooks_data.go +++ b/services/bedrockagentcorecontrol/hooks_data.go @@ -3,7 +3,7 @@ package bedrockagentcorecontrol import ( "bufio" "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -11,6 +11,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -122,7 +123,7 @@ func (b *InMemoryBackend) loadExamples(source map[string]any) ([]any, string) { } var m any - if err := json.Unmarshal([]byte(line), &m); err != nil { + if err := json.Unmarshal([]byte(line), &m, jsonx.V1()); err != nil { return nil, fmt.Sprintf("line %d of the S3 object is not valid JSON", len(out)+1) } diff --git a/services/bedrockagentcorecontrol/hooks_policy.go b/services/bedrockagentcorecontrol/hooks_policy.go index 8e378dd0bb..bbf6f3daa4 100644 --- a/services/bedrockagentcorecontrol/hooks_policy.go +++ b/services/bedrockagentcorecontrol/hooks_policy.go @@ -1,7 +1,7 @@ package bedrockagentcorecontrol import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -310,7 +311,7 @@ func (b *InMemoryBackend) managedSecret(sc scope, kind, name, member string, val return b.managedSecretARN(sc, kind, name, member) } - payload, _ := json.Marshal(map[string]string{member: text}) + payload, _ := json.Marshal(map[string]string{member: text}, jsonx.V1()) secretName := fmt.Sprintf("%s%s/%s/%s", managedSecretName, kind, name, member) if secretARN, err := st.PutSecret(sc.account, sc.region, secretName, string(payload)); err == nil { diff --git a/services/bedrockagentcorecontrol/ops_special.go b/services/bedrockagentcorecontrol/ops_special.go index 2a60d73592..719f58f645 100644 --- a/services/bedrockagentcorecontrol/ops_special.go +++ b/services/bedrockagentcorecontrol/ops_special.go @@ -1,12 +1,13 @@ package bedrockagentcorecontrol import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "slices" "strconv" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -469,7 +470,7 @@ func (b *InMemoryBackend) putResourcePolicy(sc scope, in args) (*reply, error) { var parsed struct { Statement any `json:"Statement"` } - if err := json.Unmarshal([]byte(doc), &parsed); err != nil || parsed.Statement == nil { + if err := json.Unmarshal([]byte(doc), &parsed, jsonx.V1()); err != nil || parsed.Statement == nil { return nil, invalid("policy must be a JSON policy document with a Statement") } diff --git a/services/bedrockagentcorecontrol/persistence.go b/services/bedrockagentcorecontrol/persistence.go index d447b04922..da12c5b5a9 100644 --- a/services/bedrockagentcorecontrol/persistence.go +++ b/services/bedrockagentcorecontrol/persistence.go @@ -2,7 +2,7 @@ package bedrockagentcorecontrol import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/bedrockagentcorecontrol/spec.go b/services/bedrockagentcorecontrol/spec.go index 4f8ba89c7c..6a38841680 100644 --- a/services/bedrockagentcorecontrol/spec.go +++ b/services/bedrockagentcorecontrol/spec.go @@ -2,12 +2,13 @@ package bedrockagentcorecontrol import ( _ "embed" // opsJSON - "encoding/json" + "encoding/json/v2" "slices" "sort" "strings" "sync" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -41,7 +42,7 @@ type specFile struct { var specs = sync.OnceValue(func() map[string]*opSpec { //nolint:gochecknoglobals // immutable embedded spec var f specFile - if err := json.Unmarshal(opsJSON, &f); err != nil { + if err := json.Unmarshal(opsJSON, &f, jsonx.V1()); err != nil { panic("bedrockagentcorecontrol: corrupt ops.json: " + err.Error()) } diff --git a/services/bedrockruntime/converse_validation.go b/services/bedrockruntime/converse_validation.go index f73659e973..69e7b5db0c 100644 --- a/services/bedrockruntime/converse_validation.go +++ b/services/bedrockruntime/converse_validation.go @@ -1,11 +1,14 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxRequestMetadataEntries = 16 @@ -47,13 +50,13 @@ func validateRequestMetadata(md map[string]string) string { // validateGuardrailConfig applies the patterns in API_runtime_GuardrailConfiguration.html and // API_runtime_GuardrailStreamConfiguration.html. -func validateGuardrailConfig(raw json.RawMessage) string { +func validateGuardrailConfig(raw jsontext.Value) string { if len(raw) == 0 { return "" } var gc guardrailConfigBody - if json.Unmarshal(raw, &gc) != nil { + if json.Unmarshal(raw, &gc, jsonx.V1()) != nil { return "" } @@ -135,13 +138,13 @@ type inferenceConfigBody struct { } // validateInferenceConfig applies the InferenceConfiguration bounds. -func validateInferenceConfig(raw json.RawMessage) string { +func validateInferenceConfig(raw jsontext.Value) string { if len(raw) == 0 { return "" } var ic inferenceConfigBody - if json.Unmarshal(raw, &ic) != nil { + if json.Unmarshal(raw, &ic, jsonx.V1()) != nil { return "" } @@ -162,11 +165,11 @@ func validateInferenceConfig(raw json.RawMessage) string { } type toolConfigBody struct { - ToolChoice map[string]json.RawMessage `json:"toolChoice"` + ToolChoice map[string]jsontext.Value `json:"toolChoice"` Tools []struct { ToolSpec *struct { - Name string `json:"name"` - InputSchema json.RawMessage `json:"inputSchema"` + Name string `json:"name"` + InputSchema jsontext.Value `json:"inputSchema"` } `json:"toolSpec"` } `json:"tools"` } @@ -177,13 +180,13 @@ type specificToolChoice struct { // validateToolConfig applies the ToolConfiguration shape: at least one tool, valid tool names, // a single-member toolChoice union, and a specific choice that names a declared tool. -func validateToolConfig(raw json.RawMessage) string { +func validateToolConfig(raw jsontext.Value) string { if len(raw) == 0 { return "" } var tc toolConfigBody - if json.Unmarshal(raw, &tc) != nil { + if json.Unmarshal(raw, &tc, jsonx.V1()) != nil { return "" } @@ -212,7 +215,7 @@ func validateToolConfig(raw json.RawMessage) string { return validateToolChoice(tc.ToolChoice, names) } -func validateToolChoice(choice map[string]json.RawMessage, declared map[string]bool) string { +func validateToolChoice(choice map[string]jsontext.Value, declared map[string]bool) string { if len(choice) == 0 { return "" } @@ -223,7 +226,7 @@ func validateToolChoice(choice map[string]json.RawMessage, declared map[string]b if raw, ok := choice["tool"]; ok { var st specificToolChoice - if json.Unmarshal(raw, &st) == nil && !declared[st.Name] { + if json.Unmarshal(raw, &st, jsonx.V1()) == nil && !declared[st.Name] { return "toolConfig.toolChoice.tool.name " + st.Name + " does not match a declared tool" } diff --git a/services/bedrockruntime/eventstream_test.go b/services/bedrockruntime/eventstream_test.go index 97963a5f27..891db74db0 100644 --- a/services/bedrockruntime/eventstream_test.go +++ b/services/bedrockruntime/eventstream_test.go @@ -2,12 +2,14 @@ package bedrockruntime_test import ( "encoding/binary" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseEventStreamFrames splits a raw binary event stream body into decoded @@ -41,7 +43,7 @@ func parseEventStreamFrames(data []byte) []map[string]any { if len(payload) > 0 { var m map[string]any - if err := json.Unmarshal(payload, &m); err == nil { + if err := json.Unmarshal(payload, &m, jsonx.V1()); err == nil { frames = append(frames, m) } } diff --git a/services/bedrockruntime/guardrail_lookup.go b/services/bedrockruntime/guardrail_lookup.go index ef4fd80fbf..c15692b1be 100644 --- a/services/bedrockruntime/guardrail_lookup.go +++ b/services/bedrockruntime/guardrail_lookup.go @@ -1,12 +1,14 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/bedrock" ) @@ -94,7 +96,7 @@ func (b *InMemoryBackend) inheritGuardrailLookup(home *InMemoryBackend) { // converseGuardrailMissing writes ResourceNotFoundException when the request's guardrailConfig // references a guardrail that is not registered; the bool reports whether a response was written. -func (h *Handler) converseGuardrailMissing(c *echo.Context, raw json.RawMessage) (bool, error) { +func (h *Handler) converseGuardrailMissing(c *echo.Context, raw jsontext.Value) (bool, error) { if len(raw) == 0 { return false, nil } @@ -123,8 +125,8 @@ func (h *Handler) invokeGuardrailMissing(c *echo.Context) (bool, error) { return true, writeGuardrailNotFound(c, id, version) } -func parseGuardrailConfig(raw json.RawMessage) (guardrailConfigBody, bool) { +func parseGuardrailConfig(raw jsontext.Value) (guardrailConfigBody, bool) { var gc guardrailConfigBody - return gc, json.Unmarshal(raw, &gc) == nil + return gc, json.Unmarshal(raw, &gc, jsonx.V1()) == nil } diff --git a/services/bedrockruntime/handler_async_invoke.go b/services/bedrockruntime/handler_async_invoke.go index be87d60795..5132b16c15 100644 --- a/services/bedrockruntime/handler_async_invoke.go +++ b/services/bedrockruntime/handler_async_invoke.go @@ -1,7 +1,8 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // startAsyncInvokeInput is the parsed request body for StartAsyncInvoke. @@ -28,16 +30,16 @@ type startAsyncInvokeInput struct { S3URI string `json:"s3Uri"` } `json:"s3OutputDataConfig"` } `json:"outputDataConfig"` - ModelID string `json:"modelId"` - ClientRequestToken string `json:"clientRequestToken"` - ModelInput json.RawMessage `json:"modelInput"` + ModelID string `json:"modelId"` + ClientRequestToken string `json:"clientRequestToken"` + ModelInput jsontext.Value `json:"modelInput"` } // handleStartAsyncInvoke handles POST /async-invoke. func (h *Handler) handleStartAsyncInvoke(c *echo.Context, body []byte) error { var req startAsyncInvokeInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid request body")) } diff --git a/services/bedrockruntime/handler_async_invoke_test.go b/services/bedrockruntime/handler_async_invoke_test.go index 7aa907fdc8..22d1cb298f 100644 --- a/services/bedrockruntime/handler_async_invoke_test.go +++ b/services/bedrockruntime/handler_async_invoke_test.go @@ -2,7 +2,7 @@ package bedrockruntime_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockruntime" ) @@ -76,7 +77,7 @@ func TestHandler_StartAsyncInvoke(t *testing.T) { assert.Equal(t, http.StatusAccepted, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "invocationArn") assert.NotEmpty(t, out["invocationArn"]) }) @@ -101,7 +102,7 @@ func TestHandler_StartAsyncInvoke_MissingS3URI(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "__type") } @@ -123,7 +124,7 @@ func TestHandler_StartAsyncInvoke_WithTags(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut, jsonx.V1())) invocationArn := startOut["invocationArn"].(string) getPath := "/async-invoke/" + url.PathEscape(invocationArn) @@ -131,7 +132,7 @@ func TestHandler_StartAsyncInvoke_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.NotContains( t, getOut, "tags", "neither GetAsyncInvokeOutput nor AsyncInvokeSummary has a tags member; "+ @@ -230,7 +231,7 @@ func TestStartAsyncInvoke_ModelIDAsInferenceProfileARN(t *testing.T) { assert.Equal(t, http.StatusAccepted, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["invocationArn"]) } @@ -318,7 +319,7 @@ func TestHandler_GetAsyncInvoke(t *testing.T) { require.Equal(t, http.StatusAccepted, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) invocationArn := startOut["invocationArn"].(string) getPath := "/async-invoke/" + url.PathEscape(invocationArn) @@ -327,7 +328,7 @@ func TestHandler_GetAsyncInvoke(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, invocationArn, out["invocationArn"]) assert.Contains(t, out, "modelArn") assert.Contains(t, out, "status") @@ -362,7 +363,7 @@ func TestHandler_GetAsyncInvoke_FullFields(t *testing.T) { require.Equal(t, http.StatusAccepted, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) invocationArn := startOut["invocationArn"].(string) getPath := "/async-invoke/" + url.PathEscape(invocationArn) @@ -371,7 +372,7 @@ func TestHandler_GetAsyncInvoke_FullFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "clientRequestToken") assert.Equal(t, "my-idempotency-token", out["clientRequestToken"]) assert.NotContains( @@ -423,14 +424,14 @@ func TestAsyncInvoke_GetResponseShape(t *testing.T) { require.Equal(t, http.StatusAccepted, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) arn := createOut["invocationArn"].(string) recGet := doRequest(t, h, http.MethodGet, "/async-invoke/"+arn, nil) require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, arn, out["invocationArn"]) assert.Contains(t, out["modelArn"].(string), tt.modelID) assert.Equal(t, "InProgress", out["status"]) @@ -501,7 +502,7 @@ func TestHandler_ListAsyncInvokes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "asyncInvokeSummaries") summaries, ok := out["asyncInvokeSummaries"].([]any) @@ -568,7 +569,7 @@ func TestHandler_ListAsyncInvokes_StatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["asyncInvokeSummaries"].([]any) require.True(t, ok) @@ -595,7 +596,7 @@ func TestHandler_ListAsyncInvokes_WithClientToken(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["asyncInvokeSummaries"].([]any) require.True(t, ok) @@ -613,7 +614,7 @@ func TestListAsyncInvokes_EmptyList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) invokes, ok := out["asyncInvokeSummaries"].([]any) require.True(t, ok) assert.Empty(t, invokes) @@ -664,7 +665,7 @@ func TestListAsyncInvokes_AfterCreate(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) invokes := out["asyncInvokeSummaries"].([]any) assert.Len(t, invokes, tt.wantLen) diff --git a/services/bedrockruntime/handler_converse.go b/services/bedrockruntime/handler_converse.go index 8cdd4c9fa8..148a997961 100644 --- a/services/bedrockruntime/handler_converse.go +++ b/services/bedrockruntime/handler_converse.go @@ -1,10 +1,13 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // converseMessage represents a single message in a Converse request. @@ -25,9 +28,9 @@ type converseRequest struct { PerformanceConfig *performanceConfigBody `json:"performanceConfig,omitempty"` Messages []converseMessage `json:"messages"` System []converseContent `json:"system,omitempty"` - ToolConfig json.RawMessage `json:"toolConfig,omitempty"` - GuardrailConfig json.RawMessage `json:"guardrailConfig,omitempty"` - InferenceConfig json.RawMessage `json:"inferenceConfig,omitempty"` + ToolConfig jsontext.Value `json:"toolConfig,omitempty"` + GuardrailConfig jsontext.Value `json:"guardrailConfig,omitempty"` + InferenceConfig jsontext.Value `json:"inferenceConfig,omitempty"` } type serviceTierBody struct { @@ -133,7 +136,7 @@ func (h *Handler) handleConverse( ) error { var req converseRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if msg := validateConverseConfig(&req); msg != "" { @@ -151,7 +154,7 @@ func (h *Handler) handleConverse( resp := buildConverseResponse(&req) echoConverseConfig(resp, &req) - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -173,7 +176,7 @@ func (h *Handler) handleConverseStream( ) error { var req converseRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if msg := validateConverseConfig(&req); msg != "" { @@ -191,7 +194,7 @@ func (h *Handler) handleConverseStream( inputTokens := estimateTokensFromMessages(req.Messages, req.System) respSummary := buildConverseResponse(&req) - out, err := json.Marshal(respSummary) + out, err := json.Marshal(respSummary, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -202,7 +205,7 @@ func (h *Handler) handleConverseStream( c.Response().WriteHeader(http.StatusOK) writeStreamEvent := func(eventType string, payload any) { - data, merr := json.Marshal(payload) + data, merr := json.Marshal(payload, jsonx.V1()) if merr != nil { return } diff --git a/services/bedrockruntime/handler_converse_test.go b/services/bedrockruntime/handler_converse_test.go index d2455a7554..b2d64cef13 100644 --- a/services/bedrockruntime/handler_converse_test.go +++ b/services/bedrockruntime/handler_converse_test.go @@ -2,7 +2,7 @@ package bedrockruntime_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Converse tests --- @@ -52,7 +54,7 @@ func TestHandler_Converse(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "output") assert.Contains(t, out, "stopReason") @@ -87,7 +89,7 @@ func TestConverse_ReflectsInputTokens(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) usage, ok := out["usage"].(map[string]any) require.True(t, ok) @@ -118,7 +120,7 @@ func TestConverse_WithSystem(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "output") assert.Contains(t, out, "stopReason") assert.Contains(t, out, "usage") @@ -158,7 +160,7 @@ func TestConverse_ResponseFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, tt.wantKey) assert.Contains(t, out, "usage") assert.Contains(t, out, "stopReason") @@ -201,7 +203,7 @@ func TestConverse_WithSystemMessages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "output") }) } @@ -222,7 +224,7 @@ func TestConverse_StopReasonEndTurn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "end_turn", out["stopReason"]) } @@ -252,7 +254,7 @@ func TestConverse_WithToolConfig_ReturnsEndTurn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "end_turn", out["stopReason"]) assert.Contains(t, out, "output") assert.Contains(t, out, "usage") @@ -284,7 +286,7 @@ func TestConverse_MultiTurnMessages_AccumulatesTokens(t *testing.T) { extractInputTokens := func(body []byte) float64 { var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) usage := out["usage"].(map[string]any) return usage["inputTokens"].(float64) diff --git a/services/bedrockruntime/handler_count_tokens_test.go b/services/bedrockruntime/handler_count_tokens_test.go index 04f62b2c86..a0bd829f5f 100644 --- a/services/bedrockruntime/handler_count_tokens_test.go +++ b/services/bedrockruntime/handler_count_tokens_test.go @@ -1,13 +1,15 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // countTokensInvokeModelBody builds a real CountTokens request body wrapping @@ -68,7 +70,7 @@ func TestHandler_CountTokens(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "inputTokens") invocations := h.Backend.ListInvocations() @@ -121,7 +123,7 @@ func TestHandler_CountTokens_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.InDelta(t, tt.wantTokens, out["inputTokens"], 0) }) } @@ -149,8 +151,8 @@ func TestCountTokens_ReflectsInputLength(t *testing.T) { require.Equal(t, http.StatusOK, recLong.Code) var shortOut, longOut map[string]any - require.NoError(t, json.Unmarshal(recShort.Body.Bytes(), &shortOut)) - require.NoError(t, json.Unmarshal(recLong.Body.Bytes(), &longOut)) + require.NoError(t, json.Unmarshal(recShort.Body.Bytes(), &shortOut, jsonx.V1())) + require.NoError(t, json.Unmarshal(recLong.Body.Bytes(), &longOut, jsonx.V1())) shortTokens := shortOut["inputTokens"].(float64) longTokens := longOut["inputTokens"].(float64) @@ -175,8 +177,8 @@ func TestCountTokens_TitanUsesLargerDivisor(t *testing.T) { require.Equal(t, http.StatusOK, recTitan.Code) var claudeOut, titanOut map[string]any - require.NoError(t, json.Unmarshal(recClaude.Body.Bytes(), &claudeOut)) - require.NoError(t, json.Unmarshal(recTitan.Body.Bytes(), &titanOut)) + require.NoError(t, json.Unmarshal(recClaude.Body.Bytes(), &claudeOut, jsonx.V1())) + require.NoError(t, json.Unmarshal(recTitan.Body.Bytes(), &titanOut, jsonx.V1())) claudeTokens := claudeOut["inputTokens"].(float64) titanTokens := titanOut["inputTokens"].(float64) @@ -221,7 +223,7 @@ func TestCountTokens_MultipleModelFamilies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) count, ok := out["inputTokens"].(float64) require.True(t, ok, "inputTokens should be a number") assert.GreaterOrEqual(t, int(count), tt.minCount) @@ -245,8 +247,8 @@ func TestCountTokens_TitanUsesHigherDivisor(t *testing.T) { require.Equal(t, http.StatusOK, recTitan.Code) var claudeOut, titanOut map[string]any - require.NoError(t, json.Unmarshal(recClaude.Body.Bytes(), &claudeOut)) - require.NoError(t, json.Unmarshal(recTitan.Body.Bytes(), &titanOut)) + require.NoError(t, json.Unmarshal(recClaude.Body.Bytes(), &claudeOut, jsonx.V1())) + require.NoError(t, json.Unmarshal(recTitan.Body.Bytes(), &titanOut, jsonx.V1())) claudeTokens := claudeOut["inputTokens"].(float64) titanTokens := titanOut["inputTokens"].(float64) @@ -289,7 +291,7 @@ func TestCountTokens_MultiMessageBody(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) count := out["inputTokens"].(float64) assert.GreaterOrEqual(t, int(count), tt.minCount) }) diff --git a/services/bedrockruntime/handler_guardrail_checks.go b/services/bedrockruntime/handler_guardrail_checks.go index 2dadced9a4..8f1a6f790f 100644 --- a/services/bedrockruntime/handler_guardrail_checks.go +++ b/services/bedrockruntime/handler_guardrail_checks.go @@ -1,7 +1,7 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "strconv" @@ -9,6 +9,8 @@ import ( "unicode/utf8" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- InvokeGuardrailChecks (POST /guardrail-checks/invoke) --- @@ -162,7 +164,7 @@ type guardrailChecksTextUnitsResponse struct { func (h *Handler) handleInvokeGuardrailChecks(c *echo.Context, body []byte) error { var req invokeGuardrailChecksRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid request body")) } } @@ -204,7 +206,7 @@ func (h *Handler) handleInvokeGuardrailChecks(c *echo.Context, body []byte) erro "usage": usage, } - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } diff --git a/services/bedrockruntime/handler_guardrails.go b/services/bedrockruntime/handler_guardrails.go index 098b783e69..936e301114 100644 --- a/services/bedrockruntime/handler_guardrails.go +++ b/services/bedrockruntime/handler_guardrails.go @@ -1,11 +1,13 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // applyGuardrailRequest represents the parsed ApplyGuardrail request body. @@ -158,7 +160,7 @@ func (h *Handler) handleApplyGuardrail( var req applyGuardrailRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if msg := validateApplyGuardrail(&req); msg != "" { @@ -185,7 +187,7 @@ func (h *Handler) handleApplyGuardrail( }, } - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } diff --git a/services/bedrockruntime/handler_guardrails_test.go b/services/bedrockruntime/handler_guardrails_test.go index be49d951e1..8838c9d15a 100644 --- a/services/bedrockruntime/handler_guardrails_test.go +++ b/services/bedrockruntime/handler_guardrails_test.go @@ -1,12 +1,14 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- ApplyGuardrail tests --- @@ -51,7 +53,7 @@ func TestHandler_ApplyGuardrail(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "action") assert.Contains(t, out, "assessments") assert.Contains(t, out, "outputs") @@ -124,7 +126,7 @@ func TestApplyGuardrail_NoneForSafeContent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "NONE", out["action"]) } @@ -143,7 +145,7 @@ func TestApplyGuardrail_BlockedForHarmfulContent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "GUARDRAIL_INTERVENED", out["action"]) } @@ -162,7 +164,7 @@ func TestApplyGuardrail_BlockedForToxicContent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "GUARDRAIL_INTERVENED", out["action"]) } @@ -182,7 +184,7 @@ func TestApplyGuardrail_OutputsReflectsInput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) outputs, ok := out["outputs"].([]any) require.True(t, ok) @@ -231,7 +233,7 @@ func TestApplyGuardrail_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantAction, out["action"]) assert.Contains(t, out, "outputs") assert.Contains(t, out, "usage") @@ -269,7 +271,7 @@ func TestApplyGuardrail_OutputsMirrorInput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) outputs := out["outputs"].([]any) assert.Len(t, outputs, tt.wantLen) @@ -296,7 +298,7 @@ func TestApplyGuardrail_AssessmentsReflectBlockedMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Equal(t, "GUARDRAIL_INTERVENED", out["action"]) assessments, ok := out["assessments"].([]any) @@ -335,7 +337,7 @@ func TestApplyGuardrail_AssessmentsEmptyForNoneAction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Equal(t, "NONE", out["action"]) assessments, ok := out["assessments"].([]any) @@ -453,7 +455,7 @@ func TestHandler_InvokeGuardrailChecks(t *testing.T) { } var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "results") assert.Contains(t, out, "usage") }) @@ -486,7 +488,7 @@ func TestInvokeGuardrailChecks_ContentFilterNeverFabricatesSeverityScore(t *test require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) results, ok := out["results"].(map[string]any) require.True(t, ok) @@ -557,7 +559,7 @@ func TestInvokeGuardrailChecks_SensitiveInformation_RealRegexDetection(t *testin require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) results := out["results"].(map[string]any) sensitive := results["sensitiveInformation"].(map[string]any) diff --git a/services/bedrockruntime/handler_invoke.go b/services/bedrockruntime/handler_invoke.go index 3570170162..0b65c44c3b 100644 --- a/services/bedrockruntime/handler_invoke.go +++ b/services/bedrockruntime/handler_invoke.go @@ -2,12 +2,14 @@ package bedrockruntime import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Request/response header names used by the InvokeModel family (see @@ -147,7 +149,7 @@ func modelResponsePayloadPart(out []byte) []byte { part := map[string]string{"bytes": base64.StdEncoding.EncodeToString(out)} // Marshaling a map[string]string with one entry never fails. - b, _ := json.Marshal(part) + b, _ := json.Marshal(part, jsonx.V1()) return b } @@ -177,7 +179,7 @@ func (h *Handler) handleInvokeModel( resp := mockInvokeModelResponse(modelID) - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -234,7 +236,7 @@ func (h *Handler) handleInvokeModelWithResponseStream( resp := mockInvokeModelResponse(modelID) - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -271,7 +273,7 @@ func (h *Handler) handleInvokeModelWithBidirectionalStream( ) error { resp := mockInvokeModelResponse(modelID) - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -341,7 +343,7 @@ func estimateTokenCount(body []byte, modelID string) int { } var req countTokensRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return max(1, len(body)/charsPerToken) } @@ -392,7 +394,7 @@ func (h *Handler) handleCountTokens( keyInputTokens: inputTokens, } - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } diff --git a/services/bedrockruntime/handler_invoke_test.go b/services/bedrockruntime/handler_invoke_test.go index 7a59e9d61c..ef972f0fbf 100644 --- a/services/bedrockruntime/handler_invoke_test.go +++ b/services/bedrockruntime/handler_invoke_test.go @@ -3,7 +3,7 @@ package bedrockruntime_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- InvokeModel tests --- @@ -60,7 +62,7 @@ func TestHandler_InvokeModel(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, tt.wantKey) invocations := h.Backend.ListInvocations() @@ -107,7 +109,7 @@ func TestHandler_InvokeModel_ModelFamilies(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, tt.wantKey) }) } @@ -160,7 +162,7 @@ func TestInvokeModel_AcceptHeader_JSON(t *testing.T) { t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -180,7 +182,7 @@ func TestInvokeModel_AcceptHeader_Wildcard(t *testing.T) { t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -342,7 +344,7 @@ func TestInvocationTruncation(t *testing.T) { largePrompt[i] = 'x' } - body, _ := json.Marshal(map[string]any{"prompt": string(largePrompt)}) + body, _ := json.Marshal(map[string]any{"prompt": string(largePrompt)}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -391,7 +393,7 @@ func TestInvokeModel_Claude3_MessagesAPIFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Messages API format fields (not Claude 2 `completion` field) assert.Contains(t, out, "id", "Claude 3 response must include 'id' field") @@ -439,7 +441,7 @@ func TestInvokeModel_Claude2_LegacyCompletionFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Legacy completion format assert.Contains(t, out, "completion", "Claude 2 response must use legacy 'completion' field") @@ -478,7 +480,7 @@ func TestInvokeModel_ARNModelIDWithEmbeddedSlash(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Claude 3 Messages API format fields (not the legacy `completion` field // that the truncated-ARN bug would have produced). @@ -579,7 +581,7 @@ func TestInvokeModelWithResponseStream_ChunkPayloadIsBase64WrappedBytes(t *testi require.NoError(t, err) var modelResp map[string]any - require.NoError(t, json.Unmarshal(decoded, &modelResp)) + require.NoError(t, json.Unmarshal(decoded, &modelResp, jsonx.V1())) assert.Equal(t, "end_turn", modelResp["stop_reason"]) assert.Equal(t, "This is a mock response from Gopherstack.", modelResp["completion"]) } @@ -614,7 +616,7 @@ func TestInvokeModel_GuardrailIdentifierWithoutVersion_Returns400(t *testing.T) t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -629,7 +631,7 @@ func TestInvokeModel_GuardrailIdentifierWithoutVersion_Returns400(t *testing.T) assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ValidationException", out["__type"]) } @@ -637,7 +639,7 @@ func TestInvokeModel_GuardrailIdentifierWithVersion_Succeeds(t *testing.T) { t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -657,7 +659,7 @@ func TestInvokeModelWithResponseStream_GuardrailIdentifierWithoutVersion_Returns t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest( @@ -680,7 +682,7 @@ func TestInvokeModel_PerformanceConfigLatency_EchoedInResponse(t *testing.T) { t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/model/anthropic.claude-v2/invoke", bytes.NewReader(body)) @@ -711,7 +713,7 @@ func TestInvokeModelWithResponseStream_PerformanceConfigLatency_EchoedInResponse t.Parallel() h := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"prompt": "Hello"}) + body, _ := json.Marshal(map[string]any{"prompt": "Hello"}, jsonx.V1()) e := echo.New() req := httptest.NewRequest( diff --git a/services/bedrockruntime/handler_list_async_invokes_filters_test.go b/services/bedrockruntime/handler_list_async_invokes_filters_test.go index 7a426a43ff..cf64a4537c 100644 --- a/services/bedrockruntime/handler_list_async_invokes_filters_test.go +++ b/services/bedrockruntime/handler_list_async_invokes_filters_test.go @@ -1,7 +1,7 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func startAsyncInvokeBody(i int) map[string]any { @@ -71,7 +73,7 @@ func TestHandler_ListAsyncInvokes_SubmitTimeFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["asyncInvokeSummaries"].([]any) require.True(t, ok) @@ -93,7 +95,7 @@ func TestHandler_ListAsyncInvokes_SortOrder(t *testing.T) { require.Equal(t, http.StatusAccepted, rec1.Code) var firstBody map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &firstBody)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &firstBody, jsonx.V1())) firstARN, ok := firstBody["invocationArn"].(string) require.True(t, ok) @@ -103,7 +105,7 @@ func TestHandler_ListAsyncInvokes_SortOrder(t *testing.T) { require.Equal(t, http.StatusAccepted, rec2.Code) var secondBody map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &secondBody)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &secondBody, jsonx.V1())) secondARN, ok := secondBody["invocationArn"].(string) require.True(t, ok) @@ -111,7 +113,7 @@ func TestHandler_ListAsyncInvokes_SortOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["asyncInvokeSummaries"].([]any) require.True(t, ok) diff --git a/services/bedrockruntime/handler_sdk_route_table_test.go b/services/bedrockruntime/handler_sdk_route_table_test.go index c419ca717e..e371c8e2ae 100644 --- a/services/bedrockruntime/handler_sdk_route_table_test.go +++ b/services/bedrockruntime/handler_sdk_route_table_test.go @@ -1,7 +1,7 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sdkRouteCases is the authoritative method+path for every real Bedrock @@ -97,7 +99,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var decoded struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded, jsonx.V1())) assert.NotEqual(t, "UnknownOperationException", decoded.Type, "method=%s path=%s op=%s: dispatched to the unmatched-route default", tc.method, tc.path, tc.op) } diff --git a/services/bedrockruntime/handler_test.go b/services/bedrockruntime/handler_test.go index afca33582c..d5d9c0c61b 100644 --- a/services/bedrockruntime/handler_test.go +++ b/services/bedrockruntime/handler_test.go @@ -2,7 +2,7 @@ package bedrockruntime_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/bedrockruntime" ) @@ -34,7 +35,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/bedrockruntime/janitor_interval_test.go b/services/bedrockruntime/janitor_interval_test.go index 68f10b209c..ee17b67cf0 100644 --- a/services/bedrockruntime/janitor_interval_test.go +++ b/services/bedrockruntime/janitor_interval_test.go @@ -2,7 +2,7 @@ package bedrockruntime_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/bedrockruntime" ) @@ -44,7 +45,7 @@ func TestStartWorker_AdvancesAsyncInvoke_NearCompletionDelay(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var started map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) arn, ok := started["invocationArn"].(string) require.True(t, ok) @@ -57,7 +58,7 @@ func TestStartWorker_AdvancesAsyncInvoke_NearCompletionDelay(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, bedrockruntime.AsyncInvokeStatusCompleted, got["status"]) }) diff --git a/services/bedrockruntime/ollama_backend_test.go b/services/bedrockruntime/ollama_backend_test.go index 7f9416e617..3680e2d514 100644 --- a/services/bedrockruntime/ollama_backend_test.go +++ b/services/bedrockruntime/ollama_backend_test.go @@ -1,7 +1,8 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "maps" @@ -22,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/bedrockruntime" @@ -93,7 +95,7 @@ func newFakeOllama(t *testing.T, reply func(path string, req map[string]any) fak srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { raw, _ := io.ReadAll(r.Body) var req map[string]any - _ = json.Unmarshal(raw, &req) + _ = json.Unmarshal(raw, &req, jsonx.V1()) rec.mu.Lock() rec.reqs = append(rec.reqs, req) rec.path = append(rec.path, r.URL.Path) @@ -102,20 +104,20 @@ func newFakeOllama(t *testing.T, reply func(path string, req map[string]any) fak rp := reply(r.URL.Path, req) if rp.status != 0 { w.WriteHeader(rp.status) - _ = json.NewEncoder(w).Encode(map[string]string{"error": rp.errMsg}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{"error": rp.errMsg}) return } w.Header().Set("Content-Type", "application/x-ndjson") - enc := json.NewEncoder(w) + enc := jsontext.NewEncoder(w, jsonx.V1()) if stream, _ := req["stream"].(bool); !stream { - _ = enc.Encode(collapse(rp.lines)) + _ = json.MarshalEncode(enc, collapse(rp.lines)) return } for _, l := range rp.lines { - _ = enc.Encode(l) + _ = json.MarshalEncode(enc, l) if f, ok := w.(http.Flusher); ok { f.Flush() } @@ -734,7 +736,7 @@ func TestOllamaBackend_InvokeModel(t *testing.T) { assert.Equal(t, tc.wantPath, path) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body, &out)) + require.NoError(t, json.Unmarshal(resp.Body, &out, jsonx.V1())) tc.check(t, req, out) }) } @@ -849,7 +851,7 @@ func TestOllamaBackend_InvokeModelStream(t *testing.T) { part, ok := ev.(*types.ResponseStreamMemberChunk) require.True(t, ok, "unexpected event %T", ev) var m map[string]any - require.NoError(t, json.Unmarshal(part.Value.Bytes, &m)) + require.NoError(t, json.Unmarshal(part.Value.Bytes, &m, jsonx.V1())) chunks = append(chunks, m) } require.NoError(t, stream.Err()) diff --git a/services/bedrockruntime/ollama_client.go b/services/bedrockruntime/ollama_client.go index c4c5f3367f..c4043853a4 100644 --- a/services/bedrockruntime/ollama_client.go +++ b/services/bedrockruntime/ollama_client.go @@ -3,12 +3,15 @@ package bedrockruntime import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxOllamaErrBody = 4096 @@ -22,10 +25,10 @@ type ollamaOptions struct { } type ollamaToolFunc struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - Arguments json.RawMessage `json:"arguments,omitempty"` - Parameters json.RawMessage `json:"parameters,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Arguments jsontext.Value `json:"arguments,omitempty"` + Parameters jsontext.Value `json:"parameters,omitempty"` } type ollamaToolCall struct { @@ -135,7 +138,7 @@ func (e *ollamaStatusError) Error() string { func (o *ollamaBackend) post(ctx context.Context, call *ollamaCall) (*http.Response, error) { endpoint, payload := call.endpoint() - raw, err := json.Marshal(payload) + raw, err := json.Marshal(payload, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal ollama request: %w", err) } @@ -159,7 +162,7 @@ func (o *ollamaBackend) post(ctx context.Context, call *ollamaCall) (*http.Respo msg := strings.TrimSpace(string(b)) var e ollamaChunk - if json.Unmarshal(b, &e) == nil && e.Error != "" { + if json.Unmarshal(b, &e, jsonx.V1()) == nil && e.Error != "" { msg = e.Error } @@ -185,12 +188,12 @@ func (o *ollamaBackend) run( defer resp.Body.Close() res := &ollamaResult{} - dec := json.NewDecoder(resp.Body) + dec := jsontext.NewDecoder(resp.Body) for { var ch ollamaChunk - if derr := dec.Decode(&ch); derr != nil { + if derr := json.UnmarshalDecode(dec, &ch, jsonx.V1()); derr != nil { return nil, fmt.Errorf("%w: reading ollama response: %w", errOllamaUnreachable, derr) } diff --git a/services/bedrockruntime/ollama_converse.go b/services/bedrockruntime/ollama_converse.go index 69f9b52c7d..46a25ee42d 100644 --- a/services/bedrockruntime/ollama_converse.go +++ b/services/bedrockruntime/ollama_converse.go @@ -3,7 +3,8 @@ package bedrockruntime import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -50,16 +52,16 @@ type cvDocument struct { } type cvToolUse struct { - ToolUseID string `json:"toolUseId"` - Name string `json:"name"` - Input json.RawMessage `json:"input"` + ToolUseID string `json:"toolUseId"` + Name string `json:"name"` + Input jsontext.Value `json:"input"` } type cvToolResult struct { ToolUseID string `json:"toolUseId"` Content []struct { - Text *string `json:"text"` - JSON json.RawMessage `json:"json"` + Text *string `json:"text"` + JSON jsontext.Value `json:"json"` } `json:"content"` } @@ -73,8 +75,8 @@ type cvSystemBlock struct { } type cvRequest struct { - InferenceConfig json.RawMessage `json:"inferenceConfig"` - ToolConfig json.RawMessage `json:"toolConfig"` + InferenceConfig jsontext.Value `json:"inferenceConfig"` + ToolConfig jsontext.Value `json:"toolConfig"` Messages []cvMessage `json:"messages"` System []cvSystemBlock `json:"system"` } @@ -185,15 +187,15 @@ func ollamaMessages(req *cvRequest) ([]ollamaMessage, error) { return out, nil } -func objectOrEmpty(raw json.RawMessage) json.RawMessage { +func objectOrEmpty(raw jsontext.Value) jsontext.Value { if len(raw) == 0 || string(raw) == "null" { - return json.RawMessage("{}") + return jsontext.Value("{}") } return raw } -func ollamaTools(raw json.RawMessage) []ollamaTool { +func ollamaTools(raw jsontext.Value) []ollamaTool { if len(raw) == 0 { return nil } @@ -204,12 +206,12 @@ func ollamaTools(raw json.RawMessage) []ollamaTool { Name string `json:"name"` Description string `json:"description"` InputSchema struct { - JSON json.RawMessage `json:"json"` + JSON jsontext.Value `json:"json"` } `json:"inputSchema"` } `json:"toolSpec"` } `json:"tools"` } - if json.Unmarshal(raw, &tc) != nil { + if json.Unmarshal(raw, &tc, jsonx.V1()) != nil { return nil } @@ -228,13 +230,13 @@ func ollamaTools(raw json.RawMessage) []ollamaTool { return out } -func ollamaOptionsFromInference(raw json.RawMessage) *ollamaOptions { +func ollamaOptionsFromInference(raw jsontext.Value) *ollamaOptions { if len(raw) == 0 { return nil } var ic inferenceConfigBody - if json.Unmarshal(raw, &ic) != nil { + if json.Unmarshal(raw, &ic, jsonx.V1()) != nil { return nil } @@ -248,7 +250,7 @@ func (o *ollamaBackend) converseCall(modelID string, body []byte) (*ollamaCall, } var req cvRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errUnsupportedContent, err) } @@ -335,7 +337,7 @@ func (h *Handler) handleOllamaConverse( resp := buildOllamaConverseResponse(res, time.Since(start)) echoConverseConfig(resp, req) - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -421,7 +423,7 @@ func (s *converseStreamer) finish(res *ollamaResult, meta map[string]any) { func frameWriter(c *echo.Context) (func(string, any), func(string, string)) { write := func(headers [][2]string, payload any) { - data, err := json.Marshal(payload) + data, err := json.Marshal(payload, jsonx.V1()) if err != nil { return } @@ -486,7 +488,7 @@ func (h *Handler) handleOllamaConverseStream( } func mustJSON(v any) []byte { - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return b } diff --git a/services/bedrockruntime/ollama_invoke.go b/services/bedrockruntime/ollama_invoke.go index 4bc547f5a9..6c7d0a3999 100644 --- a/services/bedrockruntime/ollama_invoke.go +++ b/services/bedrockruntime/ollama_invoke.go @@ -1,7 +1,8 @@ package bedrockruntime import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -82,7 +84,7 @@ func familyFor(id string) familyBuilder { } func parseBody(body []byte, v any) error { - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return malformed(err) } @@ -108,13 +110,13 @@ type anthropicBlock struct { Source *struct { Data string `json:"data"` } `json:"source"` - Type string `json:"type"` - Text string `json:"text"` - ID string `json:"id"` - Name string `json:"name"` - ToolUseID string `json:"tool_use_id"` - Input json.RawMessage `json:"input"` - Content json.RawMessage `json:"content"` + Type string `json:"type"` + Text string `json:"text"` + ID string `json:"id"` + Name string `json:"name"` + ToolUseID string `json:"tool_use_id"` + Input jsontext.Value `json:"input"` + Content jsontext.Value `json:"content"` } type anthropicRequest struct { @@ -125,36 +127,36 @@ type anthropicRequest struct { TopK *int `json:"top_k"` Prompt string `json:"prompt"` Messages []struct { - Role string `json:"role"` - Content json.RawMessage `json:"content"` + Role string `json:"role"` + Content jsontext.Value `json:"content"` } `json:"messages"` - System json.RawMessage `json:"system"` + System jsontext.Value `json:"system"` Tools []anthropicTool `json:"tools"` StopSequences []string `json:"stop_sequences"` } type anthropicTool struct { - Name string `json:"name"` - Description string `json:"description"` - InputSchema json.RawMessage `json:"input_schema"` + Name string `json:"name"` + Description string `json:"description"` + InputSchema jsontext.Value `json:"input_schema"` } // anthropicBlocks reads a content value that is either a string or an array of blocks. -func anthropicBlocks(raw json.RawMessage) ([]anthropicBlock, error) { +func anthropicBlocks(raw jsontext.Value) ([]anthropicBlock, error) { var s string - if json.Unmarshal(raw, &s) == nil { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil { return []anthropicBlock{{Type: typeText, Text: s}}, nil } var blocks []anthropicBlock - if err := json.Unmarshal(raw, &blocks); err != nil { + if err := json.Unmarshal(raw, &blocks, jsonx.V1()); err != nil { return nil, malformed(err) } return blocks, nil } -func anthropicToCv(role string, raw json.RawMessage) (cvMessage, error) { +func anthropicToCv(role string, raw jsontext.Value) (cvMessage, error) { blocks, err := anthropicBlocks(raw) if err != nil { return cvMessage{}, err @@ -188,8 +190,8 @@ func anthropicToCv(role string, raw json.RawMessage) (cvMessage, error) { for _, ib := range inner { t := ib.Text res.Content = append(res.Content, struct { - Text *string `json:"text"` - JSON json.RawMessage `json:"json"` + Text *string `json:"text"` + JSON jsontext.Value `json:"json"` }{Text: &t}) } @@ -202,7 +204,7 @@ func anthropicToCv(role string, raw json.RawMessage) (cvMessage, error) { return m, nil } -func anthropicSystem(raw json.RawMessage) ([]cvSystemBlock, error) { +func anthropicSystem(raw jsontext.Value) ([]cvSystemBlock, error) { if len(raw) == 0 { return nil, nil } @@ -907,7 +909,7 @@ func (h *Handler) handleOllamaInvoke(c *echo.Context, o *ollamaBackend, modelID return writeInvokeError(c, err) } - out, err := json.Marshal(plan.render(res)) + out, err := json.Marshal(plan.render(res), jsonx.V1()) if err != nil { return c.JSON(http.StatusInternalServerError, errorResponse("InternalServerException", "internal server error")) } @@ -952,7 +954,7 @@ func (h *Handler) handleOllamaInvokeStream(c *echo.Context, o *ollamaBackend, mo } for _, ch := range chunks { - emit("chunk", json.RawMessage(modelResponsePayloadPart(mustJSON(ch)))) + emit("chunk", jsontext.Value(modelResponsePayloadPart(mustJSON(ch)))) } } diff --git a/services/bedrockruntime/persistence.go b/services/bedrockruntime/persistence.go index fe2f73ed61..1bcb0ecb35 100644 --- a/services/bedrockruntime/persistence.go +++ b/services/bedrockruntime/persistence.go @@ -2,7 +2,7 @@ package bedrockruntime import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -45,13 +45,13 @@ const bedrockruntimeSnapshotVersion = 1 // freshly restored backend never reissues an ARN already handed out // before the snapshot. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - TokenIndex map[string]string `json:"tokenIndex"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Invocations []*Invocation `json:"invocations"` - AsyncInvokeCounter int `json:"asyncInvokeCounter"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + TokenIndex map[string]string `json:"tokenIndex"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Invocations []*Invocation `json:"invocations"` + AsyncInvokeCounter int `json:"asyncInvokeCounter"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. It implements diff --git a/services/bedrockruntime/wire_field_fixes_y1zn_test.go b/services/bedrockruntime/wire_field_fixes_y1zn_test.go index b090888ebb..a7436123ba 100644 --- a/services/bedrockruntime/wire_field_fixes_y1zn_test.go +++ b/services/bedrockruntime/wire_field_fixes_y1zn_test.go @@ -1,7 +1,7 @@ package bedrockruntime_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( brtypes "github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetAsyncInvoke_NoInventedTagsKey_RealClient covers gopherstack-y1zn. @@ -38,7 +40,7 @@ func TestGetAsyncInvoke_NoInventedTagsKey_RealClient(t *testing.T) { "neither GetAsyncInvokeOutput nor AsyncInvokeSummary has a tags member") var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) _, hasTags := out["tags"] assert.False(t, hasTags) } diff --git a/services/budgets/budgets.go b/services/budgets/budgets.go index fd160e7631..5a6e8292ed 100644 --- a/services/budgets/budgets.go +++ b/services/budgets/budgets.go @@ -3,19 +3,20 @@ package budgets import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) func deepCopy[T any](v *T) *T { - data, _ := json.Marshal(v) + data, _ := json.Marshal(v, jsonx.V1()) var out T - _ = json.Unmarshal(data, &out) + _ = json.Unmarshal(data, &out, jsonx.V1()) return &out } diff --git a/services/budgets/handler.go b/services/budgets/handler.go index 8fcbcd5ecb..586e5d6e3c 100644 --- a/services/budgets/handler.go +++ b/services/budgets/handler.go @@ -2,7 +2,7 @@ package budgets import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -37,7 +38,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrInvalidParameter, "malformed request body: %v", err) } } diff --git a/services/budgets/persistence.go b/services/budgets/persistence.go index 73edc0e1e0..79dffffc92 100644 --- a/services/budgets/persistence.go +++ b/services/budgets/persistence.go @@ -2,7 +2,7 @@ package budgets import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/budgets/wire.go b/services/budgets/wire.go index 595592bc5b..572f8284ed 100644 --- a/services/budgets/wire.go +++ b/services/budgets/wire.go @@ -1,10 +1,11 @@ package budgets import ( - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Wire shapes follow the pinned aws-sdk-go-v2/service/budgets v1.53.1 awsjson1.1 schemas; @@ -14,12 +15,14 @@ import ( type Timestamp struct{ time.Time } // MarshalJSON renders the epoch-seconds number the SDK expects. -func (t Timestamp) MarshalJSON() ([]byte, error) { return json.Marshal(awstime.Epoch(t.Time)) } +func (t Timestamp) MarshalJSON() ([]byte, error) { + return json.Marshal(awstime.Epoch(t.Time), jsonx.V1()) +} // UnmarshalJSON reads an epoch-seconds number. func (t *Timestamp) UnmarshalJSON(data []byte) error { var f float64 - if err := json.Unmarshal(data, &f); err != nil { + if err := json.Unmarshal(data, &f, jsonx.V1()); err != nil { return err } diff --git a/services/ce/cost_usage_granularity_test.go b/services/ce/cost_usage_granularity_test.go index d4863146b6..d1c5189010 100644 --- a/services/ce/cost_usage_granularity_test.go +++ b/services/ce/cost_usage_granularity_test.go @@ -1,12 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // wireError decodes the AWS JSON-protocol error envelope (__type/message). @@ -45,7 +48,7 @@ func TestGetCostAndUsage_InvalidGranularityRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var wireErr wireError - require.NoError(t, json.NewDecoder(rec.Body).Decode(&wireErr)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &wireErr, jsonx.V1())) assert.Equal(t, "ValidationError", wireErr.Type) }) } @@ -74,7 +77,7 @@ func TestGetCostAndUsage_HourlyGranularityBucketsHourly(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.ResultsByTime, 24, "one bucket per hour across a single day") assert.Equal(t, "2024-01-01T00:00:00Z", out.ResultsByTime[0].TimePeriod["Start"]) diff --git a/services/ce/cost_usage_timeperiod_test.go b/services/ce/cost_usage_timeperiod_test.go index af5286da01..e9944d984d 100644 --- a/services/ce/cost_usage_timeperiod_test.go +++ b/services/ce/cost_usage_timeperiod_test.go @@ -1,12 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetCostAndUsage_UnparseableTimePeriodRejected verifies that a @@ -43,7 +46,7 @@ func TestGetCostAndUsage_UnparseableTimePeriodRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var wireErr wireError - require.NoError(t, json.NewDecoder(rec.Body).Decode(&wireErr)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &wireErr, jsonx.V1())) assert.Equal(t, "ValidationError", wireErr.Type) }) } diff --git a/services/ce/handler.go b/services/ce/handler.go index fa4666a143..9ee3720c0c 100644 --- a/services/ce/handler.go +++ b/services/ce/handler.go @@ -2,7 +2,7 @@ package ce import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -196,19 +197,16 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrUnknownMonitor): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "UnknownMonitorException", Message: err.Error(), - }) + }, jsonx.V1()) // Real AWS CE returns HTTP 400 for every modeled client-fault exception (verified // against API_DeleteAnomalyMonitor/API_UpdateAnomalyMonitor/API_GetAnomalyMonitors), @@ -218,21 +216,21 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "InvalidNextTokenException", Message: err.Error(), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrUnknownSubscription): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "UnknownSubscriptionException", Message: err.Error(), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrNotFound): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ResourceNotFoundException", Message: err.Error(), - }) + }, jsonx.V1()) // See API_DescribeCostCategoryDefinition: ResourceNotFoundException is documented // as HTTP 400, not 404. @@ -241,7 +239,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ServiceQuotaExceededException", Message: err.Error(), - }) + }, jsonx.V1()) // See API_CreateCostCategoryDefinition: ServiceQuotaExceededException is documented // as HTTP 400, not 409. @@ -250,18 +248,18 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ValidationError", Message: err.Error(), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) // "ValidationError" is CE's documented CommonErrors type for malformed/ // missing-required-member requests (see ErrValidation above) -- it is not // tied to any one operation's model, so it fits here too. case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ValidationError", Message: err.Error(), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) default: diff --git a/services/ce/handler_anomalies_test.go b/services/ce/handler_anomalies_test.go index 78bd01f3b8..dc3154adb3 100644 --- a/services/ce/handler_anomalies_test.go +++ b/services/ce/handler_anomalies_test.go @@ -1,7 +1,8 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -29,7 +31,7 @@ func TestGetAnomalyMonitors_HasCreationDateAndLastUpdatedDate(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) monARN := createOut["MonitorArn"].(string) // Describe it @@ -45,7 +47,7 @@ func TestGetAnomalyMonitors_HasCreationDateAndLastUpdatedDate(t *testing.T) { LastUpdatedDate string `json:"LastUpdatedDate"` } `json:"AnomalyMonitors"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) require.Len(t, getOut.AnomalyMonitors, 1) m := getOut.AnomalyMonitors[0] @@ -76,7 +78,7 @@ func TestGetAnomalyMonitors_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1 := out1["AnomalyMonitors"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["NextPageToken"].(string) @@ -89,7 +91,7 @@ func TestGetAnomalyMonitors_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2 := out2["AnomalyMonitors"].([]any) assert.Len(t, page2, 2) } @@ -110,7 +112,7 @@ func TestGetAnomalySubscriptions_HasAccountId(t *testing.T) { require.Equal(t, http.StatusOK, monRec.Code) var monOut map[string]any - require.NoError(t, json.Unmarshal(monRec.Body.Bytes(), &monOut)) + require.NoError(t, json.Unmarshal(monRec.Body.Bytes(), &monOut, jsonx.V1())) monARN := monOut["MonitorArn"].(string) subRec := doRequest(t, h, "CreateAnomalySubscription", map[string]any{ @@ -133,7 +135,7 @@ func TestGetAnomalySubscriptions_HasAccountId(t *testing.T) { AccountID string `json:"AccountId"` } `json:"AnomalySubscriptions"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) require.Len(t, getOut.AnomalySubscriptions, 1) assert.NotEmpty(t, getOut.AnomalySubscriptions[0].AccountID, "AccountId must be present in subscription") } @@ -153,7 +155,7 @@ func TestGetAnomalySubscriptions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, monRec.Code) var monOut map[string]any - require.NoError(t, json.Unmarshal(monRec.Body.Bytes(), &monOut)) + require.NoError(t, json.Unmarshal(monRec.Body.Bytes(), &monOut, jsonx.V1())) monARN := monOut["MonitorArn"].(string) for i := range 5 { @@ -176,7 +178,7 @@ func TestGetAnomalySubscriptions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1 := out1["AnomalySubscriptions"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["NextPageToken"].(string) @@ -203,7 +205,7 @@ func TestHandler_AnomalyMonitorCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["MonitorArn"]) monARN := out["MonitorArn"].(string) @@ -227,7 +229,7 @@ func TestHandler_AnomalyMonitorCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) monARN := createOut["MonitorArn"].(string) rec2 := doRequest(t, h, "UpdateAnomalyMonitor", map[string]any{ @@ -250,7 +252,7 @@ func TestHandler_AnomalyMonitorCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) monARN := createOut["MonitorArn"].(string) rec2 := doRequest(t, h, "DeleteAnomalyMonitor", map[string]any{ @@ -295,7 +297,7 @@ func TestHandler_AnomalySubscriptionCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["SubscriptionArn"]) }, }, @@ -316,7 +318,7 @@ func TestHandler_AnomalySubscriptionCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) subARN := createOut["SubscriptionArn"].(string) rec2 := doRequest(t, h, "DeleteAnomalySubscription", map[string]any{ @@ -399,7 +401,7 @@ func TestHandler_GetAnomalySubscriptions(t *testing.T) { var out struct { AnomalySubscriptions []map[string]any `json:"AnomalySubscriptions"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.AnomalySubscriptions, tt.wantLen) }) } @@ -461,7 +463,7 @@ func TestHandler_UpdateAnomalySubscription(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) tt.updateBody["SubscriptionArn"] = createOut["SubscriptionArn"] } @@ -470,7 +472,7 @@ func TestHandler_UpdateAnomalySubscription(t *testing.T) { if tt.wantFrequency != "" { var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["SubscriptionArn"]) } }) @@ -650,7 +652,7 @@ func TestHandler_GetAnomalySubscriptions_MonitorArnFilter(t *testing.T) { require.Equal(t, http.StatusOK, monRec.Code) var monOut map[string]any - require.NoError(t, json.NewDecoder(monRec.Body).Decode(&monOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(monRec.Body), &monOut, jsonx.V1())) monARN := monOut["MonitorArn"].(string) // Create subscription attached to monitor. @@ -693,7 +695,7 @@ func TestHandler_GetAnomalySubscriptions_MonitorArnFilter(t *testing.T) { var out struct { AnomalySubscriptions []map[string]any `json:"AnomalySubscriptions"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.AnomalySubscriptions, tt.wantLen) }) } @@ -765,7 +767,7 @@ func TestHandler_UpdateAnomalySubscription_AllBranches(t *testing.T) { require.Equal(t, http.StatusOK, monRec.Code) var monOut map[string]any - require.NoError(t, json.NewDecoder(monRec.Body).Decode(&monOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(monRec.Body), &monOut, jsonx.V1())) monARN := monOut["MonitorArn"].(string) arns, ok := tt.updateBody["MonitorArnList"].([]string) @@ -786,7 +788,7 @@ func TestHandler_UpdateAnomalySubscription_AllBranches(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) subARN := createOut["SubscriptionArn"].(string) tt.updateBody["SubscriptionArn"] = subARN @@ -807,7 +809,7 @@ func TestHandler_UpdateAnomalySubscription_AllBranches(t *testing.T) { Threshold float64 `json:"Threshold"` } `json:"AnomalySubscriptions"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) require.Len(t, getOut.AnomalySubscriptions, 1) assert.Equal(t, tt.wantFrequency, getOut.AnomalySubscriptions[0].Frequency) assert.Equal(t, tt.wantSubName, getOut.AnomalySubscriptions[0].SubscriptionName) @@ -939,7 +941,7 @@ func TestHandler_UpdateAnomalyMonitor_OmittedNameUnchanged(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) monARN := createOut["MonitorArn"].(string) updateRec := doRequest(t, h, "UpdateAnomalyMonitor", map[string]any{ @@ -957,7 +959,7 @@ func TestHandler_UpdateAnomalyMonitor_OmittedNameUnchanged(t *testing.T) { MonitorName string `json:"MonitorName"` } `json:"AnomalyMonitors"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) require.Len(t, getOut.AnomalyMonitors, 1) assert.Equal(t, "KeepMyName", getOut.AnomalyMonitors[0].MonitorName) } diff --git a/services/ce/handler_anomaly_detection_test.go b/services/ce/handler_anomaly_detection_test.go index f5b2792771..f318abe41a 100644 --- a/services/ce/handler_anomaly_detection_test.go +++ b/services/ce/handler_anomaly_detection_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -47,7 +49,7 @@ func TestAnomalyFeedback_PreviousFeedbackOverwritten(t *testing.T) { Feedback string `json:"Feedback"` } `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) require.Len(t, out.Anomalies, 1) assert.Equal(t, "feedback-overwrite-1", out.Anomalies[0].AnomalyID) assert.Equal(t, "NO", out.Anomalies[0].Feedback) @@ -120,7 +122,7 @@ func TestProvideAnomalyFeedback_PersistsAndValidates(t *testing.T) { Feedback string `json:"Feedback"` } `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) require.Len(t, out.Anomalies, 1) assert.Equal(t, "feedback-test-1", out.Anomalies[0].AnomalyID) assert.Equal(t, tt.wantFeedback, out.Anomalies[0].Feedback) @@ -181,7 +183,7 @@ func TestGetAnomalies_ScoreAndImpactAreObjects(t *testing.T) { } `json:"Impact"` } `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.Anomalies, 1) a := out.Anomalies[0] @@ -223,7 +225,7 @@ func TestGetAnomalies_DateIntervalFilters(t *testing.T) { AnomalyID string `json:"AnomalyId"` } `json:"Anomalies"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Anomalies, 1) assert.Equal(t, "recent-anomaly", out.Anomalies[0].AnomalyID) } @@ -283,7 +285,7 @@ func TestGetAnomalies_DateIntervalMatchesOnAnomalyEndDateOnly(t *testing.T) { AnomalyID string `json:"AnomalyId"` } `json:"Anomalies"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.want { require.Len(t, out.Anomalies, 1) @@ -317,7 +319,7 @@ func TestGetAnomalies_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1 := out1["Anomalies"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["NextPageToken"].(string) @@ -370,7 +372,7 @@ func TestHandler_GetAnomalies(t *testing.T) { var out struct { Anomalies []any `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.Anomalies) }) } @@ -452,7 +454,7 @@ func TestHandler_GetAnomalies_Filters(t *testing.T) { var out struct { Anomalies []map[string]any `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Anomalies, tt.wantLen) }) } @@ -483,7 +485,7 @@ func TestHandler_SnapshotRestoreWithAnomalies(t *testing.T) { var out struct { Anomalies []map[string]any `json:"Anomalies"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Anomalies, 1) assert.Equal(t, "snap-anomaly-1", out.Anomalies[0]["AnomalyId"]) } diff --git a/services/ce/handler_commitment_purchase_analysis_test.go b/services/ce/handler_commitment_purchase_analysis_test.go index cbf96d14ed..a3709ce5ca 100644 --- a/services/ce/handler_commitment_purchase_analysis_test.go +++ b/services/ce/handler_commitment_purchase_analysis_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -28,7 +30,7 @@ func TestCommitmentAnalysis_MultipleStartsListed(t *testing.T) { var out struct { AnalysisID string `json:"AnalysisId"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.AnalysisID) analysisIDs = append(analysisIDs, out.AnalysisID) } @@ -48,7 +50,7 @@ func TestCommitmentAnalysis_MultipleStartsListed(t *testing.T) { AnalysisID string `json:"AnalysisId"` } `json:"AnalysisSummaryList"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) require.Len(t, listOut.AnalysisSummaryList, 3) listedIDs := make(map[string]struct{}, 3) @@ -81,7 +83,7 @@ func TestCommitmentPurchaseAnalysis_Lifecycle(t *testing.T) { AnalysisStartedTime string `json:"AnalysisStartedTime"` EstimatedCompletionTime string `json:"EstimatedCompletionTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &startOut, jsonx.V1())) require.NotEmpty(t, startOut.AnalysisID) assert.NotEmpty(t, startOut.AnalysisStartedTime) assert.NotEmpty(t, startOut.EstimatedCompletionTime) @@ -96,7 +98,7 @@ func TestCommitmentPurchaseAnalysis_Lifecycle(t *testing.T) { AnalysisID string `json:"AnalysisId"` AnalysisStatus string `json:"AnalysisStatus"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, startOut.AnalysisID, getOut.AnalysisID) assert.Equal(t, "PROCESSING", getOut.AnalysisStatus) @@ -109,7 +111,7 @@ func TestCommitmentPurchaseAnalysis_Lifecycle(t *testing.T) { AnalysisID string `json:"AnalysisId"` } `json:"AnalysisSummaryList"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) require.Len(t, listOut.AnalysisSummaryList, 1) assert.Equal(t, startOut.AnalysisID, listOut.AnalysisSummaryList[0].AnalysisID) } @@ -128,7 +130,7 @@ func TestSnapshotRestore_IncludesCommitmentAnalyses(t *testing.T) { var startOut struct { AnalysisID string `json:"AnalysisId"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &startOut, jsonx.V1())) // Snapshot + restore snap := h.Snapshot(t.Context()) diff --git a/services/ce/handler_cost_allocation_tags_test.go b/services/ce/handler_cost_allocation_tags_test.go index db09476275..c21ef62bae 100644 --- a/services/ce/handler_cost_allocation_tags_test.go +++ b/services/ce/handler_cost_allocation_tags_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -22,7 +24,7 @@ func TestListCostAllocationTags_EmptyWithNoTags(t *testing.T) { var out struct { CostAllocationTags []any `json:"CostAllocationTags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.CostAllocationTags) assert.Empty(t, out.CostAllocationTags) } @@ -81,7 +83,7 @@ func TestUpdateCostAllocationTagsStatus_MultipleUpdates(t *testing.T) { var out struct { Errors []any `json:"Errors"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Errors, tt.wantErrors) }) } @@ -107,7 +109,7 @@ func TestBackfillMultipleJobs(t *testing.T) { var out struct { BackfillRequests []map[string]any `json:"BackfillRequests"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.BackfillRequests, 3) } @@ -179,7 +181,7 @@ func TestCostAllocationTags_UpdateAndList(t *testing.T) { Type string `json:"Type"` } `json:"CostAllocationTags"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.CostAllocationTags, tt.wantTagCount) for _, tag := range listOut.CostAllocationTags { @@ -217,7 +219,7 @@ func TestCostAllocationTags_ListFiltersWork(t *testing.T) { TagKey string `json:"TagKey"` } `json:"CostAllocationTags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.CostAllocationTags, 2) }) @@ -235,7 +237,7 @@ func TestCostAllocationTags_ListFiltersWork(t *testing.T) { Status string `json:"Status"` } `json:"CostAllocationTags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.CostAllocationTags, 2) for _, tag := range out.CostAllocationTags { @@ -262,7 +264,7 @@ func TestCostAllocationTagBackfill_StateTracking(t *testing.T) { RequestedAt string `json:"requestedAt"` } `json:"BackfillRequest"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &startOut, jsonx.V1())) assert.Equal(t, "2024-01-01T00:00:00Z", startOut.BackfillRequest.BackfillFrom) assert.Equal(t, "PROCESSING", startOut.BackfillRequest.BackfillStatus) @@ -275,7 +277,7 @@ func TestCostAllocationTagBackfill_StateTracking(t *testing.T) { BackfillStatus string `json:"backfillStatus"` } `json:"BackfillRequests"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) require.Len(t, listOut.BackfillRequests, 1) assert.Equal(t, "PROCESSING", listOut.BackfillRequests[0].BackfillStatus) } diff --git a/services/ce/handler_cost_categories_test.go b/services/ce/handler_cost_categories_test.go index 007e036a37..b49b289bf9 100644 --- a/services/ce/handler_cost_categories_test.go +++ b/services/ce/handler_cost_categories_test.go @@ -1,7 +1,8 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -39,7 +41,7 @@ func TestGetCostCategories_MultiRuleCategory(t *testing.T) { ReturnSize int `json:"ReturnSize"` TotalSize int `json:"TotalSize"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.CostCategoryValues, 3) assert.Equal(t, 3, out.ReturnSize) assert.Equal(t, 3, out.TotalSize) @@ -60,7 +62,7 @@ func TestListCostCategoryResourceAssociations(t *testing.T) { var out struct { CostCategoryResourceAssociations []map[string]any `json:"CostCategoryResourceAssociations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.CostCategoryResourceAssociations) } @@ -79,7 +81,7 @@ func TestDescribeCostCategory_HasProcessingStatus(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) catARN := createOut["CostCategoryArn"].(string) descRec := doRequest(t, h, "DescribeCostCategoryDefinition", map[string]any{ @@ -95,7 +97,7 @@ func TestDescribeCostCategory_HasProcessingStatus(t *testing.T) { } `json:"ProcessingStatus"` } `json:"CostCategory"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.NotEmpty(t, descOut.CostCategory.ProcessingStatus, "ProcessingStatus must be present") ps := descOut.CostCategory.ProcessingStatus[0] assert.Equal(t, "COST_EXPLORER", ps.Component) @@ -123,7 +125,7 @@ func TestListCostCategoryDefinitions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1 := out1["CostCategoryReferences"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["NextPageToken"].(string) @@ -136,7 +138,7 @@ func TestListCostCategoryDefinitions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2 := out2["CostCategoryReferences"].([]any) assert.Len(t, page2, 2) } @@ -160,7 +162,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["CostCategoryArn"]) assert.NotEmpty(t, out["EffectiveStart"]) }, @@ -180,7 +182,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) refs, ok := out["CostCategoryReferences"].([]any) require.True(t, ok) assert.NotEmpty(t, refs) @@ -199,7 +201,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) arn := createOut["CostCategoryArn"].(string) // Describe @@ -209,7 +211,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var describeOut map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&describeOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &describeOut, jsonx.V1())) cat := describeOut["CostCategory"].(map[string]any) assert.Equal(t, "DescribeCategory", cat["Name"]) }, @@ -227,7 +229,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) arn := createOut["CostCategoryArn"].(string) // Update @@ -239,7 +241,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var updateOut map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&updateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &updateOut, jsonx.V1())) assert.Equal(t, arn, updateOut["CostCategoryArn"]) }, }, @@ -256,7 +258,7 @@ func TestHandler_CostCategoryCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) arn := createOut["CostCategoryArn"].(string) // Delete @@ -343,7 +345,7 @@ func TestCEHandler_UpdateCostCategoryDefinition_DeepCopy(t *testing.T) { var createOut struct { CostCategoryArn string `json:"CostCategoryArn"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) updateRec := doRequest(t, h, "UpdateCostCategoryDefinition", map[string]any{ "CostCategoryArn": createOut.CostCategoryArn, @@ -363,7 +365,7 @@ func TestCEHandler_UpdateCostCategoryDefinition_DeepCopy(t *testing.T) { } `json:"CostCategory"` } - require.NoError(t, json.NewDecoder(describeRec.Body).Decode(&describeOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(describeRec.Body), &describeOut, jsonx.V1())) assert.Len(t, describeOut.CostCategory.Rules, tt.wantRulesAfterGet) }) } @@ -451,7 +453,7 @@ func TestHandler_GetCostCategories(t *testing.T) { ReturnSize int `json:"ReturnSize"` TotalSize int `json:"TotalSize"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantLen, out.ReturnSize) assert.Equal(t, tt.wantLen, out.TotalSize) @@ -523,7 +525,7 @@ func TestHandler_SortedOutput(t *testing.T) { Name string `json:"Name"` } `json:"CostCategoryReferences"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.CostCategoryReferences, 3) assert.Equal(t, "Alpha", out.CostCategoryReferences[0].Name) assert.Equal(t, "Mango", out.CostCategoryReferences[1].Name) @@ -555,7 +557,7 @@ func TestHandler_SortedOutput(t *testing.T) { var out struct { CostCategoryValues []string `json:"CostCategoryValues"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.CostCategoryValues, 3) assert.Equal(t, []string{"Alpha", "Mike", "Zulu"}, out.CostCategoryValues) }, @@ -592,7 +594,7 @@ func TestHandler_SortedOutput(t *testing.T) { Key string `json:"Key"` } `json:"ResourceTags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.ResourceTags, 3) assert.Equal(t, "Alpha", out.ResourceTags[0].Key) assert.Equal(t, "Mike", out.ResourceTags[1].Key) @@ -634,7 +636,7 @@ func TestHandler_DuplicateCostCategory_WireStatusIs400(t *testing.T) { var out struct { Type string `json:"__type"` } - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out, jsonx.V1())) assert.Equal(t, "ServiceQuotaExceededException", out.Type) } @@ -734,7 +736,7 @@ func TestHandler_UpdateCostCategoryDefinition_RequiredFields(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) tt.update["CostCategoryArn"] = createOut["CostCategoryArn"] rec := doRequest(t, h, "UpdateCostCategoryDefinition", tt.update) diff --git a/services/ce/handler_cost_usage_test.go b/services/ce/handler_cost_usage_test.go index 8af27f5906..8f7b064e63 100644 --- a/services/ce/handler_cost_usage_test.go +++ b/services/ce/handler_cost_usage_test.go @@ -1,7 +1,8 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -35,7 +37,7 @@ func TestGetCostAndUsageWithResources_ResourceIDs(t *testing.T) { ResultsByTime []any `json:"ResultsByTime"` DimensionValueAttributes []any `json:"DimensionValueAttributes"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.ResultsByTime) assert.NotNil(t, out.DimensionValueAttributes) } @@ -77,7 +79,7 @@ func TestGetApproximateUsageRecords_Shape(t *testing.T) { Services map[string]int64 `json:"Services"` TotalRecords int64 `json:"TotalRecords"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.Services) assert.Positive(t, out.TotalRecords) }) @@ -109,7 +111,7 @@ func TestGetCostAndUsageComparisons_Shape(t *testing.T) { } `json:"Metrics"` } `json:"CostAndUsageComparisons"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.CostAndUsageComparisons) assert.NotEmpty(t, out.TotalCostAndUsage["BlendedCost"]) assert.NotEmpty(t, out.CostAndUsageComparisons[0].Metrics["BlendedCost"].ComparisonTimePeriodAmount) @@ -238,7 +240,7 @@ func TestGetCostComparisonDrivers(t *testing.T) { } `json:"CostDrivers"` } `json:"CostComparisonDrivers"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.CostComparisonDrivers) assert.GreaterOrEqual(t, len(out.CostComparisonDrivers), tt.minGroups) assert.LessOrEqual(t, len(out.CostComparisonDrivers), tt.maxGroups) @@ -274,8 +276,8 @@ func TestGetUsageForecast_MatchesCostForecast(t *testing.T) { require.Equal(t, http.StatusOK, usageRec.Code) var costOut, usageOut map[string]any - require.NoError(t, json.NewDecoder(costRec.Body).Decode(&costOut)) - require.NoError(t, json.NewDecoder(usageRec.Body).Decode(&usageOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(costRec.Body), &costOut, jsonx.V1())) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(usageRec.Body), &usageOut, jsonx.V1())) // Both should have Total and ForecastResultsByTime assert.NotNil(t, costOut["Total"]) @@ -299,7 +301,7 @@ func TestGetTags_EmptyKeyReturnsAllKeys(t *testing.T) { ReturnSize int `json:"ReturnSize"` TotalSize int `json:"TotalSize"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) // Ledger may have no tags seeded — assert shape is correct regardless assert.NotNil(t, out.Tags) assert.Equal(t, len(out.Tags), out.ReturnSize) @@ -320,7 +322,7 @@ func TestGetTags_WithTagKey(t *testing.T) { var out struct { Tags []string `json:"Tags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.Tags) } @@ -348,7 +350,7 @@ func TestGetCostAndUsage_GroupByDimensions(t *testing.T) { var out struct { ResultsByTime []map[string]any `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.ResultsByTime) }) } @@ -385,7 +387,7 @@ func TestGetCostAndUsage_AlternateMetrics(t *testing.T) { var out struct { ResultsByTime []map[string]any `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.ResultsByTime) }) } @@ -467,7 +469,7 @@ func TestGetCostAndUsage_ReturnsResultsByTime(t *testing.T) { var out struct { ResultsByTime []any `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) if tt.wantNonEmpty { assert.NotEmpty(t, out.ResultsByTime) @@ -497,7 +499,7 @@ func TestGetCostAndUsage_GroupByProducesGroups(t *testing.T) { } `json:"Groups"` } `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.ResultsByTime) first := out.ResultsByTime[0] @@ -528,7 +530,7 @@ func TestGetCostAndUsage_TotalPresentWhenNoGroupBy(t *testing.T) { Estimated bool `json:"Estimated"` } `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.ResultsByTime) first := out.ResultsByTime[0] @@ -569,7 +571,7 @@ func TestGetDimensionValues_ServiceDimension(t *testing.T) { ReturnSize int `json:"ReturnSize"` TotalSize int `json:"TotalSize"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.DimensionValues) assert.Positive(t, out.ReturnSize) assert.Equal(t, out.ReturnSize, out.TotalSize) @@ -594,7 +596,7 @@ func TestGetDimensionValues_SearchStringFilters(t *testing.T) { Value string `json:"Value"` } `json:"DimensionValues"` } - require.NoError(t, json.NewDecoder(recAll.Body).Decode(&allOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(recAll.Body), &allOut, jsonx.V1())) totalCount := len(allOut.DimensionValues) // Filter by "Lambda" @@ -610,7 +612,7 @@ func TestGetDimensionValues_SearchStringFilters(t *testing.T) { Value string `json:"Value"` } `json:"DimensionValues"` } - require.NoError(t, json.NewDecoder(recFiltered.Body).Decode(&filteredOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(recFiltered.Body), &filteredOut, jsonx.V1())) assert.Less(t, len(filteredOut.DimensionValues), totalCount) for _, dv := range filteredOut.DimensionValues { assert.Contains(t, dv.Value, "Lambda") @@ -671,7 +673,7 @@ func TestGetCostForecast_ReturnsTimeSeries(t *testing.T) { MeanValue string `json:"MeanValue"` } `json:"ForecastResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Total.Amount) assert.NotEmpty(t, out.Total.Unit) assert.NotEmpty(t, out.ForecastResultsByTime) @@ -892,7 +894,7 @@ func TestHandler_Forecast_ReturnsOK(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out["Total"]) forecastResults, ok := out["ForecastResultsByTime"].([]any) require.True(t, ok) @@ -950,7 +952,7 @@ func TestHandler_GetApproximateUsageRecords(t *testing.T) { LookbackPeriod map[string]string `json:"LookbackPeriod"` TotalRecords int64 `json:"TotalRecords"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Positive(t, out.TotalRecords) assert.NotEmpty(t, out.LookbackPeriod["Start"]) assert.NotEmpty(t, out.LookbackPeriod["End"]) @@ -988,7 +990,7 @@ func TestHandler_GetCostAndUsageComparisons(t *testing.T) { var out struct { CostAndUsageComparisons []any `json:"CostAndUsageComparisons"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.CostAndUsageComparisons) }) } @@ -1057,7 +1059,7 @@ func TestHandler_GetCostAndUsageWithResources(t *testing.T) { var out struct { ResultsByTime []any `json:"ResultsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.ResultsByTime) }) } @@ -1093,7 +1095,7 @@ func TestHandler_GetCostComparisonDrivers(t *testing.T) { var out struct { CostComparisonDrivers []any `json:"CostComparisonDrivers"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.CostComparisonDrivers) }) } diff --git a/services/ce/handler_filters_test.go b/services/ce/handler_filters_test.go index 87fffc2016..e0fb2127c7 100644 --- a/services/ce/handler_filters_test.go +++ b/services/ce/handler_filters_test.go @@ -1,7 +1,8 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -42,7 +44,7 @@ func TestGetCostCategoriesFilterAndSortNarrow(t *testing.T) { var unfiltered struct { CostCategoryValues []string `json:"CostCategoryValues"` } - require.NoError(t, json.NewDecoder(unfilteredRec.Body).Decode(&unfiltered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(unfilteredRec.Body), &unfiltered, jsonx.V1())) require.Equal(t, []string{"Alpha", "Bravo", "Charlie"}, unfiltered.CostCategoryValues) filteredBody := map[string]any{ @@ -63,7 +65,7 @@ func TestGetCostCategoriesFilterAndSortNarrow(t *testing.T) { ReturnSize int `json:"ReturnSize"` TotalSize int `json:"TotalSize"` } - require.NoError(t, json.NewDecoder(filteredRec.Body).Decode(&filtered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(filteredRec.Body), &filtered, jsonx.V1())) assert.Equal(t, []string{"Alpha", "Charlie"}, filtered.CostCategoryValues) assert.Equal(t, 2, filtered.ReturnSize) assert.Equal(t, 2, filtered.TotalSize) @@ -79,7 +81,7 @@ func TestGetCostCategoriesFilterAndSortNarrow(t *testing.T) { var sorted struct { CostCategoryValues []string `json:"CostCategoryValues"` } - require.NoError(t, json.NewDecoder(sortedRec.Body).Decode(&sorted)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(sortedRec.Body), &sorted, jsonx.V1())) assert.Equal(t, []string{"Charlie", "Bravo", "Alpha"}, sorted.CostCategoryValues) } @@ -102,7 +104,7 @@ func TestGetDimensionValuesFilterAndSortNarrow(t *testing.T) { Value string `json:"Value"` } `json:"DimensionValues"` } - require.NoError(t, json.NewDecoder(unfilteredRec.Body).Decode(&unfiltered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(unfilteredRec.Body), &unfiltered, jsonx.V1())) require.Len(t, unfiltered.DimensionValues, 12, "synthetic catalog has 12 services") // AWS Lambda is the only service seeded with usage type Lambda-GB-Second, @@ -125,7 +127,7 @@ func TestGetDimensionValuesFilterAndSortNarrow(t *testing.T) { } `json:"DimensionValues"` ReturnSize int `json:"ReturnSize"` } - require.NoError(t, json.NewDecoder(filteredRec.Body).Decode(&filtered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(filteredRec.Body), &filtered, jsonx.V1())) require.Len(t, filtered.DimensionValues, 1) assert.Equal(t, "AWS Lambda", filtered.DimensionValues[0].Value) assert.Equal(t, 1, filtered.ReturnSize) @@ -144,7 +146,7 @@ func TestGetDimensionValuesFilterAndSortNarrow(t *testing.T) { Value string `json:"Value"` } `json:"DimensionValues"` } - require.NoError(t, json.NewDecoder(sortedRec.Body).Decode(&sorted)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(sortedRec.Body), &sorted, jsonx.V1())) require.NotEmpty(t, sorted.DimensionValues) assert.Equal(t, "Amazon Elastic Compute Cloud - Compute", sorted.DimensionValues[0].Value) } @@ -174,7 +176,7 @@ func TestGetTagsFilterAndSortAccepted(t *testing.T) { var out struct { Tags []string `json:"Tags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.Tags, "no ledger entry is ever tagged in this emulator") } @@ -198,7 +200,7 @@ func TestGetSavingsPlansCoverageRegionFilterNarrows(t *testing.T) { var matched struct { SavingsPlansCoverages []any `json:"SavingsPlansCoverages"` } - require.NoError(t, json.NewDecoder(matchRec.Body).Decode(&matched)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(matchRec.Body), &matched, jsonx.V1())) assert.Len(t, matched.SavingsPlansCoverages, 1) missRec := doRequest(t, h, "GetSavingsPlansCoverage", map[string]any{ @@ -212,7 +214,7 @@ func TestGetSavingsPlansCoverageRegionFilterNarrows(t *testing.T) { var missed struct { SavingsPlansCoverages []any `json:"SavingsPlansCoverages"` } - require.NoError(t, json.NewDecoder(missRec.Body).Decode(&missed)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(missRec.Body), &missed, jsonx.V1())) assert.Empty(t, missed.SavingsPlansCoverages) } @@ -232,7 +234,7 @@ func TestGetSavingsPlansPurchaseRecommendationAccountFilterNarrows(t *testing.T) require.Equal(t, http.StatusOK, matchRec.Code) var matched map[string]any - require.NoError(t, json.NewDecoder(matchRec.Body).Decode(&matched)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(matchRec.Body), &matched, jsonx.V1())) assert.NotNil(t, matched["SavingsPlansPurchaseRecommendation"]) missRec := doRequest(t, h, "GetSavingsPlansPurchaseRecommendation", map[string]any{ @@ -243,7 +245,7 @@ func TestGetSavingsPlansPurchaseRecommendationAccountFilterNarrows(t *testing.T) require.Equal(t, http.StatusOK, missRec.Code) var missed map[string]any - require.NoError(t, json.NewDecoder(missRec.Body).Decode(&missed)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(missRec.Body), &missed, jsonx.V1())) assert.Nil(t, missed["SavingsPlansPurchaseRecommendation"]) } @@ -266,7 +268,7 @@ func TestGetReservationPurchaseRecommendationAccountFilterNarrows(t *testing.T) var matched struct { Recommendations []any `json:"Recommendations"` } - require.NoError(t, json.NewDecoder(matchRec.Body).Decode(&matched)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(matchRec.Body), &matched, jsonx.V1())) assert.NotEmpty(t, matched.Recommendations) missRec := doRequest(t, h, "GetReservationPurchaseRecommendation", map[string]any{ @@ -280,7 +282,7 @@ func TestGetReservationPurchaseRecommendationAccountFilterNarrows(t *testing.T) var missed struct { Recommendations []any `json:"Recommendations"` } - require.NoError(t, json.NewDecoder(missRec.Body).Decode(&missed)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(missRec.Body), &missed, jsonx.V1())) assert.Empty(t, missed.Recommendations) } @@ -304,7 +306,7 @@ func TestGetReservationCoverageSortByTimeReorders(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"CoveragesByTime"` } - require.NoError(t, json.NewDecoder(ascRec.Body).Decode(&asc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ascRec.Body), &asc, jsonx.V1())) require.Len(t, asc.CoveragesByTime, 3) assert.Equal(t, "2024-01-01", asc.CoveragesByTime[0].TimePeriod["Start"]) assert.Equal(t, "2024-01-03", asc.CoveragesByTime[2].TimePeriod["Start"]) @@ -322,7 +324,7 @@ func TestGetReservationCoverageSortByTimeReorders(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"CoveragesByTime"` } - require.NoError(t, json.NewDecoder(descRec.Body).Decode(&desc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descRec.Body), &desc, jsonx.V1())) require.Len(t, desc.CoveragesByTime, 3) assert.Equal(t, "2024-01-03", desc.CoveragesByTime[0].TimePeriod["Start"]) assert.Equal(t, "2024-01-01", desc.CoveragesByTime[2].TimePeriod["Start"]) @@ -353,7 +355,7 @@ func TestGetReservationCoverageServiceFilterZeroesCost(t *testing.T) { } `json:"CoverageCost"` } `json:"Total"` } - require.NoError(t, json.NewDecoder(unfilteredRec.Body).Decode(&unfiltered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(unfilteredRec.Body), &unfiltered, jsonx.V1())) require.NotEqual(t, "0.0000", unfiltered.Total.CoverageCost.OnDemandCost) filteredRec := doRequest(t, h, "GetReservationCoverage", map[string]any{ @@ -372,7 +374,7 @@ func TestGetReservationCoverageServiceFilterZeroesCost(t *testing.T) { } `json:"CoverageCost"` } `json:"Total"` } - require.NoError(t, json.NewDecoder(filteredRec.Body).Decode(&filtered)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(filteredRec.Body), &filtered, jsonx.V1())) assert.Equal(t, "0.0000", filtered.Total.CoverageCost.OnDemandCost) } @@ -396,7 +398,7 @@ func TestGetReservationUtilizationSortByTimeReorders(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"UtilizationsByTime"` } - require.NoError(t, json.NewDecoder(descRec.Body).Decode(&desc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descRec.Body), &desc, jsonx.V1())) require.Len(t, desc.UtilizationsByTime, 3) assert.Equal(t, "2024-01-03", desc.UtilizationsByTime[0].TimePeriod["Start"]) assert.Equal(t, "2024-01-01", desc.UtilizationsByTime[2].TimePeriod["Start"]) @@ -424,6 +426,6 @@ func TestGetCostComparisonDriversFilterAccepted(t *testing.T) { var out struct { CostComparisonDrivers []any `json:"CostComparisonDrivers"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.CostComparisonDrivers) } diff --git a/services/ce/handler_reservations_test.go b/services/ce/handler_reservations_test.go index 858e005258..78866bf91d 100644 --- a/services/ce/handler_reservations_test.go +++ b/services/ce/handler_reservations_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -25,7 +27,7 @@ func TestGetReservationCoverage_DailyGranularity(t *testing.T) { var out struct { CoveragesByTime []any `json:"CoveragesByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.CoveragesByTime, 3) } @@ -43,7 +45,7 @@ func TestGetReservationUtilization_DailyGranularity(t *testing.T) { var out struct { UtilizationsByTime []any `json:"UtilizationsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.UtilizationsByTime, 3) } @@ -62,7 +64,7 @@ func TestGetRightsizingRecommendation_IncludesSummary(t *testing.T) { Summary map[string]string `json:"Summary"` RightsizingRecommendations []any `json:"RightsizingRecommendations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.RightsizingRecommendations) assert.NotNil(t, out.Summary) _, hasTotalCount := out.Summary["TotalRecommendationCount"] @@ -92,7 +94,7 @@ func TestGetReservationCoverage_Structure(t *testing.T) { } `json:"Total"` } `json:"CoveragesByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.CoveragesByTime) assert.NotNil(t, out.Total) @@ -121,7 +123,7 @@ func TestGetReservationUtilization_Structure(t *testing.T) { } `json:"Total"` } `json:"UtilizationsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.UtilizationsByTime) assert.NotNil(t, out.Total) @@ -175,7 +177,7 @@ func TestGetReservationPurchaseRecommendation_FieldShape(t *testing.T) { } `json:"RecommendationDetails"` } `json:"Recommendations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.Recommendations) require.NotEmpty(t, out.Recommendations[0].RecommendationDetails) @@ -212,7 +214,7 @@ func TestGetRightsizingRecommendation_FieldShape(t *testing.T) { } `json:"ModifyRecommendationDetail"` } `json:"RightsizingRecommendations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.RightsizingRecommendations) assert.NotNil(t, out.Summary) @@ -253,7 +255,7 @@ func TestHandler_GetReservationCoverage(t *testing.T) { var out struct { CoveragesByTime []any `json:"CoveragesByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.CoveragesByTime) }) } @@ -290,7 +292,7 @@ func TestHandler_GetReservationPurchaseRecommendation(t *testing.T) { var out struct { Recommendations []any `json:"Recommendations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Recommendations) }) } @@ -325,7 +327,7 @@ func TestHandler_GetReservationUtilization(t *testing.T) { var out struct { UtilizationsByTime []any `json:"UtilizationsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.UtilizationsByTime) }) } diff --git a/services/ce/handler_savings_plans_test.go b/services/ce/handler_savings_plans_test.go index 59817097fd..753d359666 100644 --- a/services/ce/handler_savings_plans_test.go +++ b/services/ce/handler_savings_plans_test.go @@ -1,7 +1,8 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -27,7 +29,7 @@ func TestGetSavingsPlanPurchaseRecommendationDetails(t *testing.T) { RecommendationDetailData map[string]any `json:"RecommendationDetailData"` RecommendationDetailID string `json:"RecommendationDetailId"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "detail-abc123", out.RecommendationDetailID) assert.NotEmpty(t, out.RecommendationDetailData) } @@ -56,7 +58,7 @@ func TestListSavingsPlansPurchaseRecommendationGeneration(t *testing.T) { var out struct { GenerationSummaryList []any `json:"GenerationSummaryList"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.GenerationSummaryList) } @@ -75,7 +77,7 @@ func TestStartSavingsPlansPurchaseRecommendationGeneration(t *testing.T) { GenerationStartedTime string `json:"GenerationStartedTime"` EstimatedCompletionTime string `json:"EstimatedCompletionTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.RecommendationID) assert.NotEmpty(t, out.GenerationStartedTime) assert.NotEmpty(t, out.EstimatedCompletionTime) @@ -89,7 +91,7 @@ func TestStartSavingsPlansPurchaseRecommendationGeneration(t *testing.T) { GenerationStatus string `json:"GenerationStatus"` } `json:"GenerationSummaryList"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) require.Len(t, listOut.GenerationSummaryList, 1) assert.Equal(t, out.RecommendationID, listOut.GenerationSummaryList[0].RecommendationID) assert.Equal(t, "PROCESSING", listOut.GenerationSummaryList[0].GenerationStatus) @@ -113,7 +115,7 @@ func TestListSavingsPlansPurchaseRecommendationGeneration_FiltersByStatus(t *tes var out struct { GenerationSummaryList []any `json:"GenerationSummaryList"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.GenerationSummaryList, "job is PROCESSING, must not match a SUCCEEDED filter") } @@ -134,7 +136,7 @@ func TestGetSavingsPlansCoverage(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"SavingsPlansCoverages"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.SavingsPlansCoverages) assert.NotEmpty(t, out.SavingsPlansCoverages[0].Coverage) } @@ -153,7 +155,7 @@ func TestGetSavingsPlansUtilization_DailyGranularity(t *testing.T) { var out struct { SavingsPlansUtilizationsByTime []any `json:"SavingsPlansUtilizationsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.SavingsPlansUtilizationsByTime, 7) } @@ -191,7 +193,7 @@ func TestCeRegion_SavingsPlansCoverageUsesCtxRegion(t *testing.T) { Attributes map[string]string `json:"Attributes"` } `json:"SavingsPlansCoverages"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.SavingsPlansCoverages) assert.Equal(t, tt.wantRegion, out.SavingsPlansCoverages[0].Attributes["Region"]) }) @@ -235,7 +237,7 @@ func TestCeRegion_SavingsPlansPurchaseRecommendationUsesCtxRegion(t *testing.T) RecommendationDetails []map[string]any `json:"SavingsPlansPurchaseRecommendationDetails"` } `json:"SavingsPlansPurchaseRecommendation"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) details := out.SavingsPlansPurchaseRecommendation.RecommendationDetails require.NotEmpty(t, details) spDetails, ok := details[0]["SavingsPlansDetails"].(map[string]any) @@ -291,7 +293,7 @@ func TestGetSavingsPlansUtilization_ReturnsTotalAndByTime(t *testing.T) { } `json:"Total"` SavingsPlansUtilizationsByTime []any `json:"SavingsPlansUtilizationsByTime"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Total.Utilization.TotalCommitment) assert.NotEmpty(t, out.Total.Utilization.UsedCommitment) @@ -320,7 +322,7 @@ func TestGetSavingsPlansUtilizationDetails_ReturnsDetails(t *testing.T) { } `json:"Utilization"` } `json:"SavingsPlansUtilizationDetails"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.SavingsPlansUtilizationDetails) assert.NotNil(t, out.Total) assert.NotEmpty(t, out.TimePeriod) @@ -373,7 +375,7 @@ func TestGetSavingsPlansPurchaseRecommendation_ReturnsRecommendation(t *testing. SavingsPlansPurchaseRecommendationDetails []map[string]any `json:"SavingsPlansPurchaseRecommendationDetails"` } `json:"SavingsPlansPurchaseRecommendation"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.SavingsPlansPurchaseRecommendation.SavingsPlansType) assert.NotEmpty(t, out.SavingsPlansPurchaseRecommendation.SavingsPlansPurchaseRecommendationDetails) assert.NotNil(t, out.Metadata) diff --git a/services/ce/handler_tags_test.go b/services/ce/handler_tags_test.go index 157531ccb5..9d6222fec8 100644 --- a/services/ce/handler_tags_test.go +++ b/services/ce/handler_tags_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -30,7 +32,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) arn := createOut["CostCategoryArn"].(string) // Tag @@ -47,7 +49,7 @@ func TestHandler_TagOperations(t *testing.T) { assert.Equal(t, http.StatusOK, rec3.Code) var listOut map[string]any - require.NoError(t, json.NewDecoder(rec3.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec3.Body), &listOut, jsonx.V1())) tags, _ := listOut["ResourceTags"].([]any) var envVal string for _, tagAny := range tags { @@ -94,7 +96,7 @@ func TestHandler_TagOperations_AnomalyMonitor(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) monitorARN := createOut["MonitorArn"].(string) // Tag. @@ -111,7 +113,7 @@ func TestHandler_TagOperations_AnomalyMonitor(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) tags, _ := listOut["ResourceTags"].([]any) var found bool @@ -153,7 +155,7 @@ func TestHandler_TagOperations_AnomalySubscription(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) subARN := createOut["SubscriptionArn"].(string) // Tag. @@ -170,7 +172,7 @@ func TestHandler_TagOperations_AnomalySubscription(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) tags, _ := listOut["ResourceTags"].([]any) assert.NotEmpty(t, tags) @@ -278,7 +280,7 @@ func TestHandler_ListTagsForResource_EmptyTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) arn := createOut["CostCategoryArn"].(string) listRec := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": arn}) @@ -287,7 +289,7 @@ func TestHandler_ListTagsForResource_EmptyTags(t *testing.T) { var out struct { ResourceTags []map[string]string `json:"ResourceTags"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) // Must be an empty array, not nil/absent. assert.NotNil(t, out.ResourceTags) assert.Empty(t, out.ResourceTags) diff --git a/services/ce/handler_test.go b/services/ce/handler_test.go index 77205600ca..8cc7111f4f 100644 --- a/services/ce/handler_test.go +++ b/services/ce/handler_test.go @@ -3,7 +3,8 @@ package ce_test import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -27,7 +29,7 @@ func doRequestWithMeta( ) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) ctx := awsmeta.Set(context.Background(), meta) @@ -55,7 +57,7 @@ func doRequest(t *testing.T, h *ce.Handler, action string, body any) *httptest.R var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -369,7 +371,7 @@ func TestHandler_Reset(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) refs, _ := listOut["CostCategoryReferences"].([]any) assert.Empty(t, refs) } @@ -472,7 +474,7 @@ func TestHandler_ErrorWireShape(t *testing.T) { var out struct { Type string `json:"__type"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out.Type) }) } diff --git a/services/ce/persistence.go b/services/ce/persistence.go index eb07cbca95..b2f1c2c5da 100644 --- a/services/ce/persistence.go +++ b/services/ce/persistence.go @@ -2,10 +2,12 @@ package ce import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -36,12 +38,12 @@ const ceSnapshotVersion = 1 // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - BackfillJobs []*BackfillJob `json:"backfillJobs"` - AnomalyTTL time.Duration `json:"anomalyTTL"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + BackfillJobs []*BackfillJob `json:"backfillJobs"` + AnomalyTTL time.Duration `json:"anomalyTTL"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -66,7 +68,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { AnomalyTTL: b.anomalyTTL, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ce: failed to marshal snapshot", "error", err) diff --git a/services/ce/persistence_version_test.go b/services/ce/persistence_version_test.go index dd6acf8b81..8bc1a7db62 100644 --- a/services/ce/persistence_version_test.go +++ b/services/ce/persistence_version_test.go @@ -1,12 +1,13 @@ package ce_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ce" ) @@ -18,11 +19,11 @@ func tamperSnapshotVersion(t *testing.T, snap []byte, newVersion int) []byte { var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) raw["version"] = newVersion - out, err := json.Marshal(raw) + out, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) return out @@ -82,10 +83,10 @@ func TestInMemoryBackend_RestoreMissingVersion(t *testing.T) { var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) delete(raw, "version") - legacy, err := json.Marshal(raw) + legacy, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) target := ce.NewInMemoryBackend("000000000000", "us-east-1") diff --git a/services/ce/store.go b/services/ce/store.go index 2b49011d8b..765d332cf0 100644 --- a/services/ce/store.go +++ b/services/ce/store.go @@ -3,12 +3,13 @@ package ce import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -233,7 +234,7 @@ func checkPageTokens(body []byte) error { NextToken string `json:"NextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) for _, tok := range []string{req.NextPageToken, req.NextToken} { if tok == "" { diff --git a/services/ce/usage_validation_test.go b/services/ce/usage_validation_test.go index 00595edad4..d90abe82ec 100644 --- a/services/ce/usage_validation_test.go +++ b/services/ce/usage_validation_test.go @@ -1,13 +1,15 @@ package ce_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func usageBody(start, end, granularity string, extra map[string]any) map[string]any { @@ -59,7 +61,7 @@ func TestGetCostAndUsage_RequestValidation(t *testing.T) { if tt.wantType != "" { var we wireError - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &we)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &we, jsonx.V1())) assert.Contains(t, we.Type, tt.wantType) } }) @@ -80,7 +82,7 @@ func TestGetCostAndUsage_HourlyBuckets(t *testing.T) { TimePeriod map[string]string `json:"TimePeriod"` } `json:"ResultsByTime"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ResultsByTime, 3) assert.Equal(t, "2026-01-01T01:00:00Z", out.ResultsByTime[1].TimePeriod["Start"]) } @@ -127,7 +129,7 @@ func TestRequestEnumValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var we wireError - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &we)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &we, jsonx.V1())) assert.Contains(t, we.Type, "ValidationError") }) } diff --git a/services/cleanrooms/analysis_templates_test.go b/services/cleanrooms/analysis_templates_test.go index 4716b0faed..05f6c9ce30 100644 --- a/services/cleanrooms/analysis_templates_test.go +++ b/services/cleanrooms/analysis_templates_test.go @@ -1,12 +1,14 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAnalysisTemplateHasIDKeys verifies AnalysisTemplate canonical ID keys. @@ -22,14 +24,14 @@ func TestAnalysisTemplateHasIDKeys(t *testing.T) { }) var colResp map[string]any rec := doRequest(t, e, "GET", "/collaborations", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp, jsonx.V1())) colID := colResp["collaborationList"].([]any)[0].(map[string]any)["id"].(string) rec2 := doRequest(t, e, "POST", "/memberships", map[string]any{ "collaborationIdentifier": colID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1())) mID := memResp["membership"].(map[string]any)["id"].(string) // Create analysis template @@ -41,7 +43,7 @@ func TestAnalysisTemplateHasIDKeys(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code, rec3.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) at := resp["analysisTemplate"].(map[string]any) assert.Contains(t, at, "id", "analysisTemplate must have 'id' key") diff --git a/services/cleanrooms/collaborations_test.go b/services/cleanrooms/collaborations_test.go index 06c2ffdf39..7d96cf7719 100644 --- a/services/cleanrooms/collaborations_test.go +++ b/services/cleanrooms/collaborations_test.go @@ -1,12 +1,14 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCollaborationWireShape verifies a Collaboration response uses only the @@ -27,7 +29,7 @@ func TestCollaborationWireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collab := resp["collaboration"].(map[string]any) id, hasID := collab["id"] @@ -58,7 +60,7 @@ func TestCollaborationARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collab := resp["collaboration"].(map[string]any) arn, ok := collab["arn"].(string) @@ -80,7 +82,7 @@ func TestCollaborationQueryLogStatusRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collab := resp["collaboration"].(map[string]any) assert.Equal(t, "ENABLED", collab["queryLogStatus"]) } @@ -101,12 +103,12 @@ func TestCollaborationCreatorMemberAbilities(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collabID := resp["collaboration"].(map[string]any)["id"].(string) listRec := doRequest(t, e, "GET", "/collaborations/"+collabID+"/members", nil) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) members, ok := listResp["memberSummaries"].([]any) require.True(t, ok, "ListMembers must return memberSummaries") require.Len(t, members, 1) @@ -137,7 +139,7 @@ func TestDeleteMember_MarksRemoved(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collabID := resp["collaboration"].(map[string]any)["id"].(string) del := doRequest(t, e, "DELETE", "/collaborations/"+collabID+"/member/222222222222", nil) @@ -146,7 +148,7 @@ func TestDeleteMember_MarksRemoved(t *testing.T) { listRec := doRequest(t, e, "GET", "/collaborations/"+collabID+"/members", nil) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) members := listResp["memberSummaries"].([]any) require.Len(t, members, 2, "removed member must still appear in the member list") @@ -178,7 +180,7 @@ func TestDeleteCollaboration_CascadesMembershipToCollaborationDeleted(t *testing }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collab := resp["collaboration"].(map[string]any) collabID := collab["id"].(string) membershipID := collab["membershipId"].(string) @@ -189,7 +191,7 @@ func TestDeleteCollaboration_CascadesMembershipToCollaborationDeleted(t *testing getRec := doRequest(t, e, "GET", "/memberships/"+membershipID, nil) require.Equal(t, http.StatusOK, getRec.Code, "membership must still be retrievable after collaboration delete") var memResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &memResp, jsonx.V1())) assert.Equal(t, "COLLABORATION_DELETED", memResp["membership"].(map[string]any)["status"]) } @@ -210,7 +212,7 @@ func TestCollaborationChangeRequest_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) collabID := resp["collaboration"].(map[string]any)["id"].(string) createRec := doRequest(t, e, "POST", "/collaborations/"+collabID+"/changeRequests", map[string]any{ @@ -227,7 +229,7 @@ func TestCollaborationChangeRequest_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cr := createResp["collaborationChangeRequest"].(map[string]any) assert.Equal(t, "PENDING", cr["status"]) assert.NotEmpty(t, cr["changes"]) @@ -239,7 +241,7 @@ func TestCollaborationChangeRequest_Lifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, approveRec.Code, approveRec.Body.String()) var approveResp map[string]any - require.NoError(t, json.Unmarshal(approveRec.Body.Bytes(), &approveResp)) + require.NoError(t, json.Unmarshal(approveRec.Body.Bytes(), &approveResp, jsonx.V1())) assert.Equal(t, "APPROVED", approveResp["collaborationChangeRequest"].(map[string]any)["status"]) commitRec := doRequest( @@ -248,7 +250,7 @@ func TestCollaborationChangeRequest_Lifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, commitRec.Code, commitRec.Body.String()) var commitResp map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp, jsonx.V1())) assert.Equal(t, "COMMITTED", commitResp["collaborationChangeRequest"].(map[string]any)["status"]) // COMMIT applies the change's real semantic effect: the ADD_MEMBER change @@ -256,7 +258,7 @@ func TestCollaborationChangeRequest_Lifecycle(t *testing.T) { getRec := doRequest(t, e, "GET", "/collaborations/"+collabID+"/members", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) members, _ := getResp["memberSummaries"].([]any) var found bool for _, m := range members { diff --git a/services/cleanrooms/configured_tables_test.go b/services/cleanrooms/configured_tables_test.go index 697e92b911..0644580c65 100644 --- a/services/cleanrooms/configured_tables_test.go +++ b/services/cleanrooms/configured_tables_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createConfiguredTable(t *testing.T, e *echo.Echo) string { @@ -22,7 +24,7 @@ func createConfiguredTable(t *testing.T, e *echo.Echo) string { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["configuredTable"].(map[string]any)["id"].(string) } @@ -67,7 +69,7 @@ func TestConfiguredTables_Create(t *testing.T) { require.Equal(t, tt.wants.status, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ct := resp["configuredTable"].(map[string]any) id, hasID := ct["id"] @@ -117,7 +119,7 @@ func TestConfiguredTableAnalysisRules_Create(t *testing.T) { require.Equal(t, tt.wants.status, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ar := resp["analysisRule"].(map[string]any) assert.Contains(t, ar, "configuredTableId", "analysisRule must have canonical 'configuredTableId' key") diff --git a/services/cleanrooms/handler.go b/services/cleanrooms/handler.go index e9789412b3..e801accf37 100644 --- a/services/cleanrooms/handler.go +++ b/services/cleanrooms/handler.go @@ -2,7 +2,7 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -464,7 +465,7 @@ func (h *Handler) dispatch( } func mustJSON(v any) []byte { - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return b } diff --git a/services/cleanrooms/handler_analysis_log_exports.go b/services/cleanrooms/handler_analysis_log_exports.go index f7569460a2..f3a88bb684 100644 --- a/services/cleanrooms/handler_analysis_log_exports.go +++ b/services/cleanrooms/handler_analysis_log_exports.go @@ -2,11 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "strconv" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -24,7 +25,7 @@ func (h *Handler) handleStartAnalysisLogExport(_ context.Context, body []byte) ( AnalysisID string `json:"analysisId"` AnalysisType string `json:"analysisType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) e, err := h.Backend.StartAnalysisLogExport( req.MembershipIdentifier, req.AnalysisID, req.AnalysisType, req.ResultConfiguration, @@ -41,7 +42,7 @@ func (h *Handler) handleGetAnalysisLogExport(_ context.Context, body []byte) ([] MembershipIdentifier string `json:"membershipIdentifier"` AnalysisLogExportIdentifier string `json:"analysisLogExportIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) e, err := h.Backend.GetAnalysisLogExport(req.MembershipIdentifier, req.AnalysisLogExportIdentifier) if err != nil { @@ -55,7 +56,7 @@ func (h *Handler) handleListAnalysisLogExports(_ context.Context, body []byte, c var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) limit := 0 if raw := qp(c, exportMaxResultsParamName); raw != "" { diff --git a/services/cleanrooms/handler_analysis_templates.go b/services/cleanrooms/handler_analysis_templates.go index f24b79721a..26abbba470 100644 --- a/services/cleanrooms/handler_analysis_templates.go +++ b/services/cleanrooms/handler_analysis_templates.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetCollaborationAnalysisTemplate( @@ -16,7 +18,7 @@ func (h *Handler) handleGetCollaborationAnalysisTemplate( CollaborationIdentifier string `json:"collaborationIdentifier"` AnalysisTemplateArn string `json:"analysisTemplateArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.GetCollaborationAnalysisTemplate( req.CollaborationIdentifier, req.AnalysisTemplateArn, @@ -36,7 +38,7 @@ func (h *Handler) handleListCollaborationAnalysisTemplates( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationAnalysisTemplates( req.CollaborationIdentifier, qp(c, "maxResults"), @@ -61,7 +63,7 @@ func (h *Handler) handleBatchGetCollaborationAnalysisTemplate( CollaborationIdentifier string `json:"collaborationIdentifier"` AnalysisTemplateArns []string `json:"analysisTemplateArns"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, errs, err := h.Backend.BatchGetCollaborationAnalysisTemplate( req.CollaborationIdentifier, req.AnalysisTemplateArns, @@ -86,7 +88,7 @@ func (h *Handler) handleCreateAnalysisTemplate(_ context.Context, body []byte) ( Format string `json:"format"` AnalysisParameters []map[string]any `json:"analysisParameters"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum("format", crtypes.AnalysisFormat(req.Format)); err != nil { return nil, err } @@ -117,7 +119,7 @@ func (h *Handler) handleGetAnalysisTemplate(_ context.Context, body []byte) ([]b MembershipIdentifier string `json:"membershipIdentifier"` AnalysisTemplateIdentifier string `json:"analysisTemplateIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.GetAnalysisTemplate( req.MembershipIdentifier, req.AnalysisTemplateIdentifier, @@ -137,7 +139,7 @@ func (h *Handler) handleListAnalysisTemplates( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListAnalysisTemplates( req.MembershipIdentifier, qp(c, "maxResults"), @@ -160,7 +162,7 @@ func (h *Handler) handleUpdateAnalysisTemplate(_ context.Context, body []byte) ( AnalysisTemplateIdentifier string `json:"analysisTemplateIdentifier"` Description string `json:"description"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.UpdateAnalysisTemplate( req.MembershipIdentifier, req.AnalysisTemplateIdentifier, @@ -178,7 +180,7 @@ func (h *Handler) handleDeleteAnalysisTemplate(_ context.Context, body []byte) ( MembershipIdentifier string `json:"membershipIdentifier"` AnalysisTemplateIdentifier string `json:"analysisTemplateIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteAnalysisTemplate( req.MembershipIdentifier, diff --git a/services/cleanrooms/handler_analysis_templates_test.go b/services/cleanrooms/handler_analysis_templates_test.go index d4c09bd3ac..2e5bb212ac 100644 --- a/services/cleanrooms/handler_analysis_templates_test.go +++ b/services/cleanrooms/handler_analysis_templates_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAnalysisTemplates_Handlers(t *testing.T) { @@ -20,14 +22,14 @@ func TestAnalysisTemplates_Handlers(t *testing.T) { "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collabID := colResp["collaboration"].(map[string]any)["id"].(string) rec2 := doRequest(t, e, "POST", "/memberships", map[string]any{ "collaborationIdentifier": collabID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - json.Unmarshal(rec2.Body.Bytes(), &memResp) + json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1()) memID := memResp["membership"].(map[string]any)["id"].(string) rec3 := doRequest(t, e, "POST", "/memberships/"+memID+"/analysistemplates", map[string]any{ @@ -36,7 +38,7 @@ func TestAnalysisTemplates_Handlers(t *testing.T) { "source": map[string]any{"text": "SELECT 1"}, }) var atResp map[string]any - json.Unmarshal(rec3.Body.Bytes(), &atResp) + json.Unmarshal(rec3.Body.Bytes(), &atResp, jsonx.V1()) atID := atResp["analysisTemplate"].(map[string]any)["id"].(string) tests := []struct { diff --git a/services/cleanrooms/handler_collaborations.go b/services/cleanrooms/handler_collaborations.go index 49d0543ec9..1fc8dd5d43 100644 --- a/services/cleanrooms/handler_collaborations.go +++ b/services/cleanrooms/handler_collaborations.go @@ -2,12 +2,14 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // collaborationWire shadows the embedded Members tag at depth 0 so the @@ -38,7 +40,7 @@ func (h *Handler) handleCreateCollaboration(_ context.Context, body []byte) ([]b AllowedResultRegions []string `json:"allowedResultRegions"` IsMetricsEnabled bool `json:"isMetricsEnabled"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := firstErr( checkEnumList[crtypes.MemberAbility]("creatorMemberAbilities", req.CreatorMemberAbilities), checkEnum("queryLogStatus", crtypes.CollaborationQueryLogStatus(req.QueryLogStatus)), @@ -86,7 +88,7 @@ func (h *Handler) handleGetCollaboration(_ context.Context, body []byte) ([]byte var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) c, err := h.Backend.GetCollaboration(req.CollaborationIdentifier) if err != nil { return nil, declaredCollaborationError(err) @@ -119,7 +121,7 @@ func (h *Handler) handleUpdateCollaboration(_ context.Context, body []byte) ([]b Description string `json:"description"` AnalyticsEngine string `json:"analyticsEngine"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) col, err := h.Backend.UpdateCollaboration( req.CollaborationIdentifier, req.Name, @@ -137,7 +139,7 @@ func (h *Handler) handleDeleteCollaboration(_ context.Context, body []byte) ([]b var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteCollaboration(req.CollaborationIdentifier) } @@ -150,7 +152,7 @@ func (h *Handler) handleListMembers( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListMembers( req.CollaborationIdentifier, qp(c, "maxResults"), @@ -177,7 +179,7 @@ func (h *Handler) handleDeleteMember(_ context.Context, body []byte) ([]byte, er CollaborationIdentifier string `json:"collaborationIdentifier"` AccountID string `json:"accountId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteMember(req.CollaborationIdentifier, req.AccountID) } @@ -190,7 +192,7 @@ func (h *Handler) handleCreateCollaborationChangeRequest( CollaborationIdentifier string `json:"collaborationIdentifier"` Changes []Change `json:"changes"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) r, err := h.Backend.CreateCollaborationChangeRequest( req.CollaborationIdentifier, req.Changes, @@ -210,7 +212,7 @@ func (h *Handler) handleGetCollaborationChangeRequest( CollaborationIdentifier string `json:"collaborationIdentifier"` ChangeRequestIdentifier string `json:"changeRequestIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) r, err := h.Backend.GetCollaborationChangeRequest( req.CollaborationIdentifier, req.ChangeRequestIdentifier, @@ -230,7 +232,7 @@ func (h *Handler) handleListCollaborationChangeRequests( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationChangeRequests( req.CollaborationIdentifier, qp(c, "status"), @@ -257,7 +259,7 @@ func (h *Handler) handleUpdateCollaborationChangeRequest( ChangeRequestIdentifier string `json:"changeRequestIdentifier"` Action string `json:"action"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) r, err := h.Backend.UpdateCollaborationChangeRequest( req.CollaborationIdentifier, req.ChangeRequestIdentifier, diff --git a/services/cleanrooms/handler_collaborations_test.go b/services/cleanrooms/handler_collaborations_test.go index 624a25773d..7ca3d42596 100644 --- a/services/cleanrooms/handler_collaborations_test.go +++ b/services/cleanrooms/handler_collaborations_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCollaborations_Handlers(t *testing.T) { @@ -22,7 +24,7 @@ func TestCollaborations_Handlers(t *testing.T) { require.Equal(t, http.StatusOK, res.Code) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collabID := colResp["collaboration"].(map[string]any)["id"].(string) tests := []struct { diff --git a/services/cleanrooms/handler_configured_audience_model_associations.go b/services/cleanrooms/handler_configured_audience_model_associations.go index abc4416ece..b2236e3f54 100644 --- a/services/cleanrooms/handler_configured_audience_model_associations.go +++ b/services/cleanrooms/handler_configured_audience_model_associations.go @@ -2,9 +2,11 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetCollaborationConfiguredAudienceModelAssociation( @@ -15,7 +17,7 @@ func (h *Handler) handleGetCollaborationConfiguredAudienceModelAssociation( CollaborationIdentifier string `json:"collaborationIdentifier"` ConfiguredAudienceModelAssociationIdentifier string `json:"configuredAudienceModelAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.GetCollaborationConfiguredAudienceModelAssociation( req.CollaborationIdentifier, req.ConfiguredAudienceModelAssociationIdentifier, @@ -35,7 +37,7 @@ func (h *Handler) handleListCollaborationConfiguredAudienceModelAssociations( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationConfiguredAudienceModelAssociations( req.CollaborationIdentifier, qp(c, "maxResults"), @@ -68,7 +70,7 @@ func (h *Handler) handleCreateConfiguredAudienceModelAssociation( Description string `json:"description"` ManageResourcePolicies bool `json:"manageResourcePolicies"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.CreateConfiguredAudienceModelAssociation( req.MembershipIdentifier, req.ConfiguredAudienceModelArn, @@ -92,7 +94,7 @@ func (h *Handler) handleGetConfiguredAudienceModelAssociation( MembershipIdentifier string `json:"membershipIdentifier"` ConfiguredAudienceModelAssociationIdentifier string `json:"configuredAudienceModelAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.GetConfiguredAudienceModelAssociation( req.MembershipIdentifier, req.ConfiguredAudienceModelAssociationIdentifier, @@ -112,7 +114,7 @@ func (h *Handler) handleListConfiguredAudienceModelAssociations( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListConfiguredAudienceModelAssociations( req.MembershipIdentifier, qp(c, "maxResults"), @@ -139,7 +141,7 @@ func (h *Handler) handleUpdateConfiguredAudienceModelAssociation( Name string `json:"name"` Description string `json:"description"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.UpdateConfiguredAudienceModelAssociation( req.MembershipIdentifier, req.ConfiguredAudienceModelAssociationIdentifier, @@ -161,7 +163,7 @@ func (h *Handler) handleDeleteConfiguredAudienceModelAssociation( MembershipIdentifier string `json:"membershipIdentifier"` ConfiguredAudienceModelAssociationIdentifier string `json:"configuredAudienceModelAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteConfiguredAudienceModelAssociation( req.MembershipIdentifier, diff --git a/services/cleanrooms/handler_configured_audience_model_associations_test.go b/services/cleanrooms/handler_configured_audience_model_associations_test.go index fee26f9899..c7fb6026ea 100644 --- a/services/cleanrooms/handler_configured_audience_model_associations_test.go +++ b/services/cleanrooms/handler_configured_audience_model_associations_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConfiguredAudienceModelAssociations_Handlers(t *testing.T) { @@ -20,14 +22,14 @@ func TestConfiguredAudienceModelAssociations_Handlers(t *testing.T) { "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collabID := colResp["collaboration"].(map[string]any)["id"].(string) rec2 := doRequest(t, e, "POST", "/memberships", map[string]any{ "collaborationIdentifier": collabID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - json.Unmarshal(rec2.Body.Bytes(), &memResp) + json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1()) memID := memResp["membership"].(map[string]any)["id"].(string) tests := []struct { diff --git a/services/cleanrooms/handler_configured_table_associations.go b/services/cleanrooms/handler_configured_table_associations.go index e9af44e4f8..4b7e79d71e 100644 --- a/services/cleanrooms/handler_configured_table_associations.go +++ b/services/cleanrooms/handler_configured_table_associations.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateConfiguredTableAssociation( @@ -20,7 +22,7 @@ func (h *Handler) handleCreateConfiguredTableAssociation( ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` RoleArn string `json:"roleArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.CreateConfiguredTableAssociation( req.MembershipIdentifier, req.Name, @@ -44,7 +46,7 @@ func (h *Handler) handleGetConfiguredTableAssociation( MembershipIdentifier string `json:"membershipIdentifier"` ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.GetConfiguredTableAssociation( req.MembershipIdentifier, req.ConfiguredTableAssociationIdentifier, @@ -64,7 +66,7 @@ func (h *Handler) handleListConfiguredTableAssociations( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListConfiguredTableAssociations( req.MembershipIdentifier, qp(c, "maxResults"), @@ -91,7 +93,7 @@ func (h *Handler) handleUpdateConfiguredTableAssociation( Description string `json:"description"` RoleArn string `json:"roleArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.UpdateConfiguredTableAssociation( req.MembershipIdentifier, req.ConfiguredTableAssociationIdentifier, @@ -113,7 +115,7 @@ func (h *Handler) handleDeleteConfiguredTableAssociation( MembershipIdentifier string `json:"membershipIdentifier"` ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteConfiguredTableAssociation( req.MembershipIdentifier, @@ -131,7 +133,7 @@ func (h *Handler) handleCreateConfiguredTableAssociationAnalysisRule( ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAssociationAnalysisRuleType(req.AnalysisRuleType), @@ -161,7 +163,7 @@ func (h *Handler) handleGetConfiguredTableAssociationAnalysisRule( ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAssociationAnalysisRuleType(req.AnalysisRuleType), @@ -191,7 +193,7 @@ func (h *Handler) handleUpdateConfiguredTableAssociationAnalysisRule( ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAssociationAnalysisRuleType(req.AnalysisRuleType), @@ -221,7 +223,7 @@ func (h *Handler) handleDeleteConfiguredTableAssociationAnalysisRule( ConfiguredTableAssociationIdentifier string `json:"configuredTableAssociationIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAssociationAnalysisRuleType(req.AnalysisRuleType), diff --git a/services/cleanrooms/handler_configured_table_associations_test.go b/services/cleanrooms/handler_configured_table_associations_test.go index e3d7109ec5..946cbd5008 100644 --- a/services/cleanrooms/handler_configured_table_associations_test.go +++ b/services/cleanrooms/handler_configured_table_associations_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConfiguredTableAssociations_Handlers(t *testing.T) { @@ -20,14 +22,14 @@ func TestConfiguredTableAssociations_Handlers(t *testing.T) { "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collabID := colResp["collaboration"].(map[string]any)["id"].(string) rec2 := doRequest(t, e, "POST", "/memberships", map[string]any{ "collaborationIdentifier": collabID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - json.Unmarshal(rec2.Body.Bytes(), &memResp) + json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1()) memID := memResp["membership"].(map[string]any)["id"].(string) rec3 := doRequest(t, e, "POST", "/configuredTables", map[string]any{ @@ -36,7 +38,7 @@ func TestConfiguredTableAssociations_Handlers(t *testing.T) { "allowedColumns": []string{"id"}, "analysisMethod": "DIRECT_QUERY", }) var ctResp map[string]any - json.Unmarshal(rec3.Body.Bytes(), &ctResp) + json.Unmarshal(rec3.Body.Bytes(), &ctResp, jsonx.V1()) ctID := ctResp["configuredTable"].(map[string]any)["id"].(string) tests := []struct { diff --git a/services/cleanrooms/handler_configured_tables.go b/services/cleanrooms/handler_configured_tables.go index 61474b68ad..990d126101 100644 --- a/services/cleanrooms/handler_configured_tables.go +++ b/services/cleanrooms/handler_configured_tables.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateConfiguredTable(_ context.Context, body []byte) ([]byte, error) { @@ -18,7 +20,7 @@ func (h *Handler) handleCreateConfiguredTable(_ context.Context, body []byte) ([ AllowedColumns []string `json:"allowedColumns"` SelectedMethods []string `json:"selectedAnalysisMethods"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := firstErr( checkEnum("analysisMethod", crtypes.AnalysisMethod(req.AnalysisMethod)), @@ -47,7 +49,7 @@ func (h *Handler) handleGetConfiguredTable(_ context.Context, body []byte) ([]by var req struct { ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) ct, err := h.Backend.GetConfiguredTable(req.ConfiguredTableIdentifier) if err != nil { return nil, err @@ -76,7 +78,7 @@ func (h *Handler) handleUpdateConfiguredTable(_ context.Context, body []byte) ([ Description string `json:"description"` SelectedMethods []string `json:"selectedAnalysisMethods"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) ct, err := h.Backend.UpdateConfiguredTable( req.ConfiguredTableIdentifier, req.Name, @@ -94,7 +96,7 @@ func (h *Handler) handleDeleteConfiguredTable(_ context.Context, body []byte) ([ var req struct { ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteConfiguredTable(req.ConfiguredTableIdentifier) } @@ -108,7 +110,7 @@ func (h *Handler) handleCreateConfiguredTableAnalysisRule( ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAnalysisRuleType(req.AnalysisRuleType), @@ -136,7 +138,7 @@ func (h *Handler) handleGetConfiguredTableAnalysisRule( ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAnalysisRuleType(req.AnalysisRuleType), @@ -164,7 +166,7 @@ func (h *Handler) handleUpdateConfiguredTableAnalysisRule( ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAnalysisRuleType(req.AnalysisRuleType), @@ -192,7 +194,7 @@ func (h *Handler) handleDeleteConfiguredTableAnalysisRule( ConfiguredTableIdentifier string `json:"configuredTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.ConfiguredTableAnalysisRuleType(req.AnalysisRuleType), diff --git a/services/cleanrooms/handler_configured_tables_test.go b/services/cleanrooms/handler_configured_tables_test.go index a373d17b35..8f63564886 100644 --- a/services/cleanrooms/handler_configured_tables_test.go +++ b/services/cleanrooms/handler_configured_tables_test.go @@ -1,12 +1,14 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConfiguredTables_Handlers(t *testing.T) { @@ -23,7 +25,7 @@ func TestConfiguredTables_Handlers(t *testing.T) { require.Equal(t, http.StatusOK, res.Code) var ctResp map[string]any - json.Unmarshal(res.Body.Bytes(), &ctResp) + json.Unmarshal(res.Body.Bytes(), &ctResp, jsonx.V1()) ct := ctResp["configuredTable"].(map[string]any) id := ct["id"].(string) diff --git a/services/cleanrooms/handler_id_mapping_tables.go b/services/cleanrooms/handler_id_mapping_tables.go index eb47201eb7..2e56499935 100644 --- a/services/cleanrooms/handler_id_mapping_tables.go +++ b/services/cleanrooms/handler_id_mapping_tables.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateIDMappingTable(_ context.Context, body []byte) ([]byte, error) { @@ -17,7 +19,7 @@ func (h *Handler) handleCreateIDMappingTable(_ context.Context, body []byte) ([] Description string `json:"description"` KmsKeyArn string `json:"kmsKeyArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.CreateIDMappingTable( req.MembershipIdentifier, req.Name, @@ -38,7 +40,7 @@ func (h *Handler) handleGetIDMappingTable(_ context.Context, body []byte) ([]byt MembershipIdentifier string `json:"membershipIdentifier"` IDMappingTableIdentifier string `json:"idMappingTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.GetIDMappingTable(req.MembershipIdentifier, req.IDMappingTableIdentifier) if err != nil { return nil, err @@ -55,7 +57,7 @@ func (h *Handler) handleListIDMappingTables( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListIDMappingTables( req.MembershipIdentifier, qp(c, "maxResults"), @@ -79,7 +81,7 @@ func (h *Handler) handleUpdateIDMappingTable(_ context.Context, body []byte) ([] Description string `json:"description"` KmsKeyArn string `json:"kmsKeyArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.UpdateIDMappingTable( req.MembershipIdentifier, req.IDMappingTableIdentifier, @@ -98,7 +100,7 @@ func (h *Handler) handleDeleteIDMappingTable(_ context.Context, body []byte) ([] MembershipIdentifier string `json:"membershipIdentifier"` IDMappingTableIdentifier string `json:"idMappingTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteIDMappingTable( req.MembershipIdentifier, @@ -112,7 +114,7 @@ func (h *Handler) handlePopulateIDMappingTable(_ context.Context, body []byte) ( IDMappingTableIdentifier string `json:"idMappingTableIdentifier"` JobType string `json:"jobType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch req.JobType { case "", "INCREMENTAL", "BATCH", "DELETE_ONLY": diff --git a/services/cleanrooms/handler_id_namespace_associations.go b/services/cleanrooms/handler_id_namespace_associations.go index 2f678df1ff..ffde89ac6f 100644 --- a/services/cleanrooms/handler_id_namespace_associations.go +++ b/services/cleanrooms/handler_id_namespace_associations.go @@ -2,9 +2,11 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetCollaborationIDNamespaceAssociation( @@ -15,7 +17,7 @@ func (h *Handler) handleGetCollaborationIDNamespaceAssociation( CollaborationIdentifier string `json:"collaborationIdentifier"` IDNamespaceAssociationIdentifier string `json:"idNamespaceAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.GetCollaborationIDNamespaceAssociation( req.CollaborationIdentifier, req.IDNamespaceAssociationIdentifier, @@ -35,7 +37,7 @@ func (h *Handler) handleListCollaborationIDNamespaceAssociations( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationIDNamespaceAssociations( req.CollaborationIdentifier, qp(c, "maxResults"), @@ -64,7 +66,7 @@ func (h *Handler) handleCreateIDNamespaceAssociation( Name string `json:"name"` Description string `json:"description"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.CreateIDNamespaceAssociation( req.MembershipIdentifier, req.Name, @@ -85,7 +87,7 @@ func (h *Handler) handleGetIDNamespaceAssociation(_ context.Context, body []byte MembershipIdentifier string `json:"membershipIdentifier"` IDNamespaceAssociationIdentifier string `json:"idNamespaceAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.GetIDNamespaceAssociation( req.MembershipIdentifier, req.IDNamespaceAssociationIdentifier, @@ -105,7 +107,7 @@ func (h *Handler) handleListIDNamespaceAssociations( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListIDNamespaceAssociations( req.MembershipIdentifier, qp(c, "maxResults"), @@ -132,7 +134,7 @@ func (h *Handler) handleUpdateIDNamespaceAssociation( IDNamespaceAssociationIdentifier string `json:"idNamespaceAssociationIdentifier"` Description string `json:"description"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) a, err := h.Backend.UpdateIDNamespaceAssociation( req.MembershipIdentifier, req.IDNamespaceAssociationIdentifier, @@ -154,7 +156,7 @@ func (h *Handler) handleDeleteIDNamespaceAssociation( MembershipIdentifier string `json:"membershipIdentifier"` IDNamespaceAssociationIdentifier string `json:"idNamespaceAssociationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteIDNamespaceAssociation( req.MembershipIdentifier, diff --git a/services/cleanrooms/handler_intermediate_tables.go b/services/cleanrooms/handler_intermediate_tables.go index 974457f51a..d8c6e384a3 100644 --- a/services/cleanrooms/handler_intermediate_tables.go +++ b/services/cleanrooms/handler_intermediate_tables.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateIntermediateTable(_ context.Context, body []byte) ([]byte, error) { @@ -18,7 +20,7 @@ func (h *Handler) handleCreateIntermediateTable(_ context.Context, body []byte) KmsKeyArn string `json:"kmsKeyArn"` RetentionInDays int32 `json:"retentionInDays"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) it, err := h.Backend.CreateIntermediateTable( req.MembershipIdentifier, req.Name, @@ -40,7 +42,7 @@ func (h *Handler) handleGetIntermediateTable(_ context.Context, body []byte) ([] MembershipIdentifier string `json:"membershipIdentifier"` IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) it, err := h.Backend.GetIntermediateTable(req.MembershipIdentifier, req.IntermediateTableIdentifier) if err != nil { return nil, err @@ -57,7 +59,7 @@ func (h *Handler) handleListIntermediateTables( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListIntermediateTables( req.MembershipIdentifier, qp(c, "maxResults"), @@ -81,7 +83,7 @@ func (h *Handler) handleUpdateIntermediateTable(_ context.Context, body []byte) Description string `json:"description"` KmsKeyArn string `json:"kmsKeyArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) it, err := h.Backend.UpdateIntermediateTable( req.MembershipIdentifier, req.IntermediateTableIdentifier, @@ -100,7 +102,7 @@ func (h *Handler) handleDeleteIntermediateTable(_ context.Context, body []byte) MembershipIdentifier string `json:"membershipIdentifier"` IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteIntermediateTable(req.MembershipIdentifier, req.IntermediateTableIdentifier) } @@ -114,7 +116,7 @@ func (h *Handler) handleListIntermediateTableVersions( MembershipIdentifier string `json:"membershipIdentifier"` IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListIntermediateTableVersions( req.MembershipIdentifier, req.IntermediateTableIdentifier, @@ -143,7 +145,7 @@ func (h *Handler) handlePopulateIntermediateTable(_ context.Context, body []byte IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` AnalysisPayerAccountID string `json:"analysisPayerAccountId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) result, err := h.Backend.PopulateIntermediateTable( req.MembershipIdentifier, req.IntermediateTableIdentifier, @@ -164,7 +166,7 @@ func (h *Handler) handleDisallowIntermediateTable(_ context.Context, body []byte MembershipIdentifier string `json:"membershipIdentifier"` IntermediateTableName string `json:"intermediateTableName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) // IncludeDescendants defaults to true on the wire (see // DisallowIntermediateTableInput's doc comment in the real SDK). includeDescendants := true @@ -184,7 +186,7 @@ func (h *Handler) handleCreateIntermediateTableAnalysisRule(_ context.Context, b IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.IntermediateTableAnalysisRuleType(req.AnalysisRuleType), @@ -211,7 +213,7 @@ func (h *Handler) handleGetIntermediateTableAnalysisRule(_ context.Context, body IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) r, err := h.Backend.GetIntermediateTableAnalysisRule( req.MembershipIdentifier, req.IntermediateTableIdentifier, @@ -231,7 +233,7 @@ func (h *Handler) handleUpdateIntermediateTableAnalysisRule(_ context.Context, b IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.IntermediateTableAnalysisRuleType(req.AnalysisRuleType), @@ -258,7 +260,7 @@ func (h *Handler) handleDeleteIntermediateTableAnalysisRule(_ context.Context, b IntermediateTableIdentifier string `json:"intermediateTableIdentifier"` AnalysisRuleType string `json:"analysisRuleType"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum( "analysisRuleType", crtypes.IntermediateTableAnalysisRuleType(req.AnalysisRuleType), diff --git a/services/cleanrooms/handler_memberships.go b/services/cleanrooms/handler_memberships.go index ee5184e3f4..c1e5c3283f 100644 --- a/services/cleanrooms/handler_memberships.go +++ b/services/cleanrooms/handler_memberships.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateMembership(_ context.Context, body []byte) ([]byte, error) { @@ -20,7 +22,7 @@ func (h *Handler) handleCreateMembership(_ context.Context, body []byte) ([]byte MemberAbilities []string `json:"memberAbilities"` IsMetricsEnabled bool `json:"isMetricsEnabled"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := firstErr( checkEnum("queryLogStatus", crtypes.MembershipQueryLogStatus(req.QueryLogStatus)), checkEnum("jobLogStatus", crtypes.MembershipJobLogStatus(req.JobLogStatus)), @@ -50,7 +52,7 @@ func (h *Handler) handleGetMembership(_ context.Context, body []byte) ([]byte, e var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) m, err := h.Backend.GetMembership(req.MembershipIdentifier) if err != nil { return nil, err @@ -84,7 +86,7 @@ func (h *Handler) handleUpdateMembership(_ context.Context, body []byte) ([]byte QueryLogStatus string `json:"queryLogStatus"` JobLogStatus string `json:"jobLogStatus"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := firstErr( checkEnum("jobLogStatus", crtypes.MembershipJobLogStatus(req.JobLogStatus)), checkEnum("queryLogStatus", crtypes.MembershipQueryLogStatus(req.QueryLogStatus)), @@ -110,7 +112,7 @@ func (h *Handler) handleDeleteMembership(_ context.Context, body []byte) ([]byte var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeleteMembership(req.MembershipIdentifier) } diff --git a/services/cleanrooms/handler_privacy_budgets.go b/services/cleanrooms/handler_privacy_budgets.go index 00d6f523d1..dcd7b5108b 100644 --- a/services/cleanrooms/handler_privacy_budgets.go +++ b/services/cleanrooms/handler_privacy_budgets.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetCollaborationPrivacyBudgetTemplate( @@ -16,7 +18,7 @@ func (h *Handler) handleGetCollaborationPrivacyBudgetTemplate( CollaborationIdentifier string `json:"collaborationIdentifier"` PrivacyBudgetTemplateIdentifier string `json:"privacyBudgetTemplateIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.GetCollaborationPrivacyBudgetTemplate( req.CollaborationIdentifier, req.PrivacyBudgetTemplateIdentifier, @@ -36,7 +38,7 @@ func (h *Handler) handleListCollaborationPrivacyBudgetTemplates( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationPrivacyBudgetTemplates( req.CollaborationIdentifier, qp(c, "maxResults"), @@ -61,7 +63,7 @@ func (h *Handler) handleListCollaborationPrivacyBudgets( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListCollaborationPrivacyBudgets( req.CollaborationIdentifier, qp(c, "privacyBudgetType"), @@ -90,7 +92,7 @@ func (h *Handler) handleCreatePrivacyBudgetTemplate( PrivacyBudgetType string `json:"privacyBudgetType"` AutoRefresh string `json:"autoRefresh"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum("privacyBudgetType", crtypes.PrivacyBudgetType(req.PrivacyBudgetType)); err != nil { return nil, err } @@ -114,7 +116,7 @@ func (h *Handler) handleGetPrivacyBudgetTemplate(_ context.Context, body []byte) MembershipIdentifier string `json:"membershipIdentifier"` PrivacyBudgetTemplateIdentifier string `json:"privacyBudgetTemplateIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.GetPrivacyBudgetTemplate( req.MembershipIdentifier, req.PrivacyBudgetTemplateIdentifier, @@ -134,7 +136,7 @@ func (h *Handler) handleListPrivacyBudgetTemplates( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListPrivacyBudgetTemplates( req.MembershipIdentifier, qp(c, "privacyBudgetType"), @@ -162,7 +164,7 @@ func (h *Handler) handleUpdatePrivacyBudgetTemplate( PrivacyBudgetTemplateIdentifier string `json:"privacyBudgetTemplateIdentifier"` AutoRefresh string `json:"autoRefresh"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) t, err := h.Backend.UpdatePrivacyBudgetTemplate( req.MembershipIdentifier, req.PrivacyBudgetTemplateIdentifier, @@ -184,7 +186,7 @@ func (h *Handler) handleDeletePrivacyBudgetTemplate( MembershipIdentifier string `json:"membershipIdentifier"` PrivacyBudgetTemplateIdentifier string `json:"privacyBudgetTemplateIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.DeletePrivacyBudgetTemplate( req.MembershipIdentifier, @@ -200,7 +202,7 @@ func (h *Handler) handleListPrivacyBudgets( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListPrivacyBudgets( req.MembershipIdentifier, qp(c, "privacyBudgetType"), @@ -223,7 +225,7 @@ func (h *Handler) handlePreviewPrivacyImpact(_ context.Context, body []byte) ([] Parameters map[string]any `json:"parameters"` MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) result, err := h.Backend.PreviewPrivacyImpact(req.MembershipIdentifier, req.Parameters) if err != nil { return nil, err diff --git a/services/cleanrooms/handler_protected_jobs.go b/services/cleanrooms/handler_protected_jobs.go index 7001ced7f6..88a64fe9a8 100644 --- a/services/cleanrooms/handler_protected_jobs.go +++ b/services/cleanrooms/handler_protected_jobs.go @@ -2,9 +2,11 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // protectedJobWire shadows the request-only "type" key; types.ProtectedJob has no type member. @@ -23,7 +25,7 @@ func (h *Handler) handleStartProtectedJob(_ context.Context, body []byte) ([]byt Type string `json:"type"` PayerAccountID string `json:"jobComputePayerAccountId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) j, err := h.Backend.StartProtectedJob( req.MembershipIdentifier, req.Type, @@ -43,7 +45,7 @@ func (h *Handler) handleGetProtectedJob(_ context.Context, body []byte) ([]byte, MembershipIdentifier string `json:"membershipIdentifier"` ProtectedJobIdentifier string `json:"protectedJobIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) j, err := h.Backend.GetProtectedJob(req.MembershipIdentifier, req.ProtectedJobIdentifier) if err != nil { return nil, err @@ -60,7 +62,7 @@ func (h *Handler) handleListProtectedJobs( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListProtectedJobs( req.MembershipIdentifier, qp(c, "status"), @@ -84,7 +86,7 @@ func (h *Handler) handleUpdateProtectedJob(_ context.Context, body []byte) ([]by ProtectedJobIdentifier string `json:"protectedJobIdentifier"` TargetStatus string `json:"targetStatus"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) j, err := h.Backend.UpdateProtectedJob( req.MembershipIdentifier, req.ProtectedJobIdentifier, diff --git a/services/cleanrooms/handler_protected_jobs_test.go b/services/cleanrooms/handler_protected_jobs_test.go index 68ea59ca33..5f628c6a71 100644 --- a/services/cleanrooms/handler_protected_jobs_test.go +++ b/services/cleanrooms/handler_protected_jobs_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestProtectedJobs_Handlers(t *testing.T) { @@ -20,14 +22,14 @@ func TestProtectedJobs_Handlers(t *testing.T) { "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collabID := colResp["collaboration"].(map[string]any)["id"].(string) rec2 := doRequest(t, e, "POST", "/memberships", map[string]any{ "collaborationIdentifier": collabID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - json.Unmarshal(rec2.Body.Bytes(), &memResp) + json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1()) memID := memResp["membership"].(map[string]any)["id"].(string) tests := []struct { diff --git a/services/cleanrooms/handler_protected_queries.go b/services/cleanrooms/handler_protected_queries.go index c85a1d324c..b6c0d1883a 100644 --- a/services/cleanrooms/handler_protected_queries.go +++ b/services/cleanrooms/handler_protected_queries.go @@ -2,9 +2,11 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleStartProtectedQuery(_ context.Context, body []byte) ([]byte, error) { @@ -16,7 +18,7 @@ func (h *Handler) handleStartProtectedQuery(_ context.Context, body []byte) ([]b MembershipIdentifier string `json:"membershipIdentifier"` PayerAccountID string `json:"queryComputePayerAccountId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := validateProtectedType(req.Type); err != nil { return nil, err @@ -41,7 +43,7 @@ func (h *Handler) handleGetProtectedQuery(_ context.Context, body []byte) ([]byt MembershipIdentifier string `json:"membershipIdentifier"` ProtectedQueryIdentifier string `json:"protectedQueryIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) q, err := h.Backend.GetProtectedQuery(req.MembershipIdentifier, req.ProtectedQueryIdentifier) if err != nil { return nil, err @@ -58,7 +60,7 @@ func (h *Handler) handleListProtectedQueries( var req struct { MembershipIdentifier string `json:"membershipIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListProtectedQueries( req.MembershipIdentifier, qp(c, "status"), @@ -82,7 +84,7 @@ func (h *Handler) handleUpdateProtectedQuery(_ context.Context, body []byte) ([] ProtectedQueryIdentifier string `json:"protectedQueryIdentifier"` TargetStatus string `json:"targetStatus"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := validateTargetStatus(req.TargetStatus); err != nil { return nil, err diff --git a/services/cleanrooms/handler_routing.go b/services/cleanrooms/handler_routing.go index 50ab501e41..0fde29aeb3 100644 --- a/services/cleanrooms/handler_routing.go +++ b/services/cleanrooms/handler_routing.go @@ -1,9 +1,12 @@ package cleanrooms import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // classifyPath maps (method, path) to an operation name and primary resource. @@ -643,17 +646,17 @@ func injectPathParams(path, _ string, body []byte) []byte { path = strings.TrimPrefix(path, "/") segs := strings.Split(path, "/") - var m map[string]json.RawMessage + var m map[string]jsontext.Value if len(body) > 0 { - _ = json.Unmarshal(body, &m) + _ = json.Unmarshal(body, &m, jsonx.V1()) } if m == nil { - m = make(map[string]json.RawMessage) + m = make(map[string]jsontext.Value) } setStr := func(key, val string) { if val != "" { - b, _ := json.Marshal(val) + b, _ := json.Marshal(val, jsonx.V1()) m[key] = b } } @@ -673,7 +676,7 @@ func injectPathParams(path, _ string, body []byte) []byte { setStr("resourceArn", arnVal) } - out, _ := json.Marshal(m) + out, _ := json.Marshal(m, jsonx.V1()) return out } diff --git a/services/cleanrooms/handler_schemas.go b/services/cleanrooms/handler_schemas.go index 9921c283c3..c4f5226da6 100644 --- a/services/cleanrooms/handler_schemas.go +++ b/services/cleanrooms/handler_schemas.go @@ -2,10 +2,12 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" crtypes "github.com/aws/aws-sdk-go-v2/service/cleanrooms/types" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleBatchGetSchema(_ context.Context, body []byte) ([]byte, error) { @@ -13,7 +15,7 @@ func (h *Handler) handleBatchGetSchema(_ context.Context, body []byte) ([]byte, CollaborationIdentifier string `json:"collaborationIdentifier"` Names []string `json:"names"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, errs, err := h.Backend.BatchGetSchema(req.CollaborationIdentifier, req.Names) if err != nil { return nil, err @@ -30,7 +32,7 @@ func (h *Handler) handleBatchGetSchemaAnalysisRule(_ context.Context, body []byt Type string `json:"type"` } `json:"schemaAnalysisRuleRequests"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) names := make([]string, 0, len(req.SchemaAnalysisRuleRequests)) var ruleType string for _, r := range req.SchemaAnalysisRuleRequests { @@ -56,7 +58,7 @@ func (h *Handler) handleGetSchema(_ context.Context, body []byte) ([]byte, error CollaborationIdentifier string `json:"collaborationIdentifier"` Name string `json:"name"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) s, err := h.Backend.GetSchema(req.CollaborationIdentifier, req.Name) if err != nil { return nil, err @@ -73,7 +75,7 @@ func (h *Handler) handleListSchemas( var req struct { CollaborationIdentifier string `json:"collaborationIdentifier"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) items, next, err := h.Backend.ListSchemas( req.CollaborationIdentifier, qp(c, "schemaType"), @@ -97,7 +99,7 @@ func (h *Handler) handleGetSchemaAnalysisRule(_ context.Context, body []byte) ([ Name string `json:"name"` Type string `json:"type"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := checkEnum("type", crtypes.AnalysisRuleType(req.Type)); err != nil { return nil, err } diff --git a/services/cleanrooms/handler_schemas_test.go b/services/cleanrooms/handler_schemas_test.go index d15490b244..5bb05ed47d 100644 --- a/services/cleanrooms/handler_schemas_test.go +++ b/services/cleanrooms/handler_schemas_test.go @@ -1,12 +1,14 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSchemas_Handlers(t *testing.T) { @@ -23,7 +25,7 @@ func TestSchemas_Handlers(t *testing.T) { require.Equal(t, http.StatusOK, res.Code) var colResp map[string]any - json.Unmarshal(res.Body.Bytes(), &colResp) + json.Unmarshal(res.Body.Bytes(), &colResp, jsonx.V1()) collab := colResp["collaboration"].(map[string]any) collabID := collab["id"].(string) diff --git a/services/cleanrooms/handler_tags.go b/services/cleanrooms/handler_tags.go index e259526472..44ceb5a767 100644 --- a/services/cleanrooms/handler_tags.go +++ b/services/cleanrooms/handler_tags.go @@ -2,16 +2,18 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]byte, error) { var req struct { ResourceArn string `json:"resourceArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) tags, err := h.Backend.ListTagsForResource(req.ResourceArn) if err != nil { return nil, err @@ -25,7 +27,7 @@ func (h *Handler) handleTagResource(_ context.Context, body []byte) ([]byte, err Tags map[string]string `json:"tags"` ResourceArn string `json:"resourceArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return nil, h.Backend.TagResource(req.ResourceArn, req.Tags) } @@ -39,7 +41,7 @@ func (h *Handler) handleUntagResource( ResourceArn string `json:"resourceArn"` TagKeys []string `json:"tagKeys"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) // tagKeys can also come from query params if len(req.TagKeys) == 0 { req.TagKeys = c.Request().URL.Query()["tagKeys"] diff --git a/services/cleanrooms/handler_test.go b/services/cleanrooms/handler_test.go index 11206a13ad..f3e8ae551e 100644 --- a/services/cleanrooms/handler_test.go +++ b/services/cleanrooms/handler_test.go @@ -2,13 +2,15 @@ package cleanrooms_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cleanrooms" ) @@ -32,7 +34,7 @@ func doRequest( var reqBody []byte if body != nil { var err error - reqBody, err = json.Marshal(body) + reqBody, err = json.Marshal(body, jsonx.V1()) if err != nil { t.Fatalf("marshal request: %v", err) } @@ -65,7 +67,7 @@ func TestCollaborationCRUD(t *testing.T) { t.Fatalf("create: status %d want %d: %s", rec.Code, http.StatusOK, rec.Body.String()) } var createResp map[string]any - if err := json.NewDecoder(rec.Body).Decode(&createResp); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1()); err != nil { t.Fatalf("decode create: %v", err) } if _, ok := createResp["collaboration"]; !ok { @@ -79,7 +81,7 @@ func TestCollaborationCRUD(t *testing.T) { t.Fatalf("list: status %d want %d: %s", rec.Code, http.StatusOK, rec.Body.String()) } var listResp map[string]any - if err := json.NewDecoder(rec.Body).Decode(&listResp); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1()); err != nil { t.Fatalf("decode list: %v", err) } if _, ok := listResp["collaborationList"]; !ok { @@ -126,7 +128,7 @@ func TestConfiguredTableCRUD(t *testing.T) { t.Fatalf("create: status %d want %d: %s", rec.Code, http.StatusOK, rec.Body.String()) } var createResp map[string]any - if err := json.NewDecoder(rec.Body).Decode(&createResp); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1()); err != nil { t.Fatalf("decode: %v", err) } ctID := createResp["configuredTable"].(map[string]any)["id"].(string) @@ -173,7 +175,7 @@ func TestMembershipCRUD(t *testing.T) { t.Fatalf("create collab: %s", colRec.Body.String()) } var colResp map[string]any - _ = json.NewDecoder(colRec.Body).Decode(&colResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(colRec.Body), &colResp, jsonx.V1()) colID := colResp["collaboration"].(map[string]any)["id"].(string) createBody := map[string]any{ @@ -187,7 +189,7 @@ func TestMembershipCRUD(t *testing.T) { t.Fatalf("create: status %d want %d: %s", rec.Code, http.StatusOK, rec.Body.String()) } var createResp map[string]any - _ = json.NewDecoder(rec.Body).Decode(&createResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1()) mID := createResp["membership"].(map[string]any)["id"].(string) // List memberships @@ -223,7 +225,7 @@ func TestTagOperations(t *testing.T) { "creatorMemberAbilities": []string{}, "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - _ = json.NewDecoder(colRec.Body).Decode(&colResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(colRec.Body), &colResp, jsonx.V1()) testARN := colResp["collaboration"].(map[string]any)["arn"].(string) // Tag resource @@ -293,14 +295,14 @@ func TestProtectedQueryLifecycle(t *testing.T) { "creatorMemberAbilities": []string{}, "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - _ = json.NewDecoder(colRec.Body).Decode(&colResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(colRec.Body), &colResp, jsonx.V1()) colID := colResp["collaboration"].(map[string]any)["id"].(string) // Create membership memRec := doRequest(t, e, http.MethodPost, "/memberships", map[string]any{"collaborationIdentifier": colID, "queryLogStatus": "DISABLED"}) var memResp map[string]any - _ = json.NewDecoder(memRec.Body).Decode(&memResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(memRec.Body), &memResp, jsonx.V1()) mID := memResp["membership"].(map[string]any)["id"].(string) // Start protected query @@ -313,7 +315,7 @@ func TestProtectedQueryLifecycle(t *testing.T) { t.Fatalf("start query: status %d: %s", rec.Code, rec.Body.String()) } var resp map[string]any - _ = json.NewDecoder(rec.Body).Decode(&resp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1()) if _, ok := resp["protectedQuery"]; !ok { t.Fatal("missing protectedQuery in response") } diff --git a/services/cleanrooms/id_mapping_tables_test.go b/services/cleanrooms/id_mapping_tables_test.go index b06b8f5950..5aad3ecbed 100644 --- a/services/cleanrooms/id_mapping_tables_test.go +++ b/services/cleanrooms/id_mapping_tables_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHTTP_PopulateIDMappingTable(t *testing.T) { @@ -22,7 +24,7 @@ func TestHTTP_PopulateIDMappingTable(t *testing.T) { }) rec := doRequest(t, e, "GET", "/collaborations", nil) var colResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp, jsonx.V1())) colID := colResp["collaborationList"].([]any)[0].(map[string]any)["id"].(string) // Create Membership @@ -30,7 +32,7 @@ func TestHTTP_PopulateIDMappingTable(t *testing.T) { "collaborationIdentifier": colID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1())) mID := memResp["membership"].(map[string]any)["id"].(string) // Create Table @@ -44,7 +46,7 @@ func TestHTTP_PopulateIDMappingTable(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["idMappingTable"]["id"].(string) // Populate @@ -68,7 +70,7 @@ func createIntermediateTable(t *testing.T, e *echo.Echo, mID string) string { }) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["intermediateTable"].(map[string]any)["id"].(string) } @@ -220,7 +222,7 @@ func TestHTTP_UpdateIntermediateTable(t *testing.T) { map[string]any{"description": "updated"}) require.Equal(t, http.StatusOK, updRec.Code, updRec.Body.String()) var updResp map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) assert.Equal(t, "updated", updResp["intermediateTable"].(map[string]any)["description"]) }) } @@ -268,7 +270,7 @@ func TestHTTP_UpdateIntermediateTableAnalysisRule(t *testing.T) { "/memberships/"+mID+"/intermediateTables/"+tableID+"/analysisRule/CUSTOM", nil) require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rule := getResp["analysisRule"].(map[string]any) policy := rule["analysisRulePolicy"].(map[string]any) v1 := policy["v1"].(map[string]any) @@ -312,7 +314,7 @@ func TestHTTP_PopulateIntermediateTable_AdvancesToSuccess(t *testing.T) { "/memberships/"+mID+"/intermediateTables/"+tableID+"/populate", nil) require.Equal(t, http.StatusOK, popRec.Code, popRec.Body.String()) var popResp map[string]any - require.NoError(t, json.Unmarshal(popRec.Body.Bytes(), &popResp)) + require.NoError(t, json.Unmarshal(popRec.Body.Bytes(), &popResp, jsonx.V1())) assert.Equal(t, tt.wants.analysisType, popResp["analysisType"]) assert.NotEmpty(t, popResp["analysisId"]) assert.NotEmpty(t, popResp["versionId"]) @@ -321,7 +323,7 @@ func TestHTTP_PopulateIntermediateTable_AdvancesToSuccess(t *testing.T) { "/memberships/"+mID+"/intermediateTables/"+tableID, nil) require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) it := getResp["intermediateTable"].(map[string]any) assert.Equal(t, "POPULATE_SUCCESS", it["status"]) assert.Contains(t, it, "intermediateTableVersion") @@ -332,7 +334,7 @@ func TestHTTP_PopulateIntermediateTable_AdvancesToSuccess(t *testing.T) { "/memberships/"+mID+"/intermediateTables/"+tableID+"/versions", nil) require.Equal(t, http.StatusOK, verRec.Code, verRec.Body.String()) var verResp map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp, jsonx.V1())) versions, ok := verResp["intermediateTableVersionSummaries"].([]any) require.True(t, ok) require.Len(t, versions, 1) @@ -429,7 +431,7 @@ func TestIntermediateTables_WireShape(t *testing.T) { getRec := doRequest(t, e, http.MethodGet, "/memberships/"+mID+"/intermediateTables/"+tableID, nil) require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) it := getResp["intermediateTable"].(map[string]any) assert.Contains(t, it, "id") assert.NotContains(t, it, "intermediateTableIdentifier") @@ -446,7 +448,7 @@ func TestIntermediateTables_WireShape(t *testing.T) { }) require.Equal(t, http.StatusOK, ruleRec.Code, ruleRec.Body.String()) var ruleResp map[string]any - require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp)) + require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp, jsonx.V1())) rule := ruleResp["analysisRule"].(map[string]any) assert.Contains(t, rule, "intermediateTableIdentifier") assert.Equal(t, tableID, rule["intermediateTableIdentifier"]) diff --git a/services/cleanrooms/id_namespace_associations_test.go b/services/cleanrooms/id_namespace_associations_test.go index 69e1698f56..b06227e2e3 100644 --- a/services/cleanrooms/id_namespace_associations_test.go +++ b/services/cleanrooms/id_namespace_associations_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createIDNamespaceAssociation(t *testing.T, e *echo.Echo, mID string) string { @@ -21,7 +23,7 @@ func createIDNamespaceAssociation(t *testing.T, e *echo.Echo, mID string) string }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) return createResp["idNamespaceAssociation"]["id"].(string) } diff --git a/services/cleanrooms/list_configured_table_associations_rule_types_test.go b/services/cleanrooms/list_configured_table_associations_rule_types_test.go index 497cab0016..007278aff2 100644 --- a/services/cleanrooms/list_configured_table_associations_rule_types_test.go +++ b/services/cleanrooms/list_configured_table_associations_rule_types_test.go @@ -1,11 +1,13 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListConfiguredTableAssociations_SummaryHasAnalysisRuleTypes proves @@ -30,14 +32,14 @@ func TestListConfiguredTableAssociations_SummaryHasAnalysisRuleTypes(t *testing. "members": []any{}, "queryLogStatus": "DISABLED", }) var colResp map[string]any - require.NoError(t, json.Unmarshal(colRec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(colRec.Body.Bytes(), &colResp, jsonx.V1())) collabID := colResp["collaboration"].(map[string]any)["id"].(string) memRec := doRequest(t, e, http.MethodPost, "/memberships", map[string]any{ "collaborationIdentifier": collabID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &memResp, jsonx.V1())) memID := memResp["membership"].(map[string]any)["id"].(string) ctRec := doRequest(t, e, http.MethodPost, "/configuredTables", map[string]any{ @@ -46,7 +48,7 @@ func TestListConfiguredTableAssociations_SummaryHasAnalysisRuleTypes(t *testing. "allowedColumns": []string{"id"}, "analysisMethod": "DIRECT_QUERY", }) var ctResp map[string]any - require.NoError(t, json.Unmarshal(ctRec.Body.Bytes(), &ctResp)) + require.NoError(t, json.Unmarshal(ctRec.Body.Bytes(), &ctResp, jsonx.V1())) ctID := ctResp["configuredTable"].(map[string]any)["id"].(string) ctaRec := doRequest(t, e, http.MethodPost, "/memberships/"+memID+"/configuredTableAssociations", map[string]any{ @@ -56,7 +58,7 @@ func TestListConfiguredTableAssociations_SummaryHasAnalysisRuleTypes(t *testing. }) require.Equal(t, http.StatusOK, ctaRec.Code) var ctaResp map[string]any - require.NoError(t, json.Unmarshal(ctaRec.Body.Bytes(), &ctaResp)) + require.NoError(t, json.Unmarshal(ctaRec.Body.Bytes(), &ctaResp, jsonx.V1())) ctaID := ctaResp["configuredTableAssociation"].(map[string]any)["id"].(string) ruleRec := doRequest( @@ -74,7 +76,7 @@ func TestListConfiguredTableAssociations_SummaryHasAnalysisRuleTypes(t *testing. var listResp struct { Summaries []map[string]any `json:"configuredTableAssociationSummaries"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Summaries, 1) ruleTypes, ok := listResp.Summaries[0]["analysisRuleTypes"].([]any) diff --git a/services/cleanrooms/memberships_test.go b/services/cleanrooms/memberships_test.go index 57048cce96..6ffc75e8e9 100644 --- a/services/cleanrooms/memberships_test.go +++ b/services/cleanrooms/memberships_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createMembership(t *testing.T, e *echo.Echo, colID string, queryLog string) (map[string]any, string) { @@ -19,7 +21,7 @@ func createMembership(t *testing.T, e *echo.Echo, colID string, queryLog string) }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mem := resp["membership"].(map[string]any) @@ -35,7 +37,7 @@ func createBaseCollaboration(t *testing.T, e *echo.Echo, name string) string { }) var colResp map[string]any rec := doRequest(t, e, "GET", "/collaborations", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp, jsonx.V1())) return colResp["collaborationList"].([]any)[0].(map[string]any)["id"].(string) } @@ -135,7 +137,7 @@ func TestMemberships_List(t *testing.T) { require.Equal(t, tt.wants.status, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["membershipSummaries"].([]any) require.Len(t, summaries, 2) summary := summaries[0].(map[string]any) diff --git a/services/cleanrooms/overwide_collaboration_list_test.go b/services/cleanrooms/overwide_collaboration_list_test.go index 9787ac3cac..0be703eeff 100644 --- a/services/cleanrooms/overwide_collaboration_list_test.go +++ b/services/cleanrooms/overwide_collaboration_list_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestOverWideCollaborationListResponses asserts on the raw response body @@ -270,7 +272,7 @@ func TestOverWideCollaborationListResponses(t *testing.T) { body := tt.raw(t) var decoded map[string]any - require.NoError(t, json.Unmarshal(body, &decoded)) + require.NoError(t, json.Unmarshal(body, &decoded, jsonx.V1())) rawItems, ok := decoded[tt.itemsKey] require.True(t, ok, "missing items key %q in response: %s", tt.itemsKey, body) @@ -309,7 +311,7 @@ func createCleanroomsCollabAndMembership(t *testing.T, e *echo.Echo) (string, st }) require.Equal(t, http.StatusOK, colRec.Code) var colResp map[string]any - require.NoError(t, json.Unmarshal(colRec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(colRec.Body.Bytes(), &colResp, jsonx.V1())) collabID, _ := colResp["collaboration"].(map[string]any)["id"].(string) require.NotEmpty(t, collabID) @@ -318,7 +320,7 @@ func createCleanroomsCollabAndMembership(t *testing.T, e *echo.Echo) (string, st }) require.Equal(t, http.StatusOK, memRec.Code) var memResp map[string]any - require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &memResp, jsonx.V1())) memID, _ := memResp["membership"].(map[string]any)["id"].(string) require.NotEmpty(t, memID) diff --git a/services/cleanrooms/persistence.go b/services/cleanrooms/persistence.go index 9fd8936602..af0c8e1d7a 100644 --- a/services/cleanrooms/persistence.go +++ b/services/cleanrooms/persistence.go @@ -2,7 +2,7 @@ package cleanrooms import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -39,7 +39,7 @@ const cleanroomsSnapshotVersion = 1 // key on (see backend.go's InMemoryBackend field doc comment). It is // persisted directly here. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TagsByArn map[string]map[string]string `json:"tagsByArn"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/cleanrooms/privacy_budgets_test.go b/services/cleanrooms/privacy_budgets_test.go index 0549b4ce72..ddfd096c7b 100644 --- a/services/cleanrooms/privacy_budgets_test.go +++ b/services/cleanrooms/privacy_budgets_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // setupTestEnvironment is a helper to bootstrap a collaboration and membership @@ -22,7 +24,7 @@ func setupTestEnvironment(t *testing.T, e *echo.Echo) (string, string) { }) rec := doRequest(t, e, http.MethodGet, "/collaborations", nil) var colResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &colResp, jsonx.V1())) colID := colResp["collaborationList"].([]any)[0].(map[string]any)["id"].(string) // Create Membership @@ -30,7 +32,7 @@ func setupTestEnvironment(t *testing.T, e *echo.Echo) (string, string) { "collaborationIdentifier": colID, "queryLogStatus": "DISABLED", }) var memResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1())) mID := memResp["membership"].(map[string]any)["id"].(string) return colID, mID @@ -51,7 +53,7 @@ func createBudgetTemplate(t *testing.T, e *echo.Echo, mID string) string { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) return createResp["privacyBudgetTemplate"]["id"].(string) } @@ -319,7 +321,7 @@ func TestPreviewPrivacyImpact(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) impact := resp["privacyImpact"].(map[string]any)["differentialPrivacy"].(map[string]any) aggs := impact["aggregations"].([]any) require.Len(t, aggs, 5) @@ -342,7 +344,7 @@ func TestListPrivacyBudgets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) budgets := resp["privacyBudgetSummaries"].([]any) require.Len(t, budgets, 1) diff --git a/services/cleanrooms/protected_jobs_test.go b/services/cleanrooms/protected_jobs_test.go index 1f05a59dd8..9e9c6b91b1 100644 --- a/services/cleanrooms/protected_jobs_test.go +++ b/services/cleanrooms/protected_jobs_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createProtectedJob(t *testing.T, e *echo.Echo, mID string) string { @@ -21,7 +23,7 @@ func createProtectedJob(t *testing.T, e *echo.Echo, mID string) string { }) require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) return startResp["protectedJob"]["id"].(string) } diff --git a/services/cleanrooms/protected_queries_test.go b/services/cleanrooms/protected_queries_test.go index 6b33ec6d15..102bc037b7 100644 --- a/services/cleanrooms/protected_queries_test.go +++ b/services/cleanrooms/protected_queries_test.go @@ -1,13 +1,15 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createProtectedQuery(t *testing.T, e *echo.Echo, mID string) string { @@ -21,7 +23,7 @@ func createProtectedQuery(t *testing.T, e *echo.Echo, mID string) string { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) return startResp["protectedQuery"]["id"].(string) } @@ -63,7 +65,7 @@ func TestProtectedQueries_Create(t *testing.T) { require.Equal(t, tt.wants.status, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pq := resp["protectedQuery"].(map[string]any) assert.Equal(t, "SUBMITTED", pq["status"], diff --git a/services/cleanrooms/protected_query_lifecycle_test.go b/services/cleanrooms/protected_query_lifecycle_test.go index c60b5ef325..c5b6ecb352 100644 --- a/services/cleanrooms/protected_query_lifecycle_test.go +++ b/services/cleanrooms/protected_query_lifecycle_test.go @@ -1,7 +1,7 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cleanrooms" ) @@ -22,7 +23,7 @@ func queryStatus(t *testing.T, e *echo.Echo, memID, id string) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["protectedQuery"]["status"].(string) } @@ -142,7 +143,7 @@ func TestStartProtectedQuery_KeepsSQLParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) got := resp["protectedQuery"]["sqlParameters"].(map[string]any) assert.Equal(t, tmpl, got["analysisTemplateArn"]) assert.Equal(t, map[string]any{"k": "v"}, got["parameters"]) diff --git a/services/cleanrooms/undeclared_members_test.go b/services/cleanrooms/undeclared_members_test.go index 47b0119745..b501244366 100644 --- a/services/cleanrooms/undeclared_members_test.go +++ b/services/cleanrooms/undeclared_members_test.go @@ -1,7 +1,7 @@ package cleanrooms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cleanrooms" ) @@ -114,7 +115,7 @@ func decodeBody(t *testing.T, b []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(b, &m)) + require.NoError(t, json.Unmarshal(b, &m, jsonx.V1())) return m } diff --git a/services/cloudcontrol/handler.go b/services/cloudcontrol/handler.go index 5744ef34ed..36877139ad 100644 --- a/services/cloudcontrol/handler.go +++ b/services/cloudcontrol/handler.go @@ -2,7 +2,7 @@ package cloudcontrol import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -125,12 +126,12 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // marshalError serialises a typed AWS error response into bytes. func marshalError(errType, message string) []byte { - payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}) + payload, _ := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}, jsonx.V1()) return payload } @@ -142,9 +143,6 @@ func marshalError(errType, message string) []byte { // exceptions (e.g. ConcurrentModificationException, ServiceInternalErrorException) use 500. // See e.g. https://docs.aws.amazon.com/cloudcontrolapi/latest/APIReference/API_GetResource.html#API_GetResource_Errors func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrClientTokenConflict): return ccError(c, http.StatusBadRequest, "ClientTokenConflictException", err, @@ -170,7 +168,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, ErrValidation): return ccError(c, http.StatusBadRequest, "InvalidRequestException", err, "The request is invalid.") - case errors.Is(err, errUnknownAction), errors.As(err, &syntaxErr), errors.As(err, &typeErr): + case errors.Is(err, errUnknownAction), jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return ccError(c, http.StatusBadRequest, "InvalidRequestException", err, "The request is invalid.") default: return ccError(c, http.StatusInternalServerError, "ServiceInternalErrorException", err, diff --git a/services/cloudcontrol/handler_test.go b/services/cloudcontrol/handler_test.go index 1f929ac55b..609876b2b3 100644 --- a/services/cloudcontrol/handler_test.go +++ b/services/cloudcontrol/handler_test.go @@ -2,7 +2,7 @@ package cloudcontrol_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -27,7 +28,7 @@ func doRequest(t *testing.T, h *cloudcontrol.Handler, action string, body any) * var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -55,7 +56,7 @@ func errType(t *testing.T, body []byte) string { var env struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(body, &env)) + require.NoError(t, json.Unmarshal(body, &env, jsonx.V1())) return env.Type } diff --git a/services/cloudcontrol/models.go b/services/cloudcontrol/models.go index 79734e1058..75d70111f7 100644 --- a/services/cloudcontrol/models.go +++ b/services/cloudcontrol/models.go @@ -1,8 +1,10 @@ package cloudcontrol import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -24,12 +26,12 @@ type unixEpochTime struct { } func (t unixEpochTime) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Unix()) + return json.Marshal(t.Unix(), jsonx.V1()) } func (t *unixEpochTime) UnmarshalJSON(b []byte) error { var epoch int64 - if err := json.Unmarshal(b, &epoch); err != nil { + if err := json.Unmarshal(b, &epoch, jsonx.V1()); err != nil { return err } diff --git a/services/cloudcontrol/models_test.go b/services/cloudcontrol/models_test.go index d0f0f122c4..e9067fc76d 100644 --- a/services/cloudcontrol/models_test.go +++ b/services/cloudcontrol/models_test.go @@ -1,13 +1,14 @@ package cloudcontrol_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -30,7 +31,7 @@ func TestProgressEvent_OptionalFieldsOmittedWhenTerminalSuccess(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok, "ProgressEvent must be present") @@ -57,7 +58,7 @@ func TestGetResourceRequestStatus_HooksProgressEventOmittedWhenEmpty(t *testing. require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) pe, ok := createOut["ProgressEvent"].(map[string]any) require.True(t, ok) @@ -71,7 +72,7 @@ func TestGetResourceRequestStatus_HooksProgressEventOmittedWhenEmpty(t *testing. require.Equal(t, http.StatusOK, statusRec.Code) var statusOut map[string]any - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusOut)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusOut, jsonx.V1())) assert.NotContains(t, statusOut, "HooksProgressEvent", "HooksProgressEvent must be omitted since this backend has no Hooks concept") diff --git a/services/cloudcontrol/persistence.go b/services/cloudcontrol/persistence.go index 43d226b661..96c48c137b 100644 --- a/services/cloudcontrol/persistence.go +++ b/services/cloudcontrol/persistence.go @@ -2,7 +2,7 @@ package cloudcontrol import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -42,7 +42,7 @@ const cloudcontrolSnapshotVersion = 2 // persisted directly here so a repeated ClientToken remains idempotent (and // conflict-detectable) across a restart. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ClientTokens map[string]clientTokenEntry `json:"clientTokens"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/cloudcontrol/provisioned.go b/services/cloudcontrol/provisioned.go index d087325c99..ee08aaa692 100644 --- a/services/cloudcontrol/provisioned.go +++ b/services/cloudcontrol/provisioned.go @@ -2,10 +2,12 @@ package cloudcontrol import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Provisioner creates, changes and destroys the real resource behind a Cloud @@ -96,7 +98,7 @@ func (h *provisionedHandler) stored(identifier string) (map[string]any, error) { } var model map[string]any - if err := json.Unmarshal([]byte(r.Properties), &model); err != nil { + if err := json.Unmarshal([]byte(r.Properties), &model, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: stored model is not a JSON object", ErrValidation) } diff --git a/services/cloudcontrol/resource_requests_test.go b/services/cloudcontrol/resource_requests_test.go index 2c19918bfa..a78420a32c 100644 --- a/services/cloudcontrol/resource_requests_test.go +++ b/services/cloudcontrol/resource_requests_test.go @@ -1,7 +1,7 @@ package cloudcontrol_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -61,7 +62,7 @@ func TestHandler_GetResourceRequestStatus(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantOp, pe["Operation"]) @@ -189,7 +190,7 @@ func TestHandler_CancelResourceRequest(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantOpStatus, pe["OperationStatus"]) @@ -263,7 +264,7 @@ func TestHandler_CancelResourceRequest_OnlyAllowsInProgress(t *testing.T) { if tt.wantHTTP == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok, "ProgressEvent must be present") assert.Equal(t, "CANCEL_COMPLETE", pe["OperationStatus"]) @@ -350,7 +351,7 @@ func TestHandler_EventTimeIsUnixNumber(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) pe, ok := createOut["ProgressEvent"].(map[string]any) require.True(t, ok) @@ -375,7 +376,7 @@ func TestHandler_EventTimeIsUnixNumber(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok, "ProgressEvent should be present in response") @@ -488,7 +489,7 @@ func TestHandler_ListResourceRequests(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ResourceRequestStatusSummaries"].([]any) require.True(t, ok, "ResourceRequestStatusSummaries should be an array") assert.Len(t, summaries, tt.wantCount) @@ -565,7 +566,7 @@ func TestHandler_ListResourceRequests_FilterByOperationStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, _ := out["ResourceRequestStatusSummaries"].([]any) assert.Len(t, summaries, tt.wantLen, "filter %v", tt.filter) }) @@ -581,7 +582,7 @@ func TestHandler_ListResourceRequests_ContainsExpectedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ResourceRequestStatusSummaries"].([]any) require.True(t, ok) @@ -655,7 +656,7 @@ func TestHandler_ListResourceRequests_EnumValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ResourceRequestStatusSummaries"].([]any) if tt.wantMatches { @@ -693,7 +694,7 @@ func TestHandler_ListResourceRequests_TypeNameFilterIsIgnored(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ResourceRequestStatusSummaries"].([]any) require.True(t, ok) @@ -716,7 +717,7 @@ func TestHandler_ListResourceRequests_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ResourceRequestStatusSummaries"].([]any) require.True(t, ok) @@ -734,7 +735,7 @@ func TestHandler_ListResourceRequests_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) summaries2, ok := out2["ResourceRequestStatusSummaries"].([]any) require.True(t, ok) diff --git a/services/cloudcontrol/resources.go b/services/cloudcontrol/resources.go index ead679b243..dc380f1b0e 100644 --- a/services/cloudcontrol/resources.go +++ b/services/cloudcontrol/resources.go @@ -1,7 +1,7 @@ package cloudcontrol import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -122,7 +123,7 @@ func (b *InMemoryBackend) ListResources( var modelFilter map[string]any if resourceModel != "" { - if err := json.Unmarshal([]byte(resourceModel), &modelFilter); err != nil { + if err := json.Unmarshal([]byte(resourceModel), &modelFilter, jsonx.V1()); err != nil { return []*Resource{}, "" } } @@ -433,7 +434,7 @@ func extractIdentifier(desiredState string) string { } var props map[string]any - if err := json.Unmarshal([]byte(desiredState), &props); err != nil { + if err := json.Unmarshal([]byte(desiredState), &props, jsonx.V1()); err != nil { return "" } @@ -462,7 +463,7 @@ func matchesResourceModel(properties string, modelFilter map[string]any) bool { } var props map[string]any - if err := json.Unmarshal([]byte(properties), &props); err != nil { + if err := json.Unmarshal([]byte(properties), &props, jsonx.V1()); err != nil { return false } @@ -472,8 +473,8 @@ func matchesResourceModel(properties string, modelFilter map[string]any) bool { return false } - wantJSON, errW := json.Marshal(want) - gotJSON, errG := json.Marshal(got) + wantJSON, errW := json.Marshal(want, jsonx.V1()) + gotJSON, errG := json.Marshal(got, jsonx.V1()) if errW != nil || errG != nil || string(wantJSON) != string(gotJSON) { return false @@ -508,13 +509,13 @@ type patchOperation struct { // unchanged with no error (also pre-existing). func applyPatch(document, patchDocument string) (string, error) { var doc map[string]any - if err := json.Unmarshal([]byte(document), &doc); err != nil { + if err := json.Unmarshal([]byte(document), &doc, jsonx.V1()); err != nil { //nolint:nilerr // malformed document: return it unchanged, not an error (pre-existing behavior) return document, nil } var ops []patchOperation - if err := json.Unmarshal([]byte(patchDocument), &ops); err != nil { + if err := json.Unmarshal([]byte(patchDocument), &ops, jsonx.V1()); err != nil { //nolint:nilerr // malformed patch JSON: return document unchanged, not an error (pre-existing behavior) return document, nil } @@ -528,7 +529,7 @@ func applyPatch(document, patchDocument string) (string, error) { } } - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { //nolint:nilerr // doc built entirely from valid JSON decodes, so this can't actually fail; fail closed anyway return document, nil @@ -705,8 +706,8 @@ func deepCopyJSON(v any) any { // comparing equal -- both decode to the same float64 and marshal back // identically) -- same convention as matchesResourceModel above. func jsonValuesEqual(a, b any) bool { - aJSON, errA := json.Marshal(a) - bJSON, errB := json.Marshal(b) + aJSON, errA := json.Marshal(a, jsonx.V1()) + bJSON, errB := json.Marshal(b, jsonx.V1()) return errA == nil && errB == nil && string(aJSON) == string(bJSON) } diff --git a/services/cloudcontrol/resources_test.go b/services/cloudcontrol/resources_test.go index 843c12a826..662e2653be 100644 --- a/services/cloudcontrol/resources_test.go +++ b/services/cloudcontrol/resources_test.go @@ -1,7 +1,7 @@ package cloudcontrol_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -59,7 +60,7 @@ func TestHandler_CreateResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantOp, pe["Operation"]) @@ -119,7 +120,7 @@ func TestHandler_CreateResource_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) pe1 := out1["ProgressEvent"].(map[string]any) // Same clientToken must return the same RequestToken (idempotent). @@ -127,7 +128,7 @@ func TestHandler_CreateResource_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) pe2 := out2["ProgressEvent"].(map[string]any) assert.Equal(t, pe1["RequestToken"], pe2["RequestToken"]) @@ -210,14 +211,14 @@ func TestHandler_ProgressEvent_ResourceModel(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) createPE := createOut["ProgressEvent"].(map[string]any) createModel, ok := createPE["ResourceModel"].(string) require.True(t, ok, "ResourceModel must be present as a string on CreateResource") var createParsed map[string]any - require.NoError(t, json.Unmarshal([]byte(createModel), &createParsed)) + require.NoError(t, json.Unmarshal([]byte(createModel), &createParsed, jsonx.V1())) assert.Equal(t, "model-test", createParsed["LogGroupName"]) updateRec := doRequest(t, h, "UpdateResource", map[string]any{ @@ -228,14 +229,14 @@ func TestHandler_ProgressEvent_ResourceModel(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) updatePE := updateOut["ProgressEvent"].(map[string]any) updateModel, ok := updatePE["ResourceModel"].(string) require.True(t, ok, "ResourceModel must be present as a string on UpdateResource") var updateParsed map[string]any - require.NoError(t, json.Unmarshal([]byte(updateModel), &updateParsed)) + require.NoError(t, json.Unmarshal([]byte(updateModel), &updateParsed, jsonx.V1())) assert.InEpsilon(t, float64(30), updateParsed["RetentionInDays"], 0, "ResourceModel must reflect the patched value") } func TestHandler_GetResource(t *testing.T) { @@ -301,7 +302,7 @@ func TestHandler_GetResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) rd, ok := out["ResourceDescription"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantProps, rd["Properties"]) @@ -325,7 +326,7 @@ func TestHandler_GetResource_PropertiesIsJSONString(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) pe := createOut["ProgressEvent"].(map[string]any) identifier := pe["Identifier"].(string) @@ -336,7 +337,7 @@ func TestHandler_GetResource_PropertiesIsJSONString(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) desc, ok := getOut["ResourceDescription"].(map[string]any) require.True(t, ok, "ResourceDescription must be present") @@ -348,7 +349,7 @@ func TestHandler_GetResource_PropertiesIsJSONString(t *testing.T) { // The string must be valid JSON. var parsed map[string]any - require.NoError(t, json.Unmarshal([]byte(propertiesRaw.(string)), &parsed)) + require.NoError(t, json.Unmarshal([]byte(propertiesRaw.(string)), &parsed, jsonx.V1())) assert.Equal(t, "my-bucket", parsed["BucketName"]) } @@ -479,7 +480,7 @@ func TestHandler_ListResources(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descs, ok := out["ResourceDescriptions"].([]any) require.True(t, ok) assert.Len(t, descs, tt.wantCount) @@ -506,7 +507,7 @@ func TestHandler_ListResources_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1, jsonx.V1())) descs1, ok := out1["ResourceDescriptions"].([]any) require.True(t, ok) @@ -526,7 +527,7 @@ func TestHandler_ListResources_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) descs2, ok := out2["ResourceDescriptions"].([]any) require.True(t, ok) @@ -598,7 +599,7 @@ func TestHandler_DeleteResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantOp, pe["Operation"]) @@ -678,7 +679,7 @@ func TestHandler_UpdateResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantOp, pe["Operation"]) @@ -713,14 +714,14 @@ func TestHandler_UpdateResource_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) pe1 := out1["ProgressEvent"].(map[string]any) rec2 := doRequest(t, h, "UpdateResource", body) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) pe2 := out2["ProgressEvent"].(map[string]any) assert.Equal(t, pe1["RequestToken"], pe2["RequestToken"], @@ -1109,7 +1110,7 @@ func TestHandler_DeleteResource_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) pe1 := out1["ProgressEvent"].(map[string]any) // Without idempotency this second call would 400 with ResourceNotFoundException, @@ -1118,7 +1119,7 @@ func TestHandler_DeleteResource_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) pe2 := out2["ProgressEvent"].(map[string]any) assert.Equal(t, pe1["RequestToken"], pe2["RequestToken"], @@ -1461,7 +1462,7 @@ func TestHandler_ProgressEvent_RequiredFields(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) pe := out["ProgressEvent"].(map[string]any) return map[string]any{ @@ -1479,7 +1480,7 @@ func TestHandler_ProgressEvent_RequiredFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pe, ok := out["ProgressEvent"].(map[string]any) require.True(t, ok, "ProgressEvent must be present") diff --git a/services/cloudcontrol/type_handlers.go b/services/cloudcontrol/type_handlers.go index 3b1fcf67e4..e8259c3748 100644 --- a/services/cloudcontrol/type_handlers.go +++ b/services/cloudcontrol/type_handlers.go @@ -2,13 +2,14 @@ package cloudcontrol import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "sort" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -38,7 +39,7 @@ func (b *InMemoryBackend) typeHandler(typeName string) TypeHandler { } func marshalModel(model map[string]any) (string, error) { - raw, err := json.Marshal(model) + raw, err := json.Marshal(model, jsonx.V1()) if err != nil { return "", fmt.Errorf("%w: %w", ErrValidation, err) } @@ -90,7 +91,7 @@ func (b *InMemoryBackend) CreateResourceContext( } var desired map[string]any - if err := json.Unmarshal([]byte(desiredState), &desired); err != nil { + if err := json.Unmarshal([]byte(desiredState), &desired, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: DesiredState must be a JSON object", ErrValidation) } @@ -202,7 +203,7 @@ func (b *InMemoryBackend) UpdateResourceContext( } var desired map[string]any - if jsonErr := json.Unmarshal([]byte(patched), &desired); jsonErr != nil { + if jsonErr := json.Unmarshal([]byte(patched), &desired, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: patched document is not a JSON object", ErrValidation) } @@ -278,7 +279,7 @@ func parseModelFilter(resourceModel string) (map[string]any, bool) { var filter map[string]any - return filter, json.Unmarshal([]byte(resourceModel), &filter) == nil + return filter, json.Unmarshal([]byte(resourceModel), &filter, jsonx.V1()) == nil } var errInvalidNextToken = fmt.Errorf("%w: NextToken is not valid", ErrValidation) diff --git a/services/cloudformation/cfn_attributes.go b/services/cloudformation/cfn_attributes.go index 9cb2da75de..ecf168b665 100644 --- a/services/cloudformation/cfn_attributes.go +++ b/services/cloudformation/cfn_attributes.go @@ -2,8 +2,10 @@ package cloudformation import ( _ "embed" - "encoding/json" + "encoding/json/v2" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // cfn_attributes.json is generated by cmd/cfnattrgen from the CloudFormation @@ -23,7 +25,7 @@ var ( func resourceAttributeTable() map[string]map[string]struct{} { cfnResourceAttributesOnce.Do(func() { var raw map[string][]string - if err := json.Unmarshal(cfnAttributesJSON, &raw); err != nil { + if err := json.Unmarshal(cfnAttributesJSON, &raw, jsonx.V1()); err != nil { panic("cloudformation: invalid embedded cfn_attributes.json: " + err.Error()) } diff --git a/services/cloudformation/cfn_property_schemas.go b/services/cloudformation/cfn_property_schemas.go index 3f3c1e99e8..60398ec59e 100644 --- a/services/cloudformation/cfn_property_schemas.go +++ b/services/cloudformation/cfn_property_schemas.go @@ -2,12 +2,14 @@ package cloudformation import ( _ "embed" - "encoding/json" + "encoding/json/v2" "fmt" "math" "strconv" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Property kinds in cfn_property_schemas.json, written by cmd/cfnpropgen. @@ -37,7 +39,7 @@ var ( func embeddedPropertySchemas() propertySchemas { cfnPropertySchemasOnce.Do(func() { - if err := json.Unmarshal(cfnPropertySchemasJSON, &cfnPropertySchemas); err != nil { + if err := json.Unmarshal(cfnPropertySchemasJSON, &cfnPropertySchemas, jsonx.V1()); err != nil { panic("cloudformation: invalid embedded cfn_property_schemas.json: " + err.Error()) } }) diff --git a/services/cloudformation/changeset_diff.go b/services/cloudformation/changeset_diff.go index 1ae698fc62..f7bb9d742d 100644 --- a/services/cloudformation/changeset_diff.go +++ b/services/cloudformation/changeset_diff.go @@ -1,12 +1,13 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Replacement / recreation constants used by change-set previews. @@ -431,8 +432,8 @@ func unionMapKeys(a, b map[string]any) []string { } func jsonEqual(a, b any) bool { - ab, err1 := json.Marshal(a) - bb, err2 := json.Marshal(b) + ab, err1 := json.Marshal(a, jsonx.V1()) + bb, err2 := json.Marshal(b, jsonx.V1()) if err1 != nil || err2 != nil { return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b) } @@ -447,7 +448,7 @@ func toJSONString(v any) string { if s, ok := v.(string); ok { return s } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Sprintf("%v", v) } diff --git a/services/cloudformation/cloudcontrol_provisioner.go b/services/cloudformation/cloudcontrol_provisioner.go index bbac51d002..f487216c18 100644 --- a/services/cloudformation/cloudcontrol_provisioner.go +++ b/services/cloudformation/cloudcontrol_provisioner.go @@ -3,7 +3,7 @@ package cloudformation import ( "context" _ "embed" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "reflect" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudcontrol" ) @@ -29,7 +30,7 @@ var ( // against real backends; Cloud Control serves the same set. func ProvisionableTypes() []string { cfnProvisionableTypesOnce.Do(func() { - if err := json.Unmarshal(cfnProvisionableTypesJSON, &cfnProvisionableTypes); err != nil { + if err := json.Unmarshal(cfnProvisionableTypesJSON, &cfnProvisionableTypes, jsonx.V1()); err != nil { panic("cloudformation: invalid embedded cfn_provisionable_types.json: " + err.Error()) } }) diff --git a/services/cloudformation/deletion_policy_test.go b/services/cloudformation/deletion_policy_test.go index 82180ce0eb..2d09b77ca8 100644 --- a/services/cloudformation/deletion_policy_test.go +++ b/services/cloudformation/deletion_policy_test.go @@ -2,13 +2,14 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudformation" ) @@ -53,7 +54,7 @@ func templateWithDeletionPolicy(policy string) string { body, _ := json.Marshal(map[string]any{ "AWSTemplateFormatVersion": "2010-09-09", "Resources": map[string]any{"Bucket": res}, - }) + }, jsonx.V1()) return string(body) } diff --git a/services/cloudformation/drift_detection.go b/services/cloudformation/drift_detection.go index 65b482fff1..17ba7ce3db 100644 --- a/services/cloudformation/drift_detection.go +++ b/services/cloudformation/drift_detection.go @@ -1,12 +1,14 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // pruneDriftDetections removes all drift detection entries associated with a stack @@ -218,12 +220,12 @@ func deepCopyProps(props map[string]any) map[string]any { if props == nil { return nil } - data, err := json.Marshal(props) + data, err := json.Marshal(props, jsonx.V1()) if err != nil { return props } var out map[string]any - if uerr := json.Unmarshal(data, &out); uerr != nil { + if uerr := json.Unmarshal(data, &out, jsonx.V1()); uerr != nil { return props } diff --git a/services/cloudformation/drift_detection_test.go b/services/cloudformation/drift_detection_test.go index 836f35bf0e..87918a1de6 100644 --- a/services/cloudformation/drift_detection_test.go +++ b/services/cloudformation/drift_detection_test.go @@ -2,12 +2,13 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudformation" ) @@ -306,7 +307,7 @@ func TestDeleteStack_ClearsDriftMaps(t *testing.T) { ResourceDriftStatus map[string]map[string]string `json:"resourceDriftStatus"` ResourceDriftDetail map[string]map[string]any `json:"resourceDriftDetail"` } - require.NoError(t, json.Unmarshal(b.Snapshot(context.Background()), &probe)) + require.NoError(t, json.Unmarshal(b.Snapshot(context.Background()), &probe, jsonx.V1())) assert.NotContains(t, probe.ResourceDriftStatus, stack.StackID) assert.NotContains(t, probe.ResourceDriftDetail, stack.StackID) assert.Contains(t, probe.ResourceDriftStatus, otherStack.StackID, diff --git a/services/cloudformation/dynamic_refs.go b/services/cloudformation/dynamic_refs.go index 29989c99d5..5fd024164c 100644 --- a/services/cloudformation/dynamic_refs.go +++ b/services/cloudformation/dynamic_refs.go @@ -2,11 +2,13 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dynamicRefPattern matches CloudFormation dynamic reference strings: @@ -186,7 +188,7 @@ func ResolveDynamicRefsInTemplate(ctx context.Context, tmpl *Template, resolver // If parsing fails or the key is absent, an error is returned. func resolveJSONKey(secretValue, key string) (string, error) { var obj map[string]any - if err := json.Unmarshal([]byte(secretValue), &obj); err != nil { + if err := json.Unmarshal([]byte(secretValue), &obj, jsonx.V1()); err != nil { return "", fmt.Errorf("secret is not valid JSON: %w", err) } diff --git a/services/cloudformation/generated_templates.go b/services/cloudformation/generated_templates.go index 09cb5b3f40..44c55ab745 100644 --- a/services/cloudformation/generated_templates.go +++ b/services/cloudformation/generated_templates.go @@ -1,7 +1,7 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -79,7 +80,7 @@ func marshalGeneratedTemplate(resources map[string]cfnResource) string { AWSTemplateFormatVersion: "2010-09-09", Description: "Generated template", Resources: resources, - }) + }, jsonx.V1()) if err != nil { return `{"AWSTemplateFormatVersion":"2010-09-09","Resources":{}}` } diff --git a/services/cloudformation/handler_type_registry.go b/services/cloudformation/handler_type_registry.go index d848973e63..d7aa1fd2bf 100644 --- a/services/cloudformation/handler_type_registry.go +++ b/services/cloudformation/handler_type_registry.go @@ -1,7 +1,7 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // typeSchemaFor returns the primary identifier property name for a given CloudFormation resource type. @@ -112,11 +113,11 @@ func describeTypeSchemaJSON(typeName string) (string, error) { // Round-trip through a generic map to normalize formatting and to fail // loudly (via the error return) if a catalog entry is ever malformed. var generic map[string]any - if err := json.Unmarshal([]byte(raw), &generic); err != nil { + if err := json.Unmarshal([]byte(raw), &generic, jsonx.V1()); err != nil { return "", err } - normalized, err := json.Marshal(generic) + normalized, err := json.Marshal(generic, jsonx.V1()) if err != nil { return "", err } @@ -143,7 +144,7 @@ func describeTypeSchemaJSON(typeName string) (string, error) { AdditionalProperties: true, } - schemaBytes, err := json.Marshal(schemaObj) + schemaBytes, err := json.Marshal(schemaObj, jsonx.V1()) if err != nil { return "", err } diff --git a/services/cloudformation/persistence.go b/services/cloudformation/persistence.go index 4294a104c3..584c527c42 100644 --- a/services/cloudformation/persistence.go +++ b/services/cloudformation/persistence.go @@ -2,7 +2,7 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -29,7 +29,7 @@ const cfnSnapshotVersion = 1 // comment on registerAllTables) and are persisted exactly as before the // conversion. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Events map[string][]StackEvent `json:"events"` Resources map[string]map[string]*StackResource `json:"resources"` ChangeSets map[string]map[string]*ChangeSet `json:"changeSets"` diff --git a/services/cloudformation/resource_type_schemas_test.go b/services/cloudformation/resource_type_schemas_test.go index f235ffd9b4..a13d193811 100644 --- a/services/cloudformation/resource_type_schemas_test.go +++ b/services/cloudformation/resource_type_schemas_test.go @@ -1,13 +1,15 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // describeTypeXMLResult mirrors the DescribeTypeResult XML shape returned by the @@ -170,7 +172,7 @@ func TestHandler_DescribeType_CatalogSchemas(t *testing.T) { require.NotEmpty(t, parsed.Schema) var schema parsedProviderSchema - require.NoError(t, json.Unmarshal([]byte(parsed.Schema), &schema)) + require.NoError(t, json.Unmarshal([]byte(parsed.Schema), &schema, jsonx.V1())) assert.Equal(t, tc.typeName, schema.TypeName) assert.NotEmpty(t, schema.Description) require.Contains(t, schema.PrimaryIdentifier, tc.wantPrimaryID) @@ -214,7 +216,7 @@ func TestHandler_DescribeType_UnknownType_ValidFallback(t *testing.T) { require.NotEmpty(t, parsed.Schema) var schema parsedProviderSchema - require.NoError(t, json.Unmarshal([]byte(parsed.Schema), &schema)) + require.NoError(t, json.Unmarshal([]byte(parsed.Schema), &schema, jsonx.V1())) assert.Equal(t, "AWS::Totally::Unmodeled", schema.TypeName) require.NotEmpty(t, schema.PrimaryIdentifier) assert.NotEmpty(t, schema.Properties) diff --git a/services/cloudformation/resource_update_inplace.go b/services/cloudformation/resource_update_inplace.go index 21c0bfd90a..5acb836766 100644 --- a/services/cloudformation/resource_update_inplace.go +++ b/services/cloudformation/resource_update_inplace.go @@ -4,12 +4,13 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "time" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" sqsbackend "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -114,7 +115,7 @@ func sqsAttributesFromProps(props map[string]any, params, physicalIDs map[string } if rp, ok := props["RedrivePolicy"]; ok { - if b, err := json.Marshal(resolveDeep(rp, params, physicalIDs)); err == nil { + if b, err := json.Marshal(resolveDeep(rp, params, physicalIDs), jsonx.V1()); err == nil { attrs["RedrivePolicy"] = string(b) } } diff --git a/services/cloudformation/resources.go b/services/cloudformation/resources.go index 32f57ebd4a..6e0df898e2 100644 --- a/services/cloudformation/resources.go +++ b/services/cloudformation/resources.go @@ -2,7 +2,6 @@ package cloudformation import ( "context" - "encoding/json" "fmt" "maps" "math" @@ -16,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/ctxval" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" acmbackend "github.com/blackbirdworks/gopherstack/services/acm" apigwbackend "github.com/blackbirdworks/gopherstack/services/apigateway" apigatewayv2backend "github.com/blackbirdworks/gopherstack/services/apigatewayv2" @@ -3510,7 +3510,7 @@ func int64Val(v any) int64 { return int64(n) case int64: return n - case json.Number: + case jsonx.Number: i, err := n.Int64() if err == nil { return i diff --git a/services/cloudformation/resources_apigatewayv2.go b/services/cloudformation/resources_apigatewayv2.go index bb678e056c..9a7ba36910 100644 --- a/services/cloudformation/resources_apigatewayv2.go +++ b/services/cloudformation/resources_apigatewayv2.go @@ -2,10 +2,11 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" apigatewayv2backend "github.com/blackbirdworks/gopherstack/services/apigatewayv2" ) @@ -99,11 +100,11 @@ func (rc *ResourceCreator) deleteAPIGatewayV2Authorizer(physicalID string) error // decodeProps resolves intrinsics in props and decodes them into out; the // backend inputs' JSON tags match CloudFormation's property names case-insensitively. func decodeProps(props map[string]any, params, physicalIDs map[string]string, out any) error { - raw, err := json.Marshal(resolveDeep(props, params, physicalIDs)) + raw, err := json.Marshal(resolveDeep(props, params, physicalIDs), jsonx.V1()) if err != nil { return fmt.Errorf("encode properties: %w", err) } - if err = json.Unmarshal(raw, out); err != nil { + if err = json.Unmarshal(raw, out, jsonx.V1()); err != nil { return fmt.Errorf("decode properties: %w", err) } diff --git a/services/cloudformation/resources_cfnrest.go b/services/cloudformation/resources_cfnrest.go index 1f9dbefb60..e95c045f71 100644 --- a/services/cloudformation/resources_cfnrest.go +++ b/services/cloudformation/resources_cfnrest.go @@ -1,12 +1,13 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -28,12 +29,12 @@ func decodeCFNProps(dst any, props map[string]any, params, physicalIDs map[strin resolved["Tags"] = tagListProp(props, params, physicalIDs) } - raw, err := json.Marshal(resolved) + raw, err := json.Marshal(resolved, jsonx.V1()) if err != nil { return fmt.Errorf("encode properties: %w", err) } - if err = json.Unmarshal(raw, dst); err != nil { + if err = json.Unmarshal(raw, dst, jsonx.V1()); err != nil { return fmt.Errorf("decode properties: %w", err) } diff --git a/services/cloudformation/resources_connect_content.go b/services/cloudformation/resources_connect_content.go index 1ec915703f..a425ae49f2 100644 --- a/services/cloudformation/resources_connect_content.go +++ b/services/cloudformation/resources_connect_content.go @@ -1,12 +1,13 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" connectbackend "github.com/blackbirdworks/gopherstack/services/connect" ) @@ -21,7 +22,7 @@ func (c *connectCall) viewContent() (*connectbackend.ViewInputContent, error) { return &connectbackend.ViewInputContent{Template: s, Actions: c.actionStrings()}, nil } - raw, err := json.Marshal(template) + raw, err := json.Marshal(template, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/cloudformation/resources_connect_core_test.go b/services/cloudformation/resources_connect_core_test.go index ef8ca381c2..f9bf3c17bb 100644 --- a/services/cloudformation/resources_connect_core_test.go +++ b/services/cloudformation/resources_connect_core_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudformation" connectbackend "github.com/blackbirdworks/gopherstack/services/connect" ) @@ -32,7 +33,7 @@ func connectFlowContent(t *testing.T) string { "Identifier": "end", "Type": "DisconnectParticipant", "Parameters": map[string]any{}, "Transitions": map[string]any{}, }}, - }) + }, jsonx.V1()) require.NoError(t, err) return string(doc) diff --git a/services/cloudformation/resources_connect_people.go b/services/cloudformation/resources_connect_people.go index 4b937f0fda..0e47efad98 100644 --- a/services/cloudformation/resources_connect_people.go +++ b/services/cloudformation/resources_connect_people.go @@ -1,9 +1,11 @@ package cloudformation import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" connectbackend "github.com/blackbirdworks/gopherstack/services/connect" ) @@ -348,10 +350,10 @@ func connectRuleActionMembers() map[string][2]string { // ruleActions turns the template's Actions object (one list per action kind) into the API's list of // {ActionType, Action} entries. -func (c *connectCall) ruleActions() ([]json.RawMessage, error) { +func (c *connectCall) ruleActions() ([]jsontext.Value, error) { actions, _ := c.wire["Actions"].(map[string]any) - var out []json.RawMessage + var out []jsontext.Value for _, key := range sortedKeys(actions) { kind, known := connectRuleActionMembers()[key] @@ -361,7 +363,7 @@ func (c *connectCall) ruleActions() ([]json.RawMessage, error) { list, _ := actions[key].([]any) for _, item := range list { - raw, err := json.Marshal(map[string]any{"ActionType": kind[0], kind[1]: item}) + raw, err := json.Marshal(map[string]any{"ActionType": kind[0], kind[1]: item}, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/cloudformation/resources_connect_workspace.go b/services/cloudformation/resources_connect_workspace.go index 77d79f0fa2..09a6b48b2f 100644 --- a/services/cloudformation/resources_connect_workspace.go +++ b/services/cloudformation/resources_connect_workspace.go @@ -1,9 +1,11 @@ package cloudformation import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" connectbackend "github.com/blackbirdworks/gopherstack/services/connect" ) @@ -56,11 +58,11 @@ func updateConnectWorkspace(c *connectCall, physicalID string) error { return err } - theme := connectbackend.UpdateWorkspaceThemeInput{Theme: json.RawMessage("null")} + theme := connectbackend.UpdateWorkspaceThemeInput{Theme: jsontext.Value("null")} theme.InstanceID, theme.WorkspaceID = meta.InstanceID, meta.WorkspaceID if t, ok := c.wire["Theme"]; ok { - raw, err := json.Marshal(t) + raw, err := json.Marshal(t, jsonx.V1()) if err != nil { return err } diff --git a/services/cloudformation/resources_connectfamily_helpers_test.go b/services/cloudformation/resources_connectfamily_helpers_test.go index 105c65407a..31bbe9f474 100644 --- a/services/cloudformation/resources_connectfamily_helpers_test.go +++ b/services/cloudformation/resources_connectfamily_helpers_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,7 @@ import ( cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudformation" @@ -84,7 +85,7 @@ func stackTemplate(t *testing.T, resources map[string]any, outputs map[string]an outs[k] = map[string]any{"Value": v} } - doc, err := json.Marshal(map[string]any{"Resources": resources, "Outputs": outs}) + doc, err := json.Marshal(map[string]any{"Resources": resources, "Outputs": outs}, jsonx.V1()) require.NoError(t, err) return string(doc) diff --git a/services/cloudformation/resources_deep.go b/services/cloudformation/resources_deep.go index 95a65af3a8..c6f8721631 100644 --- a/services/cloudformation/resources_deep.go +++ b/services/cloudformation/resources_deep.go @@ -4,10 +4,11 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ebbackend "github.com/blackbirdworks/gopherstack/services/eventbridge" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -58,7 +59,7 @@ func resolvedJSONProp(props map[string]any, key string, params, physicalIDs map[ case string: return t default: - raw, err := json.Marshal(resolveDeep(t, params, physicalIDs)) + raw, err := json.Marshal(resolveDeep(t, params, physicalIDs), jsonx.V1()) if err != nil { return "" } diff --git a/services/cloudformation/resources_extensibility.go b/services/cloudformation/resources_extensibility.go index c88cc090c0..adce9a8607 100644 --- a/services/cloudformation/resources_extensibility.go +++ b/services/cloudformation/resources_extensibility.go @@ -2,7 +2,8 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net" @@ -13,6 +14,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" snsbackend "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -227,7 +229,7 @@ func startCFNResponseServer( } var resp cfnCustomResourceResponse - _ = json.NewDecoder(r.Body).Decode(&resp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &resp, jsonx.V1()) select { case respCh <- resp: @@ -318,7 +320,7 @@ func (rc *ResourceCreator) createCustomResource( ResourceProperties: props, } - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { return defaultPhysID, nil } @@ -421,7 +423,7 @@ func (rc *ResourceCreator) updateCustomResource( OldResourceProperties: oldProps, } - payload, merr := json.Marshal(event) + payload, merr := json.Marshal(event, jsonx.V1()) if merr != nil { return nil } @@ -487,7 +489,7 @@ func (rc *ResourceCreator) deleteCustomResource( ResourceProperties: props, } - payload, merr := json.Marshal(event) + payload, merr := json.Marshal(event, jsonx.V1()) if merr != nil { return nil } @@ -674,14 +676,14 @@ func (r *MacroRegistry) InvokeMacro( event := map[string]any{ "region": cfnDefaultRegion, "accountId": "000000000000", - "fragment": json.RawMessage(fragmentJSON), + "fragment": jsontext.Value(fragmentJSON), "transformId": macroName, "params": map[string]any{}, "templateParameterValues": params, "requestedTransforms": []string{macroName}, } - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { return fragmentJSON, err } @@ -699,12 +701,12 @@ func (r *MacroRegistry) InvokeMacro( // Parse macro response: {"status": "success", "fragment": {...}} var resp struct { - Status string `json:"status"` - ErrorMessage string `json:"errorMessage,omitempty"` - Fragment json.RawMessage `json:"fragment"` + Status string `json:"status"` + ErrorMessage string `json:"errorMessage,omitempty"` + Fragment jsontext.Value `json:"fragment"` } - if unmarshalErr := json.Unmarshal(result, &resp); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(result, &resp, jsonx.V1()); unmarshalErr != nil { return fragmentJSON, unmarshalErr } diff --git a/services/cloudformation/resources_extensibility_customresource_lifecycle_test.go b/services/cloudformation/resources_extensibility_customresource_lifecycle_test.go index a810cd49e6..67ced1c868 100644 --- a/services/cloudformation/resources_extensibility_customresource_lifecycle_test.go +++ b/services/cloudformation/resources_extensibility_customresource_lifecycle_test.go @@ -2,7 +2,7 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudformation" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -38,7 +39,7 @@ func TestResourceCreator_CustomResource_DeleteEvent(t *testing.T) { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, 0, err } @@ -62,7 +63,7 @@ func TestResourceCreator_CustomResource_DeleteEvent(t *testing.T) { "Status": "SUCCESS", "PhysicalResourceId": ev.PhysicalResourceID, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{Timeout: 2 * time.Second} req, reqErr := http.NewRequestWithContext( ctx, @@ -129,7 +130,7 @@ func TestResourceCreator_CustomResource_UpdateEvent(t *testing.T) { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, 0, err } @@ -157,7 +158,7 @@ func TestResourceCreator_CustomResource_UpdateEvent(t *testing.T) { "Status": "SUCCESS", "PhysicalResourceId": ev.PhysicalResourceID, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{Timeout: 2 * time.Second} req, reqErr := http.NewRequestWithContext( ctx, @@ -248,7 +249,7 @@ func TestStack_CustomResource_UpdateStack_SendsUpdateEvent(t *testing.T) { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, 0, err } @@ -266,7 +267,7 @@ func TestStack_CustomResource_UpdateStack_SendsUpdateEvent(t *testing.T) { "Status": "SUCCESS", "PhysicalResourceId": physID, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{Timeout: 2 * time.Second} req, reqErr := http.NewRequestWithContext( ctx, diff --git a/services/cloudformation/resources_extensibility_customresource_test.go b/services/cloudformation/resources_extensibility_customresource_test.go index 167266a818..2161711146 100644 --- a/services/cloudformation/resources_extensibility_customresource_test.go +++ b/services/cloudformation/resources_extensibility_customresource_test.go @@ -2,7 +2,7 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/services/cloudformation" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" @@ -112,7 +113,7 @@ func TestResourceCreator_CustomResource_ResponseURL_RoundTrip(t *testing.T) { var event struct { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &event); err == nil && event.ResponseURL != "" { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err == nil && event.ResponseURL != "" { select { case capturedURL <- event.ResponseURL: default: @@ -160,7 +161,7 @@ func TestResourceCreator_CustomResource_ResponseURL_RoundTrip(t *testing.T) { "Data": map[string]any{"BucketName": "my-bucket"}, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{} putReq, _ := http.NewRequestWithContext(t.Context(), http.MethodPut, url, newBytesReader(body)) @@ -319,7 +320,7 @@ func TestResourceCreator_CustomResource_FAILEDResponse(t *testing.T) { var event struct { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &event); err == nil { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err == nil { select { case capturedURL <- event.ResponseURL: default: @@ -363,7 +364,7 @@ func TestResourceCreator_CustomResource_FAILEDResponse(t *testing.T) { "Reason": "Validation error: bad-data is not allowed", "PhysicalResourceId": "failing-resource-001", } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{} req, _ := http.NewRequestWithContext(t.Context(), http.MethodPut, url, newBytesReader(body)) req.ContentLength = int64(len(body)) @@ -395,7 +396,7 @@ func TestStack_CustomResource_GetAtt_DataOutputs(t *testing.T) { var event struct { ResponseURL string `json:"ResponseURL"` } - if err := json.Unmarshal(payload, &event); err == nil { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err == nil { select { case capturedURL <- event.ResponseURL: default: @@ -459,7 +460,7 @@ func TestStack_CustomResource_GetAtt_DataOutputs(t *testing.T) { "BucketArn": "arn:aws:s3:::my-prefix-generated-bucket", }, } - body, _ := json.Marshal(resp) + body, _ := json.Marshal(resp, jsonx.V1()) client := &http.Client{} req, _ := http.NewRequestWithContext(t.Context(), http.MethodPut, url, newBytesReader(body)) req.ContentLength = int64(len(body)) diff --git a/services/cloudformation/resources_extensibility_macro_test.go b/services/cloudformation/resources_extensibility_macro_test.go index d4327d90f5..4e4e6cccdf 100644 --- a/services/cloudformation/resources_extensibility_macro_test.go +++ b/services/cloudformation/resources_extensibility_macro_test.go @@ -2,12 +2,13 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudformation" lambdabackend "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -170,7 +171,7 @@ func TestMacroRegistry_InvokeMacro_WithMockLambda(t *testing.T) { mockLambda := &mockLambdaStorageBackend{ invokeFunc: func(_ context.Context, _ string, _ lambdabackend.InvocationType, _ []byte) ([]byte, int, error) { - respBody, _ := json.Marshal(tt.lambdaResp) + respBody, _ := json.Marshal(tt.lambdaResp, jsonx.V1()) return respBody, 200, nil }, @@ -183,14 +184,14 @@ func TestMacroRegistry_InvokeMacro_WithMockLambda(t *testing.T) { "", ) - fragmentJSON, _ := json.Marshal(tt.fragment) + fragmentJSON, _ := json.Marshal(tt.fragment, jsonx.V1()) result, err := registry.InvokeMacro( t.Context(), lambdabackend.NewHandler(mockLambda), tt.macroName, fragmentJSON, nil, ) require.NoError(t, err) var got map[string]any - require.NoError(t, json.Unmarshal(result, &got)) + require.NoError(t, json.Unmarshal(result, &got, jsonx.V1())) assert.Equal(t, tt.wantFragment, got) }) } @@ -205,7 +206,7 @@ func TestMacroRegistry_InvokeMacro_NoLambdaBackend(t *testing.T) { registry.RegisterForTest("MyMacro", "arn:aws:lambda:us-east-1:000000000000:function:fn", "") fragment := map[string]any{"unchanged": true} - fragmentJSON, _ := json.Marshal(fragment) + fragmentJSON, _ := json.Marshal(fragment, jsonx.V1()) // nil lambda backend → returns fragment unchanged. result, err := registry.InvokeMacro(t.Context(), nil, "MyMacro", fragmentJSON, nil) @@ -221,7 +222,7 @@ func TestMacroRegistry_InvokeMacro_UnknownMacro(t *testing.T) { registry := cloudformation.NewMacroRegistry() fragment := map[string]any{"pass": "through"} - fragmentJSON, _ := json.Marshal(fragment) + fragmentJSON, _ := json.Marshal(fragment, jsonx.V1()) mockLambda := &mockLambdaStorageBackend{} result, err := registry.InvokeMacro( diff --git a/services/cloudformation/resources_iot_more_test.go b/services/cloudformation/resources_iot_more_test.go index 81940b5a48..1449883eeb 100644 --- a/services/cloudformation/resources_iot_more_test.go +++ b/services/cloudformation/resources_iot_more_test.go @@ -6,7 +6,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/json" + "encoding/json/v2" "encoding/pem" "testing" @@ -14,6 +14,8 @@ import ( cfnsdk "github.com/aws/aws-sdk-go-v2/service/cloudformation" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateStack_IoTMoreTypes(t *testing.T) { @@ -203,7 +205,10 @@ func testIoTCertificate(t *testing.T) { &x509.CertificateRequest{Subject: pkix.Name{CommonName: "device"}}, key) require.NoError(t, err) - csrJSON, err := json.Marshal(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}))) + csrJSON, err := json.Marshal( + string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER})), + jsonx.V1(), + ) require.NoError(t, err) tmpl := `{ diff --git a/services/cloudformation/resources_networkmanager.go b/services/cloudformation/resources_networkmanager.go index 006a9ca8d2..d6625aac73 100644 --- a/services/cloudformation/resources_networkmanager.go +++ b/services/cloudformation/resources_networkmanager.go @@ -2,11 +2,12 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" networkmanagerbackend "github.com/blackbirdworks/gopherstack/services/networkmanager" ) @@ -538,7 +539,7 @@ func nmCoreNetworkResource() nmResource { create: func(bk *networkmanagerbackend.InMemoryBackend, c nmCreateCtx) (string, error) { policy := c.str("PolicyDocument") if doc, ok := c.props["PolicyDocument"].(map[string]any); ok { - raw, err := json.Marshal(resolveDeep(doc, c.params, c.physicalIDs)) + raw, err := json.Marshal(resolveDeep(doc, c.params, c.physicalIDs), jsonx.V1()) if err != nil { return "", err } diff --git a/services/cloudformation/resources_oam_test.go b/services/cloudformation/resources_oam_test.go index 023c7d9553..31a64bf564 100644 --- a/services/cloudformation/resources_oam_test.go +++ b/services/cloudformation/resources_oam_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/services/cloudformation" oambackend "github.com/blackbirdworks/gopherstack/services/oam" @@ -96,7 +97,7 @@ func oamTemplate(t *testing.T, sinkName string, linkTypes []string, linkFilter, "LinkArn": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Link", "Arn"}}}, "LinkLabl": map[string]any{"Value": map[string]any{"Fn::GetAtt": []string{"Link", "Label"}}}, }, - }) + }, jsonx.V1()) require.NoError(t, err) return string(doc) diff --git a/services/cloudformation/resources_rum_test.go b/services/cloudformation/resources_rum_test.go index ad0a638a92..7b66e924c4 100644 --- a/services/cloudformation/resources_rum_test.go +++ b/services/cloudformation/resources_rum_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/services/cloudformation" rumbackend "github.com/blackbirdworks/gopherstack/services/rum" @@ -87,7 +88,7 @@ func rumTemplate(name, domain, cwLog, custom, metric, team string) string { rumResMetric: "com.amazon.rum.performance_resource_event", }[metric] - policy, _ := json.Marshal(rumPolicyDocument) + policy, _ := json.Marshal(rumPolicyDocument, jsonx.V1()) return fmt.Sprintf(rumMonitorTemplate, name, domain, cwLog, custom, metric, eventType, policy, team) } diff --git a/services/cloudformation/resources_sagemaker.go b/services/cloudformation/resources_sagemaker.go index 81dbac7d9a..f2c15aa03c 100644 --- a/services/cloudformation/resources_sagemaker.go +++ b/services/cloudformation/resources_sagemaker.go @@ -2,7 +2,7 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "strconv" @@ -267,13 +267,13 @@ func sagemakerFloatProp(props map[string]any, key string) float64 { // rawJSONProp reads a property as opaque JSON, returning nil when absent // (unlike jsonProp's "", which is not valid json.RawMessage). -func rawJSONProp(props map[string]any, key string) json.RawMessage { +func rawJSONProp(props map[string]any, key string) jsontext.Value { s := jsonProp(props, key) if s == "" { return nil } - return json.RawMessage(s) + return jsontext.Value(s) } // ---- AWS::SageMaker::Model ---- diff --git a/services/cloudformation/resources_sagemaker_athena_wafv2_test.go b/services/cloudformation/resources_sagemaker_athena_wafv2_test.go index a11b2cb7f9..e050745bbc 100644 --- a/services/cloudformation/resources_sagemaker_athena_wafv2_test.go +++ b/services/cloudformation/resources_sagemaker_athena_wafv2_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "testing" @@ -553,7 +553,7 @@ func testWAFv2WebACLAssociation(t *testing.T) { // association template below can reference its real ARN. acl, err := backends.WAFv2.Backend.CreateWebACL( t.Context(), "acl-1", "REGIONAL", "", - json.RawMessage(`{"Allow":{}}`), nil, + jsontext.Value(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, diff --git a/services/cloudformation/resources_ssm.go b/services/cloudformation/resources_ssm.go index d147af7838..a1eb48d2d1 100644 --- a/services/cloudformation/resources_ssm.go +++ b/services/cloudformation/resources_ssm.go @@ -2,9 +2,10 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ssmbackend "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -209,5 +210,5 @@ func (rc *ResourceCreator) deleteSSMDocument(ctx context.Context, name string) e // marshalJSON serializes a value to compact JSON bytes. func marshalJSON(v any) ([]byte, error) { - return json.Marshal(v) + return json.Marshal(v, jsonx.V1()) } diff --git a/services/cloudformation/resources_ssm_more.go b/services/cloudformation/resources_ssm_more.go index bcb61a67e2..d3edc31559 100644 --- a/services/cloudformation/resources_ssm_more.go +++ b/services/cloudformation/resources_ssm_more.go @@ -2,10 +2,11 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ssmbackend "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -382,7 +383,7 @@ func jsonProp(props map[string]any, key string) string { return s } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "" } diff --git a/services/cloudformation/resources_wafv2.go b/services/cloudformation/resources_wafv2.go index 1fa6b9c3f9..bd31053531 100644 --- a/services/cloudformation/resources_wafv2.go +++ b/services/cloudformation/resources_wafv2.go @@ -2,7 +2,7 @@ package cloudformation import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" ) @@ -33,7 +33,7 @@ func (rc *ResourceCreator) createWAFv2WebACL( acl, err := rc.backends.WAFv2.Backend.CreateWebACL( ctx, name, scope, "", - json.RawMessage(`{"Allow":{}}`), nil, + jsontext.Value(`{"Allow":{}}`), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, diff --git a/services/cloudformation/sam_api.go b/services/cloudformation/sam_api.go index 35154935f7..5739d68406 100644 --- a/services/cloudformation/sam_api.go +++ b/services/cloudformation/sam_api.go @@ -1,10 +1,12 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "maps" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -188,11 +190,11 @@ func (a *samAPI) stageProps(depID string) map[string]any { func (a *samAPI) body() (map[string]any, error) { doc := map[string]any{} if def := asMap(a.props["DefinitionBody"]); def != nil { - raw, err := json.Marshal(def) + raw, err := json.Marshal(def, jsonx.V1()) if err != nil { return nil, err } - if err = json.Unmarshal(raw, &doc); err != nil { + if err = json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return nil, err } } else { diff --git a/services/cloudformation/sam_events_test.go b/services/cloudformation/sam_events_test.go index d5ab71bba7..d82dfee656 100644 --- a/services/cloudformation/sam_events_test.go +++ b/services/cloudformation/sam_events_test.go @@ -2,7 +2,7 @@ package cloudformation_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -20,6 +20,7 @@ import ( cfnsdk "github.com/aws/aws-sdk-go-v2/service/cloudformation" "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" apigatewayv2backend "github.com/blackbirdworks/gopherstack/services/apigatewayv2" "github.com/blackbirdworks/gopherstack/services/cloudformation" @@ -54,12 +55,12 @@ func (r *recordingInvoker) InvokeFunction( "Version": "2012-10-17", "Statement": []any{map[string]any{ "Action": "execute-api:Invoke", "Effect": effect, "Resource": "*", }}, - }}) + }}, jsonx.V1()) return policy, http.StatusOK, nil } r.calls <- invocation{name: fn, payload: string(payload)} - resp, _ := json.Marshal(map[string]any{"statusCode": 200, "body": fn}) + resp, _ := json.Marshal(map[string]any{"statusCode": 200, "body": fn}, jsonx.V1()) return resp, http.StatusOK, nil } diff --git a/services/cloudformation/sam_misc.go b/services/cloudformation/sam_misc.go index ea165a9402..c68816bbb3 100644 --- a/services/cloudformation/sam_misc.go +++ b/services/cloudformation/sam_misc.go @@ -1,6 +1,10 @@ package cloudformation -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) func samTableAllowed() map[string]bool { return keySet("PrimaryKey", "ProvisionedThroughput", samKeyTableName, "Tags", "SSESpecification", @@ -134,7 +138,7 @@ func (t *samTranslator) translateStateMachine(id string, r map[string]any) error func smDefinition(id string, props, sm map[string]any) error { switch { case props["Definition"] != nil: - raw, err := json.Marshal(props["Definition"]) + raw, err := json.Marshal(props["Definition"], jsonx.V1()) if err != nil { return samErr(id, "Definition is not valid JSON: %v", err) } diff --git a/services/cloudformation/sam_statemachine_events_test.go b/services/cloudformation/sam_statemachine_events_test.go index 14b9ede776..35b530fdae 100644 --- a/services/cloudformation/sam_statemachine_events_test.go +++ b/services/cloudformation/sam_statemachine_events_test.go @@ -1,7 +1,7 @@ package cloudformation_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const samSMDefinition = ` Definition: @@ -47,7 +49,7 @@ func processedResources(t *testing.T, env *samEventEnv) map[string]map[string]an require.NoError( t, - json.Unmarshal([]byte(getTemplate(t, env.client, "sam-stack", types.TemplateStageProcessed)), &doc), + json.Unmarshal([]byte(getTemplate(t, env.client, "sam-stack", types.TemplateStageProcessed)), &doc, jsonx.V1()), ) return doc.Resources diff --git a/services/cloudformation/sam_transform.go b/services/cloudformation/sam_transform.go index f3177abd7a..bda238ffbc 100644 --- a/services/cloudformation/sam_transform.go +++ b/services/cloudformation/sam_transform.go @@ -3,12 +3,15 @@ package cloudformation import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -83,7 +86,7 @@ func samGetAtt(id, attr string) map[string]any { } func samHash(v any) string { - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) sum := sha256.Sum256(raw) return hex.EncodeToString(sum[:])[:samHashLen] @@ -361,7 +364,7 @@ func processedTemplateBody(body string) (string, error) { } else { delete(doc, "Transform") } - out, err := json.MarshalIndent(doc, "", " ") + out, err := json.Marshal(doc, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) return string(out), err } @@ -379,7 +382,7 @@ func expandSAMBody(body string) (string, error) { if err = expandSAMDocument(doc); err != nil { return "", err } - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) return string(out), err } @@ -394,7 +397,7 @@ func decodeTemplateDoc(body string) (map[string]any, error) { jsonBody = converted } var doc map[string]any - if err := json.Unmarshal([]byte(jsonBody), &doc); err != nil { + if err := json.Unmarshal([]byte(jsonBody), &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("failed to parse JSON template: %w", err) } diff --git a/services/cloudformation/stack_policy_eval.go b/services/cloudformation/stack_policy_eval.go index bdb8cfed6a..eed967eae2 100644 --- a/services/cloudformation/stack_policy_eval.go +++ b/services/cloudformation/stack_policy_eval.go @@ -1,10 +1,12 @@ package cloudformation import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Stack policy evaluation semantics below are transcribed from AWS's @@ -59,14 +61,14 @@ type stackPolicyStringSet []string func (s *stackPolicyStringSet) UnmarshalJSON(data []byte) error { var single string - if err := json.Unmarshal(data, &single); err == nil { + if err := json.Unmarshal(data, &single, jsonx.V1()); err == nil { *s = stackPolicyStringSet{single} return nil } var multi []string - if err := json.Unmarshal(data, &multi); err != nil { + if err := json.Unmarshal(data, &multi, jsonx.V1()); err != nil { return err } @@ -91,7 +93,7 @@ const ( // rather than silently never enforcing anything at UpdateStack time. func parseStackPolicyDocument(policy string) (*stackPolicyDocument, error) { var doc stackPolicyDocument - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("malformed stack policy: %w", err) } diff --git a/services/cloudformation/template.go b/services/cloudformation/template.go index 9a5d478237..fc7f0112f5 100644 --- a/services/cloudformation/template.go +++ b/services/cloudformation/template.go @@ -2,7 +2,7 @@ package cloudformation import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -18,6 +18,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" directconnectbackend "github.com/blackbirdworks/gopherstack/services/directconnect" ) @@ -106,7 +107,7 @@ func (t *Template) fromPlain(p templatePlain) { // the real CloudFormation template schema. func (t *Template) UnmarshalJSON(data []byte) error { var p templatePlain - if err := json.Unmarshal(data, &p); err != nil { + if err := json.Unmarshal(data, &p, jsonx.V1()); err != nil { return err } t.fromPlain(p) @@ -189,7 +190,7 @@ func (r *TemplateResource) UnmarshalJSON(data []byte) error { } var p plain - if err := json.Unmarshal(data, &p); err != nil { + if err := json.Unmarshal(data, &p, jsonx.V1()); err != nil { return err } @@ -301,7 +302,7 @@ func ParseTemplate(body string) (*Template, error) { } var tmpl Template - if err := json.Unmarshal([]byte(body), &tmpl); err != nil { + if err := json.Unmarshal([]byte(body), &tmpl, jsonx.V1()); err != nil { return nil, fmt.Errorf("failed to parse JSON template: %w", err) } @@ -360,7 +361,7 @@ func yamlToJSON(body string) (string, error) { return "", err } - out, err := json.Marshal(val) + out, err := json.Marshal(val, jsonx.V1()) if err != nil { return "", fmt.Errorf("failed to serialise normalised YAML template: %w", err) } @@ -492,7 +493,7 @@ func expandLanguageExtensions(body string) (string, error) { } doc["Resources"] = expanded - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return "", fmt.Errorf("failed to serialise expanded template: %w", err) } @@ -513,7 +514,7 @@ func parseGenericTemplate(body string) (map[string]any, error) { } var doc map[string]any - if err := json.Unmarshal([]byte(trimmed), &doc); err != nil { + if err := json.Unmarshal([]byte(trimmed), &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("failed to parse JSON template: %w", err) } @@ -1073,7 +1074,7 @@ func resolveMiscIntrinsic(val map[string]any, ctx resolveCtx) string { // Fn::ToJsonString: serialize value to JSON string (#13) if jsonVal, hasJSON := val["Fn::ToJsonString"]; hasJSON { - data, err := json.Marshal(jsonVal) + data, err := json.Marshal(jsonVal, jsonx.V1()) if err != nil { return "" } @@ -1914,7 +1915,7 @@ func invokeMacroTransform(macroName string, transformSpec map[string]any, ctx re } // Build a minimal fragment from the transform spec parameters. - fragmentJSON, err := json.Marshal(resolvedParams) + fragmentJSON, err := json.Marshal(resolvedParams, jsonx.V1()) if err != nil { return "" } diff --git a/services/cloudformation/template_preflight.go b/services/cloudformation/template_preflight.go index 59687e5ed7..c4045b4cc8 100644 --- a/services/cloudformation/template_preflight.go +++ b/services/cloudformation/template_preflight.go @@ -1,13 +1,14 @@ package cloudformation import ( - "encoding/json" "errors" "reflect" "regexp" "slices" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrTemplateFormat is the "Template format error: ..." family real @@ -65,18 +66,18 @@ func lineColumn(body string, offset int64) (int, int) { // templateParseFormatErr maps a ParseTemplate failure to AWS's synchronous // "Template format error" wording; non-syntax failures pass through unchanged. func templateParseFormatErr(body string, err error) error { - if syn, ok := errors.AsType[*json.SyntaxError](err); ok { - line, col := lineColumn(strings.TrimSpace(body), syn.Offset) + if offset, ok := jsonx.SyntaxError(err); ok { + line, col := lineColumn(strings.TrimSpace(body), offset) return templateFormatErr("JSON not well-formed. (line %d, column %d)", line, col) } - if typ, ok := errors.AsType[*json.UnmarshalTypeError](err); ok { - if strings.HasSuffix(typ.Field, "Description") { + if field, ok := jsonx.TypeError(err); ok { + if strings.HasSuffix(field, "Description") { return templateFormatErr("Every Description member must be a string.") } - return templateFormatErr("%s has an invalid type", typ.Field) + return templateFormatErr("%s has an invalid type", field) } if strings.Contains(err.Error(), "failed to parse YAML template") { diff --git a/services/cloudformation/template_strictness.go b/services/cloudformation/template_strictness.go index fe5707dcf2..05e949eb03 100644 --- a/services/cloudformation/template_strictness.go +++ b/services/cloudformation/template_strictness.go @@ -2,12 +2,14 @@ package cloudformation import ( _ "embed" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "sort" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed cfn_resource_services.json @@ -22,7 +24,7 @@ var ( func knownResourceServices() map[string]struct{} { cfnResourceServicesOnce.Do(func() { var raw []string - if err := json.Unmarshal(cfnResourceServicesJSON, &raw); err != nil { + if err := json.Unmarshal(cfnResourceServicesJSON, &raw, jsonx.V1()); err != nil { panic("cloudformation: invalid embedded cfn_resource_services.json: " + err.Error()) } @@ -272,7 +274,7 @@ var ( // every other type stays unchecked rather than guessed. func requiredResourceProperties() map[string][]string { cfnRequiredPropertiesOnce.Do(func() { - if err := json.Unmarshal(cfnRequiredPropertiesJSON, &cfnRequiredProperties); err != nil { + if err := json.Unmarshal(cfnRequiredPropertiesJSON, &cfnRequiredProperties, jsonx.V1()); err != nil { panic("cloudformation: invalid embedded cfn_required_properties.json: " + err.Error()) } }) diff --git a/services/cloudfront/dataplane_edge.go b/services/cloudfront/dataplane_edge.go index f34aac37f4..31f97d097b 100644 --- a/services/cloudfront/dataplane_edge.go +++ b/services/cloudfront/dataplane_edge.go @@ -3,13 +3,15 @@ package cloudfront import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "slices" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -246,7 +248,7 @@ func (h *Handler) edgeInvoke( } } - payload, _ := json.Marshal(map[string]any{"Records": []any{map[string]any{"cf": cf}}}) + payload, _ := json.Marshal(map[string]any{"Records": []any{map[string]any{"cf": cf}}}, jsonx.V1()) res, fnErr, err := edge.InvokeEdge(ctx, arn.name, arn.version, payload) @@ -264,7 +266,7 @@ func (h *Handler) edgeInvoke( } var out map[string]any - if json.Unmarshal(res, &out) != nil || out == nil { + if json.Unmarshal(res, &out, jsonx.V1()) != nil || out == nil { return nil, edgeValidation( "The Lambda function returned a value that is not a valid request or response object.", ) diff --git a/services/cloudfront/dataplane_function.go b/services/cloudfront/dataplane_function.go index 5f26a50e0e..0ca63448de 100644 --- a/services/cloudfront/dataplane_function.go +++ b/services/cloudfront/dataplane_function.go @@ -1,12 +1,14 @@ package cloudfront import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/url" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -115,7 +117,7 @@ func (h *Handler) runFunction( } var out map[string]any - if err := json.Unmarshal([]byte(res.Output), &out); err != nil { + if err := json.Unmarshal([]byte(res.Output), &out, jsonx.V1()); err != nil { return nil, res, false } diff --git a/services/cloudfront/dataplane_s3_policy.go b/services/cloudfront/dataplane_s3_policy.go index b9c27e2788..7e3fa673fd 100644 --- a/services/cloudfront/dataplane_s3_policy.go +++ b/services/cloudfront/dataplane_s3_policy.go @@ -1,9 +1,12 @@ package cloudfront import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -39,7 +42,7 @@ const ( ) type policyDoc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } type policyStatement struct { @@ -52,17 +55,17 @@ type policyStatement struct { func parseStatements(policyJSON string) []policyStatement { var doc policyDoc - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return nil } var many []policyStatement - if err := json.Unmarshal(doc.Statement, &many); err == nil { + if err := json.Unmarshal(doc.Statement, &many, jsonx.V1()); err == nil { return many } var one policyStatement - if err := json.Unmarshal(doc.Statement, &one); err == nil { + if err := json.Unmarshal(doc.Statement, &one, jsonx.V1()); err == nil { return []policyStatement{one} } diff --git a/services/cloudfront/dataplane_signing.go b/services/cloudfront/dataplane_signing.go index 86f91e57d9..ab2770938f 100644 --- a/services/cloudfront/dataplane_signing.go +++ b/services/cloudfront/dataplane_signing.go @@ -6,13 +6,15 @@ import ( "crypto/sha1" //nolint:gosec // CloudFront signed URLs mandate RSA-SHA1 "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "net" "net/http" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func isSignedQueryKey(name string) bool { @@ -224,7 +226,7 @@ func cannedPolicyDoc(resource string, expires int64) ([]byte, error) { return json.Marshal(cannedPolicy{Statement: []cannedStatement{{ Resource: resource, Condition: cannedCondition{DateLessThan: cannedEpoch{EpochTime: expires}}, - }}}) + }}}, jsonx.V1()) } func rsaSHA1Verify(key *rsa.PublicKey, doc, sig []byte) bool { @@ -240,7 +242,7 @@ func verifyCustomPolicy(v *viewerReq, d *Distribution, key *rsa.PublicKey, polic } var pol signedPolicy - if json.Unmarshal(doc, &pol) != nil || len(pol.Statement) == 0 { + if json.Unmarshal(doc, &pol, jsonx.V1()) != nil || len(pol.Statement) == 0 { return false } diff --git a/services/cloudfront/dataplane_signing_internal_test.go b/services/cloudfront/dataplane_signing_internal_test.go index 7964ec99b7..7e30c91562 100644 --- a/services/cloudfront/dataplane_signing_internal_test.go +++ b/services/cloudfront/dataplane_signing_internal_test.go @@ -1,11 +1,13 @@ package cloudfront import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCannedPolicyDoc(t *testing.T) { @@ -37,7 +39,7 @@ func TestCannedPolicyDoc(t *testing.T) { doc, err := cannedPolicyDoc(tt.resource, 42) require.NoError(t, err) assert.Equal(t, tt.want, string(doc)) - assert.True(t, json.Valid(doc)) + assert.True(t, jsontext.Value(doc).IsValid(jsonx.V1())) }) } } diff --git a/services/cloudfront/distributions_transition_test.go b/services/cloudfront/distributions_transition_test.go index 08464133f4..f060b6d5f8 100644 --- a/services/cloudfront/distributions_transition_test.go +++ b/services/cloudfront/distributions_transition_test.go @@ -1,12 +1,13 @@ package cloudfront_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudfront" ) @@ -131,7 +132,7 @@ func TestDeleteDistribution_CleansUpSideMaps(t *testing.T) { require.NotEmpty(t, data) var probe distSnapshotProbe - require.NoError(t, json.Unmarshal(data, &probe)) + require.NoError(t, json.Unmarshal(data, &probe, jsonx.V1())) _, leaked := probe.FuncAssocs[d.ID] require.False(t, leaked, "distributionFunctionAssociations must not retain a deleted distribution's entry") diff --git a/services/cloudfront/function_exec.go b/services/cloudfront/function_exec.go index c8befa45ca..03461c20b5 100644 --- a/services/cloudfront/function_exec.go +++ b/services/cloudfront/function_exec.go @@ -1,13 +1,15 @@ package cloudfront import ( - "encoding/json" + "encoding/json/v2" "fmt" "html" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // storeKVS adapts the backend's Key Value Stores to a running function. @@ -74,7 +76,7 @@ func (b *InMemoryBackend) ExecuteFunction(name, stage string, event any) (*Funct func (h *Handler) runTestFunction(c *echo.Context, name, stage string, eventObject []byte) error { var event any - if err := json.Unmarshal(eventObject, &event); err != nil { + if err := json.Unmarshal(eventObject, &event, jsonx.V1()); err != nil { return xmlResp(c, http.StatusBadRequest, cfErrorXML("InvalidArgument", "EventObject must be valid JSON")) } diff --git a/services/cloudfront/handler_distribution_tenants.go b/services/cloudfront/handler_distribution_tenants.go index f77e1f182b..954f57e886 100644 --- a/services/cloudfront/handler_distribution_tenants.go +++ b/services/cloudfront/handler_distribution_tenants.go @@ -1,7 +1,7 @@ package cloudfront import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -11,6 +11,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDomainAssociationError maps UpdateDomainAssociation errors. Its own @@ -194,13 +196,13 @@ func (c *tenantCustomizationsXML) toMap() map[string]any { return nil } - raw, err := json.Marshal(c) + raw, err := json.Marshal(c, jsonx.V1()) if err != nil { return nil } var out map[string]any - if json.Unmarshal(raw, &out) != nil { + if json.Unmarshal(raw, &out, jsonx.V1()) != nil { return nil } @@ -212,13 +214,13 @@ func tenantCustomizationsFromMap(m map[string]any) *tenantCustomizationsXML { return nil } - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil } var out tenantCustomizationsXML - if json.Unmarshal(raw, &out) != nil { + if json.Unmarshal(raw, &out, jsonx.V1()) != nil { return nil } diff --git a/services/cloudfront/handler_functions.go b/services/cloudfront/handler_functions.go index 538931e81c..5fd0936fb3 100644 --- a/services/cloudfront/handler_functions.go +++ b/services/cloudfront/handler_functions.go @@ -2,13 +2,15 @@ package cloudfront import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" "encoding/xml" "fmt" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type functionConfigXML struct { @@ -383,7 +385,7 @@ func (h *Handler) handleTestFunction(c *echo.Context, name string) error { ) } - if !json.Valid(eventObject) { + if !jsontext.Value(eventObject).IsValid(jsonx.V1()) { return xmlResp( c, http.StatusBadRequest, diff --git a/services/cloudfront/handler_key_value_store_test.go b/services/cloudfront/handler_key_value_store_test.go index 68b7458743..6e8c188a41 100644 --- a/services/cloudfront/handler_key_value_store_test.go +++ b/services/cloudfront/handler_key_value_store_test.go @@ -1,7 +1,7 @@ package cloudfront_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudfront" ) @@ -412,7 +413,7 @@ func TestInMemoryBackend_KeyValueStore(t *testing.T) { KeyValueStoreData map[string]map[string]string `json:"keyValueStoreData"` KeyValueDataETags map[string]string `json:"keyValueDataETags"` } - require.NoError(t, json.Unmarshal(snap, &decoded)) + require.NoError(t, json.Unmarshal(snap, &decoded, jsonx.V1())) assert.NotContains(t, decoded.KeyValueStoreData, kvs.ID, "deleted KVS's data-plane values must not survive in the persisted snapshot") assert.NotContains(t, decoded.KeyValueDataETags, kvs.ID, diff --git a/services/cloudfront/kvs_import.go b/services/cloudfront/kvs_import.go index 0290080d45..01ce59e584 100644 --- a/services/cloudfront/kvs_import.go +++ b/services/cloudfront/kvs_import.go @@ -2,9 +2,11 @@ package cloudfront import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -64,7 +66,7 @@ func (h *Handler) readKVSImport(ctx context.Context, src *kvsImportSourceXML) ([ func parseKVSImport(raw []byte) ([]*KVSItem, error) { var doc kvsImportDocument - if err := json.Unmarshal(raw, &doc); err != nil { + if err := json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: import source is not valid JSON: %w", ErrValidation, err) } diff --git a/services/cloudfront/lambda_edge_test.go b/services/cloudfront/lambda_edge_test.go index 8d6be91e34..5505e2da27 100644 --- a/services/cloudfront/lambda_edge_test.go +++ b/services/cloudfront/lambda_edge_test.go @@ -3,7 +3,7 @@ package cloudfront_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudfront" ) @@ -48,7 +49,7 @@ func (f *fakeEdge) InvokeEdge(_ context.Context, name, version string, payload [ } `json:"Records"` } - if err := json.Unmarshal(payload, &in); err != nil || len(in.Records) != 1 { + if err := json.Unmarshal(payload, &in, jsonx.V1()); err != nil || len(in.Records) != 1 { return nil, "", fmt.Errorf("%w: bad event", errFakeEdge) } @@ -62,7 +63,7 @@ func (f *fakeEdge) InvokeEdge(_ context.Context, name, version string, payload [ } `json:"Records"` } - _ = json.Unmarshal(payload, &snapshot) + _ = json.Unmarshal(payload, &snapshot, jsonx.V1()) f.mu.Lock() f.calls[name]++ @@ -75,7 +76,7 @@ func (f *fakeEdge) InvokeEdge(_ context.Context, name, version string, payload [ return nil, fnErr, err } - b, _ := json.Marshal(out) + b, _ := json.Marshal(out, jsonx.V1()) return b, "", nil } diff --git a/services/cloudfront/persistence.go b/services/cloudfront/persistence.go index 715b1c985a..1c1af6d512 100644 --- a/services/cloudfront/persistence.go +++ b/services/cloudfront/persistence.go @@ -2,11 +2,13 @@ package cloudfront import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -112,7 +114,7 @@ var dirtyTableNames = struct { // registerAllTables doc for the clean/dirty split), merged into one map so a // single Tables blob round-trips the whole backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` DistributionFunctionAssociations map[string][]FunctionAssociation `json:"distributionFunctionAssociations,omitempty"` DistributionAliases map[string][]string `json:"distributionAliases,omitempty"` @@ -199,7 +201,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "cloudfront: Snapshot marshal failure", "error", err) @@ -277,7 +279,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // b.registry -- see store_setup.go's registerAllTables doc) from an ephemeral DTO // [store.Registry] built from tables, converting each DTO back to a live // Invalidation. Must be called with the lock held. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtoReg := store.NewRegistry() invalidationDTOs := store.Register(dtoReg, dirtyTableNames.invalidations, store.New(invalidationSnapshotKey)) tenantInvalidationDTOs := store.Register( @@ -550,7 +552,7 @@ func tableFrom[T any](snap *backendSnapshot, name string) []*T { } var out []*T - if err := json.Unmarshal(raw, &out); err != nil { + if err := json.Unmarshal(raw, &out, jsonx.V1()); err != nil { return nil } diff --git a/services/cloudfrontkeyvaluestore/handler.go b/services/cloudfrontkeyvaluestore/handler.go index b8d025af7e..cd15e2b7c3 100644 --- a/services/cloudfrontkeyvaluestore/handler.go +++ b/services/cloudfrontkeyvaluestore/handler.go @@ -6,7 +6,7 @@ package cloudfrontkeyvaluestore import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strconv" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -451,7 +452,7 @@ func (h *Handler) handlePutKey(c *echo.Context, kvs *cloudfrontbackend.KeyValueS var req putKeyInput if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeAWSError(c, http.StatusBadRequest, exceptionValidation, "invalid JSON body") } } @@ -549,7 +550,7 @@ func (h *Handler) handleUpdateKeys(c *echo.Context, kvs *cloudfrontbackend.KeyVa var req updateKeysInput if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeAWSError(c, http.StatusBadRequest, exceptionValidation, "invalid JSON body") } } @@ -656,7 +657,7 @@ func epochSeconds(rfc3339 string) float64 { // typical REST delete, their outputs carry a body, so 204 No Content is not // an option). func writeJSON(c *echo.Context, v any) error { - body, err := json.Marshal(v) + body, err := json.Marshal(v, jsonx.V1()) if err != nil { return writeAWSError(c, http.StatusInternalServerError, exceptionInternalServer, "response marshal failed") } diff --git a/services/cloudhsmv2/backups.go b/services/cloudhsmv2/backups.go index c87ed61208..13875e07e2 100644 --- a/services/cloudhsmv2/backups.go +++ b/services/cloudhsmv2/backups.go @@ -7,12 +7,13 @@ import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "slices" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -110,7 +111,7 @@ func policyGrants(policy, account string) bool { Effect string `json:"Effect"` } `json:"Statement"` } - if json.Unmarshal([]byte(policy), &doc) != nil { + if json.Unmarshal([]byte(policy), &doc, jsonx.V1()) != nil { return false } diff --git a/services/cloudhsmv2/handler.go b/services/cloudhsmv2/handler.go index c9c6c786e1..f178614d76 100644 --- a/services/cloudhsmv2/handler.go +++ b/services/cloudhsmv2/handler.go @@ -2,7 +2,7 @@ package cloudhsmv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -36,7 +37,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrInvalidRequest, "malformed request body: %v", err) } } diff --git a/services/cloudhsmv2/persistence.go b/services/cloudhsmv2/persistence.go index f3efa4b1ed..ba92833366 100644 --- a/services/cloudhsmv2/persistence.go +++ b/services/cloudhsmv2/persistence.go @@ -2,7 +2,7 @@ package cloudhsmv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/cloudhsmv2/policies.go b/services/cloudhsmv2/policies.go index 39a851ac7d..14d3087bc2 100644 --- a/services/cloudhsmv2/policies.go +++ b/services/cloudhsmv2/policies.go @@ -2,8 +2,11 @@ package cloudhsmv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (b *InMemoryBackend) policyBackupLocked(sc scope, resourceARN string) (*Backup, error) { @@ -21,10 +24,10 @@ func (b *InMemoryBackend) policyBackupLocked(sc scope, resourceARN string) (*Bac // PutResourcePolicy attaches a policy to a READY backup the caller owns. func (b *InMemoryBackend) PutResourcePolicy(ctx context.Context, resourceARN, policy string) (string, error) { var doc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if policy == "" || json.Unmarshal([]byte(policy), &doc) != nil || len(doc.Statement) == 0 { + if policy == "" || json.Unmarshal([]byte(policy), &doc, jsonx.V1()) != nil || len(doc.Statement) == 0 { return "", fail(ErrInvalidRequest, "Policy must be a JSON policy document with a Statement") } diff --git a/services/cloudtrail/delivery.go b/services/cloudtrail/delivery.go index a9774446d5..19aa741590 100644 --- a/services/cloudtrail/delivery.go +++ b/services/cloudtrail/delivery.go @@ -4,7 +4,8 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" "sync" @@ -13,6 +14,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // logFileRecords is the top-level shape of a CloudTrail log file: a single @@ -20,7 +23,7 @@ import ( // User Guide "CloudTrail log file examples"), not part of the pinned SDK -- // see PARITY.md. type logFileRecords struct { - Records []json.RawMessage `json:"Records"` + Records []jsontext.Value `json:"Records"` } // deliveryTarget is a snapshot of the fields deliverLogFile needs from one @@ -107,7 +110,7 @@ var gzipWriterPool = sync.Pool{ //nolint:gochecknoglobals // sync.Pool requires // logFileBody gzip-compresses a single-record CloudTrail log file body. func logFileBody(ev Event) ([]byte, error) { encoded, err := json.Marshal( - logFileRecords{Records: []json.RawMessage{json.RawMessage(ev.CloudTrailEvent)}}, + logFileRecords{Records: []jsontext.Value{jsontext.Value(ev.CloudTrailEvent)}}, jsonx.V1(), ) if err != nil { return nil, err diff --git a/services/cloudtrail/handler.go b/services/cloudtrail/handler.go index 1c3ed6b8aa..59216625ce 100644 --- a/services/cloudtrail/handler.go +++ b/services/cloudtrail/handler.go @@ -1,7 +1,8 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -184,9 +186,9 @@ func (h *Handler) Handler() echo.HandlerFunc { var body []byte if c.Request().Body != nil { - decoder := json.NewDecoder(c.Request().Body) - var raw json.RawMessage - if err := decoder.Decode(&raw); err == nil { + decoder := jsontext.NewDecoder(c.Request().Body) + var raw jsontext.Value + if err := json.UnmarshalDecode(decoder, &raw, jsonx.V1()); err == nil { body = raw } } diff --git a/services/cloudtrail/handler_channels.go b/services/cloudtrail/handler_channels.go index 664471deed..c783e5497f 100644 --- a/services/cloudtrail/handler_channels.go +++ b/services/cloudtrail/handler_channels.go @@ -1,11 +1,12 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -27,7 +28,7 @@ type createChannelBody struct { func (h *Handler) handleCreateChannel(c *echo.Context, body []byte) error { var in createChannelBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -81,7 +82,7 @@ type deleteChannelBody struct { func (h *Handler) handleDeleteChannel(c *echo.Context, body []byte) error { var in deleteChannelBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -100,7 +101,7 @@ type getChannelBody struct { func (h *Handler) handleGetChannel(c *echo.Context, body []byte) error { var in getChannelBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -127,7 +128,7 @@ type updateChannelBody struct { func (h *Handler) handleUpdateChannel(c *echo.Context, body []byte) error { var in updateChannelBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -154,7 +155,7 @@ type listChannelsBody struct { func (h *Handler) handleListChannels(c *echo.Context, body []byte) error { var in listChannelsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), diff --git a/services/cloudtrail/handler_dashboards.go b/services/cloudtrail/handler_dashboards.go index e6d3c9c8c3..284b4b545f 100644 --- a/services/cloudtrail/handler_dashboards.go +++ b/services/cloudtrail/handler_dashboards.go @@ -1,12 +1,13 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -74,7 +75,7 @@ type createDashboardBody struct { func (h *Handler) handleCreateDashboard(c *echo.Context, body []byte) error { var in createDashboardBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -104,7 +105,7 @@ type deleteDashboardBody struct { func (h *Handler) handleDeleteDashboard(c *echo.Context, body []byte) error { var in deleteDashboardBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -123,7 +124,7 @@ type getDashboardBody struct { func (h *Handler) handleGetDashboard(c *echo.Context, body []byte) error { var in getDashboardBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -151,7 +152,7 @@ type updateDashboardBody struct { func (h *Handler) handleUpdateDashboard(c *echo.Context, body []byte) error { var in updateDashboardBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -180,7 +181,7 @@ type listDashboardsBody struct { func (h *Handler) handleListDashboards(c *echo.Context, body []byte) error { var in listDashboardsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -232,7 +233,7 @@ type startDashboardRefreshBody struct { func (h *Handler) handleStartDashboardRefresh(c *echo.Context, body []byte) error { var in startDashboardRefreshBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_event_data_stores.go b/services/cloudtrail/handler_event_data_stores.go index 4e1112ebe5..e5314f57af 100644 --- a/services/cloudtrail/handler_event_data_stores.go +++ b/services/cloudtrail/handler_event_data_stores.go @@ -1,11 +1,12 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -33,7 +34,7 @@ type createEventDataStoreBody struct { func (h *Handler) handleCreateEventDataStore(c *echo.Context, body []byte) error { var in createEventDataStoreBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -74,7 +75,7 @@ type deleteEventDataStoreBody struct { func (h *Handler) handleDeleteEventDataStore(c *echo.Context, body []byte) error { var in deleteEventDataStoreBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -93,7 +94,7 @@ type getEventDataStoreBody struct { func (h *Handler) handleGetEventDataStore(c *echo.Context, body []byte) error { var in getEventDataStoreBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -121,7 +122,7 @@ type updateEventDataStoreBody struct { func (h *Handler) handleUpdateEventDataStore(c *echo.Context, body []byte) error { var in updateEventDataStoreBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -150,7 +151,7 @@ type listEventDataStoresBody struct { func (h *Handler) handleListEventDataStores(c *echo.Context, body []byte) error { var in listEventDataStoresBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -186,7 +187,7 @@ type restoreEventDataStoreBody struct { func (h *Handler) handleRestoreEventDataStore(c *echo.Context, body []byte) error { var in restoreEventDataStoreBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -206,7 +207,7 @@ type startEventDataStoreIngestionBody struct { func (h *Handler) handleStartEventDataStoreIngestion(c *echo.Context, body []byte) error { var in startEventDataStoreIngestionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -225,7 +226,7 @@ type stopEventDataStoreIngestionBody struct { func (h *Handler) handleStopEventDataStoreIngestion(c *echo.Context, body []byte) error { var in stopEventDataStoreIngestionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -244,7 +245,7 @@ type disableFederationBody struct { func (h *Handler) handleDisableFederation(c *echo.Context, body []byte) error { var in disableFederationBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -275,7 +276,7 @@ type enableFederationBody struct { func (h *Handler) handleEnableFederation(c *echo.Context, body []byte) error { var in enableFederationBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_event_selectors.go b/services/cloudtrail/handler_event_selectors.go index 5e68e4ba3e..36fa093faa 100644 --- a/services/cloudtrail/handler_event_selectors.go +++ b/services/cloudtrail/handler_event_selectors.go @@ -1,12 +1,13 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -53,7 +54,7 @@ type putEventSelectorsBody struct { func (h *Handler) handlePutEventSelectors(c *echo.Context, body []byte) error { var in putEventSelectorsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -95,7 +96,7 @@ type getEventSelectorsBody struct { func (h *Handler) handleGetEventSelectors(c *echo.Context, body []byte) error { var in getEventSelectorsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -133,7 +134,7 @@ type putInsightSelectorsBody struct { func (h *Handler) handlePutInsightSelectors(c *echo.Context, body []byte) error { var in putInsightSelectorsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -182,7 +183,7 @@ type getInsightSelectorsBody struct { func (h *Handler) handleGetInsightSelectors(c *echo.Context, body []byte) error { var in getInsightSelectorsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -229,7 +230,7 @@ type getEventConfigurationBody struct { func (h *Handler) handleGetEventConfiguration(c *echo.Context, body []byte) error { var in getEventConfigurationBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -255,7 +256,7 @@ type putEventConfigurationBody struct { func (h *Handler) handlePutEventConfiguration(c *echo.Context, body []byte) error { var in putEventConfigurationBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -336,7 +337,7 @@ type listInsightsDataBody struct { func (h *Handler) handleListInsightsData(c *echo.Context, body []byte) error { var in listInsightsDataBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -382,7 +383,7 @@ type listInsightsMetricDataBody struct { func (h *Handler) handleListInsightsMetricData(c *echo.Context, body []byte) error { var in listInsightsMetricDataBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } if in.EventName == "" { diff --git a/services/cloudtrail/handler_events.go b/services/cloudtrail/handler_events.go index 72354ef6bd..6018e849bb 100644 --- a/services/cloudtrail/handler_events.go +++ b/services/cloudtrail/handler_events.go @@ -1,7 +1,7 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "math" "net/http" "time" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- LookupEvents --- @@ -25,7 +26,7 @@ type lookupEventsBody struct { func (h *Handler) handleLookupEvents(c *echo.Context, body []byte) error { var in lookupEventsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), diff --git a/services/cloudtrail/handler_imports.go b/services/cloudtrail/handler_imports.go index c090c0fb80..fcadc39111 100644 --- a/services/cloudtrail/handler_imports.go +++ b/services/cloudtrail/handler_imports.go @@ -1,7 +1,7 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "time" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -65,7 +66,7 @@ func (in startImportBody) toBackendImportSource() *ImportSource { func (h *Handler) handleStartImport(c *echo.Context, body []byte) error { var in startImportBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -101,7 +102,7 @@ type getImportBody struct { func (h *Handler) handleGetImport(c *echo.Context, body []byte) error { var in getImportBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -125,7 +126,7 @@ type listImportsBody struct { func (h *Handler) handleListImports(c *echo.Context, body []byte) error { var in listImportsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -182,7 +183,7 @@ type stopImportBody struct { func (h *Handler) handleStopImport(c *echo.Context, body []byte) error { var in stopImportBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -233,7 +234,7 @@ type listImportFailuresBody struct { func (h *Handler) handleListImportFailures(c *echo.Context, body []byte) error { var in listImportFailuresBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_queries.go b/services/cloudtrail/handler_queries.go index c6900281dc..ffbc9b79ac 100644 --- a/services/cloudtrail/handler_queries.go +++ b/services/cloudtrail/handler_queries.go @@ -1,13 +1,14 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -36,7 +37,7 @@ type startQueryBody struct { func (h *Handler) handleStartQuery(c *echo.Context, body []byte) error { var in startQueryBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -87,7 +88,7 @@ type cancelQueryBody struct { func (h *Handler) handleCancelQuery(c *echo.Context, body []byte) error { var in cancelQueryBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -125,7 +126,7 @@ type describeQueryBody struct { func (h *Handler) handleDescribeQuery(c *echo.Context, body []byte) error { var in describeQueryBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -188,7 +189,7 @@ type getQueryResultsBody struct { func (h *Handler) handleGetQueryResults(c *echo.Context, body []byte) error { var in getQueryResultsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -241,7 +242,7 @@ type listQueriesBody struct { func (h *Handler) handleListQueries(c *echo.Context, body []byte) error { var in listQueriesBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -329,7 +330,7 @@ type generateQueryBody struct { func (h *Handler) handleGenerateQuery(c *echo.Context, body []byte) error { var in generateQueryBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_resource_policies.go b/services/cloudtrail/handler_resource_policies.go index a7f93d7db4..ef84552a63 100644 --- a/services/cloudtrail/handler_resource_policies.go +++ b/services/cloudtrail/handler_resource_policies.go @@ -1,10 +1,12 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- DeleteResourcePolicy --- @@ -15,7 +17,7 @@ type deleteResourcePolicyBody struct { func (h *Handler) handleDeleteResourcePolicy(c *echo.Context, body []byte) error { var in deleteResourcePolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -34,7 +36,7 @@ type getResourcePolicyBody struct { func (h *Handler) handleGetResourcePolicy(c *echo.Context, body []byte) error { var in getResourcePolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -58,7 +60,7 @@ type putResourcePolicyBody struct { func (h *Handler) handlePutResourcePolicy(c *echo.Context, body []byte) error { var in putResourcePolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_tags.go b/services/cloudtrail/handler_tags.go index 44325dafa7..747dd87297 100644 --- a/services/cloudtrail/handler_tags.go +++ b/services/cloudtrail/handler_tags.go @@ -1,10 +1,12 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- AddTags --- @@ -19,7 +21,7 @@ type addTagsBody struct { func (h *Handler) handleAddTags(c *echo.Context, body []byte) error { var in addTagsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -46,7 +48,7 @@ type removeTagsBody struct { func (h *Handler) handleRemoveTags(c *echo.Context, body []byte) error { var in removeTagsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -70,7 +72,7 @@ type listTagsBody struct { func (h *Handler) handleListTags(c *echo.Context, body []byte) error { var in listTagsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/handler_test.go b/services/cloudtrail/handler_test.go index bf4f236574..71fd921b31 100644 --- a/services/cloudtrail/handler_test.go +++ b/services/cloudtrail/handler_test.go @@ -2,7 +2,8 @@ package cloudtrail_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudtrail" ) @@ -34,7 +36,7 @@ func doCloudTrailOp( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -55,7 +57,7 @@ func parseCloudTrailResp(t *testing.T, rec *httptest.ResponseRecorder) map[strin t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -535,7 +537,7 @@ func TestCloudTrailListOperationsSmoke(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code, "op %s should return 200", op) var m map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &m, jsonx.V1())) } // ListQueries takes EventDataStore as required input (real @@ -551,7 +553,7 @@ func TestCloudTrailListOperationsSmoke(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code, "op ListQueries should return 200") var m map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &m, jsonx.V1())) } // TestCloudTrailAncillaryOperationsSmoke covers ancillary read-only handlers diff --git a/services/cloudtrail/handler_trails.go b/services/cloudtrail/handler_trails.go index ed8f58559a..60bf5429e8 100644 --- a/services/cloudtrail/handler_trails.go +++ b/services/cloudtrail/handler_trails.go @@ -1,12 +1,13 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -44,7 +45,7 @@ type createTrailBody struct { func (h *Handler) handleCreateTrail(c *echo.Context, body []byte) error { var in createTrailBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -87,7 +88,7 @@ type getTrailBody struct { func (h *Handler) handleGetTrail(c *echo.Context, body []byte) error { var in getTrailBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -109,7 +110,7 @@ type describeTrailsBody struct { func (h *Handler) handleDescribeTrails(c *echo.Context, body []byte) error { var in describeTrailsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -144,7 +145,7 @@ type updateTrailBody struct { func (h *Handler) handleUpdateTrail(c *echo.Context, body []byte) error { var in updateTrailBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -176,7 +177,7 @@ type deleteTrailBody struct { func (h *Handler) handleDeleteTrail(c *echo.Context, body []byte) error { var in deleteTrailBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -195,7 +196,7 @@ type startLoggingBody struct { func (h *Handler) handleStartLogging(c *echo.Context, body []byte) error { var in startLoggingBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -218,7 +219,7 @@ type stopLoggingBody struct { func (h *Handler) handleStopLogging(c *echo.Context, body []byte) error { var in stopLoggingBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -241,7 +242,7 @@ type getTrailStatusBody struct { func (h *Handler) handleGetTrailStatus(c *echo.Context, body []byte) error { var in getTrailStatusBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -277,7 +278,7 @@ type listTrailsBody struct { func (h *Handler) handleListTrails(c *echo.Context, body []byte) error { var in listTrailsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body"), @@ -313,7 +314,7 @@ type deregisterOrgDelegatedAdminBody struct { func (h *Handler) handleDeregisterOrganizationDelegatedAdmin(c *echo.Context, body []byte) error { var in deregisterOrgDelegatedAdminBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } @@ -332,7 +333,7 @@ type registerOrgDelegatedAdminBody struct { func (h *Handler) handleRegisterOrganizationDelegatedAdmin(c *echo.Context, body []byte) error { var in registerOrgDelegatedAdminBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterCombinationException", "invalid request body")) } diff --git a/services/cloudtrail/insights_metrics.go b/services/cloudtrail/insights_metrics.go index acbd6db797..5e3989f2e3 100644 --- a/services/cloudtrail/insights_metrics.go +++ b/services/cloudtrail/insights_metrics.go @@ -1,11 +1,13 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -80,7 +82,7 @@ func eventErrorCode(ev Event) string { ErrorCode string `json:"errorCode"` } - if ev.CloudTrailEvent == "" || json.Unmarshal([]byte(ev.CloudTrailEvent), &rec) != nil { + if ev.CloudTrailEvent == "" || json.Unmarshal([]byte(ev.CloudTrailEvent), &rec, jsonx.V1()) != nil { return "" } diff --git a/services/cloudtrail/management_event.go b/services/cloudtrail/management_event.go index c4870507c9..18b93a1bc6 100644 --- a/services/cloudtrail/management_event.go +++ b/services/cloudtrail/management_event.go @@ -1,12 +1,13 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "strconv" "time" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -126,7 +127,7 @@ func (b *InMemoryBackend) RecordManagementEvent(ev service.CloudTrailEventInput) // common case where every string is plain printable ASCII needing no escaping. func renderManagementEvent(d *managementEventDetail) string { if !d.plainStrings() { - blob, err := json.Marshal(d) + blob, err := json.Marshal(d, jsonx.V1()) if err != nil { return "" } diff --git a/services/cloudtrail/management_event_render_whitebox_test.go b/services/cloudtrail/management_event_render_whitebox_test.go index 180805f302..00aa993623 100644 --- a/services/cloudtrail/management_event_render_whitebox_test.go +++ b/services/cloudtrail/management_event_render_whitebox_test.go @@ -1,10 +1,12 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRenderManagementEventMatchesJSONMarshal(t *testing.T) { @@ -56,7 +58,7 @@ func TestRenderManagementEventMatchesJSONMarshal(t *testing.T) { d := base tt.mutate(&d) - want, err := json.Marshal(d) + want, err := json.Marshal(d, jsonx.V1()) require.NoError(t, err) require.Equal(t, string(want), renderManagementEvent(&d)) }) diff --git a/services/cloudtrail/models.go b/services/cloudtrail/models.go index c42ae1818b..50311a9d2b 100644 --- a/services/cloudtrail/models.go +++ b/services/cloudtrail/models.go @@ -1,10 +1,11 @@ package cloudtrail import ( - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -75,7 +76,7 @@ func (e Event) MarshalJSON() ([]byte, error) { }{ alias: alias(e), EventTime: awstime.Epoch(e.EventTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of MarshalJSON: it decodes the epoch-seconds @@ -92,7 +93,7 @@ func (e *Event) UnmarshalJSON(data []byte) error { EventTime float64 `json:"EventTime"` }{alias: (*alias)(e)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/cloudtrail/multi_region_test.go b/services/cloudtrail/multi_region_test.go index bdf01dc7f4..f32c31125b 100644 --- a/services/cloudtrail/multi_region_test.go +++ b/services/cloudtrail/multi_region_test.go @@ -2,7 +2,7 @@ package cloudtrail_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudtrail" ) @@ -42,7 +43,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/cloudtrail/persistence.go b/services/cloudtrail/persistence.go index 4d6b3990fd..77911dc1be 100644 --- a/services/cloudtrail/persistence.go +++ b/services/cloudtrail/persistence.go @@ -2,7 +2,7 @@ package cloudtrail import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -30,7 +30,7 @@ const cloudtrailSnapshotVersion = 1 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` EventConfigs map[string]*EventConfiguration `json:"eventConfigs,omitempty"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/cloudtrail/query_exec.go b/services/cloudtrail/query_exec.go index 2cb1b7eec2..0cc89fc4d4 100644 --- a/services/cloudtrail/query_exec.go +++ b/services/cloudtrail/query_exec.go @@ -2,13 +2,15 @@ package cloudtrail import ( "cmp" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // This file implements a bounded, honest subset of CloudTrail Lake SQL @@ -96,7 +98,7 @@ func eventToRow(ev Event) map[string]string { if ev.CloudTrailEvent != "" { var detail map[string]any - if err := json.Unmarshal([]byte(ev.CloudTrailEvent), &detail); err == nil { + if err := json.Unmarshal([]byte(ev.CloudTrailEvent), &detail, jsonx.V1()); err == nil { flattenJSONInto(row, "", detail) } } @@ -126,7 +128,7 @@ func flattenJSONInto(row map[string]string, prefix string, obj map[string]any) { // missing/null field. default: if _, exists := row[key]; !exists { - if b, err := json.Marshal(tv); err == nil { + if b, err := json.Marshal(tv, jsonx.V1()); err == nil { row[key] = string(b) } } diff --git a/services/cloudtrail/s3_delivery_test.go b/services/cloudtrail/s3_delivery_test.go index 50f71ceeae..a1596762db 100644 --- a/services/cloudtrail/s3_delivery_test.go +++ b/services/cloudtrail/s3_delivery_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudtrail" ) @@ -252,7 +253,7 @@ func TestRecordManagementEvent_DeliversLogFileToS3(t *testing.T) { var records struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(decoded, &records)) + require.NoError(t, json.Unmarshal(decoded, &records, jsonx.V1())) require.Len(t, records.Records, 1) assert.Equal(t, "CreateBucket", records.Records[0]["eventName"]) } diff --git a/services/cloudwatch/alarm_actions.go b/services/cloudwatch/alarm_actions.go index 10ae72f587..562af00410 100644 --- a/services/cloudwatch/alarm_actions.go +++ b/services/cloudwatch/alarm_actions.go @@ -2,12 +2,13 @@ package cloudwatch import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -53,7 +54,7 @@ func (b *InMemoryBackend) buildAlarmActionPayload( "StateChangeTime": time.Now().UTC().Format(time.RFC3339), } // map[string]string marshaling cannot fail; error is intentionally ignored. - bs, _ := json.Marshal(data) + bs, _ := json.Marshal(data, jsonx.V1()) return bs } diff --git a/services/cloudwatch/alarm_history.go b/services/cloudwatch/alarm_history.go index 93014f2eb4..8f73087bac 100644 --- a/services/cloudwatch/alarm_history.go +++ b/services/cloudwatch/alarm_history.go @@ -1,10 +1,11 @@ package cloudwatch import ( - "encoding/json" + "encoding/json/v2" "sort" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -182,7 +183,7 @@ func stateChangeHistoryData(oldState, newState, reason, reasonData string) strin } if reasonData != "" { var parsed any - if json.Unmarshal([]byte(reasonData), &parsed) == nil { + if json.Unmarshal([]byte(reasonData), &parsed, jsonx.V1()) == nil { next["stateReasonData"] = parsed } } @@ -191,7 +192,7 @@ func stateChangeHistoryData(oldState, newState, reason, reasonData string) strin "version": "1.0", "oldState": map[string]any{"stateValue": oldState}, "newState": next, - }) + }, jsonx.V1()) return string(bs) } diff --git a/services/cloudwatch/alarm_reason.go b/services/cloudwatch/alarm_reason.go index 575738d9d7..762f289da2 100644 --- a/services/cloudwatch/alarm_reason.go +++ b/services/cloudwatch/alarm_reason.go @@ -1,12 +1,14 @@ package cloudwatch import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -212,7 +214,7 @@ func alarmReasonData(a MetricAlarm, d alarmEvalDetail, now time.Time) string { "evaluatedDatapoints": evaluated, } - bs, err := json.Marshal(data) + bs, err := json.Marshal(data, jsonx.V1()) if err != nil { return "" } diff --git a/services/cloudwatch/alarm_validation_sdk_test.go b/services/cloudwatch/alarm_validation_sdk_test.go index b468cfd6a8..de91b43197 100644 --- a/services/cloudwatch/alarm_validation_sdk_test.go +++ b/services/cloudwatch/alarm_validation_sdk_test.go @@ -1,7 +1,8 @@ package cloudwatch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" "testing" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatch" ) @@ -76,7 +78,7 @@ func TestSDK_AlarmStateTransitionRealism(t *testing.T) { got := out.MetricAlarms[0] assert.Equal(t, tt.wantState, got.StateValue) assert.Contains(t, aws.ToString(got.StateReason), tt.wantReason) - assert.True(t, json.Valid([]byte(aws.ToString(got.StateReasonData)))) + assert.True(t, jsontext.Value([]byte(aws.ToString(got.StateReasonData))).IsValid(jsonx.V1())) putBreachAlarm(t, client, "a", 6) out, err = client.DescribeAlarms(t.Context(), &cwsdk.DescribeAlarmsInput{}) @@ -96,7 +98,10 @@ func TestSDK_AlarmStateTransitionRealism(t *testing.T) { StateValue string `json:"stateValue"` } `json:"newState"` } - require.NoError(t, json.Unmarshal([]byte(aws.ToString(hist.AlarmHistoryItems[0].HistoryData)), &data)) + require.NoError( + t, + json.Unmarshal([]byte(aws.ToString(hist.AlarmHistoryItems[0].HistoryData)), &data, jsonx.V1()), + ) assert.Equal(t, string(tt.wantState), data.NewState.StateValue) }) } diff --git a/services/cloudwatch/anomaly_detectors.go b/services/cloudwatch/anomaly_detectors.go index 103bcacca4..ccb213f0a4 100644 --- a/services/cloudwatch/anomaly_detectors.go +++ b/services/cloudwatch/anomaly_detectors.go @@ -3,7 +3,7 @@ package cloudwatch import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "math" "slices" @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -27,7 +28,7 @@ func detectorKey(d *AnomalyDetector) string { return anomalyDetectorKey(d.Namespace, d.MetricName, d.Stat, d.Dimensions) } - raw, err := json.Marshal(d.MetricMath) + raw, err := json.Marshal(d.MetricMath, jsonx.V1()) if err != nil { return "math/" + d.ID } diff --git a/services/cloudwatch/contributor_insights_sdk_test.go b/services/cloudwatch/contributor_insights_sdk_test.go index 19503121fd..9f1a5f2c30 100644 --- a/services/cloudwatch/contributor_insights_sdk_test.go +++ b/services/cloudwatch/contributor_insights_sdk_test.go @@ -3,7 +3,7 @@ package cloudwatch_test import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatch" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -431,7 +432,7 @@ func TestInsightRule_PutValidation(t *testing.T) { fiveFilters := jsonRule(`"$.a"`, `,"Filters":[`+ `{"Match":"$.a","IsPresent":true},{"Match":"$.b","IsPresent":true},{"Match":"$.c","IsPresent":true},`+ `{"Match":"$.d","IsPresent":true},{"Match":"$.e","IsPresent":true}]`) + `}` - elevenValues, err := json.Marshal([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}) + elevenValues, err := json.Marshal([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}, jsonx.V1()) require.NoError(t, err) invalid := &cwtypes.InvalidParameterValueException{} diff --git a/services/cloudwatch/dashboard_validation.go b/services/cloudwatch/dashboard_validation.go index dcd1d76bd1..6955e8e67c 100644 --- a/services/cloudwatch/dashboard_validation.go +++ b/services/cloudwatch/dashboard_validation.go @@ -1,9 +1,12 @@ package cloudwatch import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // knownDashboardWidgetTypes are the CloudWatch dashboard widget "type" values @@ -57,10 +60,10 @@ func validateDashboardBody(body string) []DashboardValidationMessage { }} } - dec := json.NewDecoder(strings.NewReader(trimmed)) + dec := jsontext.NewDecoder(strings.NewReader(trimmed), jsonx.V1()) var raw any - if err := dec.Decode(&raw); err != nil { + if err := json.UnmarshalDecode(dec, &raw, jsonx.V1()); err != nil { return []DashboardValidationMessage{{ DataPath: "/", Message: fmt.Sprintf("The dashboard body is not valid JSON: %s", err.Error()), @@ -68,7 +71,11 @@ func validateDashboardBody(body string) []DashboardValidationMessage { }} } - if dec.More() { + if rest := strings.TrimLeft( + trimmed[dec.InputOffset():], + " \t\r\n", + ); rest != "" && rest[0] != ']' && + rest[0] != '}' { return []DashboardValidationMessage{{ DataPath: "/", Message: "The dashboard body contains trailing content after the JSON value", diff --git a/services/cloudwatch/firehose_interop_test.go b/services/cloudwatch/firehose_interop_test.go index 1f8cadd450..3cf5c687b5 100644 --- a/services/cloudwatch/firehose_interop_test.go +++ b/services/cloudwatch/firehose_interop_test.go @@ -2,7 +2,7 @@ package cloudwatch_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "sync" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatch" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -110,7 +111,7 @@ func TestMetricStream_EndToEnd_RealFirehoseBackend(t *testing.T) { body := s3mock.captured()[0] var rec map[string]any - require.NoError(t, json.Unmarshal([]byte(firstLine(body)), &rec)) + require.NoError(t, json.Unmarshal([]byte(firstLine(body)), &rec, jsonx.V1())) assert.Equal(t, "CPUUtilization", rec["metric_name"]) assert.Equal(t, "AWS/EC2", rec["namespace"]) } diff --git a/services/cloudwatch/insight_rule_eval.go b/services/cloudwatch/insight_rule_eval.go index f5a9470a4b..d352eb62f2 100644 --- a/services/cloudwatch/insight_rule_eval.go +++ b/services/cloudwatch/insight_rule_eval.go @@ -2,11 +2,14 @@ package cloudwatch import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // fieldLookup returns the leaf value at a rule field path in one log event. @@ -27,11 +30,10 @@ func (s *insightRuleSpec) lookupFor(msg string) (fieldLookup, bool) { }, true } - dec := json.NewDecoder(bytes.NewReader([]byte(msg))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(msg))) var root map[string]any - if err := dec.Decode(&root); err != nil || root == nil { + if err := json.UnmarshalDecode(dec, &root, jsonx.V1(), jsonx.UseNumber()); err != nil || root == nil { return nil, false } @@ -69,7 +71,7 @@ func jsonLeaf(root any, path string) (string, bool) { switch v := cur.(type) { case string: return v, true - case json.Number: + case jsonx.Number: return v.String(), true case bool: return strconv.FormatBool(v), true diff --git a/services/cloudwatch/insight_rule_spec.go b/services/cloudwatch/insight_rule_spec.go index 5bd476adaf..b421eb3727 100644 --- a/services/cloudwatch/insight_rule_spec.go +++ b/services/cloudwatch/insight_rule_spec.go @@ -1,11 +1,13 @@ package cloudwatch import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -49,7 +51,7 @@ type insightRuleSpec struct { func parseInsightRuleSpec(definition string) (*insightRuleSpec, error) { var spec insightRuleSpec - if err := json.Unmarshal([]byte(definition), &spec); err != nil { + if err := json.Unmarshal([]byte(definition), &spec, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: RuleDefinition has an invalid field type: %s", ErrValidation, err.Error()) } diff --git a/services/cloudwatch/insight_rule_validation.go b/services/cloudwatch/insight_rule_validation.go index d2a142976a..97d14aa7ee 100644 --- a/services/cloudwatch/insight_rule_validation.go +++ b/services/cloudwatch/insight_rule_validation.go @@ -1,9 +1,11 @@ package cloudwatch import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxContributionKeys is Contributor Insights' documented limit on @@ -26,7 +28,7 @@ func validateInsightRuleDefinition(definition string) error { } var raw any - if err := json.Unmarshal([]byte(trimmed), &raw); err != nil { + if err := json.Unmarshal([]byte(trimmed), &raw, jsonx.V1()); err != nil { return fmt.Errorf("%w: RuleDefinition is not valid JSON: %s", ErrValidation, err.Error()) } diff --git a/services/cloudwatch/json_target.go b/services/cloudwatch/json_target.go index ba9562bc39..5cb7d12745 100644 --- a/services/cloudwatch/json_target.go +++ b/services/cloudwatch/json_target.go @@ -3,13 +3,16 @@ package cloudwatch import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "net/http" "strings" "github.com/aws/smithy-go/encoding/cbor" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const jsonContentTypePrefix = "application/x-amz-json" @@ -47,11 +50,10 @@ func (h *Handler) dispatchJSONTarget(op string, body []byte, c *echo.Context) er input := cbor.Map{} if len(bytes.TrimSpace(body)) > 0 { - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) var raw any - if err := dec.Decode(&raw); err != nil { + if err := json.UnmarshalDecode(dec, &raw, jsonx.V1(), jsonx.UseNumber()); err != nil { return writeJSONError(c, http.StatusBadRequest, "SerializationException", err.Error()) } @@ -100,7 +102,7 @@ func writeBufferedAsJSON(c *echo.Context, buf *bufferedResponse) error { } func writeJSON(c *echo.Context, status int, v any) error { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return err } @@ -138,14 +140,14 @@ func jsonToCBOR(v any) cbor.Value { return cbor.String(t) case bool: return cbor.Bool(t) - case json.Number: + case jsonx.Number: return jsonNumberToCBOR(t) default: return &cbor.Nil{} } } -func jsonNumberToCBOR(n json.Number) cbor.Value { +func jsonNumberToCBOR(n jsonx.Number) cbor.Value { if i, err := n.Int64(); err == nil { if i >= 0 { return cbor.Uint(i) diff --git a/services/cloudwatch/json_target_test.go b/services/cloudwatch/json_target_test.go index 7476b280f0..8699a20ce9 100644 --- a/services/cloudwatch/json_target_test.go +++ b/services/cloudwatch/json_target_test.go @@ -3,7 +3,7 @@ package cloudwatch_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -69,7 +70,7 @@ func TestJSONTarget_RoundTrip(t *testing.T) { MetricName string `json:"MetricName"` } `json:"Metrics"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) require.Len(t, out.Metrics, 1) assert.Equal(t, "M", out.Metrics[0].MetricName) }, @@ -90,7 +91,7 @@ func TestJSONTarget_RoundTrip(t *testing.T) { Values []float64 `json:"Values"` } `json:"MetricDataResults"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) require.Len(t, out.MetricDataResults, 1) assert.Equal(t, []float64{-2.5}, out.MetricDataResults[0].Values) assert.Equal(t, []float64{float64(ts)}, out.MetricDataResults[0].Timestamps) @@ -119,7 +120,7 @@ func TestJSONTarget_RoundTrip(t *testing.T) { } func epochStr(n int64) string { - b, _ := json.Marshal(n) + b, _ := json.Marshal(n, jsonx.V1()) return string(b) } diff --git a/services/cloudwatch/metric_stream_delivery.go b/services/cloudwatch/metric_stream_delivery.go index 38d0af6f47..bb2ecf7af3 100644 --- a/services/cloudwatch/metric_stream_delivery.go +++ b/services/cloudwatch/metric_stream_delivery.go @@ -2,9 +2,10 @@ package cloudwatch import ( "context" - "encoding/json" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -105,7 +106,7 @@ func buildMetricStreamRecords( Unit: d.Unit, } - body, err := json.Marshal(rec) + body, err := json.Marshal(rec, jsonx.V1()) if err != nil { continue } diff --git a/services/cloudwatch/metric_stream_delivery_test.go b/services/cloudwatch/metric_stream_delivery_test.go index 64669e3598..d45100dc18 100644 --- a/services/cloudwatch/metric_stream_delivery_test.go +++ b/services/cloudwatch/metric_stream_delivery_test.go @@ -2,13 +2,15 @@ package cloudwatch_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // mockFirehosePutter captures PutRecordBatch calls for assertions. @@ -111,7 +113,7 @@ func TestMetricStream_DeliversToFirehose(t *testing.T) { require.Len(t, calls[0].records, 1) var rec2 map[string]any - require.NoError(t, json.Unmarshal(calls[0].records[0], &rec2)) + require.NoError(t, json.Unmarshal(calls[0].records[0], &rec2, jsonx.V1())) assert.Equal(t, tt.wantMetricName, rec2["metric_name"]) assert.Equal(t, "AWS/EC2", rec2["namespace"]) value := rec2["value"].(map[string]any) diff --git a/services/cloudwatch/metricdata.go b/services/cloudwatch/metricdata.go index 110821c7cd..ab045d826a 100644 --- a/services/cloudwatch/metricdata.go +++ b/services/cloudwatch/metricdata.go @@ -2,12 +2,14 @@ package cloudwatch import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "math" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -36,7 +38,7 @@ type metricDataCursor struct { // encodeMetricDataToken encodes a pagination cursor into an opaque base64 token. func encodeMetricDataToken(c metricDataCursor) string { - raw, err := json.Marshal(c) + raw, err := json.Marshal(c, jsonx.V1()) if err != nil { return "" } @@ -59,7 +61,7 @@ func validateMetricDataWindow(startTime, endTime time.Time, nextToken string) er } var c metricDataCursor - if err = json.Unmarshal(raw, &c); err != nil || c.ResultIndex < 0 || c.PointOffset < 0 { + if err = json.Unmarshal(raw, &c, jsonx.V1()); err != nil || c.ResultIndex < 0 || c.PointOffset < 0 { return ErrInvalidNextToken } @@ -79,7 +81,7 @@ func decodeMetricDataToken(token string) metricDataCursor { } var c metricDataCursor - if err = json.Unmarshal(raw, &c); err != nil { + if err = json.Unmarshal(raw, &c, jsonx.V1()); err != nil { return metricDataCursor{} } diff --git a/services/cloudwatch/persistence.go b/services/cloudwatch/persistence.go index 190556775a..bc021588d7 100644 --- a/services/cloudwatch/persistence.go +++ b/services/cloudwatch/persistence.go @@ -2,9 +2,11 @@ package cloudwatch import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -21,7 +23,7 @@ import ( const cloudwatchSnapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Metrics map[string]map[string]*metricRecord `json:"metrics"` AlarmHistory map[string][]AlarmHistoryItem `json:"alarmHistory"` AccountID string `json:"accountID"` @@ -171,7 +173,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { } h.tagsMu.RUnlock() - data, err := json.Marshal(handlerSnapshot{Backend: backendData, Tags: tagsCopy}) + data, err := json.Marshal(handlerSnapshot{Backend: backendData, Tags: tagsCopy}, jsonx.V1()) if err != nil { return backendData } @@ -196,7 +198,7 @@ func (h *Handler) Restore(ctx context.Context, data []byte) error { func (h *Handler) restoreHome(ctx context.Context, data []byte) error { // Attempt to unmarshal as a handlerSnapshot first. var hs handlerSnapshot - if unmarshalErr := json.Unmarshal(data, &hs); unmarshalErr == nil && hs.Backend != nil { + if unmarshalErr := json.Unmarshal(data, &hs, jsonx.V1()); unmarshalErr == nil && hs.Backend != nil { type restorer interface { Restore(context.Context, []byte) error } diff --git a/services/cloudwatch/persistence_test.go b/services/cloudwatch/persistence_test.go index bb7bf1db55..80f13224ee 100644 --- a/services/cloudwatch/persistence_test.go +++ b/services/cloudwatch/persistence_test.go @@ -1,7 +1,7 @@ package cloudwatch_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatch" ) @@ -625,9 +626,9 @@ func TestInMemoryBackend_Restore_VersionGuard(t *testing.T) { // Corrupt the version field to simulate an incompatible (older/newer) snapshot. var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) raw["version"] = 999999 - corrupted, err := json.Marshal(raw) + corrupted, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) // Restore into a backend that already has unrelated state, to confirm the diff --git a/services/cloudwatch/widget.go b/services/cloudwatch/widget.go index 3e765a7feb..1aecd84050 100644 --- a/services/cloudwatch/widget.go +++ b/services/cloudwatch/widget.go @@ -2,13 +2,16 @@ package cloudwatch import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "image/png" "math" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Widget rendering constants. @@ -40,14 +43,14 @@ const ( // widgetSpec is the decoded MetricWidget JSON document. type widgetSpec struct { - Title string `json:"title"` - Start string `json:"start"` - End string `json:"end"` - Stat string `json:"stat"` - Metrics [][]json.RawMessage `json:"metrics"` - Width int `json:"width"` - Height int `json:"height"` - Period int `json:"period"` + Title string `json:"title"` + Start string `json:"start"` + End string `json:"end"` + Stat string `json:"stat"` + Metrics [][]jsontext.Value `json:"metrics"` + Width int `json:"width"` + Height int `json:"height"` + Period int `json:"period"` } // widgetSeriesQuery describes a single metric line to plot. @@ -108,7 +111,7 @@ func parseWidgetSpec(widgetJSON string) widgetSpec { if strings.TrimSpace(widgetJSON) == "" { return spec } - _ = json.Unmarshal([]byte(widgetJSON), &spec) + _ = json.Unmarshal([]byte(widgetJSON), &spec, jsonx.V1()) return spec } @@ -241,24 +244,24 @@ func scanDurationComponent(s string, unit byte, scale time.Duration) (time.Durat // namespace, metric name, then dimension name/value pairs, optionally followed by // an options object ({"stat":...,"period":...,"label":...}). Render-only entries // (leading object, e.g. metric-math expressions) are skipped. -func parseWidgetSeries(elem []json.RawMessage, spec widgetSpec) (widgetSeriesQuery, bool) { +func parseWidgetSeries(elem []jsontext.Value, spec widgetSpec) (widgetSeriesQuery, bool) { q := widgetSeriesQuery{ stat: firstNonEmpty(spec.Stat, widgetDefaultStat), period: firstPositive(spec.Period, widgetDefaultPeriod), } var strs []string - var opts map[string]json.RawMessage + var opts map[string]jsontext.Value for _, raw := range elem { trimmed := strings.TrimSpace(string(raw)) if strings.HasPrefix(trimmed, "{") { - _ = json.Unmarshal(raw, &opts) + _ = json.Unmarshal(raw, &opts, jsonx.V1()) continue } var s string - if err := json.Unmarshal(raw, &s); err == nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err == nil { strs = append(strs, s) } } @@ -284,25 +287,25 @@ func parseWidgetSeries(elem []json.RawMessage, spec widgetSpec) (widgetSeriesQue } // applyWidgetOptions overlays the trailing options object onto a series query. -func applyWidgetOptions(q *widgetSeriesQuery, opts map[string]json.RawMessage) { +func applyWidgetOptions(q *widgetSeriesQuery, opts map[string]jsontext.Value) { if opts == nil { return } if raw, ok := opts["stat"]; ok { var s string - if json.Unmarshal(raw, &s) == nil && s != "" { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil && s != "" { q.stat = s } } if raw, ok := opts["label"]; ok { var s string - if json.Unmarshal(raw, &s) == nil && s != "" { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil && s != "" { q.label = s } } if raw, ok := opts["period"]; ok { var p int - if json.Unmarshal(raw, &p) == nil && p > 0 { + if json.Unmarshal(raw, &p, jsonx.V1()) == nil && p > 0 { q.period = p } } diff --git a/services/cloudwatchlogs/data_protection_mask.go b/services/cloudwatchlogs/data_protection_mask.go index 7917ca629b..c20ccfa5f1 100644 --- a/services/cloudwatchlogs/data_protection_mask.go +++ b/services/cloudwatchlogs/data_protection_mask.go @@ -2,11 +2,13 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type unmaskContextKey struct{} @@ -133,7 +135,7 @@ func luhnValid(s string) bool { // parseMaskPolicy compiles the masking (Deidentify) statements of doc; unsupported managed identifiers are skipped. func parseMaskPolicy(doc string, since int64) (maskPolicy, error) { var parsed protectionPolicyDoc - if err := json.Unmarshal([]byte(doc), &parsed); err != nil { + if err := json.Unmarshal([]byte(doc), &parsed, jsonx.V1()); err != nil { return maskPolicy{}, fmt.Errorf("%w: policyDocument must be valid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/filter_pattern_json.go b/services/cloudwatchlogs/filter_pattern_json.go index dc046571f0..f8ebfa00bb 100644 --- a/services/cloudwatchlogs/filter_pattern_json.go +++ b/services/cloudwatchlogs/filter_pattern_json.go @@ -1,10 +1,12 @@ package cloudwatchlogs import ( - "encoding/json" + "encoding/json/v2" "regexp" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // compileJSONFilterPattern compiles an AWS CloudWatch Logs JSON selector pattern @@ -26,7 +28,7 @@ func compileJSONFilterPattern(pattern string) func(string) bool { return func(message string) bool { var root any - if err := json.Unmarshal([]byte(strings.TrimSpace(message)), &root); err != nil { + if err := json.Unmarshal([]byte(strings.TrimSpace(message)), &root, jsonx.V1()); err != nil { return false } @@ -42,7 +44,7 @@ func compileJSONFilterPattern(pattern string) func(string) bool { func compileJSONFilterPatternExtract(_ string) func(message, fieldRef string) (string, bool) { return func(message, fieldRef string) (string, bool) { var root any - if err := json.Unmarshal([]byte(strings.TrimSpace(message)), &root); err != nil { + if err := json.Unmarshal([]byte(strings.TrimSpace(message)), &root, jsonx.V1()); err != nil { return "", false } diff --git a/services/cloudwatchlogs/golden_perf_test.go b/services/cloudwatchlogs/golden_perf_test.go index c031a848e5..9209c1d173 100644 --- a/services/cloudwatchlogs/golden_perf_test.go +++ b/services/cloudwatchlogs/golden_perf_test.go @@ -12,7 +12,8 @@ package cloudwatchlogs_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "os" @@ -22,6 +23,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -63,13 +65,13 @@ func checkGoldenLogs(t *testing.T, name string, got []byte) { t.Helper() var parsed any - require.NoError(t, json.Unmarshal(got, &parsed)) - redacted, err := json.Marshal(redactVolatile(parsed)) + require.NoError(t, json.Unmarshal(got, &parsed, jsonx.V1())) + redacted, err := json.Marshal(redactVolatile(parsed), jsonx.V1()) require.NoError(t, err) - var buf bytes.Buffer - require.NoError(t, json.Indent(&buf, redacted, "", " ")) - pretty := buf.Bytes() + indented := jsontext.Value(bytes.Clone(redacted)) + require.NoError(t, indented.Indent(jsonx.V1(), jsontext.WithIndent(" "))) + pretty := []byte(indented) path := goldenLogsPath(name) if os.Getenv("CWL_GOLDEN_UPDATE") != "" { @@ -107,7 +109,7 @@ func seedGoldenStream( } body, err := json.Marshal(map[string]any{ "logGroupName": group, "logStreamName": stream, "logEvents": events, - }) + }, jsonx.V1()) require.NoError(t, err) rec = doLogsRequest(t, h, e, "PutLogEvents", string(body)) @@ -135,7 +137,7 @@ func TestGolden_FilterLogEventsInterleaved(t *testing.T) { "logGroupName": "golden-grp", "filterPattern": "line", "limit": 50, - }) + }, jsonx.V1()) require.NoError(t, err) rec = doLogsRequest(t, h, e, "FilterLogEvents", string(body)) @@ -161,7 +163,7 @@ func TestGolden_GetLogEvents(t *testing.T) { "logGroupName": "golden-grp2", "logStreamName": "s", "limit": 10, - }) + }, jsonx.V1()) require.NoError(t, err) rec = doLogsRequest(t, h, e, "GetLogEvents", string(body)) diff --git a/services/cloudwatchlogs/handler.go b/services/cloudwatchlogs/handler.go index 17960eb777..275138af19 100644 --- a/services/cloudwatchlogs/handler.go +++ b/services/cloudwatchlogs/handler.go @@ -2,7 +2,7 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -259,7 +260,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -406,7 +407,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a standardized JSON error response. @@ -475,7 +476,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin Message: reqErr.Error(), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/cloudwatchlogs/handler_anomaly_detectors.go b/services/cloudwatchlogs/handler_anomaly_detectors.go index f049b5c04e..deeeca8996 100644 --- a/services/cloudwatchlogs/handler_anomaly_detectors.go +++ b/services/cloudwatchlogs/handler_anomaly_detectors.go @@ -2,7 +2,9 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createLogAnomalyDetectorInput struct { @@ -111,7 +113,7 @@ func (h *Handler) handleCreateLogAnomalyDetector( b []byte, ) (any, error) { var input createLogAnomalyDetectorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -135,7 +137,7 @@ func (h *Handler) handleDeleteLogAnomalyDetector( b []byte, ) (any, error) { var input deleteLogAnomalyDetectorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteLogAnomalyDetector(input.AnomalyDetectorArn); err != nil { @@ -150,7 +152,7 @@ func (h *Handler) handleListLogAnomalyDetectors( b []byte, ) (any, error) { var input listLogAnomalyDetectorsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } var filter []string @@ -171,7 +173,7 @@ func (h *Handler) handleUpdateLogAnomalyDetector( b []byte, ) (any, error) { var input updateLogAnomalyDetectorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.UpdateLogAnomalyDetector( @@ -192,7 +194,7 @@ func (h *Handler) handleGetLogAnomalyDetector( b []byte, ) (any, error) { var input getLogAnomalyDetectorInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } d, err := h.Backend.GetLogAnomalyDetector(input.AnomalyDetectorArn) @@ -215,7 +217,7 @@ func (h *Handler) handleGetLogAnomalyDetector( func (h *Handler) handleListAnomalies(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input listAnomaliesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } anomalies, next, err := h.Backend.ListAnomalies(input.AnomalyDetectorArn, input.Limit, input.NextToken) @@ -228,7 +230,7 @@ func (h *Handler) handleListAnomalies(ctx context.Context, b []byte) (any, error func (h *Handler) handleUpdateAnomaly(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input updateAnomalyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/cloudwatchlogs/handler_anomaly_detectors_test.go b/services/cloudwatchlogs/handler_anomaly_detectors_test.go index dcdaf41a4b..50b1cc0209 100644 --- a/services/cloudwatchlogs/handler_anomaly_detectors_test.go +++ b/services/cloudwatchlogs/handler_anomaly_detectors_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -34,7 +35,7 @@ func TestHandler_LogAnomalyDetector_Create(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arn, ok := out["anomalyDetectorArn"].(string) require.True(t, ok) assert.Contains(t, arn, "log-anomaly-detector") @@ -193,7 +194,7 @@ func TestHandler_CreateLogAnomalyDetectorOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -201,7 +202,7 @@ func TestHandler_CreateLogAnomalyDetectorOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { diff --git a/services/cloudwatchlogs/handler_data_protection.go b/services/cloudwatchlogs/handler_data_protection.go index 63ddeb10cd..3d95342fde 100644 --- a/services/cloudwatchlogs/handler_data_protection.go +++ b/services/cloudwatchlogs/handler_data_protection.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type deleteAccountPolicyInput struct { @@ -48,7 +50,7 @@ func (h *Handler) handleDeleteAccountPolicy( b []byte, ) (any, error) { var input deleteAccountPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -64,7 +66,7 @@ func (h *Handler) handlePutAccountPolicy( b []byte, ) (any, error) { var input putAccountPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } policy, err := h.Backend.PutAccountPolicy( @@ -86,7 +88,7 @@ func (h *Handler) handleDescribeAccountPolicies( b []byte, ) (any, error) { var input describeAccountPoliciesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } policies, nextToken, err := h.Backend.DescribeAccountPolicies( @@ -112,7 +114,7 @@ func (h *Handler) handleDeleteDataProtectionPolicy( body []byte, ) (any, error) { var in deleteDataProtectionPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -134,7 +136,7 @@ func (h *Handler) handleGetDataProtectionPolicy( body []byte, ) (any, error) { var in getDataProtectionPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -170,7 +172,7 @@ func (h *Handler) handlePutDataProtectionPolicy( body []byte, ) (any, error) { var in putDataProtectionPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_data_protection_test.go b/services/cloudwatchlogs/handler_data_protection_test.go index 3c4b4343cd..977031c0d8 100644 --- a/services/cloudwatchlogs/handler_data_protection_test.go +++ b/services/cloudwatchlogs/handler_data_protection_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -82,7 +83,7 @@ func TestHandler_DataProtectionPolicy(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -138,7 +139,7 @@ func TestHandler_DeleteAccountPolicyOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -146,7 +147,7 @@ func TestHandler_DeleteAccountPolicyOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { diff --git a/services/cloudwatchlogs/handler_deliveries.go b/services/cloudwatchlogs/handler_deliveries.go index ea5418eaa4..fe019982b5 100644 --- a/services/cloudwatchlogs/handler_deliveries.go +++ b/services/cloudwatchlogs/handler_deliveries.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createDeliveryInput struct { @@ -48,7 +50,7 @@ type deleteDeliveryOutput struct{} func (h *Handler) handleCreateDelivery(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input createDeliveryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -68,7 +70,7 @@ func (h *Handler) handleDescribeDeliveries( b []byte, ) (any, error) { var input describeDeliveriesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } deliveries, next, err := h.Backend.DescribeDeliveries(input.Limit, input.NextToken) @@ -81,7 +83,7 @@ func (h *Handler) handleDescribeDeliveries( func (h *Handler) handleGetDelivery(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input getDeliveryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } d, err := h.Backend.GetDelivery(input.ID) @@ -94,7 +96,7 @@ func (h *Handler) handleGetDelivery(ctx context.Context, b []byte) (any, error) func (h *Handler) handleDeleteDelivery(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input deleteDeliveryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteDelivery(input.ID); err != nil { @@ -144,7 +146,7 @@ func (h *Handler) handlePutDeliveryDestination( body []byte, ) (any, error) { var in putDeliveryDestinationInputFull - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -171,7 +173,7 @@ func (h *Handler) handleGetDeliveryDestination( body []byte, ) (any, error) { var in getDeliveryDestinationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -197,7 +199,7 @@ func (h *Handler) handleDescribeDeliveryDestinations( body []byte, ) (any, error) { var in describeDeliveryDestinationsInput - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) if b := cwlBackend(h); b != nil { dests, next := b.DescribeDeliveryDestinations(in.NextToken, in.Limit) @@ -226,7 +228,7 @@ func (h *Handler) handleDeleteDeliveryDestination( body []byte, ) (any, error) { var in deleteDeliveryDestinationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -249,7 +251,7 @@ func (h *Handler) handlePutDeliveryDestinationPolicy( body []byte, ) (any, error) { var in putDeliveryDestinationPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -273,7 +275,7 @@ func (h *Handler) handleGetDeliveryDestinationPolicy( body []byte, ) (any, error) { var in getDeliveryDestinationPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -298,7 +300,7 @@ func (h *Handler) handleDeleteDeliveryDestinationPolicy( body []byte, ) (any, error) { var in deleteDeliveryDestinationPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -353,7 +355,7 @@ func (h *Handler) handlePutDeliverySource( body []byte, ) (any, error) { var in putDeliverySourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -383,7 +385,7 @@ func (h *Handler) handleGetDeliverySource( body []byte, ) (any, error) { var in getDeliverySourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -409,7 +411,7 @@ func (h *Handler) handleDescribeDeliverySources( body []byte, ) (any, error) { var in describeDeliverySourcesInput - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) if b := cwlBackend(h); b != nil { srcs, next := b.DescribeDeliverySources(in.NextToken, in.Limit) @@ -438,7 +440,7 @@ func (h *Handler) handleDeleteDeliverySource( body []byte, ) (any, error) { var in deleteDeliverySourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -463,7 +465,7 @@ func (h *Handler) handleUpdateDeliveryConfiguration( body []byte, ) (any, error) { var in updateDeliveryConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } diff --git a/services/cloudwatchlogs/handler_deliveries_test.go b/services/cloudwatchlogs/handler_deliveries_test.go index a485b3757b..5da0e86ba1 100644 --- a/services/cloudwatchlogs/handler_deliveries_test.go +++ b/services/cloudwatchlogs/handler_deliveries_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -26,7 +27,7 @@ func TestHandler_Delivery_CreateWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) delivery, ok := out["delivery"].(map[string]any) require.True(t, ok) @@ -242,7 +243,7 @@ func TestHandler_DeliveryDestination(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -284,7 +285,7 @@ func TestHandler_DeliveryDestination_WireShape(t *testing.T) { } var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) putDest, ok := putOut["deliveryDestination"].(map[string]any) require.True(t, ok) checkShape(t, putDest) @@ -293,7 +294,7 @@ func TestHandler_DeliveryDestination_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) getDest, ok := getOut["deliveryDestination"].(map[string]any) require.True(t, ok) checkShape(t, getDest) @@ -302,7 +303,7 @@ func TestHandler_DeliveryDestination_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut, jsonx.V1())) dests, ok := describeOut["deliveryDestinations"].([]any) require.True(t, ok) require.Len(t, dests, 1) @@ -394,7 +395,7 @@ func TestHandler_DeliverySource(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -437,7 +438,7 @@ func TestHandler_DeliverySource_WireShape(t *testing.T) { } var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) putSrc, ok := putOut["deliverySource"].(map[string]any) require.True(t, ok) checkShape(t, putSrc) @@ -446,7 +447,7 @@ func TestHandler_DeliverySource_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) getSrc, ok := getOut["deliverySource"].(map[string]any) require.True(t, ok) checkShape(t, getSrc) @@ -455,7 +456,7 @@ func TestHandler_DeliverySource_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut, jsonx.V1())) srcs, ok := describeOut["deliverySources"].([]any) require.True(t, ok) require.Len(t, srcs, 1) @@ -488,7 +489,7 @@ func TestHandler_UpdateDeliveryConfiguration(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ := resp["delivery"]["id"].(string) return id @@ -512,7 +513,7 @@ func TestHandler_UpdateDeliveryConfiguration(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ := resp["delivery"]["id"].(string) return id @@ -545,7 +546,7 @@ func TestHandler_UpdateDeliveryConfiguration(t *testing.T) { deliveryID = tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body(deliveryID)) + bodyBytes, err := json.Marshal(tt.body(deliveryID), jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -604,7 +605,7 @@ func TestHandler_CreateDeliveryOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -612,7 +613,7 @@ func TestHandler_CreateDeliveryOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { @@ -679,13 +680,13 @@ func TestHandler_DeliveryResponseShape(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } diff --git a/services/cloudwatchlogs/handler_destinations.go b/services/cloudwatchlogs/handler_destinations.go index e542412472..7d80f3868f 100644 --- a/services/cloudwatchlogs/handler_destinations.go +++ b/services/cloudwatchlogs/handler_destinations.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putDestinationInput struct { @@ -18,7 +20,7 @@ func (h *Handler) handlePutDestination( body []byte, ) (any, error) { var in putDestinationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -69,7 +71,7 @@ func (h *Handler) handlePutDestinationPolicy( body []byte, ) (any, error) { var in putDestinationPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -93,7 +95,7 @@ func (h *Handler) handleDescribeDestinations( body []byte, ) (any, error) { var in describeDestinationsInput - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) if b := cwlBackend(h); b != nil { dests, next := b.DescribeDestinations(in.DestinationNamePrefix, in.Limit, in.NextToken) @@ -122,7 +124,7 @@ func (h *Handler) handleDeleteDestination( body []byte, ) (any, error) { var in deleteDestinationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_destinations_test.go b/services/cloudwatchlogs/handler_destinations_test.go index 73c10713d4..decfac1762 100644 --- a/services/cloudwatchlogs/handler_destinations_test.go +++ b/services/cloudwatchlogs/handler_destinations_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -132,7 +133,7 @@ func TestHandler_Destination(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -155,7 +156,7 @@ func TestHandler_Destination_WireShapeIncludesCreationTimeAndAccessPolicy(t *tes require.Equal(t, http.StatusOK, putRec.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) putDest, ok := putOut["destination"].(map[string]any) require.True(t, ok) ct, hasCreationTime := putDest["creationTime"] @@ -172,7 +173,7 @@ func TestHandler_Destination_WireShapeIncludesCreationTimeAndAccessPolicy(t *tes require.Equal(t, http.StatusOK, describeRec.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut, jsonx.V1())) dests, ok := describeOut["destinations"].([]any) require.True(t, ok) require.Len(t, dests, 1) @@ -225,13 +226,13 @@ func TestHandler_DestinationResponseShape(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } diff --git a/services/cloudwatchlogs/handler_export_tasks.go b/services/cloudwatchlogs/handler_export_tasks.go index 796b4370bc..d22576415f 100644 --- a/services/cloudwatchlogs/handler_export_tasks.go +++ b/services/cloudwatchlogs/handler_export_tasks.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type cancelExportTaskInput struct { @@ -146,7 +148,7 @@ func (h *Handler) handleCancelExportTask( b []byte, ) (any, error) { var input cancelExportTaskInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.CancelExportTask(input.TaskID); err != nil { @@ -161,7 +163,7 @@ func (h *Handler) handleCancelImportTask( b []byte, ) (any, error) { var input cancelImportTaskInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -183,7 +185,7 @@ func (h *Handler) handleCreateExportTask( b []byte, ) (any, error) { var input createExportTaskInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -204,7 +206,7 @@ func (h *Handler) handleCreateImportTask( b []byte, ) (any, error) { var input createImportTaskInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -227,7 +229,7 @@ func (h *Handler) handleDescribeExportTasks( b []byte, ) (any, error) { var input describeExportTasksInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } tasks, next, err := h.Backend.DescribeExportTasks(input.TaskID, input.StatusCode, input.Limit, input.NextToken) @@ -248,7 +250,7 @@ func (h *Handler) handleDescribeImportTasks( b []byte, ) (any, error) { var input describeImportTasksInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } tasks, next, err := h.Backend.DescribeImportTasks(input.ImportID, input.Limit, input.NextToken) @@ -279,7 +281,7 @@ func (h *Handler) handleDescribeImportTaskBatches( ImportID string `json:"importId"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } diff --git a/services/cloudwatchlogs/handler_export_tasks_test.go b/services/cloudwatchlogs/handler_export_tasks_test.go index c87881b59a..000ef98f10 100644 --- a/services/cloudwatchlogs/handler_export_tasks_test.go +++ b/services/cloudwatchlogs/handler_export_tasks_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -31,13 +32,13 @@ func TestHandler_ExportTask_CancelRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) taskID, ok := createOut["taskId"].(string) require.True(t, ok) require.NotEmpty(t, taskID) // Cancel the task. - bodyBytes, err := json.Marshal(map[string]any{"taskId": taskID}) + bodyBytes, err := json.Marshal(map[string]any{"taskId": taskID}, jsonx.V1()) require.NoError(t, err) rec = doLogsRequest(t, h, e, "CancelExportTask", string(bodyBytes)) assert.Equal(t, http.StatusOK, rec.Code) @@ -87,7 +88,7 @@ func TestHandler_DescribeExportTasks_WireShape(t *testing.T) { TaskID string `json:"taskId"` } `json:"exportTasks"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ExportTasks, 1) task := out.ExportTasks[0] @@ -102,7 +103,7 @@ func TestHandler_DescribeExportTasks_WireShape(t *testing.T) { // The raw JSON must not carry the old flat keys at the top level of an // export task entry. var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) rawTasks, ok := raw["exportTasks"].([]any) require.True(t, ok) require.Len(t, rawTasks, 1) @@ -130,7 +131,7 @@ func TestHandler_ImportTask_CancelRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) importID, ok := createOut["importId"].(string) require.True(t, ok) require.NotEmpty(t, importID) @@ -140,13 +141,13 @@ func TestHandler_ImportTask_CancelRoundTrip(t *testing.T) { require.NotEmpty(t, importDestARN) // Cancel the task. - bodyBytes, err := json.Marshal(map[string]any{"importId": importID}) + bodyBytes, err := json.Marshal(map[string]any{"importId": importID}, jsonx.V1()) require.NoError(t, err) rec = doLogsRequest(t, h, e, "CancelImportTask", string(bodyBytes)) require.Equal(t, http.StatusOK, rec.Code) var cancelOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cancelOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cancelOut, jsonx.V1())) assert.Equal(t, importID, cancelOut["importId"]) assert.Equal(t, "CANCELLED", cancelOut["importStatus"]) } @@ -254,7 +255,7 @@ func TestHandler_CancelExportTask_StateValidation(t *testing.T) { e := echo.New() h := cloudwatchlogs.NewHandler(backend) - bodyBytes, _ := json.Marshal(map[string]any{"taskId": taskID}) + bodyBytes, _ := json.Marshal(map[string]any{"taskId": taskID}, jsonx.V1()) rec := doLogsRequest(t, h, e, "CancelExportTask", string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) }) @@ -313,7 +314,7 @@ func TestHandler_CancelImportTask_StateValidation(t *testing.T) { e := echo.New() h := cloudwatchlogs.NewHandler(backend) - bodyBytes, _ := json.Marshal(map[string]any{"importId": importID}) + bodyBytes, _ := json.Marshal(map[string]any{"importId": importID}, jsonx.V1()) rec := doLogsRequest(t, h, e, "CancelImportTask", string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) }) @@ -490,7 +491,7 @@ func TestHandler_ExportImportTaskOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -498,7 +499,7 @@ func TestHandler_ExportImportTaskOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { @@ -533,7 +534,7 @@ func TestHandler_ImportTaskBatchesValidation(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, tt.action, string(bodyBytes)) @@ -541,7 +542,7 @@ func TestHandler_ImportTaskBatchesValidation(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.wantListField].([]any) require.True(t, ok, "expected list field %q in response", tt.wantListField) assert.Empty(t, list) diff --git a/services/cloudwatchlogs/handler_index_policies.go b/services/cloudwatchlogs/handler_index_policies.go index 3e6812d3e4..87c91380a4 100644 --- a/services/cloudwatchlogs/handler_index_policies.go +++ b/services/cloudwatchlogs/handler_index_policies.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putIndexPolicyInput struct { @@ -20,7 +22,7 @@ func (h *Handler) handlePutIndexPolicy( body []byte, ) (any, error) { var in putIndexPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -51,7 +53,7 @@ func (h *Handler) handleDescribeIndexPolicies( body []byte, ) (any, error) { var in describeIndexPoliciesInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -77,7 +79,7 @@ func (h *Handler) handleDeleteIndexPolicy( body []byte, ) (any, error) { var in deleteIndexPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_index_policies_test.go b/services/cloudwatchlogs/handler_index_policies_test.go index 1fde81d7ee..7729809ba7 100644 --- a/services/cloudwatchlogs/handler_index_policies_test.go +++ b/services/cloudwatchlogs/handler_index_policies_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -103,7 +104,7 @@ func TestHandler_IndexPolicy(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -141,7 +142,7 @@ func TestHandler_ConfigTemplatesAndFieldIndexes(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, tt.action, string(bodyBytes)) @@ -149,7 +150,7 @@ func TestHandler_ConfigTemplatesAndFieldIndexes(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.wantListField].([]any) require.True(t, ok, "expected list field %q in response", tt.wantListField) assert.Empty(t, list) @@ -204,13 +205,13 @@ func TestHandler_IndexPolicyResponseShape(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } diff --git a/services/cloudwatchlogs/handler_integrations.go b/services/cloudwatchlogs/handler_integrations.go index c1060397f2..dac48c8931 100644 --- a/services/cloudwatchlogs/handler_integrations.go +++ b/services/cloudwatchlogs/handler_integrations.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type associateSourceToS3TableIntegrationInput struct { @@ -20,7 +22,7 @@ func (h *Handler) handleAssociateSourceToS3TableIntegration( b []byte, ) (any, error) { var input associateSourceToS3TableIntegrationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -72,7 +74,7 @@ func (h *Handler) handlePutIntegration( body []byte, ) (any, error) { var in putIntegrationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -159,7 +161,7 @@ func (h *Handler) handleGetIntegration( body []byte, ) (any, error) { var in getIntegrationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -194,7 +196,7 @@ func (h *Handler) handleListIntegrations( body []byte, ) (any, error) { var in listIntegrationsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } @@ -224,7 +226,7 @@ func (h *Handler) handleDeleteIntegration( body []byte, ) (any, error) { var in deleteIntegrationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -250,7 +252,7 @@ func (h *Handler) handleDisassociateSourceFromS3TableIntegration( body []byte, ) (any, error) { var in disassociateSourceFromS3TableIntegrationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } @@ -289,7 +291,7 @@ func (h *Handler) handleListSourcesForS3TableIntegration( body []byte, ) (any, error) { var input listSourcesForS3TableIntegrationInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } diff --git a/services/cloudwatchlogs/handler_integrations_test.go b/services/cloudwatchlogs/handler_integrations_test.go index fa860558e7..5f16fbbd78 100644 --- a/services/cloudwatchlogs/handler_integrations_test.go +++ b/services/cloudwatchlogs/handler_integrations_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -135,7 +136,7 @@ func TestHandler_Integration(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -186,7 +187,7 @@ func TestHandler_AssociateSourceToS3TableIntegrationOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -194,7 +195,7 @@ func TestHandler_AssociateSourceToS3TableIntegrationOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { @@ -252,7 +253,7 @@ func TestHandler_S3TableIntegrationSourceOperations(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, tt.action, string(bodyBytes)) @@ -260,7 +261,7 @@ func TestHandler_S3TableIntegrationSourceOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.wantListField].([]any) require.True(t, ok, "expected list field %q in response", tt.wantListField) assert.Empty(t, list) @@ -280,7 +281,7 @@ func TestHandler_DisassociateSourceFromS3TableIntegration_RoundTrip(t *testing.T require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) identifier, ok := assocResp["identifier"].(string) require.True(t, ok, "expected identifier in AssociateSourceToS3TableIntegration response") require.NotEmpty(t, identifier) @@ -290,7 +291,7 @@ func TestHandler_DisassociateSourceFromS3TableIntegration_RoundTrip(t *testing.T require.Equal(t, http.StatusOK, disRec.Code) var disResp map[string]any - require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp)) + require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp, jsonx.V1())) assert.Equal(t, identifier, disResp["identifier"]) // A second disassociate of the same identifier must now fail: the @@ -340,13 +341,13 @@ func TestHandler_IntegrationResponseShape(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } diff --git a/services/cloudwatchlogs/handler_log_events.go b/services/cloudwatchlogs/handler_log_events.go index e8c76da697..d01248126d 100644 --- a/services/cloudwatchlogs/handler_log_events.go +++ b/services/cloudwatchlogs/handler_log_events.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putLogEventsInput struct { @@ -103,7 +105,7 @@ func (h *Handler) logEventActions() map[string]actionFn { return map[string]actionFn{ "PutLogEvents": func(ctx context.Context, b []byte) (any, error) { var input putLogEventsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } result, err := h.Backend.PutLogEvents( @@ -125,7 +127,7 @@ func (h *Handler) logEventActions() map[string]actionFn { }, "GetLogEvents": func(ctx context.Context, b []byte) (any, error) { var input getLogEventsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } groupName, err := resolveLogGroupRef(input.LogGroupName, input.LogGroupIdentifier) @@ -147,7 +149,7 @@ func (h *Handler) logEventActions() map[string]actionFn { }, "FilterLogEvents": func(ctx context.Context, b []byte) (any, error) { var input filterLogEventsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } groupName, err := resolveLogGroupRef(input.LogGroupName, input.LogGroupIdentifier) @@ -180,7 +182,7 @@ func (h *Handler) logEventActions() map[string]actionFn { func (h *Handler) handleGetLogRecord(ctx context.Context, b []byte) (any, error) { var input getLogRecordInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } record, err := h.Backend.GetLogRecord(withUnmask(ctx, input.Unmask), input.LogRecordPointer) @@ -205,7 +207,7 @@ func (h *Handler) handleGetLogFields(ctx context.Context, body []byte) (any, err LogGroupName string `json:"logGroupName"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } @@ -253,7 +255,7 @@ func (h *Handler) handleGetLogObject(ctx context.Context, body []byte) (any, err Unmask bool `json:"unmask"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } @@ -293,7 +295,7 @@ func (h *Handler) handlePutBearerTokenAuthentication( ctx context.Context, body []byte, ) (any, error) { var in putBearerTokenAuthenticationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -330,7 +332,7 @@ func (h *Handler) handleStartLiveTail(ctx context.Context, body []byte) (any, er LogGroupIdentifiers []string `json:"logGroupIdentifiers"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } diff --git a/services/cloudwatchlogs/handler_log_events_test.go b/services/cloudwatchlogs/handler_log_events_test.go index ae10b15188..98fdc12bdb 100644 --- a/services/cloudwatchlogs/handler_log_events_test.go +++ b/services/cloudwatchlogs/handler_log_events_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -57,7 +58,7 @@ func TestHandler_PutLogEvents_MismatchedSequenceToken_StillAccepted(t *testing.T "logEvents": []any{ map[string]any{"message": "setup", "timestamp": now + int64(i)}, }, - }) + }, jsonx.V1()) doLogsRequest(t, h, e, "PutLogEvents", string(body)) } @@ -66,7 +67,7 @@ func TestHandler_PutLogEvents_MismatchedSequenceToken_StillAccepted(t *testing.T "logStreamName": "s", "sequenceToken": tt.sequenceToken, "logEvents": []any{map[string]any{"message": "new", "timestamp": now}}, - }) + }, jsonx.V1()) rec := doLogsRequest(t, h, e, "PutLogEvents", string(reqBody)) assert.Equal(t, http.StatusOK, rec.Code, @@ -75,7 +76,7 @@ func TestHandler_PutLogEvents_MismatchedSequenceToken_StillAccepted(t *testing.T var okResp struct { NextSequenceToken string `json:"nextSequenceToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &okResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &okResp, jsonx.V1())) assert.Equal(t, strconv.Itoa(tt.setupEvents+1), okResp.NextSequenceToken) }) } @@ -212,7 +213,7 @@ func TestHandler_LogEventsOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -220,13 +221,13 @@ func TestHandler_LogEventsOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) @@ -258,7 +259,7 @@ func TestHandler_PutBearerTokenAuthentication_RoundTrip(t *testing.T) { var desc struct { LogGroups []map[string]any `json:"logGroups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) require.Len(t, desc.LogGroups, 1) assert.Equal(t, true, desc.LogGroups[0]["bearerTokenAuthenticationEnabled"]) @@ -314,7 +315,7 @@ func TestHandler_LiveTailAndLogFieldOperations(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, tt.action, string(bodyBytes)) @@ -322,7 +323,7 @@ func TestHandler_LiveTailAndLogFieldOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.wantListField].([]any) require.True(t, ok, "expected list field %q in response", tt.wantListField) assert.Empty(t, list) @@ -368,7 +369,7 @@ func TestHandler_PutLogEvents_EntityRejection(t *testing.T) { if tt.entity != nil { req["entity"] = tt.entity } - body, _ := json.Marshal(req) + body, _ := json.Marshal(req, jsonx.V1()) rec := doLogsRequest(t, h, e, "PutLogEvents", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -377,7 +378,7 @@ func TestHandler_PutLogEvents_EntityRejection(t *testing.T) { ErrorType string `json:"errorType"` } `json:"rejectedEntityInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantType == "" { assert.Nil(t, out.RejectedEntityInfo) diff --git a/services/cloudwatchlogs/handler_log_groups.go b/services/cloudwatchlogs/handler_log_groups.go index ccbcd39359..1f27cb4199 100644 --- a/services/cloudwatchlogs/handler_log_groups.go +++ b/services/cloudwatchlogs/handler_log_groups.go @@ -2,11 +2,13 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" sdktypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createLogGroupInput struct { @@ -124,7 +126,7 @@ func (h *Handler) logGroupActions() map[string]actionFn { // keep that function's cognitive complexity under the gocognit limit. func (h *Handler) handleCreateLogGroup(ctx context.Context, b []byte) (any, error) { var input createLogGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -158,7 +160,7 @@ func (h *Handler) handleCreateLogGroup(ctx context.Context, b []byte) (any, erro func (h *Handler) handleDeleteLogGroup(ctx context.Context, b []byte) (any, error) { var input deleteLogGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteLogGroup(ctx, input.LogGroupName); err != nil { @@ -170,7 +172,7 @@ func (h *Handler) handleDeleteLogGroup(ctx context.Context, b []byte) (any, erro func (h *Handler) handleDescribeLogGroups(ctx context.Context, b []byte) (any, error) { var input describeLogGroupsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -234,7 +236,7 @@ func (h *Handler) retentionActions() map[string]actionFn { return map[string]actionFn{ "PutRetentionPolicy": func(ctx context.Context, b []byte) (any, error) { var input putRetentionPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } days := input.RetentionInDays @@ -246,7 +248,7 @@ func (h *Handler) retentionActions() map[string]actionFn { }, "DeleteRetentionPolicy": func(ctx context.Context, b []byte) (any, error) { var input deleteRetentionPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.SetRetentionPolicy(ctx, input.LogGroupName, nil); err != nil { @@ -260,7 +262,7 @@ func (h *Handler) retentionActions() map[string]actionFn { func (h *Handler) handleAssociateKmsKey(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input associateKmsKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.AssociateKmsKey(input.LogGroupName, input.ResourceIdentifier, input.KmsKeyID); err != nil { @@ -275,7 +277,7 @@ func (h *Handler) handleDisassociateKmsKey( b []byte, ) (any, error) { var input disassociateKmsKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DisassociateKmsKey(input.LogGroupName, input.ResourceIdentifier); err != nil { @@ -287,7 +289,7 @@ func (h *Handler) handleDisassociateKmsKey( func (h *Handler) handleGetLogGroupFields(ctx context.Context, b []byte) (any, error) { var input getLogGroupFieldsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -306,7 +308,7 @@ func (h *Handler) handleGetLogGroupFields(ctx context.Context, b []byte) (any, e func (h *Handler) handleListLogGroups(ctx context.Context, b []byte) (any, error) { var input listLogGroupsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } for _, f := range input.LogGroupTags { @@ -344,7 +346,7 @@ func (h *Handler) handlePutLogGroupDeletionProtection( body []byte, ) (any, error) { var in putLogGroupDeletionProtectionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -388,7 +390,7 @@ func (h *Handler) handleListAggregateLogGroupSummaries( GroupBy string `json:"groupBy"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidationException, err) } } diff --git a/services/cloudwatchlogs/handler_log_groups_test.go b/services/cloudwatchlogs/handler_log_groups_test.go index ddf06b5b3a..2e18ddbcfe 100644 --- a/services/cloudwatchlogs/handler_log_groups_test.go +++ b/services/cloudwatchlogs/handler_log_groups_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -131,7 +132,7 @@ func TestHandler_LogGroupDeletionProtection(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -168,7 +169,7 @@ func TestHandler_PutLogGroupDeletionProtection_WireName(t *testing.T) { body, err := json.Marshal(map[string]any{ "logGroupIdentifier": lg, "deletionProtectionEnabled": tt.enabled, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, handler, e, "PutLogGroupDeletionProtection", string(body)) @@ -217,7 +218,7 @@ func TestHandler_PutRetentionPolicy_ZeroDays_ReturnsInvalidParameter(t *testing. body, _ := json.Marshal(map[string]any{ "logGroupName": "ret-grp", "retentionInDays": tt.retentionInDays, - }) + }, jsonx.V1()) rec := doLogsRequest(t, h, e, "PutRetentionPolicy", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code, @@ -226,7 +227,7 @@ func TestHandler_PutRetentionPolicy_ZeroDays_ReturnsInvalidParameter(t *testing. var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type, "retentionInDays=%d must return InvalidParameterException", tt.retentionInDays) }) @@ -317,7 +318,7 @@ func TestHandler_LogGroupOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -325,13 +326,13 @@ func TestHandler_LogGroupOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) @@ -386,7 +387,7 @@ func TestHandler_RetentionPolicyOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -394,13 +395,13 @@ func TestHandler_RetentionPolicyOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) @@ -458,7 +459,7 @@ func TestHandler_AssociateKmsKeyOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -466,7 +467,7 @@ func TestHandler_AssociateKmsKeyOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { @@ -500,7 +501,7 @@ func TestHandler_AggregateLogGroupSummariesEmpty(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, tt.action, string(bodyBytes)) @@ -508,7 +509,7 @@ func TestHandler_AggregateLogGroupSummariesEmpty(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.wantListField].([]any) require.True(t, ok, "expected list field %q in response", tt.wantListField) assert.Empty(t, list) diff --git a/services/cloudwatchlogs/handler_log_streams.go b/services/cloudwatchlogs/handler_log_streams.go index 0fa2046618..1a2d853064 100644 --- a/services/cloudwatchlogs/handler_log_streams.go +++ b/services/cloudwatchlogs/handler_log_streams.go @@ -2,7 +2,9 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createLogStreamInput struct { @@ -38,7 +40,7 @@ func (h *Handler) logStreamActions() map[string]actionFn { return map[string]actionFn{ "CreateLogStream": func(ctx context.Context, b []byte) (any, error) { var input createLogStreamInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if _, err := h.Backend.CreateLogStream(ctx, input.LogGroupName, input.LogStreamName); err != nil { @@ -49,7 +51,7 @@ func (h *Handler) logStreamActions() map[string]actionFn { }, "DeleteLogStream": func(ctx context.Context, b []byte) (any, error) { var input deleteLogStreamInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteLogStream(ctx, input.LogGroupName, input.LogStreamName); err != nil { @@ -60,7 +62,7 @@ func (h *Handler) logStreamActions() map[string]actionFn { }, "DescribeLogStreams": func(ctx context.Context, b []byte) (any, error) { var input describeLogStreamsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := validateDescribeLimit(input.Limit); err != nil { diff --git a/services/cloudwatchlogs/handler_log_streams_test.go b/services/cloudwatchlogs/handler_log_streams_test.go index 5d78f05e13..2d4a094925 100644 --- a/services/cloudwatchlogs/handler_log_streams_test.go +++ b/services/cloudwatchlogs/handler_log_streams_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -25,7 +26,7 @@ func TestHandler_DeleteLogStream(t *testing.T) { rec := doLogsRequest(t, h, e, "DescribeLogStreams", `{"logGroupName":"g"}`) require.Equal(t, http.StatusOK, rec.Code) var desc1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc1, jsonx.V1())) assert.Len(t, desc1["logStreams"], 1) // Delete it. @@ -36,7 +37,7 @@ func TestHandler_DeleteLogStream(t *testing.T) { rec = doLogsRequest(t, h, e, "DescribeLogStreams", `{"logGroupName":"g"}`) require.Equal(t, http.StatusOK, rec.Code) var desc2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc2, jsonx.V1())) assert.Empty(t, desc2["logStreams"]) // Delete non-existent stream → 404. @@ -171,7 +172,7 @@ func TestHandler_LogStreamOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -179,13 +180,13 @@ func TestHandler_LogStreamOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) diff --git a/services/cloudwatchlogs/handler_lookup_tables.go b/services/cloudwatchlogs/handler_lookup_tables.go index 80ebb3626b..10de41158a 100644 --- a/services/cloudwatchlogs/handler_lookup_tables.go +++ b/services/cloudwatchlogs/handler_lookup_tables.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createLookupTableInput struct { @@ -20,7 +22,7 @@ func (h *Handler) handleCreateLookupTable( body []byte, ) (any, error) { var in createLookupTableInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -53,7 +55,7 @@ func (h *Handler) handleGetLookupTable( body []byte, ) (any, error) { var in getLookupTableInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -83,7 +85,7 @@ func (h *Handler) handleUpdateLookupTable( body []byte, ) (any, error) { var in updateLookupTableInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -112,7 +114,7 @@ func (h *Handler) handleDeleteLookupTable( body []byte, ) (any, error) { var in deleteLookupTableInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -136,7 +138,7 @@ func (h *Handler) handleDescribeLookupTables( body []byte, ) (any, error) { var in describeLookupTablesInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_metric_filters.go b/services/cloudwatchlogs/handler_metric_filters.go index 00a6582879..2c86af05b2 100644 --- a/services/cloudwatchlogs/handler_metric_filters.go +++ b/services/cloudwatchlogs/handler_metric_filters.go @@ -2,7 +2,9 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- PutMetricFilter ---. @@ -53,7 +55,7 @@ type testMetricFilterOutput struct { func (h *Handler) handlePutMetricFilter(ctx context.Context, b []byte) (any, error) { var input putMetricFilterInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.PutMetricFilterWithOptions( @@ -76,7 +78,7 @@ func (h *Handler) handlePutMetricFilter(ctx context.Context, b []byte) (any, err func (h *Handler) handleDescribeMetricFilters(ctx context.Context, b []byte) (any, error) { var input describeMetricFiltersInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } filters, next, err := h.Backend.DescribeMetricFilters( @@ -97,7 +99,7 @@ func (h *Handler) handleDescribeMetricFilters(ctx context.Context, b []byte) (an func (h *Handler) handleDeleteMetricFilter(ctx context.Context, b []byte) (any, error) { var input deleteMetricFilterInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteMetricFilter(ctx, input.LogGroupName, input.FilterName); err != nil { @@ -112,7 +114,7 @@ func (h *Handler) handleTestMetricFilter( b []byte, ) (any, error) { var input testMetricFilterInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } matches, err := h.Backend.TestMetricFilter(input.FilterPattern, input.LogEventMessages) diff --git a/services/cloudwatchlogs/handler_queries.go b/services/cloudwatchlogs/handler_queries.go index 4887014d96..c6a1fd70ed 100644 --- a/services/cloudwatchlogs/handler_queries.go +++ b/services/cloudwatchlogs/handler_queries.go @@ -2,9 +2,11 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type startQueryInput struct { @@ -74,7 +76,7 @@ type listLogGroupsForQueryOutput struct { func (h *Handler) handleStartQuery(ctx context.Context, b []byte) (any, error) { var input startQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -103,7 +105,7 @@ func (h *Handler) handleStartQuery(ctx context.Context, b []byte) (any, error) { func (h *Handler) handleGetQueryResults(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input getQueryResultsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } results, stats, status, err := h.Backend.GetQueryResults(input.QueryID) @@ -140,7 +142,7 @@ func (h *Handler) handleGetQueryResults(ctx context.Context, b []byte) (any, err func (h *Handler) handleStopQuery(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input stopQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.StopQuery(input.QueryID); err != nil { @@ -152,7 +154,7 @@ func (h *Handler) handleStopQuery(ctx context.Context, b []byte) (any, error) { func (h *Handler) handleDescribeQueries(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input describeQueriesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } queries, next, err := h.Backend.DescribeQueries( @@ -179,7 +181,7 @@ func (h *Handler) handleListLogGroupsForQuery( b []byte, ) (any, error) { var input listLogGroupsForQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } groups, err := h.Backend.ListLogGroupsForQuery(input.QueryID) diff --git a/services/cloudwatchlogs/handler_queries_test.go b/services/cloudwatchlogs/handler_queries_test.go index 16ef2d1511..5267878b5c 100644 --- a/services/cloudwatchlogs/handler_queries_test.go +++ b/services/cloudwatchlogs/handler_queries_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -45,7 +46,7 @@ func TestHandler_InsightsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut, jsonx.V1())) queryID := startOut["queryId"] require.NotEmpty(t, queryID) @@ -54,7 +55,7 @@ func TestHandler_InsightsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resultsOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut, jsonx.V1())) assert.Equal(t, "Complete", resultsOut["status"]) rows, ok := resultsOut["results"].([]any) require.True(t, ok) @@ -65,7 +66,7 @@ func TestHandler_InsightsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &descOut, jsonx.V1())) queries, ok := descOut["queries"].([]any) require.True(t, ok) assert.Len(t, queries, 1) @@ -83,7 +84,7 @@ func TestHandler_InsightsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var stopOut map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &stopOut)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &stopOut, jsonx.V1())) assert.Equal(t, true, stopOut["success"]) // GetQueryResults now shows Cancelled. @@ -91,7 +92,7 @@ func TestHandler_InsightsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, rec5.Code) var resultsOut2 map[string]any - require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &resultsOut2)) + require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &resultsOut2, jsonx.V1())) assert.Equal(t, "Cancelled", resultsOut2["status"]) } @@ -163,21 +164,21 @@ func TestHandler_InsightsQueryLanguage(t *testing.T) { "queryString": tt.query, "startTime": 0, "endTime": 0, - }) + }, jsonx.V1()) require.NoError(t, merr) rec := doLogsRequest(t, h, e, "StartQuery", string(bodyJSON)) require.Equal(t, http.StatusOK, rec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut, jsonx.V1())) queryID := startOut["queryId"] rec2 := doLogsRequest(t, h, e, "GetQueryResults", `{"queryId":"`+queryID+`"}`) require.Equal(t, http.StatusOK, rec2.Code) var resultsOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut, jsonx.V1())) rows, ok := resultsOut["results"].([]any) require.True(t, ok, "results should be an array") assert.Len(t, rows, tt.wantResults) @@ -232,14 +233,14 @@ func TestHandler_InsightsARNIdentifier(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, "StartQuery", string(bodyBytes)) require.Equal(t, http.StatusOK, rec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startOut, jsonx.V1())) queryID := startOut["queryId"] require.NotEmpty(t, queryID) @@ -247,7 +248,7 @@ func TestHandler_InsightsARNIdentifier(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resultsOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resultsOut, jsonx.V1())) rows, ok := resultsOut["results"].([]any) require.True(t, ok) assert.Len(t, rows, tt.wantResults) @@ -271,7 +272,7 @@ func TestHandler_DescribeQueries_Pagination(t *testing.T) { "queryString": "fields @message", "startTime": i, "endTime": 0, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, "StartQuery", string(bodyBytes)) @@ -283,7 +284,7 @@ func TestHandler_DescribeQueries_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) page1Queries, ok := page1["queries"].([]any) require.True(t, ok, "page1 queries should be an array") assert.Len(t, page1Queries, 2) @@ -295,14 +296,14 @@ func TestHandler_DescribeQueries_Pagination(t *testing.T) { "logGroupName": "/pg", "maxResults": 2, "nextToken": nextToken, - }) + }, jsonx.V1()) require.NoError(t, err) rec2 := doLogsRequest(t, h, e, "DescribeQueries", string(bodyBytes)) require.Equal(t, http.StatusOK, rec2.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) page2Queries, ok := page2["queries"].([]any) require.True(t, ok, "page2 queries should be an array") assert.Len(t, page2Queries, 1) @@ -416,7 +417,7 @@ func TestHandler_QueryOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -424,13 +425,13 @@ func TestHandler_QueryOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) diff --git a/services/cloudwatchlogs/handler_query_definitions.go b/services/cloudwatchlogs/handler_query_definitions.go index ab82acbb26..b849622350 100644 --- a/services/cloudwatchlogs/handler_query_definitions.go +++ b/services/cloudwatchlogs/handler_query_definitions.go @@ -2,7 +2,9 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- PutQueryDefinition ---. @@ -46,7 +48,7 @@ func (h *Handler) handlePutQueryDefinition( b []byte, ) (any, error) { var input putQueryDefinitionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } id, err := h.Backend.PutQueryDefinitionWithLanguage( @@ -69,7 +71,7 @@ func (h *Handler) handleDescribeQueryDefinitions( b []byte, ) (any, error) { var input describeQueryDefinitionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } defs, next, err := h.Backend.DescribeQueryDefinitionsByLanguage( @@ -90,7 +92,7 @@ func (h *Handler) handleDeleteQueryDefinition( b []byte, ) (any, error) { var input deleteQueryDefinitionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteQueryDefinition(input.QueryDefinitionID); err != nil { diff --git a/services/cloudwatchlogs/handler_resource_policies.go b/services/cloudwatchlogs/handler_resource_policies.go index 244539152f..39aa9697f9 100644 --- a/services/cloudwatchlogs/handler_resource_policies.go +++ b/services/cloudwatchlogs/handler_resource_policies.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putResourcePolicyInput struct { @@ -23,7 +25,7 @@ func (h *Handler) handlePutResourcePolicy( body []byte, ) (any, error) { var in putResourcePolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -58,7 +60,7 @@ func (h *Handler) handleDescribeResourcePolicies( body []byte, ) (any, error) { var in describeResourcePoliciesInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -82,7 +84,7 @@ func (h *Handler) handleDeleteResourcePolicy( body []byte, ) (any, error) { var in deleteResourcePolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_resource_policies_test.go b/services/cloudwatchlogs/handler_resource_policies_test.go index 888a2b5f77..6c9c72186b 100644 --- a/services/cloudwatchlogs/handler_resource_policies_test.go +++ b/services/cloudwatchlogs/handler_resource_policies_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -82,7 +83,7 @@ func TestHandler_ResourcePolicy(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -126,13 +127,13 @@ func TestHandler_ResourcePolicyResponseShape(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } diff --git a/services/cloudwatchlogs/handler_scheduled_queries.go b/services/cloudwatchlogs/handler_scheduled_queries.go index 836e74bd55..ae5688bcf9 100644 --- a/services/cloudwatchlogs/handler_scheduled_queries.go +++ b/services/cloudwatchlogs/handler_scheduled_queries.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createScheduledQueryInput struct { @@ -153,7 +155,7 @@ func (h *Handler) handleCreateScheduledQuery( b []byte, ) (any, error) { var input createScheduledQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -195,7 +197,7 @@ func (h *Handler) handleDeleteScheduledQuery( b []byte, ) (any, error) { var input deleteScheduledQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteScheduledQuery(input.Identifier); err != nil { @@ -210,7 +212,7 @@ func (h *Handler) handleListScheduledQueries( b []byte, ) (any, error) { var input listScheduledQueriesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } queries, next, err := h.Backend.ListScheduledQueries( @@ -233,7 +235,7 @@ func (h *Handler) handleUpdateScheduledQuery( b []byte, ) (any, error) { var input updateScheduledQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -252,7 +254,7 @@ func (h *Handler) handleGetScheduledQuery( b []byte, ) (any, error) { var input getScheduledQueryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } sq, err := h.Backend.GetScheduledQuery(input.Identifier) @@ -274,7 +276,7 @@ func (h *Handler) handleGetScheduledQueryHistory( b []byte, ) (any, error) { var input getScheduledQueryHistoryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/cloudwatchlogs/handler_scheduled_queries_test.go b/services/cloudwatchlogs/handler_scheduled_queries_test.go index 393d065a8f..e4d2e7e1d7 100644 --- a/services/cloudwatchlogs/handler_scheduled_queries_test.go +++ b/services/cloudwatchlogs/handler_scheduled_queries_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -31,7 +32,7 @@ func TestHandler_ScheduledQuery_Create(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) queryARN, ok := out["scheduledQueryArn"].(string) require.True(t, ok) assert.Contains(t, queryARN, "scheduled-query") @@ -60,7 +61,7 @@ func TestHandler_GetScheduledQuery_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) queryARN, ok := createOut["scheduledQueryArn"].(string) require.True(t, ok) require.NotEmpty(t, queryARN) @@ -69,7 +70,7 @@ func TestHandler_GetScheduledQuery_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var sq map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &sq)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &sq, jsonx.V1())) assert.Equal(t, queryARN, sq["scheduledQueryArn"], "wire key must be scheduledQueryArn, not arn") _, hasOldKey := sq["arn"] @@ -129,7 +130,7 @@ func TestHandler_ScheduledQuery_DestinationConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) queryARN, arnOK := createOut["scheduledQueryArn"].(string) require.True(t, arnOK) @@ -137,7 +138,7 @@ func TestHandler_ScheduledQuery_DestinationConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var sq map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &sq)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &sq, jsonx.V1())) destRaw, hasDest := sq["destinationConfiguration"] if !tt.wantS3 && !tt.wantLookup { @@ -282,7 +283,7 @@ func TestHandler_CreateScheduledQueryOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -290,7 +291,7 @@ func TestHandler_CreateScheduledQueryOperations(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantKey != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantVal != "" { assert.Equal(t, tt.wantVal, out[tt.wantKey]) } else { @@ -321,7 +322,7 @@ func TestHandler_GetScheduledQueryHistory_RequiresTimeRange(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) arn, _ := created["scheduledQueryArn"].(string) require.NotEmpty(t, arn) diff --git a/services/cloudwatchlogs/handler_storage_tier_policy.go b/services/cloudwatchlogs/handler_storage_tier_policy.go index 1853cf8301..14ec12c599 100644 --- a/services/cloudwatchlogs/handler_storage_tier_policy.go +++ b/services/cloudwatchlogs/handler_storage_tier_policy.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // storageTierPolicyWireShape maps a StorageTierPolicy to the real @@ -42,7 +44,7 @@ func (h *Handler) handlePutStorageTierPolicy( body []byte, ) (any, error) { var in putStorageTierPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_subscription_filters.go b/services/cloudwatchlogs/handler_subscription_filters.go index 4da6f705ab..735e1133b0 100644 --- a/services/cloudwatchlogs/handler_subscription_filters.go +++ b/services/cloudwatchlogs/handler_subscription_filters.go @@ -2,7 +2,9 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putSubscriptionFilterInput struct { @@ -43,7 +45,7 @@ func (h *Handler) subscriptionFilterActions() map[string]actionFn { return map[string]actionFn{ "PutSubscriptionFilter": func(ctx context.Context, b []byte) (any, error) { var input putSubscriptionFilterInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.PutSubscriptionFilterWithOptions( @@ -63,7 +65,7 @@ func (h *Handler) subscriptionFilterActions() map[string]actionFn { }, "DescribeSubscriptionFilters": func(ctx context.Context, b []byte) (any, error) { var input describeSubscriptionFiltersInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } filters, next, err := h.Backend.DescribeSubscriptionFilters( @@ -78,7 +80,7 @@ func (h *Handler) subscriptionFilterActions() map[string]actionFn { }, "DeleteSubscriptionFilter": func(ctx context.Context, b []byte) (any, error) { var input deleteSubscriptionFilterInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteSubscriptionFilter(ctx, input.LogGroupName, input.FilterName); err != nil { diff --git a/services/cloudwatchlogs/handler_subscription_filters_test.go b/services/cloudwatchlogs/handler_subscription_filters_test.go index f5146adba7..962cff3059 100644 --- a/services/cloudwatchlogs/handler_subscription_filters_test.go +++ b/services/cloudwatchlogs/handler_subscription_filters_test.go @@ -2,7 +2,7 @@ package cloudwatchlogs_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -152,7 +153,7 @@ func TestHandler_SubscriptionFilterOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -160,13 +161,13 @@ func TestHandler_SubscriptionFilterOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) diff --git a/services/cloudwatchlogs/handler_syslog_configurations.go b/services/cloudwatchlogs/handler_syslog_configurations.go index 5609fd91a7..681532efa3 100644 --- a/services/cloudwatchlogs/handler_syslog_configurations.go +++ b/services/cloudwatchlogs/handler_syslog_configurations.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putSyslogConfigurationInput struct { @@ -13,7 +15,7 @@ type putSyslogConfigurationInput struct { func (h *Handler) handlePutSyslogConfiguration(ctx context.Context, body []byte) (any, error) { var in putSyslogConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -39,7 +41,7 @@ func (h *Handler) handleDeleteSyslogConfiguration( body []byte, ) (any, error) { var in deleteSyslogConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -64,7 +66,7 @@ func (h *Handler) handleListSyslogConfigurations( body []byte, ) (any, error) { var in listSyslogConfigurationsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } diff --git a/services/cloudwatchlogs/handler_tags.go b/services/cloudwatchlogs/handler_tags.go index 3f457eadc1..da61a90e39 100644 --- a/services/cloudwatchlogs/handler_tags.go +++ b/services/cloudwatchlogs/handler_tags.go @@ -2,10 +2,11 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "maps" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -136,7 +137,7 @@ func (h *Handler) logTagActions() map[string]actionFn { return map[string]actionFn{ "ListTagsLogGroup": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input listTagsLogGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -144,7 +145,7 @@ func (h *Handler) logTagActions() map[string]actionFn { }, "ListTagsForResource": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input listTagsForResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -152,7 +153,7 @@ func (h *Handler) logTagActions() map[string]actionFn { }, "TagLogGroup": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input tagLogGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } var kv map[string]string @@ -165,7 +166,7 @@ func (h *Handler) logTagActions() map[string]actionFn { }, "UntagLogGroup": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input untagLogGroupInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } h.removeTags(input.LogGroupName, input.Tags) @@ -174,7 +175,7 @@ func (h *Handler) logTagActions() map[string]actionFn { }, "TagResource": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input tagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } h.setTags(input.ResourceArn, maps.Clone(input.Tags)) @@ -183,7 +184,7 @@ func (h *Handler) logTagActions() map[string]actionFn { }, "UntagResource": func(ctx context.Context, b []byte) (any, error) { //nolint:revive // existing issue. var input untagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } h.removeTags(input.ResourceArn, input.TagKeys) diff --git a/services/cloudwatchlogs/handler_tags_test.go b/services/cloudwatchlogs/handler_tags_test.go index ef4204dd59..9da29d833a 100644 --- a/services/cloudwatchlogs/handler_tags_test.go +++ b/services/cloudwatchlogs/handler_tags_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -33,7 +34,7 @@ func TestHandler_TagRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Equal(t, "prod", listResp["tags"]["env"]) assert.Equal(t, "ops", listResp["tags"]["team"]) @@ -42,7 +43,7 @@ func TestHandler_TagRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp2 map[string]map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp2, jsonx.V1())) assert.Len(t, listResp2["tags"], 2) // Remove one tag. @@ -53,7 +54,7 @@ func TestHandler_TagRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listResp3 map[string]map[string]string - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp3, jsonx.V1())) assert.Len(t, listResp3["tags"], 1) assert.Equal(t, "ops", listResp3["tags"]["team"]) } @@ -75,7 +76,7 @@ func TestHandler_TagResource_UntagResource(t *testing.T) { rec = doLogsRequest(t, h, e, "ListTagsForResource", `{"resourceArn":"`+arn+`"}`) require.Equal(t, http.StatusOK, rec.Code) var listOut map[string]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Equal(t, "prod", listOut["tags"]["env"]) assert.Equal(t, "platform", listOut["tags"]["team"]) @@ -87,7 +88,7 @@ func TestHandler_TagResource_UntagResource(t *testing.T) { // Verify env tag removed, team tag remains. rec = doLogsRequest(t, h, e, "ListTagsForResource", `{"resourceArn":"`+arn+`"}`) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut["tags"]["env"]) assert.Equal(t, "platform", listOut["tags"]["team"]) } @@ -111,7 +112,7 @@ func TestHandler_Reset_ClearsTags(t *testing.T) { `{"resourceArn":"arn:aws:logs:us-east-1:123:log-group:/g"}`) require.Equal(t, http.StatusOK, rec.Code) var listOut map[string]map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut["tags"]) } @@ -205,7 +206,7 @@ func TestHandler_TagOperations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -213,13 +214,13 @@ func TestHandler_TagOperations(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) diff --git a/services/cloudwatchlogs/handler_test.go b/services/cloudwatchlogs/handler_test.go index ed85c2b0d2..4e23db3a6d 100644 --- a/services/cloudwatchlogs/handler_test.go +++ b/services/cloudwatchlogs/handler_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -65,7 +66,7 @@ func TestHandler_GetSupportedOperations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.Contains(t, ops, "CreateLogGroup") assert.Contains(t, ops, "PutLogEvents") } @@ -299,14 +300,14 @@ func TestHandler_LookupTables(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "response should contain field %q", field) } @@ -325,7 +326,7 @@ func TestHandler_LookupTable_GetRealResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) tableArn, ok := created["lookupTableArn"].(string) require.True(t, ok) require.NotEmpty(t, tableArn) @@ -334,7 +335,7 @@ func TestHandler_LookupTable_GetRealResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "shape_table", got["lookupTableName"]) assert.Equal(t, "id,name\n1,foo\n", got["tableBody"]) assert.Contains(t, got, "sizeBytes") @@ -402,7 +403,7 @@ func TestHandler_SyslogConfigurations(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) @@ -426,7 +427,7 @@ func TestHandler_SyslogConfiguration_ListResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["syslogConfigurations"].([]any) require.True(t, ok) require.Len(t, list, 1) @@ -496,7 +497,7 @@ func TestHandler_StorageTierPolicy(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) require.Equal(t, tt.wantCode, rec.Code) @@ -506,7 +507,7 @@ func TestHandler_StorageTierPolicy(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantTier, resp["storageTier"]) if tt.wantHasLUT { @@ -550,7 +551,7 @@ func TestHandler_UnknownOperation(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) @@ -558,13 +559,13 @@ func TestHandler_UnknownOperation(t *testing.T) { if tt.wantListField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp[tt.wantListField].([]any), tt.wantListLen) } if tt.wantNotEmptyField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) diff --git a/services/cloudwatchlogs/handler_transformers.go b/services/cloudwatchlogs/handler_transformers.go index 2af8d8dd9c..4f141b167f 100644 --- a/services/cloudwatchlogs/handler_transformers.go +++ b/services/cloudwatchlogs/handler_transformers.go @@ -2,8 +2,10 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putTransformerInput struct { @@ -16,7 +18,7 @@ func (h *Handler) handlePutTransformer( body []byte, ) (any, error) { var in putTransformerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -38,7 +40,7 @@ func (h *Handler) handleGetTransformer( body []byte, ) (any, error) { var in getTransformerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -78,7 +80,7 @@ func (h *Handler) handleDeleteTransformer( body []byte, ) (any, error) { var in deleteTransformerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } @@ -103,7 +105,7 @@ func (h *Handler) handleTestTransformer( LogEventMessages []string `json:"logEventMessages"` } if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } diff --git a/services/cloudwatchlogs/handler_transformers_test.go b/services/cloudwatchlogs/handler_transformers_test.go index 040bd90f76..e69fc09d2b 100644 --- a/services/cloudwatchlogs/handler_transformers_test.go +++ b/services/cloudwatchlogs/handler_transformers_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -112,7 +113,7 @@ func TestHandler_Transformer(t *testing.T) { tt.setup(t, h, e) } - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, h, e, tt.action, string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) diff --git a/services/cloudwatchlogs/insights_parse.go b/services/cloudwatchlogs/insights_parse.go index 9fe13e8782..487e3ac956 100644 --- a/services/cloudwatchlogs/insights_parse.go +++ b/services/cloudwatchlogs/insights_parse.go @@ -1,12 +1,14 @@ package cloudwatchlogs import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strconv" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseParseCommand parses "parse [source] <"glob"|/regex/> [as f1, f2, ...]". @@ -199,7 +201,7 @@ func resolveJSONField(message, name string) cwValue { } var root any - if err := json.Unmarshal([]byte(message), &root); err != nil { + if err := json.Unmarshal([]byte(message), &root, jsonx.V1()); err != nil { return cwValue{} } @@ -273,7 +275,7 @@ func jsonScalar(v any) cwValue { case nil: return cwValue{} default: - if b, err := json.Marshal(t); err == nil { + if b, err := json.Marshal(t, jsonx.V1()); err == nil { return literalString(string(b)) } diff --git a/services/cloudwatchlogs/log_events.go b/services/cloudwatchlogs/log_events.go index c8e807d3f8..518be23d78 100644 --- a/services/cloudwatchlogs/log_events.go +++ b/services/cloudwatchlogs/log_events.go @@ -4,7 +4,8 @@ import ( "cmp" "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "sort" @@ -13,6 +14,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validatePutLogEventsBatch checks the batch size constraints for PutLogEvents. @@ -954,8 +956,8 @@ func jsonMessageFields(message string) []string { return nil } - var obj map[string]json.RawMessage - if err := json.Unmarshal([]byte(trimmed), &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal([]byte(trimmed), &obj, jsonx.V1()); err != nil { return nil } diff --git a/services/cloudwatchlogs/perf_sweep_bench_test.go b/services/cloudwatchlogs/perf_sweep_bench_test.go index ab5cb2f792..074e4a388a 100644 --- a/services/cloudwatchlogs/perf_sweep_bench_test.go +++ b/services/cloudwatchlogs/perf_sweep_bench_test.go @@ -6,7 +6,7 @@ package cloudwatchlogs_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -29,7 +30,7 @@ func invokeLogsB( ) (int, map[string]any) { tb.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(tb, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -41,7 +42,7 @@ func invokeLogsB( var resp map[string]any if rec.Body.Len() > 0 { - require.NoError(tb, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(tb, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) } return rec.Code, resp diff --git a/services/cloudwatchlogs/persistence.go b/services/cloudwatchlogs/persistence.go index c04fc4a132..ec5d874167 100644 --- a/services/cloudwatchlogs/persistence.go +++ b/services/cloudwatchlogs/persistence.go @@ -2,7 +2,7 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -319,10 +319,10 @@ func newDirtyDTORegistry() ( // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/cloudwatchlogs/presence_validation_test.go b/services/cloudwatchlogs/presence_validation_test.go index a4437127fc..1f31e55408 100644 --- a/services/cloudwatchlogs/presence_validation_test.go +++ b/services/cloudwatchlogs/presence_validation_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -50,7 +51,7 @@ func TestGetLogFields_DataSourceTypePresenceValidation(t *testing.T) { handler := cloudwatchlogs.NewHandler(backend) doLogsRequest(t, handler, e, "CreateLogGroup", `{"logGroupName":"grp"}`) - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := doLogsRequest(t, handler, e, "GetLogFields", string(bodyBytes)) @@ -58,7 +59,7 @@ func TestGetLogFields_DataSourceTypePresenceValidation(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } }) @@ -115,14 +116,14 @@ func TestListAggregateLogGroupSummaries_GroupByPresenceValidation(t *testing.T) t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := makeLogsRequest(t, "ListAggregateLogGroupSummaries", string(bodyBytes)) assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantCode == http.StatusBadRequest { assert.Equal(t, tt.wantError, resp["__type"]) diff --git a/services/cloudwatchlogs/subscription_filters.go b/services/cloudwatchlogs/subscription_filters.go index f8b5fddd2f..53b66e2216 100644 --- a/services/cloudwatchlogs/subscription_filters.go +++ b/services/cloudwatchlogs/subscription_filters.go @@ -5,7 +5,7 @@ import ( "compress/gzip" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -283,7 +284,7 @@ func (b *InMemoryBackend) deliverToFilters( // encodeSubscriptionPayload gzips the JSON payload and base64-encodes it. func encodeSubscriptionPayload(payload subscriptionPayload) ([]byte, error) { - raw, err := json.Marshal(payload) + raw, err := json.Marshal(payload, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/cloudwatchlogs/transformers.go b/services/cloudwatchlogs/transformers.go index a47387ce62..0d18ea0701 100644 --- a/services/cloudwatchlogs/transformers.go +++ b/services/cloudwatchlogs/transformers.go @@ -2,10 +2,12 @@ package cloudwatchlogs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutTransformer creates or updates a log transformer. @@ -134,7 +136,7 @@ func applyProcessorsToMessage(message string, processors []map[string]any) strin return message } - out, err := json.Marshal(obj) + out, err := json.Marshal(obj, jsonx.V1()) if err != nil { return message } @@ -149,7 +151,7 @@ func decodeJSONObject(message string) (map[string]any, bool) { } var obj map[string]any - if err := json.Unmarshal([]byte(trimmed), &obj); err != nil { + if err := json.Unmarshal([]byte(trimmed), &obj, jsonx.V1()); err != nil { return nil, false } diff --git a/services/cloudwatchlogs/undeclared_members_test.go b/services/cloudwatchlogs/undeclared_members_test.go index 1e1b3c99c9..1e6c7c42e4 100644 --- a/services/cloudwatchlogs/undeclared_members_test.go +++ b/services/cloudwatchlogs/undeclared_members_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -41,7 +42,7 @@ func TestHandler_DescribeExportTasks_OmitsLogStreamNamePrefix(t *testing.T) { var out struct { ExportTasks []map[string]any `json:"exportTasks"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ExportTasks, 1) assert.NotContains(t, out.ExportTasks[0], "logStreamNamePrefix") assert.Equal(t, "/grp", out.ExportTasks[0]["logGroupName"]) diff --git a/services/cloudwatchlogs/wire_field_fixes_test.go b/services/cloudwatchlogs/wire_field_fixes_test.go index 37e966cf35..b2b17b3a5b 100644 --- a/services/cloudwatchlogs/wire_field_fixes_test.go +++ b/services/cloudwatchlogs/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package cloudwatchlogs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -711,7 +712,7 @@ func TestGetScheduledQueryHistory_RealShape(t *testing.T) { require.Equal(t, http.StatusOK, rawRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rawRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rawRec.Body.Bytes(), &raw, jsonx.V1())) _, hasReal := raw["triggerHistory"] assert.True(t, hasReal, "wrapper key must be triggerHistory") _, hasFabricated := raw["scheduledQueryRunSummaries"] diff --git a/services/codeartifact/domain_owner_and_error_message_test.go b/services/codeartifact/domain_owner_and_error_message_test.go index 6ce1d3cf9d..4e5241bddd 100644 --- a/services/codeartifact/domain_owner_and_error_message_test.go +++ b/services/codeartifact/domain_owner_and_error_message_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDomainOwnerQuery(t *testing.T) { @@ -64,7 +65,7 @@ func TestPackageVersionErrors_CarryMessage(t *testing.T) { } `json:"failedVersions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Contains(t, out.Failed, "99.0.0") assert.Equal(t, "NOT_FOUND", out.Failed["99.0.0"].Code) assert.Contains(t, out.Failed["99.0.0"].Message, "99.0.0") diff --git a/services/codeartifact/handler.go b/services/codeartifact/handler.go index 1ad712b2be..992331a2cc 100644 --- a/services/codeartifact/handler.go +++ b/services/codeartifact/handler.go @@ -2,7 +2,8 @@ package codeartifact import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -632,9 +634,9 @@ func (h *Handler) readRequestBody(c *echo.Context, op string) ([]byte, error) { return raw, nil } - decoder := json.NewDecoder(c.Request().Body) - var raw json.RawMessage - if err := decoder.Decode(&raw); err != nil { + decoder := jsontext.NewDecoder(c.Request().Body) + var raw jsontext.Value + if err := json.UnmarshalDecode(decoder, &raw, jsonx.V1()); err != nil { raw = nil } diff --git a/services/codeartifact/handler_domains.go b/services/codeartifact/handler_domains.go index 96055ddb47..4550877349 100644 --- a/services/codeartifact/handler_domains.go +++ b/services/codeartifact/handler_domains.go @@ -3,12 +3,14 @@ package codeartifact import ( "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const authTokenBytes = 48 @@ -60,7 +62,7 @@ func (h *Handler) handleCreateDomain(c *echo.Context, name string, body []byte) var in createDomainBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -105,7 +107,7 @@ type listDomainsBody struct { func (h *Handler) handleListDomains(c *echo.Context, body []byte) error { var in listDomainsBody if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } all := h.Backend.ListDomains(c.Request().Context()) @@ -217,7 +219,7 @@ type putDomainPermissionsPolicyBody struct { func (h *Handler) handlePutDomainPermissionsPolicy(c *echo.Context, body []byte) error { var in putDomainPermissionsPolicyBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } diff --git a/services/codeartifact/handler_domains_test.go b/services/codeartifact/handler_domains_test.go index 670e0b0415..37d14142cb 100644 --- a/services/codeartifact/handler_domains_test.go +++ b/services/codeartifact/handler_domains_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -50,7 +51,7 @@ func TestHandler_CreateAndDescribeDomain(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) domain, _ := resp["domain"].(map[string]any) assert.Equal(t, tt.domainName, domain["name"]) assert.NotEmpty(t, domain["arn"]) @@ -60,7 +61,7 @@ func TestHandler_CreateAndDescribeDomain(t *testing.T) { descRec := doRequest(t, h, http.MethodGet, "/v1/domain?domain="+tt.domainName, nil) assert.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) ddomain, _ := descResp["domain"].(map[string]any) assert.Equal(t, tt.domainName, ddomain["name"]) } @@ -119,7 +120,7 @@ func TestHandler_ListDomains(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) domains, _ := resp["domains"].([]any) assert.Len(t, domains, 2) } @@ -134,7 +135,7 @@ func TestHandler_GetAuthorizationToken(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["authorizationToken"]) assert.NotEmpty(t, resp["expiration"]) } @@ -167,7 +168,7 @@ func TestHandler_DomainPermissionsPolicy(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(b, &resp)) + require.NoError(t, json.Unmarshal(b, &resp, jsonx.V1())) pol, _ := resp["policy"].(map[string]any) assert.NotEmpty(t, pol["revision"]) assert.NotEmpty(t, pol["resourceArn"]) @@ -189,7 +190,7 @@ func TestHandler_DomainPermissionsPolicy(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(b, &resp)) + require.NoError(t, json.Unmarshal(b, &resp, jsonx.V1())) pol, _ := resp["policy"].(map[string]any) assert.Contains(t, pol["document"], "Allow") }, @@ -279,7 +280,7 @@ func TestHandler_DomainPermissionsPolicy_RevisionLocking(t *testing.T) { }) require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) pol, _ := putResp["policy"].(map[string]any) revision, _ := pol["revision"].(string) require.NotEmpty(t, revision) @@ -296,7 +297,7 @@ func TestHandler_DomainPermissionsPolicy_RevisionLocking(t *testing.T) { getRec := doRequest(t, h, http.MethodGet, "/v1/domain/permissions/policy?domain=lock-domain", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) getPol, _ := getResp["policy"].(map[string]any) assert.NotContains(t, getPol["document"], "Deny") @@ -318,7 +319,7 @@ func TestHandler_DomainPermissionsPolicy_RevisionLocking(t *testing.T) { }) require.Equal(t, http.StatusOK, matchPutRec.Code) var matchPutResp map[string]any - require.NoError(t, json.Unmarshal(matchPutRec.Body.Bytes(), &matchPutResp)) + require.NoError(t, json.Unmarshal(matchPutRec.Body.Bytes(), &matchPutResp, jsonx.V1())) newPol, _ := matchPutResp["policy"].(map[string]any) newRevision, _ := newPol["revision"].(string) require.NotEmpty(t, newRevision) @@ -387,7 +388,7 @@ func TestHandler_RepositoryCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) domainMap, _ := resp["domain"].(map[string]any) count, _ := domainMap["repositoryCount"].(float64) assert.InEpsilon(t, float64(2), count, 0) @@ -405,7 +406,7 @@ func TestHandler_DomainSummaryVsFullDescription(t *testing.T) { listRec := doRequest(t, h, http.MethodPost, "/v1/domains", nil) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) domains, _ := listResp["domains"].([]any) require.Len(t, domains, 1) d, _ := domains[0].(map[string]any) @@ -416,7 +417,7 @@ func TestHandler_DomainSummaryVsFullDescription(t *testing.T) { descRec := doRequest(t, h, http.MethodGet, "/v1/domain?domain=summary-domain", nil) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) dd, _ := descResp["domain"].(map[string]any) count, _ := dd["repositoryCount"].(float64) assert.InEpsilon(t, float64(2), count, 0) @@ -438,7 +439,7 @@ func TestListDomains_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1, _ := out1["domains"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["nextToken"].(string) @@ -448,7 +449,7 @@ func TestListDomains_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2, _ := out2["domains"].([]any) assert.Len(t, page2, 2) } diff --git a/services/codeartifact/handler_internal_error_test.go b/services/codeartifact/handler_internal_error_test.go index c8207c798f..7d927a1b9e 100644 --- a/services/codeartifact/handler_internal_error_test.go +++ b/services/codeartifact/handler_internal_error_test.go @@ -1,7 +1,7 @@ package codeartifact //nolint:testpackage // needs access to the unexported handleError method. import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errUnmatchedForTest = errors.New("boom: matches none of handleError's sentinel checks") @@ -48,7 +50,7 @@ func TestHandleError_DefaultBranchEmitsInternalServerException(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InternalServerException", body["code"]) assert.NotEqual(t, "InternalFailure", body["code"]) } diff --git a/services/codeartifact/handler_list_summary_test.go b/services/codeartifact/handler_list_summary_test.go index 0775835fe2..7f2b7dfa15 100644 --- a/services/codeartifact/handler_list_summary_test.go +++ b/services/codeartifact/handler_list_summary_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,8 @@ import ( casdk "github.com/aws/aws-sdk-go-v2/service/codeartifact" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListPackages_SummaryShape locks that ListPackages emits the @@ -36,7 +38,7 @@ func TestHandler_ListPackages_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkgs, ok := resp["packages"].([]any) require.True(t, ok) require.Len(t, pkgs, 1) @@ -112,7 +114,7 @@ func TestHandler_ListPackageVersions_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) require.Len(t, versions, 1) diff --git a/services/codeartifact/handler_package_groups.go b/services/codeartifact/handler_package_groups.go index 89e2d28fca..8305fb8ba8 100644 --- a/services/codeartifact/handler_package_groups.go +++ b/services/codeartifact/handler_package_groups.go @@ -2,11 +2,13 @@ package codeartifact import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createPackageGroupBody's Pattern field is tagged json:"packageGroup" -- @@ -120,7 +122,7 @@ func (h *Handler) handleCreatePackageGroup(c *echo.Context, domainName string, b var in createPackageGroupBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("ValidationException", "invalid request body"), @@ -423,7 +425,7 @@ func (h *Handler) handleUpdatePackageGroup(c *echo.Context, domainName string, b var in updatePackageGroupBody if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } pattern := c.Request().URL.Query().Get(keyPackageGroup) @@ -496,7 +498,7 @@ func (h *Handler) handleUpdatePackageGroupOriginConfiguration( var in updatePackageGroupOriginConfigurationBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errResp("ValidationException", "invalid request body"), diff --git a/services/codeartifact/handler_package_groups_list_test.go b/services/codeartifact/handler_package_groups_list_test.go index 8469ae50c2..4c49438684 100644 --- a/services/codeartifact/handler_package_groups_list_test.go +++ b/services/codeartifact/handler_package_groups_list_test.go @@ -2,7 +2,7 @@ package codeartifact_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -94,7 +95,7 @@ func TestHandler_ListAllowedRepositoriesForGroup(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["allowedRepositories"]) } }) @@ -161,7 +162,7 @@ func TestHandler_ListAssociatedPackages(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkgs, _ := resp["packages"].([]any) assert.NotNil(t, pkgs) } @@ -199,7 +200,7 @@ func TestHandler_ListAssociatedPackages_Preview(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkgs, _ := resp["packages"].([]any) require.Len(t, pkgs, 1) assert.Equal(t, "react", pkgs[0].(map[string]any)["package"]) @@ -256,7 +257,7 @@ func TestHandler_ListAssociatedPackages_MostSpecificMatch(t *testing.T) { require.Equal(t, http.StatusOK, broadRec.Code) var broadResp map[string]any - require.NoError(t, json.Unmarshal(broadRec.Body.Bytes(), &broadResp)) + require.NoError(t, json.Unmarshal(broadRec.Body.Bytes(), &broadResp, jsonx.V1())) broadPkgs, _ := broadResp["packages"].([]any) require.Len(t, broadPkgs, 1) assert.Equal(t, "react", broadPkgs[0].(map[string]any)["package"]) @@ -272,7 +273,7 @@ func TestHandler_ListAssociatedPackages_MostSpecificMatch(t *testing.T) { require.Equal(t, http.StatusOK, specificRec.Code) var specificResp map[string]any - require.NoError(t, json.Unmarshal(specificRec.Body.Bytes(), &specificResp)) + require.NoError(t, json.Unmarshal(specificRec.Body.Bytes(), &specificResp, jsonx.V1())) specificPkgs, _ := specificResp["packages"].([]any) require.Len(t, specificPkgs, 1) assert.Equal(t, "utils", specificPkgs[0].(map[string]any)["package"]) @@ -323,7 +324,7 @@ func TestHandler_ListAssociatedPackages_WeakMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkgs, _ := resp["packages"].([]any) require.Len(t, pkgs, 2, "both the strong and weak variant are associated with the same group") @@ -342,7 +343,7 @@ func TestHandler_ListAssociatedPackages_WeakMatch(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "WEAK", getResp["associationType"]) } @@ -383,7 +384,7 @@ func TestHandler_GetAssociatedPackageGroup_MostSpecificMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pg, _ := resp["packageGroup"].(map[string]any) require.NotNil(t, pg) assert.Equal(t, "/npm/space/react$", pg["pattern"]) @@ -398,7 +399,7 @@ func TestHandler_GetAssociatedPackageGroup_MostSpecificMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) pg2, _ := resp2["packageGroup"].(map[string]any) require.NotNil(t, pg2) assert.Equal(t, "/npm/*", pg2["pattern"]) @@ -495,7 +496,7 @@ func TestHandler_ListPackageGroups(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, _ := resp["packageGroups"].([]any) assert.Len(t, groups, tt.wantCount) } @@ -582,7 +583,7 @@ func TestHandler_UpdatePackageGroup(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pg, _ := resp["packageGroup"].(map[string]any) assert.NotNil(t, pg) } @@ -690,7 +691,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pg, _ := resp["packageGroup"].(map[string]any) require.NotNil(t, pg) @@ -735,7 +736,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration_AllowedRepositories(t *te require.Equal(t, http.StatusOK, addRec.Code) var addResp map[string]any - require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp)) + require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp, jsonx.V1())) updates, _ := addResp["allowedRepositoryUpdates"].(map[string]any) require.NotNil(t, updates) extUpstream, _ := updates["EXTERNAL_UPSTREAM"].(map[string]any) @@ -752,7 +753,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration_AllowedRepositories(t *te require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) allowed, _ := listResp["allowedRepositories"].([]any) assert.Equal(t, []any{"upgocr-repo"}, allowed) @@ -776,7 +777,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration_AllowedRepositories(t *te require.Equal(t, http.StatusOK, listRec2.Code) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2, jsonx.V1())) allowed2, _ := listResp2["allowedRepositories"].([]any) assert.Empty(t, allowed2) } @@ -828,7 +829,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration_Inheritance(t *testing.T) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) pg, _ := descResp["packageGroup"].(map[string]any) originConfig, _ := pg["originConfiguration"].(map[string]any) restrictions, _ := originConfig["restrictions"].(map[string]any) @@ -859,7 +860,7 @@ func TestHandler_UpdatePackageGroupOriginConfiguration_Inheritance(t *testing.T) require.Equal(t, http.StatusOK, descRec2.Code) var descResp2 map[string]any - require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2)) + require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2, jsonx.V1())) pg2, _ := descResp2["packageGroup"].(map[string]any) originConfig2, _ := pg2["originConfiguration"].(map[string]any) restrictions2, _ := originConfig2["restrictions"].(map[string]any) @@ -891,7 +892,7 @@ func TestListPackageGroups_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1, _ := out1["packageGroups"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["nextToken"].(string) @@ -902,7 +903,7 @@ func TestListPackageGroups_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2, _ := out2["packageGroups"].([]any) assert.Len(t, page2, 2) } diff --git a/services/codeartifact/handler_package_groups_test.go b/services/codeartifact/handler_package_groups_test.go index b1983944ee..e9fcf5a5bb 100644 --- a/services/codeartifact/handler_package_groups_test.go +++ b/services/codeartifact/handler_package_groups_test.go @@ -2,7 +2,7 @@ package codeartifact_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -94,7 +95,7 @@ func TestHandler_CreatePackageGroup(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pg, _ := resp["packageGroup"].(map[string]any) assert.NotEmpty(t, pg["arn"]) assert.Equal(t, "pg-domain", pg["domainName"]) @@ -125,7 +126,7 @@ func TestHandler_PackageGroupCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) pg, _ := createResp["packageGroup"].(map[string]any) assert.Equal(t, "/npm/mygroup/*", pg["pattern"]) assert.Equal(t, "test group", pg["description"]) @@ -302,7 +303,7 @@ func TestHandler_ListSubPackageGroups(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, _ := resp["packageGroups"].([]any) assert.Len(t, groups, tt.wantCount) } @@ -329,7 +330,7 @@ func TestHandler_PackageGroupTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) pg, _ := createResp["packageGroup"].(map[string]any) pgARN, _ := pg["arn"].(string) require.NotEmpty(t, pgARN) @@ -338,7 +339,7 @@ func TestHandler_PackageGroupTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagList, _ := listResp["tags"].([]any) assert.Len(t, tagList, 1) } @@ -383,7 +384,7 @@ func TestHandler_PackageGroupHierarchy(t *testing.T) { allRec := doRequest(t, h, http.MethodPost, "/v1/package-groups?domain=hier-domain", nil) require.Equal(t, http.StatusOK, allRec.Code) var allResp map[string]any - require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &allResp)) + require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &allResp, jsonx.V1())) allGroups, _ := allResp["packageGroups"].([]any) assert.Len(t, allGroups, 5) @@ -397,7 +398,7 @@ func TestHandler_PackageGroupHierarchy(t *testing.T) { ) require.Equal(t, http.StatusOK, subRec.Code) var subResp map[string]any - require.NoError(t, json.Unmarshal(subRec.Body.Bytes(), &subResp)) + require.NoError(t, json.Unmarshal(subRec.Body.Bytes(), &subResp, jsonx.V1())) subGroups, _ := subResp["packageGroups"].([]any) assert.Len(t, subGroups, 2) @@ -411,7 +412,7 @@ func TestHandler_PackageGroupHierarchy(t *testing.T) { ) require.Equal(t, http.StatusOK, subRec2.Code) var subResp2 map[string]any - require.NoError(t, json.Unmarshal(subRec2.Body.Bytes(), &subResp2)) + require.NoError(t, json.Unmarshal(subRec2.Body.Bytes(), &subResp2, jsonx.V1())) subGroups2, _ := subResp2["packageGroups"].([]any) assert.Empty(t, subGroups2) } diff --git a/services/codeartifact/handler_package_version_rejection_test.go b/services/codeartifact/handler_package_version_rejection_test.go index 694ff15b47..ac1f054df4 100644 --- a/services/codeartifact/handler_package_version_rejection_test.go +++ b/services/codeartifact/handler_package_version_rejection_test.go @@ -1,12 +1,14 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_PackageVersionOps_RejectionNotBypassed guards @@ -54,7 +56,7 @@ func TestHandler_PackageVersionOps_RejectionNotBypassed(t *testing.T) { var resp map[string]any require.NoError( - t, json.Unmarshal(rec.Body.Bytes(), &resp), + t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()), "response body must be a single well-formed JSON document, not two concatenated: %s", rec.Body.String(), ) assert.Equal(t, "ValidationException", resp["code"]) diff --git a/services/codeartifact/handler_package_versions.go b/services/codeartifact/handler_package_versions.go index 4c47974849..23a100774b 100644 --- a/services/codeartifact/handler_package_versions.go +++ b/services/codeartifact/handler_package_versions.go @@ -3,13 +3,15 @@ package codeartifact import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func packageVersionToMap(pv *PackageVersion) map[string]any { @@ -182,7 +184,7 @@ func (h *Handler) handleDeletePackageVersions( var in deletePackageVersionsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -238,7 +240,7 @@ func (h *Handler) handleCopyPackageVersions( var in copyPackageVersionsBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -291,7 +293,7 @@ func (h *Handler) handleDisposePackageVersions( var in disposeVersionsBody if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } successful, failed, err := h.Backend.DisposePackageVersions( @@ -742,7 +744,7 @@ func (h *Handler) handleUpdatePackageVersionsStatus( var in updateVersionsStatusBody if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } if in.TargetStatus == "" { diff --git a/services/codeartifact/handler_package_versions_assets_test.go b/services/codeartifact/handler_package_versions_assets_test.go index 35b6f5813f..fec917a576 100644 --- a/services/codeartifact/handler_package_versions_assets_test.go +++ b/services/codeartifact/handler_package_versions_assets_test.go @@ -2,7 +2,7 @@ package codeartifact_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -44,7 +45,7 @@ func TestHandler_PublishPackageVersion_AppearInList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) versions, _ := resp["versions"].([]any) assert.Len(t, versions, 2) } @@ -206,7 +207,7 @@ func TestHandler_GetPackageVersionReadme(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["readme"] assert.True(t, ok) } @@ -240,7 +241,7 @@ func TestHandler_GetPackageVersionReadme_FromPublishedPackageJSON(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "# My Package\n\nUsage instructions.", resp["readme"]) assert.Equal(t, "npm", resp["format"]) assert.Equal(t, "mypkg", resp["package"]) @@ -273,7 +274,7 @@ func TestHandler_GetPackageVersionReadme_NoPackageJSON(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["readme"]) } @@ -310,7 +311,7 @@ func TestHandler_ListPackageVersionDependencies_FromPublishedPackageJSON(t *test require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, _ := resp["dependencies"].([]any) require.Len(t, deps, 4) @@ -394,7 +395,7 @@ func TestHandler_ListPackageVersionAssets(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assets, _ := resp["assets"].([]any) assert.NotNil(t, assets) } @@ -470,7 +471,7 @@ func TestHandler_ListPackageVersionDependencies(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, _ := resp["dependencies"].([]any) assert.NotNil(t, deps) } @@ -551,7 +552,7 @@ func TestHandler_ListPackageVersions(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, _ := resp["versions"].([]any) assert.Len(t, versions, tt.wantCount) } @@ -646,7 +647,7 @@ func TestHandler_PublishPackageVersion(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["version"]) assert.NotEmpty(t, resp["status"]) assert.NotEmpty(t, resp["asset"]) diff --git a/services/codeartifact/handler_package_versions_test.go b/services/codeartifact/handler_package_versions_test.go index 71e314f95b..8829446637 100644 --- a/services/codeartifact/handler_package_versions_test.go +++ b/services/codeartifact/handler_package_versions_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -72,7 +73,7 @@ func TestHandler_DescribePackageVersion(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pv, _ := resp["packageVersion"].(map[string]any) assert.Equal(t, "1.0.0", pv["version"]) assert.NotEmpty(t, pv["status"]) @@ -110,7 +111,7 @@ func TestHandler_DeletePackageVersions(t *testing.T) { // aws-sdk-go-v2 deserializers.go's ...PackageVersionErrorMap/ // ...SuccessfulPackageVersionInfoMap. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failed, _ := resp["failedVersions"].(map[string]any) require.Len(t, failed, 1) failedEntry, _ := failed["99.0.0"].(map[string]any) @@ -162,7 +163,7 @@ func TestHandler_CopyPackageVersions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failed, _ := resp["failedVersions"].(map[string]any) require.Len(t, failed, 1) failedEntry, _ := failed["9.9.9"].(map[string]any) @@ -215,7 +216,7 @@ func TestHandler_PackageVersionRevision(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pv, _ := resp["packageVersion"].(map[string]any) assert.NotEmpty(t, pv["revision"]) } @@ -244,7 +245,7 @@ func TestHandler_SuccessfulVersions(t *testing.T) { // deserializers.go's ...SuccessfulPackageVersionInfoMap/ // ...PackageVersionErrorMap. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) successList, _ := resp["successfulVersions"].(map[string]any) require.Len(t, successList, 1) @@ -279,7 +280,7 @@ func TestHandler_SuccessfulVersions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // "Copied" is not a real PackageVersionStatus enum value (real values: // Published/Unfinished/Unlisted/Archived/Disposed/Deleted) -- the copied @@ -309,7 +310,7 @@ func TestHandler_PackageVersionMap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pv, _ := resp["packageVersion"].(map[string]any) publishedTime, _ := pv["publishedTime"].(float64) @@ -340,7 +341,7 @@ func TestHandler_DisposePackageVersions_StatusChange(t *testing.T) { // aws-sdk-go-v2 deserializers.go's // ...SuccessfulPackageVersionInfoMap/...PackageVersionErrorMap. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) success, _ := resp["successfulVersions"].(map[string]any) entry, _ := success["1.0.0"].(map[string]any) assert.Equal(t, "Disposed", entry["status"]) @@ -358,7 +359,7 @@ func TestHandler_DisposePackageVersions_StatusChange(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) pv, _ := descResp["packageVersion"].(map[string]any) assert.Equal(t, "Disposed", pv["status"]) } @@ -386,7 +387,7 @@ func TestHandler_UpdatePackageVersionsStatus_StatusChange(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) pv, _ := descResp["packageVersion"].(map[string]any) assert.Equal(t, "Archived", pv["status"]) } @@ -408,7 +409,7 @@ func TestHandler_PackageVersionLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, pubRec.Code) var pubResp map[string]any - require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &pubResp)) + require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &pubResp, jsonx.V1())) assert.Equal(t, "1.0.0", pubResp["version"]) assert.Equal(t, "Published", pubResp["status"]) asset, _ := pubResp["asset"].(map[string]any) @@ -422,7 +423,7 @@ func TestHandler_PackageVersionLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) pv, _ := descResp["packageVersion"].(map[string]any) assert.Equal(t, "Published", pv["status"]) @@ -442,7 +443,7 @@ func TestHandler_PackageVersionLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec2.Code) var descResp2 map[string]any - require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2)) + require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2, jsonx.V1())) pv2, _ := descResp2["packageVersion"].(map[string]any) assert.Equal(t, "Archived", pv2["status"]) @@ -462,7 +463,7 @@ func TestHandler_PackageVersionLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec3.Code) var descResp3 map[string]any - require.NoError(t, json.Unmarshal(descRec3.Body.Bytes(), &descResp3)) + require.NoError(t, json.Unmarshal(descRec3.Body.Bytes(), &descResp3, jsonx.V1())) pv3, _ := descResp3["packageVersion"].(map[string]any) assert.Equal(t, "Disposed", pv3["status"]) @@ -482,7 +483,7 @@ func TestHandler_PackageVersionLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) versions, _ := listResp["versions"].([]any) assert.Empty(t, versions) } @@ -520,7 +521,7 @@ func TestHandler_CopyPackageVersions_ToSelf(t *testing.T) { ) require.Equal(t, http.StatusOK, copyRec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(copyRec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(copyRec2.Body.Bytes(), &resp, jsonx.V1())) failed, _ := resp["failedVersions"].(map[string]any) require.Len(t, failed, 1) entry, _ := failed["18.0.0"].(map[string]any) @@ -603,7 +604,7 @@ func TestHandler_DisposePackageVersions(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["successfulVersions"]) assert.NotNil(t, resp["failedVersions"]) } @@ -691,7 +692,7 @@ func TestHandler_UpdatePackageVersionsStatus(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["successfulVersions"]) assert.NotNil(t, resp["failedVersions"]) } @@ -715,7 +716,7 @@ func TestListPackageVersions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1, _ := out1["versions"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["nextToken"].(string) @@ -728,7 +729,7 @@ func TestListPackageVersions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2, _ := out2["versions"].([]any) assert.Len(t, page2, 2) } @@ -768,7 +769,7 @@ func TestPackageVersion_HasPackageName(t *testing.T) { Version string `json:"version"` } `json:"packageVersion"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.pkgName, out.PackageVersion.PackageName) assert.Equal(t, "1.0.0", out.PackageVersion.Version) }) diff --git a/services/codeartifact/handler_packages.go b/services/codeartifact/handler_packages.go index 43e6755026..b452d4e6c8 100644 --- a/services/codeartifact/handler_packages.go +++ b/services/codeartifact/handler_packages.go @@ -1,10 +1,12 @@ package codeartifact import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // packageToMap builds the types.PackageDescription shape used by @@ -188,7 +190,7 @@ func (h *Handler) handlePutPackageOriginConfiguration( var in putPackageOriginConfigurationBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } diff --git a/services/codeartifact/handler_packages_test.go b/services/codeartifact/handler_packages_test.go index f68167d6f6..01f711a220 100644 --- a/services/codeartifact/handler_packages_test.go +++ b/services/codeartifact/handler_packages_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -81,7 +82,7 @@ func TestHandler_DescribePackage(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkg, _ := resp["package"].(map[string]any) assert.Equal(t, "npm", pkg["format"]) assert.Equal(t, "my-pkg", pkg["name"]) @@ -110,7 +111,7 @@ func TestHandler_DeletePackage(t *testing.T) { assert.Equal(t, http.StatusOK, delRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["deletedPackage"]) // Confirm deletion: a second delete should return 404 since the package no longer exists. @@ -144,7 +145,7 @@ func TestHandler_PackageMap(t *testing.T) { // declare only format/name/namespace/originConfiguration); "format" and // "name" are the ones actually on the real shape. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkg, _ := resp["package"].(map[string]any) assert.Equal(t, "npm", pkg["format"]) assert.Equal(t, "mypkg", pkg["name"]) @@ -179,7 +180,7 @@ func TestHandler_MultiFormatPackages(t *testing.T) { allRec := doRequest(t, h, http.MethodPost, "/v1/packages?domain=fmt-multi-domain&repository=fmt-multi-repo", nil) require.Equal(t, http.StatusOK, allRec.Code) var allResp map[string]any - require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &allResp)) + require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &allResp, jsonx.V1())) allPkgs, _ := allResp["packages"].([]any) assert.Len(t, allPkgs, 4) @@ -193,7 +194,7 @@ func TestHandler_MultiFormatPackages(t *testing.T) { ) require.Equal(t, http.StatusOK, npmRec.Code) var npmResp map[string]any - require.NoError(t, json.Unmarshal(npmRec.Body.Bytes(), &npmResp)) + require.NoError(t, json.Unmarshal(npmRec.Body.Bytes(), &npmResp, jsonx.V1())) npmPkgs, _ := npmResp["packages"].([]any) assert.Len(t, npmPkgs, 2) @@ -207,7 +208,7 @@ func TestHandler_MultiFormatPackages(t *testing.T) { ) require.Equal(t, http.StatusOK, pypiRec.Code) var pypiResp map[string]any - require.NoError(t, json.Unmarshal(pypiRec.Body.Bytes(), &pypiResp)) + require.NoError(t, json.Unmarshal(pypiRec.Body.Bytes(), &pypiResp, jsonx.V1())) pypiPkgs, _ := pypiResp["packages"].([]any) assert.Len(t, pypiPkgs, 1) } @@ -311,7 +312,7 @@ func TestHandler_ListPackages(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pkgs, _ := resp["packages"].([]any) assert.Len(t, pkgs, tt.wantCount) } @@ -383,7 +384,7 @@ func TestHandler_PutPackageOriginConfiguration(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) originConfig, _ := resp["originConfiguration"].(map[string]any) require.NotNil(t, originConfig) restrictions, _ := originConfig["restrictions"].(map[string]any) @@ -410,7 +411,7 @@ func TestListPackages_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1, _ := out1["packages"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["nextToken"].(string) @@ -421,7 +422,7 @@ func TestListPackages_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2, _ := out2["packages"].([]any) assert.Len(t, page2, 2) } diff --git a/services/codeartifact/handler_repositories.go b/services/codeartifact/handler_repositories.go index 89509b049d..6d96591bba 100644 --- a/services/codeartifact/handler_repositories.go +++ b/services/codeartifact/handler_repositories.go @@ -1,11 +1,13 @@ package codeartifact import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type upstreamRepoEntry struct { @@ -70,7 +72,7 @@ func (h *Handler) handleCreateRepository(c *echo.Context, domainName, repoName s var in createRepositoryBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -316,7 +318,7 @@ func (h *Handler) handlePutRepositoryPermissionsPolicy( var in putRepositoryPermissionsPolicyBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -408,7 +410,7 @@ func (h *Handler) handleUpdateRepository(c *echo.Context, domainName, repoName s var in updateRepositoryBody if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } var upstreams []string diff --git a/services/codeartifact/handler_repositories_test.go b/services/codeartifact/handler_repositories_test.go index 3adf1efd76..fb75713afd 100644 --- a/services/codeartifact/handler_repositories_test.go +++ b/services/codeartifact/handler_repositories_test.go @@ -1,7 +1,7 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -75,7 +76,7 @@ func TestHandler_CreateRepository(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) assert.Equal(t, tt.repo, repo["name"]) assert.Equal(t, tt.domain, repo["domainName"]) @@ -96,7 +97,7 @@ func TestHandler_DescribeRepository(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) assert.Equal(t, "r1", repo["name"]) assert.Equal(t, "d1", repo["domainName"]) @@ -128,7 +129,7 @@ func TestHandler_ListRepositoriesInDomain(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, _ := resp["repositories"].([]any) assert.Len(t, repos, 2) } @@ -146,7 +147,7 @@ func TestHandler_ListRepositories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, _ := resp["repositories"].([]any) assert.Len(t, repos, 2) } @@ -168,7 +169,7 @@ func TestHandler_GetRepositoryEndpoint(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["repositoryEndpoint"]) } @@ -243,7 +244,7 @@ func TestHandler_AssociateExternalConnection(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["repository"]) } }) @@ -280,7 +281,7 @@ func TestHandler_RepositoryPermissionsPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) pol, _ := putResp["policy"].(map[string]any) assert.NotEmpty(t, pol["revision"]) assert.NotEmpty(t, pol["resourceArn"]) @@ -349,7 +350,7 @@ func TestHandler_RepositoryPermissionsPolicy_RevisionLocking(t *testing.T) { ) require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) pol, _ := putResp["policy"].(map[string]any) revision, _ := pol["revision"].(string) require.NotEmpty(t, revision) @@ -380,7 +381,7 @@ func TestHandler_RepositoryPermissionsPolicy_RevisionLocking(t *testing.T) { ) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) getPol, _ := getResp["policy"].(map[string]any) assert.NotContains(t, getPol["document"], "Deny") @@ -448,7 +449,7 @@ func TestHandler_ExternalConnectionFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) conns, _ := repo["externalConnections"].([]any) require.Len(t, conns, 1) @@ -485,7 +486,7 @@ func TestHandler_UpdateRepository_DescriptionPersists(t *testing.T) { descRec := doRequest(t, h, http.MethodGet, "/v1/repository?domain=ur-desc-domain&repository=ur-desc-repo", nil) require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) assert.Equal(t, "updated", repo["description"]) } @@ -517,7 +518,7 @@ func TestHandler_ExternalConnections_MultipleConnections(t *testing.T) { ) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) repo, _ := descResp["repository"].(map[string]any) conns, _ := repo["externalConnections"].([]any) assert.Len(t, conns, 3) @@ -533,7 +534,7 @@ func TestHandler_ExternalConnections_MultipleConnections(t *testing.T) { ) require.Equal(t, http.StatusOK, disRec.Code) var disResp map[string]any - require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp)) + require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp, jsonx.V1())) disRepo, _ := disResp["repository"].(map[string]any) disConns, _ := disRepo["externalConnections"].([]any) assert.Len(t, disConns, 2) @@ -613,7 +614,7 @@ func TestHandler_DisassociateExternalConnection(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) assert.NotNil(t, repo) } @@ -684,7 +685,7 @@ func TestHandler_UpdateRepository(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, _ := resp["repository"].(map[string]any) assert.NotNil(t, repo) } @@ -706,7 +707,7 @@ func TestListRepositoriesInDomain_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) page1, _ := out1["repositories"].([]any) assert.Len(t, page1, 2) nextToken, ok := out1["nextToken"].(string) @@ -717,7 +718,7 @@ func TestListRepositoriesInDomain_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) page2, _ := out2["repositories"].([]any) assert.Len(t, page2, 2) } @@ -741,7 +742,7 @@ func TestCreateRepository_UpstreamRepositories(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) repo, _ := out["repository"].(map[string]any) upstreams, _ := repo["upstreams"].([]any) require.Len(t, upstreams, 1) @@ -770,7 +771,7 @@ func TestUpdateRepository_UpstreamRepositories(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) repo, _ := out["repository"].(map[string]any) upstreams, _ := repo["upstreams"].([]any) require.Len(t, upstreams, 1) diff --git a/services/codeartifact/handler_tags.go b/services/codeartifact/handler_tags.go index 0f0505673d..6222fad6c4 100644 --- a/services/codeartifact/handler_tags.go +++ b/services/codeartifact/handler_tags.go @@ -1,12 +1,14 @@ package codeartifact import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceBody struct { @@ -46,7 +48,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body [] } var in tagResourceBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } @@ -63,7 +65,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, resourceARN string, body } var in untagResourceBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } diff --git a/services/codeartifact/handler_tags_test.go b/services/codeartifact/handler_tags_test.go index e4426b11e3..b0457742dc 100644 --- a/services/codeartifact/handler_tags_test.go +++ b/services/codeartifact/handler_tags_test.go @@ -1,12 +1,14 @@ package codeartifact_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagsForDomain(t *testing.T) { @@ -21,7 +23,7 @@ func TestHandler_TagsForDomain(t *testing.T) { assert.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) domainMap, _ := createResp["domain"].(map[string]any) domainARN := domainMap["arn"].(string) @@ -30,7 +32,7 @@ func TestHandler_TagsForDomain(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagList, _ := listResp["tags"].([]any) assert.Len(t, tagList, 1) @@ -45,7 +47,7 @@ func TestHandler_TagsForDomain(t *testing.T) { assert.Equal(t, http.StatusOK, listRec2.Code) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2, jsonx.V1())) tagList2, _ := listResp2["tags"].([]any) assert.Len(t, tagList2, 2) @@ -60,7 +62,7 @@ func TestHandler_TagsForDomain(t *testing.T) { assert.Equal(t, http.StatusOK, listRec3.Code) var listResp3 map[string]any - require.NoError(t, json.Unmarshal(listRec3.Body.Bytes(), &listResp3)) + require.NoError(t, json.Unmarshal(listRec3.Body.Bytes(), &listResp3, jsonx.V1())) tagList3, _ := listResp3["tags"].([]any) assert.Len(t, tagList3, 1) } @@ -79,7 +81,7 @@ func TestHandler_SortedTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) domainMap, _ := createResp["domain"].(map[string]any) domainARN, _ := domainMap["arn"].(string) @@ -87,7 +89,7 @@ func TestHandler_SortedTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagList, _ := listResp["tags"].([]any) require.Len(t, tagList, 3) diff --git a/services/codeartifact/handler_test.go b/services/codeartifact/handler_test.go index d0aa8400bb..94c6ff3338 100644 --- a/services/codeartifact/handler_test.go +++ b/services/codeartifact/handler_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codeartifact" ) @@ -33,7 +34,7 @@ func doRequest(t *testing.T, h *codeartifact.Handler, method, path string, body var bodyReader io.Reader if body != nil { - data, err := json.Marshal(body) + data, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(data) } @@ -684,7 +685,7 @@ func TestHandler_SortedOutput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) domains, _ := resp["domains"].([]any) require.Len(t, domains, 3) @@ -709,7 +710,7 @@ func TestHandler_SortedOutput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, _ := resp["repositories"].([]any) require.Len(t, repos, 3) diff --git a/services/codeartifact/package_versions.go b/services/codeartifact/package_versions.go index db9fe4cf3f..2ce7bfa15b 100644 --- a/services/codeartifact/package_versions.go +++ b/services/codeartifact/package_versions.go @@ -2,13 +2,16 @@ package codeartifact import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "sort" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Package version methods --- @@ -372,20 +375,20 @@ type npmPackageJSON struct { Readme string `json:"readme"` Description string `json:"description"` Homepage string `json:"homepage"` - Repository json.RawMessage `json:"repository"` - License json.RawMessage `json:"license"` + Repository jsontext.Value `json:"repository"` + License jsontext.Value `json:"license"` } // repositoryURL reads package.json's repository, a bare string or an object with a url. func (m *npmPackageJSON) repositoryURL() string { var str string - if json.Unmarshal(m.Repository, &str) == nil { + if json.Unmarshal(m.Repository, &str, jsonx.V1()) == nil { return str } var obj struct { URL string `json:"url"` } - if json.Unmarshal(m.Repository, &obj) == nil { + if json.Unmarshal(m.Repository, &obj, jsonx.V1()) == nil { return obj.URL } @@ -395,13 +398,13 @@ func (m *npmPackageJSON) repositoryURL() string { // licenseName reads package.json's license, a bare SPDX string or an object with a type. func (m *npmPackageJSON) licenseName() string { var str string - if json.Unmarshal(m.License, &str) == nil { + if json.Unmarshal(m.License, &str, jsonx.V1()) == nil { return str } var obj struct { Type string `json:"type"` } - if json.Unmarshal(m.License, &obj) == nil { + if json.Unmarshal(m.License, &obj, jsonx.V1()) == nil { return obj.Type } @@ -417,7 +420,7 @@ func findPackageJSONMetadata(assets []AssetInfo) *npmPackageJSON { } var meta npmPackageJSON - if err := json.Unmarshal(a.Content, &meta); err != nil { + if err := json.Unmarshal(a.Content, &meta, jsonx.V1()); err != nil { return nil } diff --git a/services/codeartifact/persistence.go b/services/codeartifact/persistence.go index a87720a84b..c78c4fd404 100644 --- a/services/codeartifact/persistence.go +++ b/services/codeartifact/persistence.go @@ -2,7 +2,7 @@ package codeartifact import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -103,7 +103,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // value, []ExternalConnection, has no identity of its own -- see // store_setup.go) and is persisted here directly, as before. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ExternalConnections map[string]map[string][]ExternalConnection `json:"externalConnections"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -235,7 +235,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // store_setup.go's registerAllTables doc) from tables via the ephemeral DTO // registry, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("codeartifact: restore snapshot DTO tables: %w", err) diff --git a/services/codebuild/build_batches_test.go b/services/codebuild/build_batches_test.go index 1e86b446af..245d044b74 100644 --- a/services/codebuild/build_batches_test.go +++ b/services/codebuild/build_batches_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCodeBuild_BuildBatch covers RetryBuildBatch, StopBuildBatch, ListBuildBatchesForProject. @@ -27,7 +30,7 @@ func TestCodeBuild_BuildBatch(t *testing.T) { ID string `json:"id"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) batchID := startOut.BuildBatch.ID stopRec := doRequest(t, h, "StopBuildBatch", map[string]any{"id": batchID}) @@ -38,7 +41,7 @@ func TestCodeBuild_BuildBatch(t *testing.T) { BuildBatchStatus string `json:"buildBatchStatus"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&stopOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &stopOut, jsonx.V1())) assert.Equal(t, "STOPPED", stopOut.BuildBatch.BuildBatchStatus) }) @@ -64,7 +67,7 @@ func TestCodeBuild_BuildBatch(t *testing.T) { ID string `json:"id"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) batchID := startOut.BuildBatch.ID require.Equal(t, http.StatusOK, doRequest(t, h, "StopBuildBatch", map[string]any{"id": batchID}).Code) @@ -78,7 +81,7 @@ func TestCodeBuild_BuildBatch(t *testing.T) { ProjectName string `json:"projectName"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(retryRec.Body).Decode(&retryOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(retryRec.Body), &retryOut, jsonx.V1())) assert.NotEqual(t, batchID, retryOut.BuildBatch.ID) assert.Equal(t, "IN_PROGRESS", retryOut.BuildBatch.BuildBatchStatus) assert.Equal(t, "batch-retry-proj", retryOut.BuildBatch.ProjectName) @@ -110,7 +113,7 @@ func TestCodeBuild_BuildBatch(t *testing.T) { var out struct { IDs []string `json:"ids"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.IDs, 2) }) @@ -166,7 +169,7 @@ func TestHandler_DeleteBuildBatch_RemovesBatch(t *testing.T) { ID string `json:"id"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) batchID = startOut.BuildBatch.ID } else { batchID = "ghost-proj:ghost-batch-id" @@ -187,7 +190,7 @@ func TestHandler_DeleteBuildBatch_RemovesBatch(t *testing.T) { var listOut struct { IDs []string `json:"ids"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.NotContains(t, listOut.IDs, batchID, "batch should be removed after delete") } }) @@ -226,7 +229,7 @@ func TestHandler_StartBuildBatch_SetsInProgress(t *testing.T) { StartTime float64 `json:"startTime"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out.BuildBatch.BuildBatchStatus) if tt.wantStartTimeSet { assert.Greater(t, out.BuildBatch.StartTime, float64(0)) @@ -266,7 +269,7 @@ func TestHandler_StopBuildBatch_SetsEndTime(t *testing.T) { ID string `json:"id"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) stopRec := doRequest(t, h, "StopBuildBatch", map[string]any{"id": startOut.BuildBatch.ID}) require.Equal(t, http.StatusOK, stopRec.Code) @@ -277,7 +280,7 @@ func TestHandler_StopBuildBatch_SetsEndTime(t *testing.T) { EndTime float64 `json:"endTime"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out.BuildBatch.BuildBatchStatus) if tt.wantEndTimeSet { assert.Greater(t, out.BuildBatch.EndTime, float64(0)) @@ -300,7 +303,7 @@ func TestHandler_StartBuildBatch_ArnSet(t *testing.T) { var out struct { BuildBatch map[string]any `json:"buildBatch"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) batchArn, _ := out.BuildBatch["arn"].(string) assert.NotEmpty(t, batchArn, "StartBuildBatch must return a non-empty arn") assert.Contains(t, batchArn, "arn:aws:codebuild", "arn must be a valid ARN format") @@ -319,7 +322,7 @@ func TestHandler_RetryBuildBatch_ArnSet(t *testing.T) { var startOut struct { BuildBatch map[string]any `json:"buildBatch"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) batchID, _ := startOut.BuildBatch["id"].(string) require.NotEmpty(t, batchID) require.Equal(t, http.StatusOK, doRequest(t, h, "StopBuildBatch", map[string]any{"id": batchID}).Code) @@ -330,7 +333,7 @@ func TestHandler_RetryBuildBatch_ArnSet(t *testing.T) { var retryOut struct { BuildBatch map[string]any `json:"buildBatch"` } - require.NoError(t, json.Unmarshal(retryRec.Body.Bytes(), &retryOut)) + require.NoError(t, json.Unmarshal(retryRec.Body.Bytes(), &retryOut, jsonx.V1())) arn, _ := retryOut.BuildBatch["arn"].(string) assert.NotEmpty(t, arn, "RetryBuildBatch must return a non-empty arn") } diff --git a/services/codebuild/builds_semantics_test.go b/services/codebuild/builds_semantics_test.go index 50d4302e5c..c95ed63638 100644 --- a/services/codebuild/builds_semantics_test.go +++ b/services/codebuild/builds_semantics_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_RetryBuild_InheritsFields verifies RetryBuild inherits environment/source/serviceRole @@ -31,7 +34,7 @@ func TestHandler_RetryBuild_InheritsFields(t *testing.T) { var startOut struct { Build map[string]any `json:"build"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) buildID, _ := startOut.Build["id"].(string) require.NotEmpty(t, buildID) @@ -41,7 +44,7 @@ func TestHandler_RetryBuild_InheritsFields(t *testing.T) { var retryOut struct { Build map[string]any `json:"build"` } - require.NoError(t, json.Unmarshal(retryRec.Body.Bytes(), &retryOut)) + require.NoError(t, json.Unmarshal(retryRec.Body.Bytes(), &retryOut, jsonx.V1())) b := retryOut.Build assert.Equal(t, "arn:aws:iam::000000000000:role/my-role", b["serviceRole"], @@ -64,7 +67,7 @@ func TestHandler_BatchGetBuilds_ARNLookup(t *testing.T) { var startOut struct { Build map[string]any `json:"build"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) buildArn, _ := startOut.Build["arn"].(string) require.NotEmpty(t, buildArn) @@ -75,7 +78,7 @@ func TestHandler_BatchGetBuilds_ARNLookup(t *testing.T) { Builds []any `json:"builds"` BuildsNotFound []string `json:"buildsNotFound"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Len(t, getOut.Builds, 1, "BatchGetBuilds must find build by ARN") assert.Empty(t, getOut.BuildsNotFound, "ARN lookup must not produce buildsNotFound") } @@ -108,7 +111,7 @@ func TestHandler_StartBuild_OverrideFields(t *testing.T) { var out struct { Build map[string]any `json:"build"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) b := out.Build assert.Equal(t, "arn:aws:iam::000000000000:role/override-role", b["serviceRole"], @@ -190,7 +193,7 @@ func TestHandler_StartBuild_InheritsProjectFields(t *testing.T) { Phases []map[string]any `json:"phases"` } `json:"build"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantServiceRole, out.Build.ServiceRole) assert.Equal(t, "GITHUB", out.Build.Source.Type) assert.Len(t, out.Build.Environment.EnvironmentVariables, 1) @@ -233,7 +236,7 @@ func TestHandler_StartBuild_InitialPhase(t *testing.T) { } `json:"phases"` } `json:"build"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &out, jsonx.V1())) if tt.wantPhasesNonEmpty { require.NotEmpty(t, out.Build.Phases) assert.Equal(t, tt.wantInitialPhase, out.Build.Phases[0].PhaseType) @@ -351,7 +354,7 @@ func TestHandler_StartBuild_EnvVarOverrideMerge(t *testing.T) { } `json:"environment"` } `json:"build"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) envVars := out.Build.Environment.EnvironmentVariables assert.Len(t, envVars, tt.wantEnvCount) diff --git a/services/codebuild/builds_test.go b/services/codebuild/builds_test.go index 0bf78f2557..37fa1917e6 100644 --- a/services/codebuild/builds_test.go +++ b/services/codebuild/builds_test.go @@ -1,13 +1,16 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "sort" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_StartBuild(t *testing.T) { @@ -64,7 +67,7 @@ func TestHandler_StartBuild(t *testing.T) { BuildStatus string `json:"buildStatus"` } `json:"build"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Build.ID) assert.Equal(t, "IN_PROGRESS", out.Build.BuildStatus) } @@ -94,7 +97,7 @@ func TestHandler_StartBuild_TimestampsAreNumbers(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) build, ok := startOut["build"].(map[string]any) require.True(t, ok, "response should contain 'build' object") @@ -109,7 +112,7 @@ func TestHandler_StartBuild_TimestampsAreNumbers(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopOut map[string]any - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&stopOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &stopOut, jsonx.V1())) stoppedBuild, ok := stopOut["build"].(map[string]any) require.True(t, ok, "stop response should contain 'build' object") @@ -170,7 +173,7 @@ func TestHandler_BatchGetBuilds(t *testing.T) { ID string `json:"id"` } `json:"build"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) buildID = startOut.Build.ID } else { buildID = "nonexistent:build123" @@ -185,7 +188,7 @@ func TestHandler_BatchGetBuilds(t *testing.T) { Builds []any `json:"builds"` BuildsNotFound []string `json:"buildsNotFound"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Builds, tt.wantBuildCount) assert.Equal(t, tt.wantNotFound, out.BuildsNotFound) }) @@ -242,7 +245,7 @@ func TestHandler_StopBuild(t *testing.T) { ID string `json:"id"` } `json:"build"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) buildID = startOut.Build.ID } else { buildID = "nonexistent:build999" @@ -257,7 +260,7 @@ func TestHandler_StopBuild(t *testing.T) { BuildStatus string `json:"buildStatus"` } `json:"build"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "STOPPED", out.Build.BuildStatus) } }) @@ -297,7 +300,7 @@ func TestHandler_StopBuild_SetsStoppedNotSucceeded(t *testing.T) { ID string `json:"id"` } `json:"build"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) stopRec := doRequest(t, h, "StopBuild", map[string]any{"id": startOut.Build.ID}) require.Equal(t, http.StatusOK, stopRec.Code) @@ -310,7 +313,7 @@ func TestHandler_StopBuild_SetsStoppedNotSucceeded(t *testing.T) { EndTime float64 `json:"endTime"` } `json:"build"` } - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantBuildStatus, out.Build.BuildStatus) assert.Equal(t, tt.wantCurrentPhase, out.Build.CurrentPhase) assert.Equal(t, tt.wantBuildComplete, out.Build.BuildComplete) @@ -376,7 +379,7 @@ func TestHandler_ListBuildsForProject(t *testing.T) { var out struct { IDs []string `json:"ids"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.IDs, tt.buildCount) } }) @@ -470,7 +473,7 @@ func TestHandler_BatchDeleteBuilds(t *testing.T) { "projectName": "my-project", }) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) build := startResp["build"].(map[string]any) buildID = build["id"].(string) tt.buildIDs = []string{buildID} @@ -482,7 +485,7 @@ func TestHandler_BatchDeleteBuilds(t *testing.T) { assert.Equal(t, tt.wantHTTP, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deleted := resp["buildsDeleted"].([]any) assert.Len(t, deleted, tt.wantDeleted) }) @@ -505,7 +508,7 @@ func TestHandler_BatchDeleteBuilds_NotDeletedList(t *testing.T) { BuildsDeleted []string `json:"buildsDeleted"` BuildsNotDeleted []map[string]any `json:"buildsNotDeleted"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.BuildsDeleted, "no builds deleted when IDs do not exist") assert.Len(t, out.BuildsNotDeleted, 2, "both missing IDs must appear in buildsNotDeleted") @@ -539,7 +542,7 @@ func TestHandler_ListBuilds(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids := resp["ids"].([]any) assert.Len(t, ids, 2) } @@ -573,7 +576,7 @@ func TestHandler_SortedBuilds(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) ids, _ := out["ids"].([]any) require.Len(t, ids, 3) @@ -627,7 +630,7 @@ func TestHandler_RetryBuild(t *testing.T) { if id == "" { startRec := doRequest(t, h, "StartBuild", map[string]any{"projectName": "proj"}) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) build := startResp["build"].(map[string]any) id = build["id"].(string) } diff --git a/services/codebuild/command_executions_test.go b/services/codebuild/command_executions_test.go index 62aefd9470..c9fb9e59b2 100644 --- a/services/codebuild/command_executions_test.go +++ b/services/codebuild/command_executions_test.go @@ -1,7 +1,8 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "sort" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -70,7 +72,7 @@ func TestHandler_BatchGetCommandExecutions(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) executions, _ := out["commandExecutions"].([]any) assert.Len(t, executions, tt.wantFound) @@ -121,7 +123,7 @@ func TestHandler_StartCommandExecution_TypeStored(t *testing.T) { ID string `json:"id"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(sbRec.Body).Decode(&sbOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(sbRec.Body), &sbOut, jsonx.V1())) execRec := doRequest(t, h, "StartCommandExecution", map[string]any{ "sandboxId": sbOut.Sandbox.ID, @@ -139,7 +141,7 @@ func TestHandler_StartCommandExecution_TypeStored(t *testing.T) { EndTime float64 `json:"endTime"` } `json:"commandExecution"` } - require.NoError(t, json.NewDecoder(execRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(execRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out.CommandExecution.Type) assert.Equal(t, tt.command, out.CommandExecution.Command) assert.Equal(t, "SUCCEEDED", out.CommandExecution.Status) @@ -167,7 +169,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { ID string `json:"id"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) sandboxID := startOut.Sandbox.ID doRequest(t, h, "StartCommandExecution", map[string]any{ @@ -189,7 +191,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { var out struct { CommandExecutions []map[string]any `json:"commandExecutions"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.CommandExecutions, 2) }) @@ -222,7 +224,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { ID string `json:"id"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) sandboxID := startOut.Sandbox.ID ids := make([]string, 0, 3) @@ -240,7 +242,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { ID string `json:"id"` } `json:"commandExecution"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) ids = append(ids, out.CommandExecution.ID) } @@ -258,7 +260,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { NextToken string `json:"nextToken"` CommandExecutions []map[string]any `json:"commandExecutions"` } - require.NoError(t, json.NewDecoder(firstPage.Body).Decode(&firstOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(firstPage.Body), &firstOut, jsonx.V1())) require.Len(t, firstOut.CommandExecutions, 2) assert.Equal( t, @@ -280,7 +282,7 @@ func TestCodeBuild_CommandExecutionsForSandbox(t *testing.T) { var secondOut struct { CommandExecutions []map[string]any `json:"commandExecutions"` } - require.NoError(t, json.NewDecoder(secondPage.Body).Decode(&secondOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(secondPage.Body), &secondOut, jsonx.V1())) require.Len(t, secondOut.CommandExecutions, 1) assert.Equal(t, sortedAsc[0], secondOut.CommandExecutions[0]["id"], "the remainder must be the smallest ID") }) diff --git a/services/codebuild/curated_environments_test.go b/services/codebuild/curated_environments_test.go index a301260ad4..f457487e9d 100644 --- a/services/codebuild/curated_environments_test.go +++ b/services/codebuild/curated_environments_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCodeBuild_CuratedEnvironmentImages verifies ListCuratedEnvironmentImages returns @@ -21,7 +24,7 @@ func TestCodeBuild_CuratedEnvironmentImages(t *testing.T) { var out struct { Platforms []map[string]any `json:"platforms"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.Platforms) assert.Equal(t, "UBUNTU", out.Platforms[0]["platform"]) } diff --git a/services/codebuild/engine_env.go b/services/codebuild/engine_env.go index 74f195c96d..c4287e52eb 100644 --- a/services/codebuild/engine_env.go +++ b/services/codebuild/engine_env.go @@ -2,11 +2,13 @@ package codebuild import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errEnvResolve = errors.New("cannot resolve environment variable") @@ -65,7 +67,7 @@ func (r *buildRunner) resolveSecret(ctx context.Context, ref string) (string, er } var obj map[string]any - if jerr := json.Unmarshal([]byte(val), &obj); jerr != nil { + if jerr := json.Unmarshal([]byte(val), &obj, jsonx.V1()); jerr != nil { return "", fmt.Errorf("%w: secret %s is not JSON: %w", errEnvResolve, id, jerr) } diff --git a/services/codebuild/fleets_test.go b/services/codebuild/fleets_test.go index 7442a5027e..0f8eb316b4 100644 --- a/services/codebuild/fleets_test.go +++ b/services/codebuild/fleets_test.go @@ -1,13 +1,16 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateFleet(t *testing.T) { @@ -115,7 +118,7 @@ func TestHandler_BatchGetFleets(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) fleets, _ := out["fleets"].([]any) assert.Len(t, fleets, tt.wantFound) @@ -146,7 +149,7 @@ func TestCodeBuild_Fleet(t *testing.T) { BaseCapacity int `json:"baseCapacity"` } `json:"fleet"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) fleetArn := createOut.Fleet.Arn updRec := doRequest(t, h, "UpdateFleet", map[string]any{ @@ -160,7 +163,7 @@ func TestCodeBuild_Fleet(t *testing.T) { BaseCapacity int `json:"baseCapacity"` } `json:"fleet"` } - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updOut, jsonx.V1())) assert.Equal(t, 5, updOut.Fleet.BaseCapacity) }) @@ -215,7 +218,7 @@ func TestHandler_CreateFleet_StatusSchema(t *testing.T) { BaseCapacity float64 `json:"baseCapacity"` } `json:"fleet"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, tt.wantBaseCapacity, out.Fleet.BaseCapacity, 0) assert.Equal(t, tt.wantStatusCode, out.Fleet.Status.StatusCode) }) @@ -258,7 +261,7 @@ func TestHandler_CreateFleet_ExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out fleetExtendedFields - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Fleet.ID) assert.Equal(t, "ON_DEMAND", out.Fleet.OverflowBehavior) assert.Equal(t, "aws/codebuild/standard:7.0", out.Fleet.ImageID) @@ -282,7 +285,7 @@ func TestHandler_UpdateFleet_ExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut fleetExtendedFields - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) updRec := doRequest(t, h, "UpdateFleet", map[string]any{ "arn": createOut.Fleet.Arn, @@ -294,7 +297,7 @@ func TestHandler_UpdateFleet_ExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var out fleetExtendedFields - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &out, jsonx.V1())) assert.Equal(t, createOut.Fleet.ID, out.Fleet.ID, "id must not change across an update") assert.Equal(t, "QUEUE", out.Fleet.OverflowBehavior) assert.Equal(t, "aws/codebuild/standard:6.0", out.Fleet.ImageID) @@ -498,7 +501,7 @@ func TestHandler_Fleet_NestedConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut fleetNestedConfig - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) out := createOut if tt.updateBody != nil { @@ -509,7 +512,7 @@ func TestHandler_Fleet_NestedConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updOut fleetNestedConfig - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updOut, jsonx.V1())) out = updOut } @@ -566,7 +569,7 @@ func TestHandler_DeleteFleet_RemovesFleet(t *testing.T) { Arn string `json:"arn"` } `json:"fleet"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) fleetArn = createOut.Fleet.Arn } else { fleetArn = tt.deleteArn @@ -582,7 +585,7 @@ func TestHandler_DeleteFleet_RemovesFleet(t *testing.T) { var listOut struct { Fleets []string `json:"fleets"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Empty(t, listOut.Fleets, "fleet should be removed from list after delete") } }) diff --git a/services/codebuild/handler.go b/services/codebuild/handler.go index 91db4fbd44..485fe18ce3 100644 --- a/services/codebuild/handler.go +++ b/services/codebuild/handler.go @@ -2,7 +2,7 @@ package codebuild import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -268,18 +269,15 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - respond := func(errType string) error { payload, _ := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: strings.TrimPrefix(err.Error(), errType+": "), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) } @@ -298,7 +296,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err // (all but the parameterless ListCuratedEnvironmentImages, which never // raises errInvalidRequest), so it is a safe blanket fallback here. case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return respond("InvalidInputException") default: // codebuild@v1.72.4's types/errors.go declares no internal-server/ diff --git a/services/codebuild/handler_test.go b/services/codebuild/handler_test.go index 570d69e400..2a68019561 100644 --- a/services/codebuild/handler_test.go +++ b/services/codebuild/handler_test.go @@ -2,7 +2,8 @@ package codebuild_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -32,7 +34,7 @@ func doRequest(t *testing.T, h *codebuild.Handler, action string, body any) *htt var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -125,7 +127,7 @@ func projectARN(t *testing.T, h *codebuild.Handler, name string) string { Arn string `json:"arn"` } `json:"projects"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotEmpty(t, out.Projects) return out.Projects[0].Arn @@ -498,7 +500,7 @@ func TestHandler_Reset(t *testing.T) { listRec := doRequest(t, h, "ListProjects", nil) require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut["projects"], 1) // Reset. @@ -508,7 +510,7 @@ func TestHandler_Reset(t *testing.T) { listRec2 := doRequest(t, h, "ListProjects", nil) require.Equal(t, http.StatusOK, listRec2.Code) var listOut2 map[string]any - require.NoError(t, json.NewDecoder(listRec2.Body).Decode(&listOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec2.Body), &listOut2, jsonx.V1())) assert.Empty(t, listOut2["projects"]) } @@ -665,7 +667,7 @@ func TestHandler_ErrorTypeMapping(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) diff --git a/services/codebuild/multi_region_test.go b/services/codebuild/multi_region_test.go index ccf810d15e..885074aa91 100644 --- a/services/codebuild/multi_region_test.go +++ b/services/codebuild/multi_region_test.go @@ -2,7 +2,7 @@ package codebuild_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/codebuild/pagination_test.go b/services/codebuild/pagination_test.go index 8ef075d487..a7058fc47c 100644 --- a/services/codebuild/pagination_test.go +++ b/services/codebuild/pagination_test.go @@ -1,13 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -27,7 +29,7 @@ func TestHandler_ListProjects_SortOrderDescending(t *testing.T) { var out struct { Projects []string `json:"projects"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, []string{"sort-c", "sort-b", "sort-a"}, out.Projects) } @@ -70,7 +72,7 @@ func TestHandler_ListFleets_MaxResultsPagination(t *testing.T) { NextToken string `json:"nextToken"` Fleets []string `json:"fleets"` } - require.NoError(t, json.NewDecoder(firstRec.Body).Decode(&firstPage)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(firstRec.Body), &firstPage, jsonx.V1())) assert.Len(t, firstPage.Fleets, 2) require.NotEmpty(t, firstPage.NextToken, "a partial page must carry a nextToken") @@ -84,7 +86,7 @@ func TestHandler_ListFleets_MaxResultsPagination(t *testing.T) { NextToken string `json:"nextToken"` Fleets []string `json:"fleets"` } - require.NoError(t, json.NewDecoder(secondRec.Body).Decode(&secondPage)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(secondRec.Body), &secondPage, jsonx.V1())) assert.Len(t, secondPage.Fleets, 1) assert.Empty(t, secondPage.NextToken, "the final page must not carry a nextToken") @@ -110,7 +112,7 @@ func TestHandler_ListBuildBatches_FilterByStatus(t *testing.T) { ID string `json:"id"` } `json:"buildBatch"` } - require.NoError(t, json.NewDecoder(startRec1.Body).Decode(&started1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec1.Body), &started1, jsonx.V1())) stopRec := doRequest(t, h, "StopBuildBatch", map[string]any{"id": started1.BuildBatch.ID}) require.Equal(t, http.StatusOK, stopRec.Code) @@ -126,7 +128,7 @@ func TestHandler_ListBuildBatches_FilterByStatus(t *testing.T) { var out struct { IDs []string `json:"ids"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, []string{started1.BuildBatch.ID}, out.IDs) } @@ -165,7 +167,7 @@ func TestHandler_ListReports_FilterByStatus(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, []string{"arn:aws:codebuild:us-east-1:000000000000:report/filter-rg:bad"}, out.Reports) }) @@ -181,7 +183,7 @@ func TestHandler_ListReports_FilterByStatus(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, []string{"arn:aws:codebuild:us-east-1:000000000000:report/filter-rg:ok"}, out.Reports) }) } diff --git a/services/codebuild/persistence.go b/services/codebuild/persistence.go index 204efca8b8..7cc3e75606 100644 --- a/services/codebuild/persistence.go +++ b/services/codebuild/persistence.go @@ -2,7 +2,7 @@ package codebuild import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -30,12 +30,12 @@ const codebuildSnapshotVersion = 2 // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - ResourcePolicies map[string]string `json:"resourcePolicies"` - BuildBatchNumbers map[string]int64 `json:"buildBatchNumbers"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + ResourcePolicies map[string]string `json:"resourcePolicies"` + BuildBatchNumbers map[string]int64 `json:"buildBatchNumbers"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/codebuild/phase_duration_internal_test.go b/services/codebuild/phase_duration_internal_test.go index 0bb0b204dc..08a6cd22c5 100644 --- a/services/codebuild/phase_duration_internal_test.go +++ b/services/codebuild/phase_duration_internal_test.go @@ -1,12 +1,14 @@ package codebuild import ( - "encoding/json" + "encoding/json/v2" "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRecordSuccessfulPhasesDurationsAreWholeSeconds(t *testing.T) { @@ -31,7 +33,7 @@ func TestRecordSuccessfulPhasesDurationsAreWholeSeconds(t *testing.T) { recordSuccessfulPhases(build, build.StartTime+tt.elapsed) for _, p := range build.Phases { - raw, err := json.Marshal(p) + raw, err := json.Marshal(p, jsonx.V1()) require.NoError(t, err) assert.NotRegexp(t, `"durationInSeconds":\d*\.`, string(raw)) diff --git a/services/codebuild/projects_schema_test.go b/services/codebuild/projects_schema_test.go index fad71fbfc6..5dfe4c1871 100644 --- a/services/codebuild/projects_schema_test.go +++ b/services/codebuild/projects_schema_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_CreateProject_SourceSchema tests the expanded source schema. @@ -82,7 +85,7 @@ func TestHandler_CreateProject_SourceSchema(t *testing.T) { } `json:"source"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantLocation, out.Project.Source.Location) assert.Equal(t, tt.wantBuildspec, out.Project.Source.Buildspec) assert.Equal(t, tt.wantCloneDepth, out.Project.Source.GitCloneDepth) @@ -157,7 +160,7 @@ func TestHandler_CreateProject_ArtifactsSchema(t *testing.T) { } `json:"artifacts"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out.Project.Artifacts.Type) assert.Equal(t, tt.wantPath, out.Project.Artifacts.Path) assert.Equal(t, tt.wantNamespaceType, out.Project.Artifacts.NamespaceType) @@ -245,7 +248,7 @@ func TestHandler_CreateProject_EnvironmentSchema(t *testing.T) { } `json:"environment"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out.Project.Environment.Type) assert.Equal(t, tt.wantComputeType, out.Project.Environment.ComputeType) assert.Equal(t, tt.wantPrivilegedMode, out.Project.Environment.PrivilegedMode) @@ -317,7 +320,7 @@ func TestHandler_CreateProject_CacheSchema(t *testing.T) { } `json:"cache"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out.Project.Cache.Type) assert.Len(t, out.Project.Cache.Modes, tt.wantModeLen) }) @@ -393,7 +396,7 @@ func TestHandler_CreateProject_Timeouts(t *testing.T) { AutoRetryLimit float64 `json:"autoRetryLimit"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(tt.timeoutInMinutes), out.Project.TimeoutInMinutes, 0) assert.InDelta(t, float64(tt.queuedTimeoutInMinutes), out.Project.QueuedTimeoutInMinutes, 0) assert.InDelta(t, float64(tt.concurrentBuildLimit), out.Project.ConcurrentBuildLimit, 0) @@ -468,7 +471,7 @@ func TestHandler_CreateProject_SecondarySources(t *testing.T) { SecondarySourceVersions []map[string]any `json:"secondarySourceVersions"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Project.SecondarySources, tt.wantSecondaryCount) if tt.secondarySourceVersions != nil { assert.Len(t, out.Project.SecondarySourceVersions, len(tt.secondarySourceVersions)) @@ -524,7 +527,7 @@ func TestHandler_CreateProject_VpcConfig(t *testing.T) { } `json:"vpcConfig"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantVpcID, out.Project.VpcConfig.VpcID) }) } @@ -588,7 +591,7 @@ func TestHandler_UpdateProject_Tags(t *testing.T) { } `json:"tags"` } `json:"project"` } - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &out, jsonx.V1())) var gotVal string for _, tag := range out.Project.Tags { @@ -679,7 +682,7 @@ func TestHandler_CreateProject_LogsConfig(t *testing.T) { } `json:"logsConfig"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantCloudWatchStatus, out.Project.LogsConfig.CloudWatchLogs.Status) assert.Equal(t, tt.wantS3Status, out.Project.LogsConfig.S3Logs.Status) }) @@ -739,7 +742,7 @@ func TestHandler_CreateProject_BuildBatchConfig(t *testing.T) { } `json:"buildBatchConfig"` } `json:"project"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantServiceRole, out.Project.BuildBatchConfig.ServiceRole) assert.InDelta(t, tt.wantMaxBuilds, out.Project.BuildBatchConfig.Restrictions.MaximumBuildsAllowed, 0) }) diff --git a/services/codebuild/projects_test.go b/services/codebuild/projects_test.go index b94ff70da2..1beccbb196 100644 --- a/services/codebuild/projects_test.go +++ b/services/codebuild/projects_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateProject(t *testing.T) { @@ -87,7 +90,7 @@ func TestHandler_CreateProject_TimestampsAreNumbers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) project, ok := out["project"].(map[string]any) require.True(t, ok, "response should contain 'project' object") @@ -151,7 +154,7 @@ func TestHandler_BatchGetProjects(t *testing.T) { Projects []any `json:"projects"` ProjectsNotFound []string `json:"projectsNotFound"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Projects, tt.wantProjectCount) assert.Equal(t, tt.wantNotFound, out.ProjectsNotFound) }) @@ -236,7 +239,7 @@ func TestHandler_Project_SourceVersion(t *testing.T) { SourceVersion string `json:"sourceVersion"` } `json:"project"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) assert.Equal(t, "refs/heads/main", createOut.Project.SourceVersion) updateRec := doRequest(t, h, "UpdateProject", map[string]any{ @@ -250,7 +253,7 @@ func TestHandler_Project_SourceVersion(t *testing.T) { SourceVersion string `json:"sourceVersion"` } `json:"project"` } - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&updateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &updateOut, jsonx.V1())) assert.Equal(t, "pr/42", updateOut.Project.SourceVersion, "UpdateProject must overwrite sourceVersion") } @@ -347,7 +350,7 @@ func TestHandler_ListProjects(t *testing.T) { var out struct { Projects []string `json:"projects"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) for _, pn := range tt.projectNames { assert.Contains(t, out.Projects, pn) @@ -382,7 +385,7 @@ func TestHandler_SortedOutput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) projects, _ := out["projects"].([]any) require.Len(t, projects, 3) @@ -411,7 +414,7 @@ func TestHandler_UpdateProjectVisibility_PublicAlias(t *testing.T) { var createOut struct { Project map[string]any `json:"project"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) projectArn, _ := createOut.Project["arn"].(string) require.NotEmpty(t, projectArn) @@ -426,7 +429,7 @@ func TestHandler_UpdateProjectVisibility_PublicAlias(t *testing.T) { ProjectVisibility string `json:"projectVisibility"` PublicProjectAlias string `json:"publicProjectAlias"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "PUBLIC_READ", out.ProjectVisibility) assert.NotEmpty(t, out.PublicProjectAlias, "UpdateProjectVisibility with PUBLIC_READ must return publicProjectAlias") @@ -453,7 +456,7 @@ func TestCodeBuild_Project(t *testing.T) { ProjectArn string `json:"projectArn"` ProjectVisibility string `json:"projectVisibility"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, arn, out.ProjectArn) assert.Equal(t, "PUBLIC_READ", out.ProjectVisibility) }) @@ -509,6 +512,6 @@ func TestCodeBuild_ListSharedProjects(t *testing.T) { var out struct { Projects []string `json:"projects"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.Projects) } diff --git a/services/codebuild/report_formats.go b/services/codebuild/report_formats.go index 0e39fe2da2..5b61ac8d41 100644 --- a/services/codebuild/report_formats.go +++ b/services/codebuild/report_formats.go @@ -1,13 +1,16 @@ package codebuild import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "fmt" "sort" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -291,7 +294,7 @@ type cucumberFeature struct { func parseCucumber(data []byte, source string) ([]TestCase, error) { var features []cucumberFeature - if err := json.Unmarshal(data, &features); err != nil { + if err := json.Unmarshal(data, &features, jsonx.V1()); err != nil { return nil, fmt.Errorf("parse cucumber %s: %w", source, err) } @@ -513,9 +516,9 @@ type simpleCovFile struct { func parseSimpleCov(data []byte, source string) ([]CodeCoverage, error) { var doc map[string]struct { - Coverage map[string]json.RawMessage `json:"coverage"` + Coverage map[string]jsontext.Value `json:"coverage"` } - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("parse simplecov %s: %w", source, err) } @@ -536,13 +539,13 @@ func parseSimpleCov(data []byte, source string) ([]CodeCoverage, error) { } // decodeSimpleCovFile accepts both the bare line array and the {lines, branches} object. -func decodeSimpleCovFile(raw json.RawMessage) (simpleCovFile, error) { +func decodeSimpleCovFile(raw jsontext.Value) (simpleCovFile, error) { var f simpleCovFile - if err := json.Unmarshal(raw, &f.Lines); err == nil { + if err := json.Unmarshal(raw, &f.Lines, jsonx.V1()); err == nil { return f, nil } - err := json.Unmarshal(raw, &f) + err := json.Unmarshal(raw, &f, jsonx.V1()) return f, err } diff --git a/services/codebuild/reports_test.go b/services/codebuild/reports_test.go index 524d51c82c..cd0195baf1 100644 --- a/services/codebuild/reports_test.go +++ b/services/codebuild/reports_test.go @@ -1,13 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -103,7 +105,7 @@ func TestHandler_BatchGetReportGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) rg, _ := out["reportGroup"].(map[string]any) arns = append(arns, rg["arn"].(string)) } @@ -116,7 +118,7 @@ func TestHandler_BatchGetReportGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) groups, _ := out["reportGroups"].([]any) assert.Len(t, groups, tt.wantFound) @@ -177,7 +179,7 @@ func TestHandler_BatchGetReports(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) reports, _ := out["reports"].([]any) assert.Len(t, reports, tt.wantFound) @@ -202,7 +204,7 @@ func TestCodeBuild_Reports(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.Reports) }) @@ -225,7 +227,7 @@ func TestCodeBuild_Reports(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Reports, 2) }) @@ -247,7 +249,7 @@ func TestCodeBuild_Reports(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Empty(t, out.Reports) }) @@ -302,7 +304,7 @@ func TestCodeBuild_Reports(t *testing.T) { var out struct { Reports []string `json:"reports"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Reports, 2) for _, r := range out.Reports { assert.Contains(t, r, "my-rg") @@ -343,7 +345,7 @@ func TestCodeBuild_ReportGroups(t *testing.T) { Arn string `json:"arn"` } `json:"reportGroup"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) rgArn := createOut.ReportGroup.Arn delRec := doRequest(t, h, "DeleteReportGroup", map[string]any{"arn": rgArn}) @@ -356,7 +358,7 @@ func TestCodeBuild_ReportGroups(t *testing.T) { var batchOut struct { ReportGroupsNotFound []string `json:"reportGroupsNotFound"` } - require.NoError(t, json.NewDecoder(batchRec.Body).Decode(&batchOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(batchRec.Body), &batchOut, jsonx.V1())) assert.Len(t, batchOut.ReportGroupsNotFound, 1) }) @@ -390,7 +392,7 @@ func TestCodeBuild_ReportGroups(t *testing.T) { Arn string `json:"arn"` } `json:"reportGroup"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) rgArn := createOut.ReportGroup.Arn updateRec := doRequest(t, h, "UpdateReportGroup", map[string]any{ @@ -408,7 +410,7 @@ func TestCodeBuild_ReportGroups(t *testing.T) { } `json:"exportConfig"` } `json:"reportGroup"` } - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&updateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &updateOut, jsonx.V1())) assert.Equal(t, "S3", updateOut.ReportGroup.ExportConfig.ExportConfigType) }) @@ -444,7 +446,7 @@ func TestCodeBuild_ReportExtras(t *testing.T) { var out struct { CodeCoverages []any `json:"codeCoverages"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.CodeCoverages) }) @@ -470,7 +472,7 @@ func TestCodeBuild_ReportExtras(t *testing.T) { var out struct { TestCases []any `json:"testCases"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.TestCases) }) @@ -502,7 +504,7 @@ func TestCodeBuild_ReportExtras(t *testing.T) { var out struct { Stats map[string]any `json:"stats"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotNil(t, out.Stats) }) @@ -543,7 +545,7 @@ func TestCodeBuild_ReportExtras(t *testing.T) { var out struct { ReportGroups []string `json:"reportGroups"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.ReportGroups) }) } diff --git a/services/codebuild/resource_policies_test.go b/services/codebuild/resource_policies_test.go index c46bbac726..7eab2816b1 100644 --- a/services/codebuild/resource_policies_test.go +++ b/services/codebuild/resource_policies_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCodeBuild_ResourcePolicy covers Put, Get, Delete. @@ -30,7 +33,7 @@ func TestCodeBuild_ResourcePolicy(t *testing.T) { var putOut struct { ResourceArn string `json:"resourceArn"` } - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&putOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &putOut, jsonx.V1())) assert.Equal(t, resArn, putOut.ResourceArn) getRec := doRequest(t, h, "GetResourcePolicy", map[string]any{"resourceArn": resArn}) @@ -39,7 +42,7 @@ func TestCodeBuild_ResourcePolicy(t *testing.T) { var getOut struct { Policy string `json:"policy"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.JSONEq(t, policy, getOut.Policy) }) @@ -129,7 +132,7 @@ func TestHandler_ResourcePolicy_PutGetRoundTrip(t *testing.T) { var out struct { Policy string `json:"policy"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) assert.JSONEq(t, tt.wantPolicy, out.Policy) } }) diff --git a/services/codebuild/sandboxes_test.go b/services/codebuild/sandboxes_test.go index fd8dab2814..db29a0a446 100644 --- a/services/codebuild/sandboxes_test.go +++ b/services/codebuild/sandboxes_test.go @@ -1,13 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codebuild" ) @@ -59,7 +61,7 @@ func TestHandler_BatchGetSandboxes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) sandboxes, _ := out["sandboxes"].([]any) assert.Len(t, sandboxes, tt.wantFound) @@ -88,7 +90,7 @@ func TestCodeBuild_Sandbox(t *testing.T) { ID string `json:"id"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) sandboxID := startOut.Sandbox.ID stopRec := doRequest(t, h, "StopSandbox", map[string]any{"id": sandboxID}) @@ -99,7 +101,7 @@ func TestCodeBuild_Sandbox(t *testing.T) { Status string `json:"status"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&stopOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &stopOut, jsonx.V1())) assert.Equal(t, "STOPPED", stopOut.Sandbox.Status) }) @@ -128,7 +130,7 @@ func TestCodeBuild_Sandbox(t *testing.T) { var out struct { IDs []string `json:"ids"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.IDs, 2) }) @@ -177,7 +179,7 @@ func TestHandler_StartSandbox_Schema(t *testing.T) { StartTime float64 `json:"startTime"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out.Sandbox.Status) if tt.wantArnSet { assert.NotEmpty(t, out.Sandbox.Arn) @@ -219,7 +221,7 @@ func TestHandler_StopSandbox_SetsEndTime(t *testing.T) { ID string `json:"id"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(startRec.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(startRec.Body), &startOut, jsonx.V1())) stopRec := doRequest(t, h, "StopSandbox", map[string]any{"id": startOut.Sandbox.ID}) require.Equal(t, http.StatusOK, stopRec.Code) @@ -230,7 +232,7 @@ func TestHandler_StopSandbox_SetsEndTime(t *testing.T) { EndTime float64 `json:"endTime"` } `json:"sandbox"` } - require.NoError(t, json.NewDecoder(stopRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(stopRec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out.Sandbox.Status) if tt.wantEndTimeSet { assert.Greater(t, out.Sandbox.EndTime, float64(0)) diff --git a/services/codebuild/source_credentials_test.go b/services/codebuild/source_credentials_test.go index 39b8fa755f..92aa9811c9 100644 --- a/services/codebuild/source_credentials_test.go +++ b/services/codebuild/source_credentials_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCodeBuild_SourceCredentials covers Import, List, Delete. @@ -27,7 +30,7 @@ func TestCodeBuild_SourceCredentials(t *testing.T) { var out struct { Arn string `json:"arn"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Contains(t, out.Arn, "GITHUB") }) @@ -58,7 +61,7 @@ func TestCodeBuild_SourceCredentials(t *testing.T) { var out struct { SourceCredentialsInfos []map[string]any `json:"sourceCredentialsInfos"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.Len(t, out.SourceCredentialsInfos, 1) assert.Equal(t, "BITBUCKET", out.SourceCredentialsInfos[0]["serverType"]) assert.Equal(t, "PERSONAL_ACCESS_TOKEN", out.SourceCredentialsInfos[0]["authType"]) @@ -78,7 +81,7 @@ func TestCodeBuild_SourceCredentials(t *testing.T) { var importOut struct { Arn string `json:"arn"` } - require.NoError(t, json.NewDecoder(importRec.Body).Decode(&importOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(importRec.Body), &importOut, jsonx.V1())) delRec := doRequest(t, h, "DeleteSourceCredentials", map[string]any{"arn": importOut.Arn}) assert.Equal(t, http.StatusOK, delRec.Code) @@ -88,7 +91,7 @@ func TestCodeBuild_SourceCredentials(t *testing.T) { var listOut struct { SourceCredentialsInfos []map[string]any `json:"sourceCredentialsInfos"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Empty(t, listOut.SourceCredentialsInfos) }) diff --git a/services/codebuild/tagswire.go b/services/codebuild/tagswire.go index 312a52a52a..aab1a32c96 100644 --- a/services/codebuild/tagswire.go +++ b/services/codebuild/tagswire.go @@ -1,8 +1,10 @@ package codebuild import ( - "encoding/json" + "encoding/json/v2" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // wireTags is the wire representation of CodeBuild's tags field: a JSON @@ -25,12 +27,12 @@ func (t wireTags) MarshalJSON() ([]byte, error) { sort.Slice(items, func(i, j int) bool { return items[i].Key < items[j].Key }) - return json.Marshal(items) + return json.Marshal(items, jsonx.V1()) } func (t *wireTags) UnmarshalJSON(data []byte) error { var items []wireTag - if err := json.Unmarshal(data, &items); err != nil { + if err := json.Unmarshal(data, &items, jsonx.V1()); err != nil { return err } diff --git a/services/codebuild/webhooks_test.go b/services/codebuild/webhooks_test.go index ecb0f689df..c8cac33ef9 100644 --- a/services/codebuild/webhooks_test.go +++ b/services/codebuild/webhooks_test.go @@ -1,12 +1,15 @@ package codebuild_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateWebhook(t *testing.T) { @@ -107,7 +110,7 @@ func TestHandler_CreateWebhook_ResponseFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) webhook, ok := out["webhook"].(map[string]any) require.True(t, ok, "response should contain 'webhook' object") @@ -167,7 +170,7 @@ func TestCodeBuild_Webhooks(t *testing.T) { BuildType string `json:"buildType"` } `json:"webhook"` } - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &out, jsonx.V1())) assert.Equal(t, "develop", out.Webhook.BranchFilter) assert.Equal(t, "BUILD_BATCH", out.Webhook.BuildType) }) @@ -215,7 +218,7 @@ func TestHandler_CreateWebhook_FilterGroups(t *testing.T) { var out struct { Webhook map[string]any `json:"webhook"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) w := out.Webhook fgs, hasFGs := w["filterGroups"] @@ -263,7 +266,7 @@ func TestHandler_CreateWebhook_ExtendedFields(t *testing.T) { ManualCreation bool `json:"manualCreation"` } `json:"webhook"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, "ACTIVE", out.Webhook.Status) assert.NotEmpty(t, out.Webhook.Secret, "CreateWebhook must return a secret") @@ -295,7 +298,7 @@ func TestHandler_UpdateWebhook_RotateSecret(t *testing.T) { LastModifiedSecret float64 `json:"lastModifiedSecret"` } `json:"webhook"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) originalSecret := createOut.Webhook.Secret require.NotEmpty(t, originalSecret) @@ -312,7 +315,7 @@ func TestHandler_UpdateWebhook_RotateSecret(t *testing.T) { Secret string `json:"secret"` } `json:"webhook"` } - require.NoError(t, json.NewDecoder(noRotateRec.Body).Decode(&noRotateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(noRotateRec.Body), &noRotateOut, jsonx.V1())) assert.Equal(t, originalSecret, noRotateOut.Webhook.Secret, "secret must be unchanged without rotateSecret") rotateRec := doRequest(t, h, "UpdateWebhook", map[string]any{ @@ -327,7 +330,7 @@ func TestHandler_UpdateWebhook_RotateSecret(t *testing.T) { LastModifiedSecret float64 `json:"lastModifiedSecret"` } `json:"webhook"` } - require.NoError(t, json.NewDecoder(rotateRec.Body).Decode(&rotateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rotateRec.Body), &rotateOut, jsonx.V1())) assert.NotEqual(t, originalSecret, rotateOut.Webhook.Secret, "rotateSecret must regenerate the secret") assert.GreaterOrEqual(t, rotateOut.Webhook.LastModifiedSecret, createOut.Webhook.LastModifiedSecret) } @@ -348,7 +351,7 @@ func TestHandler_Webhook_MirroredOnProject(t *testing.T) { var out struct { Projects []map[string]any `json:"projects"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Projects, 1) w, _ := out.Projects[0]["webhook"].(map[string]any) diff --git a/services/codebuild/whitebox_test.go b/services/codebuild/whitebox_test.go index e42202386c..c93a7fdae0 100644 --- a/services/codebuild/whitebox_test.go +++ b/services/codebuild/whitebox_test.go @@ -2,7 +2,8 @@ package codebuild import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,12 +12,14 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doTestRequest(t *testing.T, h *Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -81,7 +84,7 @@ func TestHandler_ListProjects_SortByCreatedTime(t *testing.T) { var out struct { Projects []string `json:"projects"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, []string{"created-z", "created-a", "created-m"}, out.Projects, "CREATED_TIME order must ignore name ordering") } diff --git a/services/codecommit/actor_arn_test.go b/services/codecommit/actor_arn_test.go index 3efe0e891e..85ffedd558 100644 --- a/services/codecommit/actor_arn_test.go +++ b/services/codecommit/actor_arn_test.go @@ -2,7 +2,7 @@ package codecommit_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -30,7 +31,7 @@ func doRequestAsActor( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -80,7 +81,7 @@ func createTestPRForActorArn(t *testing.T, h *codecommit.Handler) (string, strin RevisionID string `json:"revisionId"` } `json:"pullRequest"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.PullRequest.PullRequestID) require.NotEmpty(t, resp.PullRequest.RevisionID) @@ -153,7 +154,7 @@ func TestOverridePullRequestApprovalRules_RecordsCallerAsActorArn(t *testing.T) var resp struct { PullRequestEvents []map[string]any `json:"pullRequestEvents"` } - require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.PullRequestEvents, tt.wantCount) if tt.wantCount > 0 { diff --git a/services/codecommit/handler.go b/services/codecommit/handler.go index e5876418d0..4513a98381 100644 --- a/services/codecommit/handler.go +++ b/services/codecommit/handler.go @@ -2,7 +2,7 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -329,7 +330,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var input struct { RepositoryName string `json:"repositoryName"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return "" } @@ -366,7 +367,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } if action == "PutCommentReaction" { @@ -375,7 +376,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } if option, isPRMerge := pullRequestMergeOption(action); isPRMerge { @@ -384,7 +385,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } if ctxFn, isCtxOp := h.ctxOps[action]; isCtxOp { @@ -393,7 +394,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } fn, ok := h.ops[action] @@ -406,7 +407,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // errCodeEntry maps a backend sentinel error to its AWS HTTP status and exception type. diff --git a/services/codecommit/handler_approval_rule_associations_test.go b/services/codecommit/handler_approval_rule_associations_test.go index d7026455bc..28198947bf 100644 --- a/services/codecommit/handler_approval_rule_associations_test.go +++ b/services/codecommit/handler_approval_rule_associations_test.go @@ -1,12 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AssociateApprovalRuleTemplateWithRepository(t *testing.T) { @@ -154,7 +156,7 @@ func TestHandler_BatchAssociateApprovalRuleTemplateWithRepositories(t *testing.T if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc, _ := resp["associatedRepositoryNames"].([]any) assert.Len(t, assoc, tt.wantAssocCount) @@ -237,7 +239,7 @@ func TestHandler_BatchDisassociateApprovalRuleTemplateFromRepositories(t *testin if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) disassoc, _ := resp["disassociatedRepositoryNames"].([]any) assert.Len(t, disassoc, tt.wantDisassocCount) @@ -262,7 +264,7 @@ func TestHandler_BatchAssociate_EmptySlicesNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Must be JSON array, not null associated := resp["associatedRepositoryNames"] @@ -284,7 +286,7 @@ func TestHandler_BatchDisassociate_EmptySlicesNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) disassoc := resp["disassociatedRepositoryNames"] assert.NotNil(t, disassoc, "disassociatedRepositoryNames must not be null") @@ -310,7 +312,7 @@ func TestHandler_ListAssociatedApprovalRuleTemplatesForRepository(t *testing.T) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := resp["approvalRuleTemplateNames"].([]any) assert.Contains(t, names, "assoc-tmpl") } @@ -335,7 +337,7 @@ func TestHandler_ListRepositoriesForApprovalRuleTemplate(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos := resp["repositoryNames"].([]any) assert.Contains(t, repos, "repo-for-tmpl") } @@ -355,7 +357,7 @@ func TestHandler_ListRepositoriesForApprovalRuleTemplate_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos := resp["repositoryNames"].([]any) assert.Empty(t, repos) } @@ -389,7 +391,7 @@ func TestHandler_ListAssociatedApprovalRuleTemplatesForRepository_AfterAssoc(t * require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := resp["approvalRuleTemplateNames"].([]any) assert.Len(t, names, 2) } @@ -525,7 +527,7 @@ func TestHandler_BatchAssociateAndDisassociate_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc := resp["associatedRepositoryNames"].([]any) errs := resp["errors"].([]any) assert.Len(t, assoc, tt.wantAssocLen) @@ -539,7 +541,7 @@ func TestHandler_BatchAssociateAndDisassociate_TableDriven(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) disassoc := resp["disassociatedRepositoryNames"].([]any) assert.Len(t, disassoc, tt.wantAssocLen) } @@ -589,13 +591,13 @@ func TestListAssociatedApprovalRuleTemplatesForRepository_RepoNotFound(t *testin if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names, ok := resp["approvalRuleTemplateNames"].([]any) require.True(t, ok, "approvalRuleTemplateNames must be a JSON array") assert.Empty(t, names) } else { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "RepositoryDoesNotExistException", errResp["__type"]) } }) @@ -647,13 +649,13 @@ func TestListRepositoriesForApprovalRuleTemplate_TemplateNotFound(t *testing.T) if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, ok := resp["repositoryNames"].([]any) require.True(t, ok, "repositoryNames must be a JSON array") assert.Empty(t, repos) } else { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ApprovalRuleTemplateDoesNotExistException", errResp["__type"]) } }) diff --git a/services/codecommit/handler_approval_rules.go b/services/codecommit/handler_approval_rules.go index 87658bff9a..2483bed47f 100644 --- a/services/codecommit/handler_approval_rules.go +++ b/services/codecommit/handler_approval_rules.go @@ -1,8 +1,10 @@ package codecommit import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createApprovalRuleTemplateInput struct { @@ -48,7 +50,7 @@ func approvalRuleTemplateToMap(t *ApprovalRuleTemplate) map[string]any { func (h *Handler) handleCreateApprovalRuleTemplate(body []byte) (any, error) { var in createApprovalRuleTemplateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -76,7 +78,7 @@ func (h *Handler) handleCreateApprovalRuleTemplate(body []byte) (any, error) { func (h *Handler) handleAssociateApprovalRuleTemplateWithRepository(body []byte) (any, error) { var in associateApprovalRuleTemplateWithRepositoryInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -100,7 +102,7 @@ func (h *Handler) handleAssociateApprovalRuleTemplateWithRepository(body []byte) func (h *Handler) handleBatchAssociateApprovalRuleTemplateWithRepositories(body []byte) (any, error) { var in batchAssociateApprovalRuleTemplateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -129,7 +131,7 @@ func (h *Handler) handleBatchAssociateApprovalRuleTemplateWithRepositories(body func (h *Handler) handleBatchDisassociateApprovalRuleTemplateFromRepositories(body []byte) (any, error) { var in batchDisassociateApprovalRuleTemplateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -160,7 +162,7 @@ func (h *Handler) handleDeleteApprovalRuleTemplate(body []byte) (any, error) { var req struct { ApprovalRuleTemplateName string `json:"approvalRuleTemplateName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" { @@ -184,7 +186,7 @@ func (h *Handler) handleGetApprovalRuleTemplate(body []byte) (any, error) { var req struct { ApprovalRuleTemplateName string `json:"approvalRuleTemplateName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" { @@ -209,7 +211,7 @@ type listPageRequest struct { func (h *Handler) handleListApprovalRuleTemplates(body []byte) (any, error) { var req listPageRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } } @@ -242,7 +244,7 @@ func (h *Handler) handleUpdateApprovalRuleTemplateContent(body []byte) (any, err NewRuleContent string `json:"newRuleContent"` ExistingRuleContentSha string `json:"existingRuleContentSha256"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" { @@ -271,7 +273,7 @@ func (h *Handler) handleUpdateApprovalRuleTemplateDescription(body []byte) (any, ApprovalRuleTemplateName string `json:"approvalRuleTemplateName"` ApprovalRuleTemplateDescription string `json:"approvalRuleTemplateDescription"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" { @@ -299,7 +301,7 @@ func (h *Handler) handleUpdateApprovalRuleTemplateName(body []byte) (any, error) OldApprovalRuleTemplateName string `json:"oldApprovalRuleTemplateName"` NewApprovalRuleTemplateName string `json:"newApprovalRuleTemplateName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.OldApprovalRuleTemplateName == "" || req.NewApprovalRuleTemplateName == "" { @@ -329,7 +331,7 @@ func (h *Handler) handleListAssociatedApprovalRuleTemplatesForRepository(body [] RepositoryName string `json:"repositoryName"` listPageRequest } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -354,7 +356,7 @@ func (h *Handler) handleListRepositoriesForApprovalRuleTemplate(body []byte) (an ApprovalRuleTemplateName string `json:"approvalRuleTemplateName"` listPageRequest } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" { @@ -384,7 +386,7 @@ func (h *Handler) handleDisassociateApprovalRuleTemplateFromRepository(body []by ApprovalRuleTemplateName string `json:"approvalRuleTemplateName"` RepositoryName string `json:"repositoryName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.ApprovalRuleTemplateName == "" || req.RepositoryName == "" { diff --git a/services/codecommit/handler_approval_rules_test.go b/services/codecommit/handler_approval_rules_test.go index cddf5fbd9b..e25831ec3d 100644 --- a/services/codecommit/handler_approval_rules_test.go +++ b/services/codecommit/handler_approval_rules_test.go @@ -1,7 +1,7 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -75,7 +76,7 @@ func TestHandler_CreateApprovalRuleTemplate(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tmpl, ok := resp["approvalRuleTemplate"].(map[string]any) require.True(t, ok) @@ -125,7 +126,7 @@ func TestHandler_GetApprovalRuleTemplate(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tmpl := resp["approvalRuleTemplate"].(map[string]any) assert.Equal(t, "tmpl-get", tmpl["approvalRuleTemplateName"]) @@ -153,7 +154,7 @@ func TestHandler_ListApprovalRuleTemplates(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := resp["approvalRuleTemplateNames"].([]any) assert.Len(t, names, 2) @@ -203,7 +204,7 @@ func TestHandler_UpdateApprovalRuleTemplateContent_Reflected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tmpl := resp["approvalRuleTemplate"].(map[string]any) assert.Equal(t, newContent, tmpl["approvalRuleTemplateContent"]) } @@ -330,7 +331,7 @@ func TestHandler_ApprovalRuleTemplate_CRUD_TableDriven(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tmpl := resp["approvalRuleTemplate"].(map[string]any) assert.Equal(t, tt.tmplName, tmpl["approvalRuleTemplateName"]) assert.Equal(t, tt.content, tmpl["approvalRuleTemplateContent"]) diff --git a/services/codecommit/handler_branches.go b/services/codecommit/handler_branches.go index 05691dfcbe..aa98392084 100644 --- a/services/codecommit/handler_branches.go +++ b/services/codecommit/handler_branches.go @@ -1,8 +1,10 @@ package codecommit import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createBranchInput struct { @@ -13,7 +15,7 @@ type createBranchInput struct { func (h *Handler) handleCreateBranch(body []byte) (any, error) { var in createBranchInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -41,7 +43,7 @@ func (h *Handler) handleDeleteBranch(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` BranchName string `json:"branchName"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -70,7 +72,7 @@ func (h *Handler) handleGetBranch(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` BranchName string `json:"branchName"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -93,7 +95,7 @@ func (h *Handler) handleListBranches(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -123,7 +125,7 @@ func (h *Handler) handleUpdateDefaultBranch(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` DefaultBranchName string `json:"defaultBranchName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_branches_test.go b/services/codecommit/handler_branches_test.go index 09ad7b4b2a..4b3180c9ee 100644 --- a/services/codecommit/handler_branches_test.go +++ b/services/codecommit/handler_branches_test.go @@ -1,12 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateBranch(t *testing.T) { @@ -29,7 +31,7 @@ func TestHandler_CreateBranch(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitResp map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp, jsonx.V1())) commitID := commitResp["commitId"].(string) rec := doRequest(t, h, "CreateBranch", map[string]any{ @@ -108,7 +110,7 @@ func TestHandler_CreateBranch(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitResp map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp, jsonx.V1())) commitID := commitResp["commitId"].(string) doRequest(t, h, "CreateBranch", map[string]any{ @@ -142,7 +144,7 @@ func TestHandler_CreateBranch_CommitValidation(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitResp map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp, jsonx.V1())) realCommitID := commitResp["commitId"].(string) tests := []struct { @@ -181,7 +183,7 @@ func TestHandler_CreateBranch_CommitValidation(t *testing.T) { }) require.Equal(t, http.StatusOK, cr.Code) var cr2 map[string]any - require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &cr2)) + require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &cr2, jsonx.V1())) tt.commitID = cr2["commitId"].(string) } diff --git a/services/codecommit/handler_comments.go b/services/codecommit/handler_comments.go index e1f1630c53..29f1d26da8 100644 --- a/services/codecommit/handler_comments.go +++ b/services/codecommit/handler_comments.go @@ -2,10 +2,11 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -66,7 +67,7 @@ func (h *Handler) handlePostCommentForComparedCommit(ctx context.Context, body [ Content string `json:"content"` ClientRequestToken string `json:"clientRequestToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" || req.Content == "" { @@ -104,7 +105,7 @@ func (h *Handler) handlePostCommentForPullRequest(ctx context.Context, body []by Content string `json:"content"` ClientRequestToken string `json:"clientRequestToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" || req.Content == "" { @@ -139,7 +140,7 @@ func (h *Handler) handlePostCommentReply(ctx context.Context, body []byte) (any, Content string `json:"content"` ClientRequestToken string `json:"clientRequestToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.InReplyTo == "" || req.Content == "" { @@ -167,7 +168,7 @@ func (h *Handler) handleGetComment(body []byte) (any, error) { var req struct { CommentID string `json:"commentId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.CommentID == "" { @@ -229,7 +230,7 @@ func (h *Handler) handleGetCommentsForComparedCommit(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -275,7 +276,7 @@ func (h *Handler) handleGetCommentsForPullRequest(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -324,7 +325,7 @@ func (h *Handler) handleUpdateComment(body []byte) (any, error) { CommentID string `json:"commentId"` Content string `json:"content"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.CommentID == "" { @@ -349,7 +350,7 @@ func (h *Handler) handleDeleteCommentContent(body []byte) (any, error) { var req struct { CommentID string `json:"commentId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.CommentID == "" { diff --git a/services/codecommit/handler_comments_test.go b/services/codecommit/handler_comments_test.go index 58ce95da1d..936665621d 100644 --- a/services/codecommit/handler_comments_test.go +++ b/services/codecommit/handler_comments_test.go @@ -1,13 +1,15 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PostCommentForComparedCommit(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_PostCommentForComparedCommit(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) comment := resp["comment"].(map[string]any) assert.Equal(t, "hello world", comment["content"]) assert.NotEmpty(t, comment["commentId"]) @@ -45,7 +47,7 @@ func TestHandler_PostCommentForPullRequest(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) comment := resp["comment"].(map[string]any) assert.Equal(t, "PR comment", comment["content"]) } @@ -64,7 +66,7 @@ func TestHandler_PostCommentReply(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var parentResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &parentResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &parentResp, jsonx.V1())) parentID := parentResp["comment"].(map[string]any)["commentId"].(string) rec = doRequest(t, h, "PostCommentReply", map[string]any{ @@ -74,7 +76,7 @@ func TestHandler_PostCommentReply(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "reply comment", resp["comment"].(map[string]any)["content"]) } @@ -92,7 +94,7 @@ func TestHandler_GetComment(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) cID := cr["comment"].(map[string]any)["commentId"].(string) rec = doRequest(t, h, "GetComment", map[string]any{"commentId": cID}) @@ -121,7 +123,7 @@ func TestHandler_GetCommentsForComparedCommit(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["commentsForComparedCommitData"].([]any) require.Len(t, groups, 1) assert.Len(t, groups[0].(map[string]any)["comments"], 1) @@ -163,7 +165,7 @@ func TestHandler_GetCommentsForComparedCommit_TableDriven(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["commentsForComparedCommitData"].([]any) if tt.commentCount == 0 { assert.Empty(t, groups) @@ -194,7 +196,7 @@ func TestHandler_GetCommentsForPullRequest(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["commentsForPullRequestData"].([]any) require.Len(t, groups, 1) assert.Len(t, groups[0].(map[string]any)["comments"], 1) @@ -234,7 +236,7 @@ func TestHandler_GetCommentsForPullRequest_TableDriven(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["commentsForPullRequestData"].([]any) if tt.commentCount == 0 { assert.Empty(t, groups) @@ -261,7 +263,7 @@ func TestHandler_UpdateComment(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) cID := cr["comment"].(map[string]any)["commentId"].(string) rec = doRequest(t, h, "UpdateComment", map[string]any{ @@ -271,7 +273,7 @@ func TestHandler_UpdateComment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "updated", resp["comment"].(map[string]any)["content"]) } @@ -289,14 +291,14 @@ func TestHandler_DeleteCommentContent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) cID := cr["comment"].(map[string]any)["commentId"].(string) rec = doRequest(t, h, "DeleteCommentContent", map[string]any{"commentId": cID}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["comment"].(map[string]any)["deleted"]) } @@ -315,7 +317,7 @@ func TestHandler_CommentLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commentID := resp["comment"].(map[string]any)["commentId"].(string) // Update the comment. @@ -325,7 +327,7 @@ func TestHandler_CommentLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "updated content", resp["comment"].(map[string]any)["content"]) // Add reaction. @@ -339,7 +341,7 @@ func TestHandler_CommentLifecycle(t *testing.T) { rec = doRequest(t, h, "GetCommentReactions", map[string]any{"commentId": commentID}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) reactions := resp["reactionsForComment"].([]any) assert.Len(t, reactions, 1) @@ -354,7 +356,7 @@ func TestHandler_CommentLifecycle(t *testing.T) { rec = doRequest(t, h, "DeleteCommentContent", map[string]any{"commentId": commentID}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) c := resp["comment"].(map[string]any) assert.Equal(t, true, c["deleted"]) assert.Empty(t, c["content"]) diff --git a/services/codecommit/handler_commits.go b/services/codecommit/handler_commits.go index baf9860ae0..7d0128030b 100644 --- a/services/codecommit/handler_commits.go +++ b/services/codecommit/handler_commits.go @@ -2,9 +2,11 @@ package codecommit import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type batchGetCommitsInput struct { @@ -70,7 +72,7 @@ func commitToMap(c *Commit) map[string]any { func (h *Handler) handleBatchGetCommits(body []byte) (any, error) { var in batchGetCommitsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -100,7 +102,7 @@ func (h *Handler) handleBatchGetCommits(body []byte) (any, error) { func (h *Handler) handleCreateCommit(body []byte) (any, error) { var in createCommitInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -209,7 +211,7 @@ func (h *Handler) handleGetCommit(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` CommitID string `json:"commitId"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -237,7 +239,7 @@ func (h *Handler) handleGetDifferences(body []byte) (any, error) { NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_commits_test.go b/services/codecommit/handler_commits_test.go index 1c6412a4c4..981da6dece 100644 --- a/services/codecommit/handler_commits_test.go +++ b/services/codecommit/handler_commits_test.go @@ -1,13 +1,15 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateCommit(t *testing.T) { @@ -69,7 +71,7 @@ func TestHandler_CreateCommit(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["commitId"]) assert.NotEmpty(t, resp["treeId"]) } @@ -97,7 +99,7 @@ func TestHandler_CreateCommit_FilesAddedBlobID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) filesAdded, ok := resp["filesAdded"].([]any) require.True(t, ok) require.Len(t, filesAdded, 2) @@ -143,7 +145,7 @@ func TestHandler_CreateCommit_FilesDeletedBlobID(t *testing.T) { require.Equal(t, http.StatusOK, addRec.Code) var addResp map[string]any - require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp)) + require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp, jsonx.V1())) filesAdded, ok := addResp["filesAdded"].([]any) require.True(t, ok) require.Len(t, filesAdded, 1) @@ -162,7 +164,7 @@ func TestHandler_CreateCommit_FilesDeletedBlobID(t *testing.T) { require.Equal(t, http.StatusOK, delRec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp, jsonx.V1())) filesDeleted, ok := delResp["filesDeleted"].([]any) require.True(t, ok) require.Len(t, filesDeleted, 1) @@ -237,7 +239,7 @@ func TestHandler_BatchGetCommits(t *testing.T) { require.Equal(t, http.StatusOK, r.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &resp, jsonx.V1())) commitIDs = append(commitIDs, resp["commitId"].(string)) } @@ -252,7 +254,7 @@ func TestHandler_BatchGetCommits(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commits, _ := resp["commits"].([]any) assert.Len(t, commits, tt.wantFoundCount) @@ -308,7 +310,7 @@ func TestHandler_BatchGetCommits_FullCommitMap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) commitID := createResp["commitId"].(string) rec = doRequest(t, h, "BatchGetCommits", map[string]any{ @@ -318,7 +320,7 @@ func TestHandler_BatchGetCommits_FullCommitMap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commits, ok := resp["commits"].([]any) require.True(t, ok) @@ -392,7 +394,7 @@ func TestHandler_BatchGetCommits_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, branchRec.Code) var brResp map[string]any - require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &brResp)) + require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &brResp, jsonx.V1())) realCommitID := brResp["branch"].(map[string]any)["commitId"].(string) var ids []string @@ -410,7 +412,7 @@ func TestHandler_BatchGetCommits_TableDriven(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commits := resp["commits"].([]any) errors := resp["errors"].([]any) @@ -437,7 +439,7 @@ func TestHandler_CommitParentTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) firstCommitID := firstResp["commitId"].(string) // Second commit on same branch — should have firstCommitID as parent @@ -449,7 +451,7 @@ func TestHandler_CommitParentTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var secondResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp, jsonx.V1())) secondCommitID := secondResp["commitId"].(string) // Fetch both commits via BatchGetCommits and check parent linkage @@ -460,7 +462,7 @@ func TestHandler_CommitParentTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp, jsonx.V1())) commits, ok := batchResp["commits"].([]any) require.True(t, ok) @@ -504,7 +506,7 @@ func TestHandler_CreateCommit_ParentCommitIdOutdated(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ParentCommitIdOutdatedException", resp["__type"]) } @@ -535,7 +537,7 @@ func TestHandler_CreateCommit_NoChange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) firstCommitID, _ := firstResp["commitId"].(string) require.NotEmpty(t, firstCommitID) @@ -549,7 +551,7 @@ func TestHandler_CreateCommit_NoChange(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "NoChangeException", errResp["__type"]) branchRec := doRequest(t, h, "GetBranch", map[string]any{ @@ -558,7 +560,7 @@ func TestHandler_CreateCommit_NoChange(t *testing.T) { require.Equal(t, http.StatusOK, branchRec.Code) var branchResp map[string]any - require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &branchResp)) + require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &branchResp, jsonx.V1())) branch, ok := branchResp["branch"].(map[string]any) require.True(t, ok) assert.Equal(t, firstCommitID, branch["commitId"], "rejected commit must not move the branch tip") @@ -602,7 +604,7 @@ func TestGetCommit_ParentsNotNull(t *testing.T) { require.Equal(t, http.StatusOK, r1.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(r1.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(r1.Body.Bytes(), &firstResp, jsonx.V1())) firstID := firstResp["commitId"].(string) // Create second commit (has first as parent). @@ -614,7 +616,7 @@ func TestGetCommit_ParentsNotNull(t *testing.T) { require.Equal(t, http.StatusOK, r2.Code) var secondResp map[string]any - require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &secondResp, jsonx.V1())) secondID := secondResp["commitId"].(string) ids := []string{firstID, secondID} @@ -627,7 +629,7 @@ func TestGetCommit_ParentsNotNull(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commit, ok := resp["commit"].(map[string]any) require.True(t, ok, "commit field must be present") @@ -664,7 +666,7 @@ func TestGetCommit_FullFieldSet(t *testing.T) { require.Equal(t, http.StatusOK, r.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &createResp, jsonx.V1())) commitID := createResp["commitId"].(string) rec := doRequest(t, h, "GetCommit", map[string]any{ @@ -674,7 +676,7 @@ func TestGetCommit_FullFieldSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commit, ok := resp["commit"].(map[string]any) require.True(t, ok) @@ -719,7 +721,7 @@ func TestHandler_GetCommit_CommitIDNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CommitIdDoesNotExistException", resp["__type"]) } @@ -747,7 +749,7 @@ func TestHandler_BatchGetCommits_ErrorCodeIsCommitIdDoesNotExist(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) errs, ok := resp["errors"].([]any) require.True(t, ok) @@ -771,7 +773,7 @@ func TestHandler_GetDifferences(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["differences"]) } @@ -831,7 +833,7 @@ func TestGetDifferences_BlobObjectShape(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitOut map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut, jsonx.V1())) commitID := commitOut["commitId"].(string) rec := doRequest(t, h, "GetDifferences", map[string]any{ @@ -843,7 +845,7 @@ func TestGetDifferences_BlobObjectShape(t *testing.T) { var resp struct { Differences []map[string]any `json:"differences"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Differences, tt.wantCount) // Each difference must have afterBlob as an object, not a string. @@ -878,7 +880,7 @@ func TestGetDifferences_FilePathInBlob(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitOut map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut, jsonx.V1())) rec := doRequest(t, h, "GetDifferences", map[string]any{ "repositoryName": "path-repo", @@ -889,7 +891,7 @@ func TestGetDifferences_FilePathInBlob(t *testing.T) { var resp struct { Differences []map[string]any `json:"differences"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Differences, 1) afterBlob := resp.Differences[0]["afterBlob"].(map[string]any) @@ -912,7 +914,7 @@ func TestGetDifferences_EmptyRepoReturnsEmptyArray(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) diffs, ok := resp["differences"].([]any) require.True(t, ok, "differences must be a JSON array, not null") assert.Empty(t, diffs) @@ -946,7 +948,7 @@ func TestGetDifferences_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, commitRec.Code) var commitOut map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut, jsonx.V1())) commitID := commitOut["commitId"].(string) seenPaths := map[string]bool{} @@ -966,7 +968,7 @@ func TestGetDifferences_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) diffs, ok := resp["differences"].([]any) require.True(t, ok) assert.LessOrEqual(t, len(diffs), 2, "each page must respect MaxResults") diff --git a/services/codecommit/handler_files.go b/services/codecommit/handler_files.go index 274bf0cf15..4eccf4ca15 100644 --- a/services/codecommit/handler_files.go +++ b/services/codecommit/handler_files.go @@ -4,9 +4,11 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handlePutFile(body []byte) (any, error) { @@ -21,7 +23,7 @@ func (h *Handler) handlePutFile(body []byte) (any, error) { CommitMessage string `json:"commitMessage"` ParentCommitID string `json:"parentCommitId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" || req.FilePath == "" { @@ -61,7 +63,7 @@ func (h *Handler) handleGetFile(body []byte) (any, error) { CommitSpecifier string `json:"commitSpecifier"` FilePath string `json:"filePath"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" || req.FilePath == "" { @@ -89,7 +91,7 @@ func (h *Handler) handleGetFolder(body []byte) (any, error) { CommitSpecifier string `json:"commitSpecifier"` FolderPath string `json:"folderPath"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -165,7 +167,7 @@ func (h *Handler) handleDeleteFile(body []byte) (any, error) { CommitMessage string `json:"commitMessage"` KeepEmptyFolders bool `json:"keepEmptyFolders"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" || req.FilePath == "" { @@ -196,7 +198,7 @@ func (h *Handler) handleGetBlob(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` BlobID string `json:"blobId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" || req.BlobID == "" { @@ -223,7 +225,7 @@ func (h *Handler) handleGetBlobDifferences(body []byte) (any, error) { BeforeBlobID string `json:"beforeBlobId"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -265,7 +267,7 @@ func (h *Handler) handleListFileCommitHistory(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_files_test.go b/services/codecommit/handler_files_test.go index c90375c158..6ac2d9c63d 100644 --- a/services/codecommit/handler_files_test.go +++ b/services/codecommit/handler_files_test.go @@ -2,13 +2,15 @@ package codecommit_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutFile_GetFile(t *testing.T) { @@ -26,7 +28,7 @@ func TestHandler_PutFile_GetFile(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) putBlobID, _ := putResp["blobId"].(string) assert.NotEmpty(t, putBlobID, "PutFileOutput.blobId is a required AWS field") @@ -38,7 +40,7 @@ func TestHandler_PutFile_GetFile(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "hello.txt", resp["filePath"]) assert.Equal( t, @@ -68,7 +70,7 @@ func TestHandler_PutFile_GetFile(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var blobResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &blobResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &blobResp, jsonx.V1())) assert.Equal(t, "aGVsbG8=", blobResp["content"]) } @@ -95,7 +97,7 @@ func TestHandler_PutFile_SameFileContent(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "SameFileContentException", errResp["__type"]) // Different content at the same path must still succeed. @@ -126,7 +128,7 @@ func TestHandler_GetFolder(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) files := resp["files"].([]any) assert.Len(t, files, 1) } @@ -144,7 +146,7 @@ func TestHandler_DeleteFile(t *testing.T) { }) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) putBlobID, _ := putResp["blobId"].(string) require.NotEmpty(t, putBlobID) putCommitID, _ := putResp["commitId"].(string) @@ -161,7 +163,7 @@ func TestHandler_DeleteFile(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["commitId"]) assert.Equal(t, putBlobID, resp["blobId"], "DeleteFileOutput.blobId must report the blob that was removed from the tree") @@ -183,7 +185,7 @@ func TestHandler_DeleteFile_NotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "FileDoesNotExistException", resp["__type"]) } @@ -211,7 +213,7 @@ func TestHandler_DeleteFile_ParentCommitIdRequired(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ParentCommitIdRequiredException", resp["__type"]) } @@ -243,7 +245,7 @@ func TestHandler_DeleteFile_ParentCommitIdOutdated(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ParentCommitIdOutdatedException", resp["__type"]) } @@ -268,7 +270,7 @@ func TestHandler_GetBlob(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fr map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &fr)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &fr, jsonx.V1())) blobID := fr["blobId"].(string) rec = doRequest(t, h, "GetBlob", map[string]any{ @@ -278,7 +280,7 @@ func TestHandler_GetBlob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["content"]) } @@ -295,7 +297,7 @@ func TestHandler_ListFileCommitHistory(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["revisionDag"]) } @@ -322,7 +324,7 @@ func TestHandler_ListFileCommitHistory_RevisionDagShape(t *testing.T) { }) require.Equal(t, http.StatusOK, firstRec.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstResp, jsonx.V1())) firstCommitID, _ := firstResp["commitId"].(string) require.NotEmpty(t, firstCommitID) @@ -335,7 +337,7 @@ func TestHandler_ListFileCommitHistory_RevisionDagShape(t *testing.T) { }) require.Equal(t, http.StatusOK, secondRec.Code) var secondResp map[string]any - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondResp, jsonx.V1())) secondCommitID, _ := secondResp["commitId"].(string) require.NotEmpty(t, secondCommitID) @@ -346,7 +348,7 @@ func TestHandler_ListFileCommitHistory_RevisionDagShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag, ok := resp["revisionDag"].([]any) require.True(t, ok) require.Len(t, dag, 2) @@ -406,7 +408,7 @@ func TestHandler_ListFileCommitHistory_IncludesPutFileWrites(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag, ok := resp["revisionDag"].([]any) require.True(t, ok) require.Len(t, dag, 1, "the PutFile commit must appear in the file's history") @@ -437,7 +439,7 @@ func TestHandler_ListFileCommitHistory_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ := resp["commitId"].(string) require.NotEmpty(t, id) commitIDs = append(commitIDs, id) @@ -460,7 +462,7 @@ func TestHandler_ListFileCommitHistory_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag, ok := resp["revisionDag"].([]any) require.True(t, ok) assert.LessOrEqual(t, len(dag), 2, "each page must respect maxResults") @@ -536,7 +538,7 @@ func TestHandler_ListFileCommitHistory_TableDriven(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag := resp["revisionDag"].([]any) assert.Len(t, dag, tt.seedCommits) }) @@ -566,7 +568,7 @@ func TestHandler_FileLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "src/main.go", resp["filePath"]) assert.NotEmpty(t, resp["blobId"]) putCommitID, _ := resp["commitId"].(string) @@ -583,7 +585,7 @@ func TestHandler_FileLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) files := resp["files"].([]any) assert.Len(t, files, 1) @@ -664,7 +666,7 @@ func TestGetFolder_AlwaysIncludesSubArrays(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // files array must always be present and correct count. files, ok := resp["files"].([]any) @@ -747,7 +749,7 @@ func TestListFileCommitHistory_FiltersByFilePath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag := resp["revisionDag"].([]any) assert.Len(t, dag, tt.wantCount, "revisionDag must contain %d commits for path %q", tt.wantCount, tt.queryPath) diff --git a/services/codecommit/handler_merges.go b/services/codecommit/handler_merges.go index 8967a767a0..ca59538d2b 100644 --- a/services/codecommit/handler_merges.go +++ b/services/codecommit/handler_merges.go @@ -2,10 +2,11 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type batchDescribeMergeConflictsInput struct { @@ -29,7 +30,7 @@ func isValidMergeOption(opt string) bool { func (h *Handler) handleBatchDescribeMergeConflicts(body []byte) (any, error) { var in batchDescribeMergeConflictsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -106,7 +107,7 @@ func (h *Handler) handleMergePullRequest(ctx context.Context, option string, bod Email string `json:"email"` mergeSettingsWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -157,7 +158,7 @@ func (h *Handler) handleMergeBranchesByFastForward(body []byte) (any, error) { DestinationCommitSpecifier string `json:"destinationCommitSpecifier"` TargetBranch string `json:"targetBranch"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -184,7 +185,7 @@ func (h *Handler) handleGetMergeOptions(body []byte) (any, error) { DestinationCommitSpecifier string `json:"destinationCommitSpecifier"` mergeSettingsWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -229,7 +230,7 @@ func (h *Handler) handleCreateUnreferencedMergeCommit(body []byte) (any, error) CommitMessage string `json:"commitMessage"` mergeSettingsWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -273,7 +274,7 @@ func (h *Handler) handleGetMergeCommit(body []byte) (any, error) { DestinationCommitSpecifier string `json:"destinationCommitSpecifier"` mergeSettingsWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -307,7 +308,7 @@ func (h *Handler) handleGetMergeConflicts(body []byte) (any, error) { MergeOption string `json:"mergeOption"` mergeQueryWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -372,7 +373,7 @@ func (h *Handler) handleDescribeMergeConflicts(body []byte) (any, error) { FilePath string `json:"filePath"` mergeQueryWire } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -458,7 +459,7 @@ func (r mergeBranchesRequest) options() (MergeBranchesOptions, error) { func (h *Handler) handleMergeBranchesBySquash(body []byte) (any, error) { var req mergeBranchesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -485,7 +486,7 @@ func (h *Handler) handleMergeBranchesBySquash(body []byte) (any, error) { func (h *Handler) handleMergeBranchesByThreeWay(body []byte) (any, error) { var req mergeBranchesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_merges_test.go b/services/codecommit/handler_merges_test.go index f7b32ac05b..0bbd37d313 100644 --- a/services/codecommit/handler_merges_test.go +++ b/services/codecommit/handler_merges_test.go @@ -1,13 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -86,7 +87,7 @@ func TestHandler_BatchDescribeMergeConflicts(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "conflicts") assert.Contains(t, resp, "destinationCommitId") assert.Contains(t, resp, "sourceCommitId") @@ -151,7 +152,7 @@ func TestHandler_BatchDescribeMergeConflicts_TableDriven(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) conflicts := resp["conflicts"].([]any) assert.Len(t, conflicts, tt.wantConflicts) assert.Len(t, resp["errors"], tt.wantErrors) @@ -174,7 +175,7 @@ func TestHandler_MergePullRequestByFastForward(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "CLOSED", pr["pullRequestStatus"]) } @@ -193,7 +194,7 @@ func TestHandler_MergePullRequestBySquash(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "CLOSED", pr["pullRequestStatus"]) } @@ -212,7 +213,7 @@ func TestHandler_MergePullRequestByThreeWay(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "CLOSED", pr["pullRequestStatus"]) } @@ -233,7 +234,7 @@ func TestHandler_MergeBranchesByFastForward(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal( t, sourceTip, @@ -271,7 +272,7 @@ func TestHandler_MergeBranchesByFastForward_UnknownSpecifier(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CommitDoesNotExistException", resp["__type"]) }) } @@ -295,7 +296,7 @@ func TestHandler_MergeBranchesBySquash(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commitID, _ := resp["commitId"].(string) require.NotEmpty(t, commitID) @@ -306,7 +307,7 @@ func TestHandler_MergeBranchesBySquash(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) commit, ok := getResp["commit"].(map[string]any) require.True(t, ok) assert.Equal(t, "squash it", commit["message"]) @@ -333,7 +334,7 @@ func TestHandler_MergeBranchesByThreeWay(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commitID, _ := resp["commitId"].(string) require.NotEmpty(t, commitID) @@ -344,7 +345,7 @@ func TestHandler_MergeBranchesByThreeWay(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) commit, ok := getResp["commit"].(map[string]any) require.True(t, ok) parents, ok := commit["parents"].([]any) @@ -373,7 +374,7 @@ func TestHandler_MergeBranchesBySquash_TargetBranch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) commitID, _ := resp["commitId"].(string) assert.Equal(t, commitID, mustBranchTip(t, h, "sq-target-repo", "release"), @@ -408,7 +409,7 @@ func mustBranchTip(t *testing.T, h *codecommit.Handler, repoName, branchName str require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) branch, ok := resp["branch"].(map[string]any) require.True(t, ok) commitID, _ := branch["commitId"].(string) @@ -443,7 +444,7 @@ func TestHandler_MergeBranches_AllStrategies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["commitId"]) assert.NotEmpty(t, resp["treeId"]) }) @@ -465,7 +466,7 @@ func TestHandler_GetMergeOptions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) opts := resp["mergeOptions"].([]any) assert.Len(t, opts, 3) @@ -475,7 +476,7 @@ func TestHandler_GetMergeOptions(t *testing.T) { CommitID string `json:"commitId"` } `json:"branch"` } - require.NoError(t, json.Unmarshal(branch.Body.Bytes(), &branchResp)) + require.NoError(t, json.Unmarshal(branch.Body.Bytes(), &branchResp, jsonx.V1())) assert.Equal(t, branchResp.Branch.CommitID, resp["destinationCommitId"]) assert.Equal(t, branchResp.Branch.CommitID, resp["baseCommitId"], "feature forked from main's tip") } @@ -512,7 +513,7 @@ func TestHandler_MergeOptions_AllStrategies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) opts := resp["mergeOptions"].([]any) assert.Len(t, opts, 3) @@ -541,7 +542,7 @@ func TestHandler_CreateUnreferencedMergeCommit(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["commitId"]) } @@ -593,7 +594,7 @@ func TestHandler_CreateUnreferencedMergeCommit_Success(t *testing.T) { require.Equal(t, http.StatusOK, branchRec.Code) var brResp map[string]any - require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &brResp)) + require.NoError(t, json.Unmarshal(branchRec.Body.Bytes(), &brResp, jsonx.V1())) commitID := brResp["branch"].(map[string]any)["commitId"].(string) rec := doRequest(t, h, "CreateUnreferencedMergeCommit", map[string]any{ @@ -605,7 +606,7 @@ func TestHandler_CreateUnreferencedMergeCommit_Success(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["commitId"]) assert.NotEmpty(t, resp["treeId"]) } @@ -631,7 +632,7 @@ func TestHandler_GetMergeCommit(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["mergedCommitId"]) assert.NotEmpty( t, @@ -672,7 +673,7 @@ func TestGetMergeCommit_ResolvesParents(t *testing.T) { require.Equal(t, http.StatusOK, baseRec.Code) var baseOut map[string]any - require.NoError(t, json.Unmarshal(baseRec.Body.Bytes(), &baseOut)) + require.NoError(t, json.Unmarshal(baseRec.Body.Bytes(), &baseOut, jsonx.V1())) baseCommitID := baseOut["commitId"].(string) // Create feature branch from base. @@ -691,7 +692,7 @@ func TestGetMergeCommit_ResolvesParents(t *testing.T) { require.Equal(t, http.StatusOK, featureRec.Code) var featureOut map[string]any - require.NoError(t, json.Unmarshal(featureRec.Body.Bytes(), &featureOut)) + require.NoError(t, json.Unmarshal(featureRec.Body.Bytes(), &featureOut, jsonx.V1())) featureCommitID := featureOut["commitId"].(string) // Fast-forward merge. @@ -712,7 +713,7 @@ func TestGetMergeCommit_ResolvesParents(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["mergedCommitId"], "GetMergeCommit must return a non-empty mergedCommitId") } @@ -733,7 +734,7 @@ func TestHandler_GetMergeConflicts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // No content-diff engine backs this emulator, so a merge between two // resolvable specifiers is always reported mergeable (see PARITY.md gaps). assert.Equal(t, true, resp["mergeable"]) @@ -757,7 +758,7 @@ func TestHandler_GetMergeConflicts_NoConflicts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["mergeable"]) } @@ -855,7 +856,7 @@ func TestHandler_DescribeMergeConflicts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEqual(t, "feature", resp["sourceCommitId"], "specifiers resolve to commit IDs") assert.NotEmpty(t, resp["sourceCommitId"]) assert.NotEmpty(t, resp["destinationCommitId"]) diff --git a/services/codecommit/handler_pull_request_approvals_test.go b/services/codecommit/handler_pull_request_approvals_test.go index f93034a7bc..1d047eb431 100644 --- a/services/codecommit/handler_pull_request_approvals_test.go +++ b/services/codecommit/handler_pull_request_approvals_test.go @@ -1,13 +1,15 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetPullRequestApprovalStates(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_GetPullRequestApprovalStates(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["approvals"]) } @@ -109,7 +111,7 @@ func TestHandler_GetPullRequestApprovalStates_TableDriven(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) approvals := resp["approvals"].([]any) // Each UpdatePullRequestApprovalState with the same (empty) userARN just overwrites, so max 1. if tt.approveUsers > 0 { @@ -148,7 +150,7 @@ func TestHandler_GetPullRequestOverrideState(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, false, resp["overridden"]) } @@ -166,7 +168,7 @@ func TestHandler_GetPullRequestOverrideState_AfterOverride(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, false, resp["overridden"]) // Override it. @@ -182,7 +184,7 @@ func TestHandler_GetPullRequestOverrideState_AfterOverride(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["overridden"]) } @@ -214,7 +216,7 @@ func TestHandler_CreatePullRequestApprovalRule(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["approvalRule"].(map[string]any) assert.Equal(t, "my-rule", rule["approvalRuleName"]) } @@ -270,7 +272,7 @@ func TestHandler_DescribePullRequestEvents(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["pullRequestEvents"]) } @@ -343,7 +345,7 @@ func TestHandler_DescribePullRequestEvents_TableDriven(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events := resp["pullRequestEvents"].([]any) assert.Len(t, events, tt.wantEvents) }) @@ -369,7 +371,7 @@ func TestHandler_EvaluatePullRequestApprovalRules(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eval := resp["evaluation"].(map[string]any) satisfied := eval["approvalRulesSatisfied"].([]any) require.Len(t, satisfied, 1) @@ -404,7 +406,7 @@ func TestHandler_EvaluatePullRequestApprovalRules_WithRules(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eval := resp["evaluation"].(map[string]any) satisfied := eval["approvalRulesSatisfied"].([]any) assert.Len(t, satisfied, 2) @@ -425,7 +427,7 @@ func TestHandler_PullRequestApprovalRule_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["approvalRule"].(map[string]any) assert.Equal(t, "my-rule", rule["approvalRuleName"]) assert.NotEmpty(t, rule["approvalRuleId"]) @@ -445,7 +447,7 @@ func TestHandler_PullRequestApprovalRule_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eval := resp["evaluation"].(map[string]any) assert.Len(t, eval["approvalRulesSatisfied"].([]any), 1) @@ -463,7 +465,7 @@ func TestHandler_PullRequestApprovalRule_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eval = resp["evaluation"].(map[string]any) assert.Empty(t, eval["approvalRulesSatisfied"]) } diff --git a/services/codecommit/handler_pull_requests.go b/services/codecommit/handler_pull_requests.go index 8ea8e04f99..456b06b35c 100644 --- a/services/codecommit/handler_pull_requests.go +++ b/services/codecommit/handler_pull_requests.go @@ -2,11 +2,12 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -96,7 +97,7 @@ func pullRequestToMap(pr *PullRequest) map[string]any { func (h *Handler) handleCreatePullRequest(body []byte) (any, error) { var in createPullRequestInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -156,7 +157,7 @@ func (h *Handler) handleGetPullRequest(body []byte) (any, error) { var in struct { PullRequestID string `json:"pullRequestId"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -186,7 +187,7 @@ func (h *Handler) handleListPullRequests(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -226,7 +227,7 @@ func (h *Handler) handleGetPullRequestApprovalStates(body []byte) (any, error) { PullRequestID string `json:"pullRequestId"` RevisionID string `json:"revisionId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -252,7 +253,7 @@ func (h *Handler) handleGetPullRequestOverrideState(body []byte) (any, error) { PullRequestID string `json:"pullRequestId"` RevisionID string `json:"revisionId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -287,7 +288,7 @@ func (h *Handler) handleOverridePullRequestApprovalRules(ctx context.Context, bo RevisionID string `json:"revisionId"` OverrideStatus string `json:"overrideStatus"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -309,7 +310,7 @@ func (h *Handler) handleUpdatePullRequestApprovalState(body []byte) (any, error) RevisionID string `json:"revisionId"` ApprovalState string `json:"approvalState"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -328,7 +329,7 @@ func (h *Handler) handleUpdatePullRequestDescription(body []byte) (any, error) { PullRequestID string `json:"pullRequestId"` Description string `json:"description"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -354,7 +355,7 @@ func (h *Handler) handleUpdatePullRequestStatus(body []byte) (any, error) { PullRequestID string `json:"pullRequestId"` PullRequestStatus string `json:"pullRequestStatus"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -383,7 +384,7 @@ func (h *Handler) handleUpdatePullRequestTitle(body []byte) (any, error) { PullRequestID string `json:"pullRequestId"` Title string `json:"title"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -410,7 +411,7 @@ func (h *Handler) handleCreatePullRequestApprovalRule(body []byte) (any, error) ApprovalRuleName string `json:"approvalRuleName"` ApprovalRuleContent string `json:"approvalRuleContent"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" || req.ApprovalRuleName == "" { @@ -441,7 +442,7 @@ func (h *Handler) handleDeletePullRequestApprovalRule(body []byte) (any, error) PullRequestID string `json:"pullRequestId"` ApprovalRuleName string `json:"approvalRuleName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" || req.ApprovalRuleName == "" { @@ -468,7 +469,7 @@ func (h *Handler) handleUpdatePullRequestApprovalRuleContent(body []byte) (any, NewRuleContent string `json:"newRuleContent"` ExistingSha string `json:"existingRuleContentSha256"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" || req.ApprovalRuleName == "" { @@ -500,7 +501,7 @@ func (h *Handler) handleDescribePullRequestEvents(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { @@ -548,7 +549,7 @@ func (h *Handler) handleEvaluatePullRequestApprovalRules(body []byte) (any, erro PullRequestID string `json:"pullRequestId"` RevisionID string `json:"revisionId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.PullRequestID == "" { diff --git a/services/codecommit/handler_pull_requests_test.go b/services/codecommit/handler_pull_requests_test.go index ba106176d6..d286eb645b 100644 --- a/services/codecommit/handler_pull_requests_test.go +++ b/services/codecommit/handler_pull_requests_test.go @@ -1,13 +1,15 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreatePullRequest(t *testing.T) { @@ -82,7 +84,7 @@ func TestHandler_CreatePullRequest(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr, ok := resp["pullRequest"].(map[string]any) require.True(t, ok, "pullRequest key should be present") @@ -154,7 +156,7 @@ func TestHandler_CreatePullRequest_RepoMetadataInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.NotEmpty(t, pr["pullRequestId"]) @@ -226,7 +228,7 @@ func TestHandler_ListPullRequests_StatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["pullRequest"].(map[string]any)["pullRequestId"].(string) } @@ -249,7 +251,7 @@ func TestHandler_ListPullRequests_StatusFilter(t *testing.T) { if tt.wantHTTPCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids := resp["pullRequestIds"].([]any) assert.Len(t, ids, tt.wantCount) } @@ -276,7 +278,7 @@ func TestHandler_ListPullRequests_ClosedFilterIncludesMerged(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var mergeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mergeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mergeResp, jsonx.V1())) mergedPR := mergeResp["pullRequest"].(map[string]any) assert.Equal( t, "CLOSED", mergedPR["pullRequestStatus"], @@ -290,7 +292,7 @@ func TestHandler_ListPullRequests_ClosedFilterIncludesMerged(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) ids := listResp["pullRequestIds"].([]any) require.Len(t, ids, 1) assert.Equal(t, prID, ids[0]) @@ -317,7 +319,7 @@ func TestHandler_ListPullRequests_NumericDescendingOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids := resp["pullRequestIds"].([]any) require.Len(t, ids, 3) @@ -361,7 +363,7 @@ func TestHandler_MergePullRequest_AlreadyMerged(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prID := resp["pullRequest"].(map[string]any)["pullRequestId"].(string) // First merge — should succeed. @@ -373,7 +375,7 @@ func TestHandler_MergePullRequest_AlreadyMerged(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "PullRequestAlreadyClosedException", errResp["__type"]) }) } @@ -405,13 +407,13 @@ func TestHandler_MergePullRequest_StatusBecomesClosed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prID := resp["pullRequest"].(map[string]any)["pullRequestId"].(string) rec = doRequest(t, h, strategy, map[string]any{"pullRequestId": prID}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mergedPR := resp["pullRequest"].(map[string]any) assert.Equal(t, "CLOSED", mergedPR["pullRequestStatus"]) }) @@ -431,7 +433,7 @@ func TestHandler_UpdatePullRequestStatus(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "CLOSED", pr["pullRequestStatus"]) } @@ -488,7 +490,7 @@ func TestHandler_UpdatePullRequestStatus_Validation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prID := resp["pullRequest"].(map[string]any)["pullRequestId"].(string) rec = doRequest(t, h, "UpdatePullRequestStatus", map[string]any{ @@ -552,7 +554,7 @@ func TestHandler_UpdatePullRequestStatus_TableDriven(t *testing.T) { if tt.wantErrType != "" { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp["__type"]) } }) @@ -581,7 +583,7 @@ func TestHandler_PullRequest_FieldsPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr, ok := resp["pullRequest"].(map[string]any) require.True(t, ok) @@ -619,7 +621,7 @@ func TestHandler_UpdatePullRequestDescription(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "new description", pr["description"]) } @@ -637,7 +639,7 @@ func TestHandler_UpdatePullRequestDescription_Reflected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "A much better description", pr["description"]) } @@ -655,7 +657,7 @@ func TestHandler_UpdatePullRequestTitle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "updated title", pr["title"]) } @@ -673,7 +675,7 @@ func TestHandler_UpdatePullRequestTitle_Reflected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) assert.Equal(t, "Updated Title", pr["title"]) assert.Equal(t, prID, pr["pullRequestId"]) diff --git a/services/codecommit/handler_reactions.go b/services/codecommit/handler_reactions.go index a55c68a7e5..97e15f499a 100644 --- a/services/codecommit/handler_reactions.go +++ b/services/codecommit/handler_reactions.go @@ -2,11 +2,12 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -22,7 +23,7 @@ func (h *Handler) handleGetCommentReactions(body []byte) (any, error) { NextToken string `json:"nextToken"` MaxResults int `json:"maxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.CommentID == "" { @@ -97,7 +98,7 @@ func (h *Handler) handlePutCommentReaction(ctx context.Context, body []byte) (an CommentID string `json:"commentId"` ReactionValue string `json:"reactionValue"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.CommentID == "" || req.ReactionValue == "" { diff --git a/services/codecommit/handler_reactions_test.go b/services/codecommit/handler_reactions_test.go index d76ff6c898..ece4dfdd1d 100644 --- a/services/codecommit/handler_reactions_test.go +++ b/services/codecommit/handler_reactions_test.go @@ -1,12 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetCommentReactions(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_GetCommentReactions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) cID := cr["comment"].(map[string]any)["commentId"].(string) doRequest(t, h, "PutCommentReaction", map[string]any{ @@ -35,7 +37,7 @@ func TestHandler_GetCommentReactions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) reactions := resp["reactionsForComment"].([]any) assert.Len(t, reactions, 1) } @@ -54,7 +56,7 @@ func TestHandler_PutCommentReaction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) cID := cr["comment"].(map[string]any)["commentId"].(string) rec = doRequest(t, h, "PutCommentReaction", map[string]any{ diff --git a/services/codecommit/handler_repositories.go b/services/codecommit/handler_repositories.go index 7ec8f411f1..d2c5ec6aef 100644 --- a/services/codecommit/handler_repositories.go +++ b/services/codecommit/handler_repositories.go @@ -1,10 +1,12 @@ package codecommit import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createRepositoryInput struct { @@ -52,7 +54,7 @@ func repoMetadata(r *Repository) map[string]any { func (h *Handler) handleCreateRepository(body []byte) (any, error) { var in createRepositoryInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -72,7 +74,7 @@ func (h *Handler) handleCreateRepository(body []byte) (any, error) { func (h *Handler) handleGetRepository(body []byte) (any, error) { var in getRepositoryInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -88,7 +90,7 @@ func (h *Handler) handleGetRepository(body []byte) (any, error) { func (h *Handler) handleDeleteRepository(body []byte) (any, error) { var in deleteRepositoryInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -113,7 +115,7 @@ func (h *Handler) handleListRepositories(body []byte) (any, error) { MaxResults int `json:"maxResults"` } // Ignore parse errors — all fields are optional. - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) if in.SortBy != "" && in.SortBy != "repositoryName" && in.SortBy != "lastModifiedDate" { return nil, fmt.Errorf("%w: sortBy %q is not valid", ErrInvalidSortBy, in.SortBy) @@ -169,7 +171,7 @@ func (h *Handler) handleListRepositories(body []byte) (any, error) { func (h *Handler) handleBatchGetRepositories(body []byte) (any, error) { var in batchGetRepositoriesInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -198,7 +200,7 @@ func (h *Handler) handleUpdateRepositoryDescription(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` RepositoryDescription string `json:"repositoryDescription"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -213,7 +215,7 @@ func (h *Handler) handleUpdateRepositoryName(body []byte) (any, error) { OldName string `json:"oldName"` NewName string `json:"newName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.OldName == "" || req.NewName == "" { @@ -228,7 +230,7 @@ func (h *Handler) handleUpdateRepositoryEncryptionKey(body []byte) (any, error) RepositoryName string `json:"repositoryName"` KmsKeyID string `json:"kmsKeyId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_repositories_test.go b/services/codecommit/handler_repositories_test.go index 9a69db037a..9977cb07bf 100644 --- a/services/codecommit/handler_repositories_test.go +++ b/services/codecommit/handler_repositories_test.go @@ -1,13 +1,15 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateRepository(t *testing.T) { @@ -48,7 +50,7 @@ func TestHandler_CreateRepository(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta, ok := resp["repositoryMetadata"].(map[string]any) require.True(t, ok, "repositoryMetadata should be present") @@ -182,7 +184,7 @@ func TestHandler_ListRepositories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, ok := resp["repositories"].([]any) require.True(t, ok) @@ -221,7 +223,7 @@ func TestHandler_SortedListRepositories(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["repositories"].([]any) require.True(t, ok) @@ -287,7 +289,7 @@ func TestHandler_DeleteRepository_Cascade_Comments(t *testing.T) { }) require.Equal(t, http.StatusOK, commitRec.Code) var commitOut map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitOut, jsonx.V1())) commitID := commitOut["commitId"].(string) commentRec := doRequest(t, h, "PostCommentForComparedCommit", map[string]any{ @@ -297,7 +299,7 @@ func TestHandler_DeleteRepository_Cascade_Comments(t *testing.T) { }) require.Equal(t, http.StatusOK, commentRec.Code) var commentOut map[string]any - require.NoError(t, json.Unmarshal(commentRec.Body.Bytes(), &commentOut)) + require.NoError(t, json.Unmarshal(commentRec.Body.Bytes(), &commentOut, jsonx.V1())) comment, ok := commentOut["comment"].(map[string]any) require.True(t, ok) commentID, _ := comment["commentId"].(string) @@ -346,7 +348,7 @@ func TestHandler_DeleteRepository_Cascade_FileHistory(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dag, ok := resp["revisionDag"].([]any) require.True(t, ok) assert.Empty(t, dag, @@ -365,7 +367,7 @@ func TestHandler_RepoMetadataTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta, ok := resp["repositoryMetadata"].(map[string]any) require.True(t, ok) @@ -446,7 +448,7 @@ func TestHandler_RepoMetadata_DefaultBranchAndKmsKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta := resp["repositoryMetadata"].(map[string]any) _, hasDefault := meta["defaultBranch"] _, hasKms := meta["kmsKeyId"] @@ -479,7 +481,7 @@ func TestHandler_RepoMetadata_DefaultBranchAndKmsKey(t *testing.T) { rec = doRequest(t, h, "GetRepository", map[string]any{"repositoryName": "repo"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta = resp["repositoryMetadata"].(map[string]any) assert.Equal(t, "main", meta["defaultBranch"]) assert.Equal(t, "arn:aws:kms:us-east-1:123456789012:key/my-key", meta["kmsKeyId"]) @@ -539,7 +541,7 @@ func TestHandler_BatchGetRepositories(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, _ := resp["repositories"].([]any) assert.Len(t, repos, tt.wantFoundCount) @@ -569,7 +571,7 @@ func TestHandler_BatchGetRepositories_MaxLimit(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "MaximumRepositoryNamesExceededException", errResp["__type"]) // Request 25 — should succeed. @@ -591,7 +593,7 @@ func TestHandler_BatchGetRepositories_PartialFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos := resp["repositories"].([]any) notFound := resp["repositoriesNotFound"].([]any) @@ -615,7 +617,7 @@ func TestHandler_GetRepository_FullMetadata(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta := resp["repositoryMetadata"].(map[string]any) assert.NotEmpty(t, meta["repositoryId"]) @@ -644,7 +646,7 @@ func TestHandler_UpdateRepositoryDescription_Reflected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta := resp["repositoryMetadata"].(map[string]any) assert.Equal(t, "new description", meta["repositoryDescription"]) } @@ -657,7 +659,7 @@ func TestHandler_CreateRepository_InvalidName_ErrorType(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidRepositoryNameException", errResp["__type"]) } diff --git a/services/codecommit/handler_tags.go b/services/codecommit/handler_tags.go index 67cd8da3ab..b365ac1fbc 100644 --- a/services/codecommit/handler_tags.go +++ b/services/codecommit/handler_tags.go @@ -1,8 +1,10 @@ package codecommit import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceInput struct { @@ -21,7 +23,7 @@ type listTagsForResourceInput struct { func (h *Handler) handleTagResource(body []byte) (any, error) { var in tagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -38,7 +40,7 @@ func (h *Handler) handleTagResource(body []byte) (any, error) { func (h *Handler) handleUntagResource(body []byte) (any, error) { var in untagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } @@ -55,7 +57,7 @@ func (h *Handler) handleUntagResource(body []byte) (any, error) { func (h *Handler) handleListTagsForResource(body []byte) (any, error) { var in listTagsForResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request body: %w", err) } diff --git a/services/codecommit/handler_tags_test.go b/services/codecommit/handler_tags_test.go index f6e6e7c6bb..750b9b5c6c 100644 --- a/services/codecommit/handler_tags_test.go +++ b/services/codecommit/handler_tags_test.go @@ -1,13 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -31,7 +32,7 @@ func TestHandler_TagAndUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) metaRaw, ok := resp["repositoryMetadata"] require.True(t, ok) @@ -61,7 +62,7 @@ func TestHandler_TagAndUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) metaRaw, ok := resp["repositoryMetadata"] require.True(t, ok) @@ -127,7 +128,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) metaRaw, ok := resp["repositoryMetadata"] require.True(t, ok) @@ -169,7 +170,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagsRaw, ok := resp["tags"].(map[string]any) require.True(t, ok) @@ -192,7 +193,7 @@ func TestHandler_ARN_TagOps_O1(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) meta := createResp["repositoryMetadata"].(map[string]any) repoARN := meta["Arn"].(string) @@ -212,7 +213,7 @@ func TestHandler_ARN_TagOps_O1(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tagMap, ok := tagsResp["tags"].(map[string]any) require.True(t, ok) @@ -231,7 +232,7 @@ func TestHandler_ARN_TagOps_O1(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tagMap, _ = tagsResp["tags"].(map[string]any) assert.Empty(t, tagMap) } diff --git a/services/codecommit/handler_test.go b/services/codecommit/handler_test.go index 4a121736db..c50b9d72aa 100644 --- a/services/codecommit/handler_test.go +++ b/services/codecommit/handler_test.go @@ -2,7 +2,7 @@ package codecommit_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *codecommit.Handler, action string, body any) *ht if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -74,7 +75,7 @@ func createBranchFromMain(t *testing.T, h *codecommit.Handler, repoName, branchN require.Equal(t, http.StatusOK, rec.Code) var branchResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &branchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &branchResp, jsonx.V1())) branchInfo, ok := branchResp["branch"].(map[string]any) require.True(t, ok, "GetBranch response must include a branch object") tipCommitID, _ := branchInfo["commitId"].(string) @@ -111,7 +112,7 @@ func setupPR(t *testing.T, h *codecommit.Handler, repoName string) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pr := resp["pullRequest"].(map[string]any) @@ -578,7 +579,7 @@ func TestHandler_ErrorTypes(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -643,7 +644,7 @@ func TestHandler_NotFoundErrorTypes_AreResourceSpecific(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) }) } diff --git a/services/codecommit/handler_triggers.go b/services/codecommit/handler_triggers.go index 4f9a2ebbc1..ae54ab1d90 100644 --- a/services/codecommit/handler_triggers.go +++ b/services/codecommit/handler_triggers.go @@ -1,15 +1,17 @@ package codecommit import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetRepositoryTriggers(body []byte) (any, error) { var req struct { RepositoryName string `json:"repositoryName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -32,7 +34,7 @@ func (h *Handler) handlePutRepositoryTriggers(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` Triggers []RepositoryTrigger `json:"triggers"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { @@ -53,7 +55,7 @@ func (h *Handler) handleTestRepositoryTriggers(body []byte) (any, error) { RepositoryName string `json:"repositoryName"` Triggers []RepositoryTrigger `json:"triggers"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } if req.RepositoryName == "" { diff --git a/services/codecommit/handler_triggers_test.go b/services/codecommit/handler_triggers_test.go index 9ac61811bf..6b013cc8f6 100644 --- a/services/codecommit/handler_triggers_test.go +++ b/services/codecommit/handler_triggers_test.go @@ -1,12 +1,14 @@ package codecommit_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutGetRepositoryTriggers(t *testing.T) { @@ -33,7 +35,7 @@ func TestHandler_PutGetRepositoryTriggers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) triggers := resp["triggers"].([]any) assert.Len(t, triggers, 1) } @@ -76,7 +78,7 @@ func TestHandler_TestRepositoryTriggers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) succeeded := resp["successfulExecutions"].([]any) require.Len(t, succeeded, 1) assert.Equal(t, "ad-hoc-trigger", succeeded[0]) @@ -89,7 +91,7 @@ func TestHandler_TestRepositoryTriggers(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) succeeded = resp["successfulExecutions"].([]any) assert.Empty(t, succeeded) } @@ -123,7 +125,7 @@ func TestHandler_TriggerLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) got := resp["triggers"].([]any) assert.Len(t, got, 2) @@ -134,7 +136,7 @@ func TestHandler_TriggerLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) succeeded := resp["successfulExecutions"].([]any) assert.Len(t, succeeded, 2) @@ -148,7 +150,7 @@ func TestHandler_TriggerLifecycle(t *testing.T) { rec = doRequest(t, h, "GetRepositoryTriggers", map[string]any{"repositoryName": "repo"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) got = resp["triggers"].([]any) assert.Empty(t, got) } diff --git a/services/codecommit/multi_region_test.go b/services/codecommit/multi_region_test.go index d6fc3fc89c..60b7e22645 100644 --- a/services/codecommit/multi_region_test.go +++ b/services/codecommit/multi_region_test.go @@ -2,7 +2,7 @@ package codecommit_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codecommit" ) @@ -40,7 +41,7 @@ func regionCall(t *testing.T, h *codecommit.Handler, region, op, body string) ma require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/codecommit/persistence.go b/services/codecommit/persistence.go index a30c3ec124..4c3b82a9d8 100644 --- a/services/codecommit/persistence.go +++ b/services/codecommit/persistence.go @@ -2,7 +2,7 @@ package codecommit import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -196,7 +196,7 @@ func buildPersistenceDTORegistry() ( // Repositories, and is rebuilt by rebuildRepositoriesByARN on Restore rather // than persisted. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` RepoTemplateAssoc map[string]map[string]struct{} `json:"repoTemplateAssoc"` PRApprovals map[string]map[string]string `json:"prApprovals"` PROverrides map[string]bool `json:"prOverrides"` @@ -337,7 +337,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // prApprovalRules; see store_setup.go's registerAllTables doc) from tables // via the ephemeral DTO registry, factored out of Restore to keep Restore's // own cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtoReg, commentDTOs, fileDTOs, prApprovalRuleDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("codecommit: restore snapshot DTO tables: %w", err) diff --git a/services/codeconnections/handler.go b/services/codeconnections/handler.go index f43f296419..def97581ec 100644 --- a/services/codeconnections/handler.go +++ b/services/codeconnections/handler.go @@ -2,7 +2,7 @@ package codeconnections import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -180,7 +181,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ResourceName string `json:"ResourceName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ConnectionArn != "": @@ -228,7 +229,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleEchoError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -237,7 +238,7 @@ func (h *Handler) handleEchoError(_ context.Context, c *echo.Context, _ string, payload, marshalErr := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: wireMessage(err, errType), - }) + }, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } diff --git a/services/codeconnections/handler_test.go b/services/codeconnections/handler_test.go index 4a04a2608c..7372e4c5a4 100644 --- a/services/codeconnections/handler_test.go +++ b/services/codeconnections/handler_test.go @@ -10,7 +10,7 @@ package codeconnections_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codeconnections" ) @@ -69,7 +70,7 @@ func doJSON( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -90,7 +91,7 @@ func parseResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -401,7 +402,7 @@ func TestExtractOperationAndResource(t *testing.T) { if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/codeconnections/persistence.go b/services/codeconnections/persistence.go index ae5fa540dd..ed5ea92dfb 100644 --- a/services/codeconnections/persistence.go +++ b/services/codeconnections/persistence.go @@ -2,7 +2,7 @@ package codeconnections import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -51,10 +51,10 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // persistenceDTOTables groups the ephemeral DTO tables Snapshot/Restore @@ -187,7 +187,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // syncConfigurations, syncBlockers; see store_setup.go's registerAllTables // doc) from tables via the ephemeral DTO registry, factored out of Restore to // keep Restore's own cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("codeconnections: restore snapshot DTO tables: %w", err) diff --git a/services/codedeploy/application_revisions_test.go b/services/codedeploy/application_revisions_test.go index e1de1c7bf0..893663e256 100644 --- a/services/codedeploy/application_revisions_test.go +++ b/services/codedeploy/application_revisions_test.go @@ -1,13 +1,14 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -42,7 +43,7 @@ func TestHandler_RegisterApplicationRevision_GetApplicationRevision(t *testing.T require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-app", resp["applicationName"]) revisionInfo, ok := resp["revisionInfo"].(map[string]any) @@ -67,7 +68,7 @@ func TestHandler_GetApplicationRevision_NotRegistered(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "RevisionDoesNotExistException", resp["__type"]) } @@ -106,7 +107,7 @@ func TestHandler_RegisterApplicationRevision_Idempotent(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) revisions, ok := listResp["revisions"].([]any) require.True(t, ok) require.Len(t, revisions, 1) @@ -116,7 +117,7 @@ func TestHandler_RegisterApplicationRevision_Idempotent(t *testing.T) { "revision": s3Revision("bucket", "key"), }) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) revisionInfo, ok := getResp["revisionInfo"].(map[string]any) require.True(t, ok) assert.Equal(t, "v2", revisionInfo["description"]) @@ -141,7 +142,7 @@ func TestHandler_ListApplicationRevisions_MultipleAndFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) revisions, ok := resp["revisions"].([]any) require.True(t, ok) require.Len(t, revisions, 2) @@ -153,7 +154,7 @@ func TestHandler_ListApplicationRevisions_MultipleAndFilters(t *testing.T) { require.Equal(t, http.StatusOK, filteredRec.Code) var filteredResp map[string]any - require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp)) + require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp, jsonx.V1())) filtered, ok := filteredResp["revisions"].([]any) require.True(t, ok) require.Len(t, filtered, 1) @@ -298,7 +299,7 @@ func TestHandler_BatchGetApplicationRevisions(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-app", resp["applicationName"]) } }) @@ -330,7 +331,7 @@ func TestHandler_BatchGetApplicationRevisions_PopulatesRegistered(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) revisions, ok := resp["revisions"].([]any) require.True(t, ok) require.Len(t, revisions, 2) diff --git a/services/codedeploy/applications_test.go b/services/codedeploy/applications_test.go index f009d3fc19..0f7df24d61 100644 --- a/services/codedeploy/applications_test.go +++ b/services/codedeploy/applications_test.go @@ -1,7 +1,7 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -71,7 +72,7 @@ func TestHandler_CreateApplication(t *testing.T) { if tt.wantAppID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["applicationId"]) } }) @@ -118,7 +119,7 @@ func TestHandler_CreateApplication_ComputePlatformValidation(t *testing.T) { if tt.wantErrType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) } }) @@ -175,7 +176,7 @@ func TestHandler_ListApplications(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps, ok := resp["applications"].([]any) require.True(t, ok) assert.Empty(t, apps) @@ -184,7 +185,7 @@ func TestHandler_ListApplications(t *testing.T) { doRequest(t, h, "CreateApplication", map[string]any{"applicationName": "app2"}) rec = doRequest(t, h, "ListApplications", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps, ok = resp["applications"].([]any) require.True(t, ok) assert.Len(t, apps, 2) @@ -206,7 +207,7 @@ func TestApplications_SortedList(t *testing.T) { Applications []string `json:"applications"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, []string{"apple", "mango", "zebra"}, resp.Applications) } @@ -329,7 +330,7 @@ func TestHandler_BatchGetApplications(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) infos, ok := resp["applicationsInfo"].([]any) require.True(t, ok) assert.Len(t, infos, tt.wantCount) diff --git a/services/codedeploy/deployment_configs_test.go b/services/codedeploy/deployment_configs_test.go index 2eca2a61c7..49ade1a09a 100644 --- a/services/codedeploy/deployment_configs_test.go +++ b/services/codedeploy/deployment_configs_test.go @@ -1,12 +1,14 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateDeploymentConfig(t *testing.T) { @@ -57,7 +59,7 @@ func TestHandler_CreateDeploymentConfig(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["deploymentConfigId"]) } }) @@ -240,7 +242,7 @@ func TestDeploymentConfigs_SubStructs(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tt.checkFn(t, resp["deploymentConfigInfo"]) } }) @@ -270,7 +272,7 @@ func TestDeploymentConfigs_DefaultsSeeded(t *testing.T) { var resp struct { DeploymentConfigsList []string `json:"deploymentConfigsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, name := range expectedDefaults { assert.Contains(t, resp.DeploymentConfigsList, name, "missing default config: %s", name) @@ -324,7 +326,7 @@ func TestDeploymentConfigs_DefaultsReturnSubStructs(t *testing.T) { ComputePlatform string `json:"computePlatform"` } `json:"deploymentConfigInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantPlatform, resp.DeploymentConfigInfo.ComputePlatform) require.NotNil(t, resp.DeploymentConfigInfo.TrafficRoutingConfig) assert.Equal(t, tt.wantRoutingType, resp.DeploymentConfigInfo.TrafficRoutingConfig.Type) @@ -342,7 +344,7 @@ func TestDeploymentConfigs_DefaultsCannotDelete(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // DeleteDeploymentConfig's own deserializer models InvalidOperationException // for this case, not DeploymentConfigInUseException (verified against // aws-sdk-go-v2/service/codedeploy deserializers.go). @@ -382,7 +384,7 @@ func TestDeploymentConfigs_InUseCannotDelete(t *testing.T) { assert.Equal(t, http.StatusConflict, delRec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DeploymentConfigInUseException", resp["__type"]) } @@ -400,7 +402,7 @@ func TestDeploymentConfigs_ErrInvalidComputePlatformMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidComputePlatformException", resp["__type"]) } diff --git a/services/codedeploy/deployment_group_tag_filters_test.go b/services/codedeploy/deployment_group_tag_filters_test.go index c9ce92ccf2..7c7783d46a 100644 --- a/services/codedeploy/deployment_group_tag_filters_test.go +++ b/services/codedeploy/deployment_group_tag_filters_test.go @@ -1,13 +1,15 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func ec2TagSetPayload() map[string]any { @@ -124,7 +126,7 @@ func TestDeploymentGroups_TagFilterValidation_Create(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -219,7 +221,7 @@ func TestDeploymentGroups_TagFilterValidation_Update(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) diff --git a/services/codedeploy/deployment_groups_test.go b/services/codedeploy/deployment_groups_test.go index e75db1b623..d45aa51e3b 100644 --- a/services/codedeploy/deployment_groups_test.go +++ b/services/codedeploy/deployment_groups_test.go @@ -1,7 +1,7 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -68,7 +69,7 @@ func TestHandler_CreateDeploymentGroup(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["deploymentGroupId"]) } }) @@ -169,7 +170,7 @@ func TestDeploymentGroups_RichFieldsRoundTrip(t *testing.T) { TerminationHookEnabled bool `json:"terminationHookEnabled"` } `json:"deploymentGroupInfo"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) info := resp.DeploymentGroupInfo assert.Equal(t, "arn:aws:iam::123:role/role", info.ServiceRoleArn) @@ -310,7 +311,7 @@ func TestDeploymentGroups_SortedList(t *testing.T) { DeploymentGroups []string `json:"deploymentGroups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, []string{"a-group", "m-group", "z-group"}, resp.DeploymentGroups) } @@ -483,7 +484,7 @@ func TestDeploymentGroups_Update(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantHooksClean { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hooksNotCleanedUp, _ := resp["hooksNotCleanedUp"].(bool) assert.True(t, hooksNotCleanedUp, "expected hooksNotCleanedUp=true when alarms were removed") } @@ -551,7 +552,7 @@ func TestDeploymentGroups_InheritsComputePlatform(t *testing.T) { ComputePlatform string `json:"computePlatform"` } `json:"deploymentGroupInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.platform, resp.DeploymentGroupInfo.ComputePlatform) }) } @@ -598,7 +599,7 @@ func TestDeploymentGroups_ComputePlatformInheritedViaHandler(t *testing.T) { ComputePlatform string `json:"computePlatform"` } `json:"deploymentGroupInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.platform, out.DeploymentGroupInfo.ComputePlatform, "deployment group must inherit computePlatform from application") }) @@ -655,7 +656,7 @@ func TestHandler_BatchGetDeploymentGroups(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) infos, ok := resp["deploymentGroupsInfo"].([]any) require.True(t, ok) assert.Len(t, infos, tt.wantCount) @@ -681,7 +682,7 @@ func TestDeploymentGroups_BatchGetEmptySlice(t *testing.T) { DeploymentGroupsInfo []any `json:"deploymentGroupsInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Should be an empty array not null assert.NotNil(t, resp.DeploymentGroupsInfo) assert.Empty(t, resp.DeploymentGroupsInfo) @@ -717,7 +718,7 @@ func TestDeploymentGroups_BatchGetRichFields(t *testing.T) { } `json:"ecsServices"` } `json:"deploymentGroupsInfo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.DeploymentGroupsInfo, 1) info := resp.DeploymentGroupsInfo[0] diff --git a/services/codedeploy/deployment_instances_test.go b/services/codedeploy/deployment_instances_test.go index b933f43a46..788a9e566b 100644 --- a/services/codedeploy/deployment_instances_test.go +++ b/services/codedeploy/deployment_instances_test.go @@ -1,13 +1,14 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -65,7 +66,7 @@ func TestDeploymentInstances_BatchGetMissingDeployment(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DeploymentDoesNotExistException", resp["__type"]) } @@ -87,7 +88,7 @@ func TestHandler_BatchGetDeploymentInstances_RealTargets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["instancesSummary"].([]any) require.True(t, ok) // Only the two tag-filter-matched instances resolve as real targets; @@ -144,7 +145,7 @@ func TestHandler_BatchGetDeploymentTargets_ECS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["deploymentTargets"].([]any) require.True(t, ok) require.Len(t, targets, 1) @@ -207,7 +208,7 @@ func TestHandler_GetDeploymentTarget_Lambda(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) target, ok := resp["deploymentTarget"].(map[string]any) require.True(t, ok) assert.Equal(t, "LambdaTarget", target["deploymentTargetType"]) @@ -222,7 +223,7 @@ func TestHandler_GetDeploymentTarget_Lambda(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "DeploymentTargetDoesNotExistException", errResp["__type"]) } @@ -250,7 +251,7 @@ func TestHandler_GetDeploymentInstance_RealMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary, ok := resp["instanceSummary"].(map[string]any) require.True(t, ok) assert.Equal(t, matched[0], summary["instanceId"]) @@ -277,7 +278,7 @@ func TestHandler_ListDeploymentTargets_ListDeploymentInstances_RealTargets(t *te require.Equal(t, http.StatusOK, rec.Code) var targetsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &targetsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &targetsResp, jsonx.V1())) targetIDs, ok := targetsResp["targetIds"].([]any) require.True(t, ok) require.Len(t, targetIDs, 2) @@ -287,7 +288,7 @@ func TestHandler_ListDeploymentTargets_ListDeploymentInstances_RealTargets(t *te require.Equal(t, http.StatusOK, rec.Code) var instResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &instResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &instResp, jsonx.V1())) instances, ok := instResp["instancesList"].([]any) require.True(t, ok) assert.ElementsMatch(t, []any{matched[0], matched[1]}, instances) @@ -339,7 +340,7 @@ func TestHandler_ListDeploymentInstances_StatusAndTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, ok := resp["instancesList"].([]any) require.True(t, ok) assert.Len(t, instances, tt.wantLen) @@ -390,7 +391,7 @@ func TestHandler_ListDeploymentTargets_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targetIDs, ok := resp["targetIds"].([]any) require.True(t, ok) assert.Len(t, targetIDs, tt.wantLen) @@ -425,7 +426,7 @@ func TestHandler_ListDeploymentInstances_ECSIsEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, ok := resp["instancesList"].([]any) require.True(t, ok) assert.Empty(t, instances) @@ -450,7 +451,7 @@ func TestDeploymentTargets_StatusTracksDeploymentStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) target, ok := resp["deploymentTarget"].(map[string]any) require.True(t, ok) instanceTarget, ok := target["instanceTarget"].(map[string]any) diff --git a/services/codedeploy/deployments_test.go b/services/codedeploy/deployments_test.go index c97a1a6dec..2ebac12df6 100644 --- a/services/codedeploy/deployments_test.go +++ b/services/codedeploy/deployments_test.go @@ -1,7 +1,7 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -111,14 +112,14 @@ func TestHandler_CreateDeployment(t *testing.T) { if tt.wantID { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["deploymentId"]) assert.True(t, len(resp["deploymentId"]) > 2 && resp["deploymentId"][:2] == "d-") } if tt.wantErrType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) } }) @@ -153,7 +154,7 @@ func TestDeployments_CreateIDFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["deploymentId"] assert.True(t, deployIDRe.MatchString(id), "deploymentId %q does not match pattern d-[A-Z0-9]{9}", id) } @@ -184,7 +185,7 @@ func TestDeployments_CreateIDFormat_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Regexp(t, deployIDRe, out["deploymentId"], "deploymentId must match d-[A-Z0-9]{9}") }) @@ -209,7 +210,7 @@ func TestDeployments_CreateExtraFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec2 := doRequest(t, h, "GetDeployment", map[string]any{"deploymentId": createResp["deploymentId"]}) require.Equal(t, http.StatusOK, rec2.Code) @@ -222,7 +223,7 @@ func TestDeployments_CreateExtraFields(t *testing.T) { IgnoreApplicationStopFailures bool `json:"ignoreApplicationStopFailures"` } `json:"deploymentInfo"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-description", getResp.DeploymentInfo.Description) assert.Equal(t, "OVERWRITE", getResp.DeploymentInfo.FileExistsBehavior) @@ -253,7 +254,7 @@ func TestDeployments_S3RevisionRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) deployID := createOut["deploymentId"] require.NotEmpty(t, deployID) @@ -274,7 +275,7 @@ func TestDeployments_S3RevisionRoundTrip(t *testing.T) { } `json:"revision"` } `json:"deploymentInfo"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) rev := getOut.DeploymentInfo.Revision assert.Equal(t, "S3", rev.RevisionType, "revisionType must round-trip") @@ -305,7 +306,7 @@ func TestDeployments_GitHubRevisionRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) deployID := createOut["deploymentId"] getRec := doRequest(t, h, "GetDeployment", map[string]any{"deploymentId": deployID}) @@ -322,7 +323,7 @@ func TestDeployments_GitHubRevisionRoundTrip(t *testing.T) { } `json:"revision"` } `json:"deploymentInfo"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) rev := getOut.DeploymentInfo.Revision assert.Equal(t, "GitHub", rev.RevisionType) @@ -397,7 +398,7 @@ func TestDeployments_InfoIncludesConfigName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) info := resp["deploymentInfo"] assert.Equal(t, "CodeDeployDefault.AllAtOnce", info["deploymentConfigName"]) @@ -416,7 +417,7 @@ func TestDeployments_GetDeploymentOverview(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) getRec := doRequest(t, h, "GetDeployment", map[string]any{"deploymentId": createOut["deploymentId"]}) require.Equal(t, http.StatusOK, getRec.Code) @@ -426,7 +427,7 @@ func TestDeployments_GetDeploymentOverview(t *testing.T) { DeploymentOverview map[string]any `json:"deploymentOverview"` } `json:"deploymentInfo"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) ov := getOut.DeploymentInfo.DeploymentOverview assert.NotNil(t, ov, "GetDeployment must include deploymentOverview") @@ -455,7 +456,7 @@ func TestHandler_ListDeployments(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deployments, ok := resp["deployments"].([]any) require.True(t, ok) assert.Len(t, deployments, 1) @@ -483,7 +484,7 @@ func TestDeployments_SortedList(t *testing.T) { Deployments []string `json:"deployments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Deployments, 2) assert.Contains(t, resp.Deployments, d1.DeploymentID) @@ -535,7 +536,7 @@ func TestDeployments_ListStatusFilter(t *testing.T) { var resp struct { Deployments []string `json:"deployments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Deployments, tt.wantLen) }) } @@ -578,7 +579,7 @@ func TestDeployments_ListExternalIDFilter(t *testing.T) { var resp struct { Deployments []string `json:"deployments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Deployments, tt.wantLen) }) } @@ -714,7 +715,7 @@ func TestHandler_ContinueDeployment(t *testing.T) { if tt.wantErrType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) } }) @@ -738,7 +739,7 @@ func TestDeployments_StopDeployment_AlreadyStopped(t *testing.T) { assert.Equal(t, http.StatusConflict, secondStop.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(secondStop.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(secondStop.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "DeploymentAlreadyCompletedException", errResp["__type"]) } @@ -756,7 +757,7 @@ func TestDeployments_StopDeploymentStatus(t *testing.T) { var stopOut struct { Status string `json:"status"` } - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopOut)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopOut, jsonx.V1())) assert.Equal(t, "Succeeded", stopOut.Status, "StopDeploymentOutput.status is the StopStatus enum (Pending|Succeeded), not the deployment status") @@ -768,7 +769,7 @@ func TestDeployments_StopDeploymentStatus(t *testing.T) { Status string `json:"status"` } `json:"deploymentInfo"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "Stopped", getOut.DeploymentInfo.Status, "GetDeployment must reflect Stopped status after StopDeployment") } @@ -821,7 +822,7 @@ func TestHandler_BatchGetDeployments(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) infos, ok := resp["deploymentsInfo"].([]any) require.True(t, ok) assert.Len(t, infos, tt.wantCount) @@ -847,7 +848,7 @@ func TestDeployments_BatchGetReturnsRevision(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) batchRec := doRequest(t, h, "BatchGetDeployments", map[string]any{ "deploymentIds": []string{createOut["deploymentId"]}, @@ -864,7 +865,7 @@ func TestDeployments_BatchGetReturnsRevision(t *testing.T) { var batchOut struct { DeploymentsInfo []batchItem `json:"deploymentsInfo"` } - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchOut)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchOut, jsonx.V1())) require.Len(t, batchOut.DeploymentsInfo, 1) info := batchOut.DeploymentsInfo[0] @@ -887,7 +888,7 @@ func TestDeployments_SkipWaitTime_DeploymentNotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DeploymentDoesNotExistException", resp["__type"]) } @@ -907,7 +908,7 @@ func TestDeployments_SkipWaitTime_DeploymentFound(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) deployID := createOut["deploymentId"] skipRec := doRequest(t, h, "SkipWaitTimeForInstanceTermination", map[string]any{ diff --git a/services/codedeploy/github_tokens_test.go b/services/codedeploy/github_tokens_test.go index 6caf26e03f..7a3c379f5d 100644 --- a/services/codedeploy/github_tokens_test.go +++ b/services/codedeploy/github_tokens_test.go @@ -1,12 +1,14 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGitHubTokens_ListAddDelete(t *testing.T) { @@ -21,7 +23,7 @@ func TestGitHubTokens_ListAddDelete(t *testing.T) { var listResp struct { TokenNameList []string `json:"tokenNameList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.TokenNameList) // Add token via internal method and verify it appears. @@ -29,7 +31,7 @@ func TestGitHubTokens_ListAddDelete(t *testing.T) { rec = doRequest(t, h, "ListGitHubAccountTokenNames", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Equal(t, []string{"my-token"}, listResp.TokenNameList) // Delete the token. @@ -39,7 +41,7 @@ func TestGitHubTokens_ListAddDelete(t *testing.T) { // List should be empty again. rec = doRequest(t, h, "ListGitHubAccountTokenNames", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.TokenNameList) } @@ -51,7 +53,7 @@ func TestGitHubTokens_DeleteNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "GitHubAccountTokenDoesNotExistException", resp["__type"]) } diff --git a/services/codedeploy/handler.go b/services/codedeploy/handler.go index 63367ebb0f..728c0243fc 100644 --- a/services/codedeploy/handler.go +++ b/services/codedeploy/handler.go @@ -2,7 +2,7 @@ package codedeploy import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -176,7 +177,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var input struct { ApplicationName string `json:"applicationName"` } - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return "" } @@ -263,7 +264,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // errorMapping maps sentinel errors to HTTP status and exception type. @@ -341,7 +342,7 @@ func stripExceptionPrefixes(msg string) string { func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { makePayload := func(code, msg string) []byte { - b, _ := json.Marshal(service.JSONErrorResponse{Type: code, Message: stripExceptionPrefixes(msg)}) + b, _ := json.Marshal(service.JSONErrorResponse{Type: code, Message: stripExceptionPrefixes(msg)}, jsonx.V1()) return b } @@ -352,10 +353,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err } } - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - - if errors.As(err, &syntaxErr) || errors.As(err, &typeErr) { + if jsonx.IsSyntaxError(err) || jsonx.IsTypeError(err) { return c.JSONBlob(http.StatusBadRequest, makePayload("InvalidRequestException", err.Error())) } diff --git a/services/codedeploy/handler_deployment_groups.go b/services/codedeploy/handler_deployment_groups.go index b8a2b34870..569cf84310 100644 --- a/services/codedeploy/handler_deployment_groups.go +++ b/services/codedeploy/handler_deployment_groups.go @@ -2,10 +2,11 @@ package codedeploy import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -58,7 +59,7 @@ type autoScalingGroupEntry struct { // UnmarshalJSON accepts a bare name (SDK AutoScalingGroups []string) or the object form. func (e *autoScalingGroupEntry) UnmarshalJSON(data []byte) error { var name string - if err := json.Unmarshal(data, &name); err == nil { + if err := json.Unmarshal(data, &name, jsonx.V1()); err == nil { *e = autoScalingGroupEntry{Name: name} return nil @@ -67,7 +68,7 @@ func (e *autoScalingGroupEntry) UnmarshalJSON(data []byte) error { type plain autoScalingGroupEntry var p plain - if err := json.Unmarshal(data, &p); err != nil { + if err := json.Unmarshal(data, &p, jsonx.V1()); err != nil { return err } diff --git a/services/codedeploy/handler_test.go b/services/codedeploy/handler_test.go index edb31c703f..48a995c23b 100644 --- a/services/codedeploy/handler_test.go +++ b/services/codedeploy/handler_test.go @@ -2,7 +2,7 @@ package codedeploy_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *codedeploy.Handler, action string, body any) *ht if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -175,7 +176,7 @@ func TestHandler_ExtractResource(t *testing.T) { h := newTestHandler(t) e := echo.New() - bodyBytes, _ := json.Marshal(map[string]string{"applicationName": "my-app"}) + bodyBytes, _ := json.Marshal(map[string]string{"applicationName": "my-app"}, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) rec := httptest.NewRecorder() c := e.NewContext(req, rec) diff --git a/services/codedeploy/lifecycle_hooks_test.go b/services/codedeploy/lifecycle_hooks_test.go index 5706b77e97..bcf8e91155 100644 --- a/services/codedeploy/lifecycle_hooks_test.go +++ b/services/codedeploy/lifecycle_hooks_test.go @@ -1,12 +1,14 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestLifecycleHooks_PutExecutionStatus_DeploymentNotFound verifies that @@ -26,7 +28,7 @@ func TestLifecycleHooks_PutExecutionStatus_DeploymentNotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DeploymentDoesNotExistException", resp["__type"]) } @@ -46,7 +48,7 @@ func TestLifecycleHooks_PutExecutionStatus_DeploymentFound(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) deployID := createOut["deploymentId"] hookRec := doRequest(t, h, "PutLifecycleEventHookExecutionStatus", map[string]any{ @@ -59,6 +61,6 @@ func TestLifecycleHooks_PutExecutionStatus_DeploymentFound(t *testing.T) { var hookOut struct { LifecycleEventHookExecutionID string `json:"lifecycleEventHookExecutionId"` } - require.NoError(t, json.Unmarshal(hookRec.Body.Bytes(), &hookOut)) + require.NoError(t, json.Unmarshal(hookRec.Body.Bytes(), &hookOut, jsonx.V1())) assert.Equal(t, "hook-1", hookOut.LifecycleEventHookExecutionID) } diff --git a/services/codedeploy/lifecycle_validation_test.go b/services/codedeploy/lifecycle_validation_test.go index 81e1d63746..9c2acb7c6b 100644 --- a/services/codedeploy/lifecycle_validation_test.go +++ b/services/codedeploy/lifecycle_validation_test.go @@ -1,13 +1,15 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateDeploymentGroup_InputValidation(t *testing.T) { @@ -68,7 +70,7 @@ func TestCreateDeploymentGroup_InputValidation(t *testing.T) { } var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantCode, resp["__type"]) assert.NotContains(t, resp["message"], "Exception:") }) diff --git a/services/codedeploy/multi_region_test.go b/services/codedeploy/multi_region_test.go index 62cbae9fa4..a10c8ca8e5 100644 --- a/services/codedeploy/multi_region_test.go +++ b/services/codedeploy/multi_region_test.go @@ -2,7 +2,7 @@ package codedeploy_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -38,7 +39,7 @@ func regionCall(t *testing.T, h *codedeploy.Handler, region, op, body string) ma require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/codedeploy/on_premises_instances_test.go b/services/codedeploy/on_premises_instances_test.go index 84dbf6e05a..24f31218c9 100644 --- a/services/codedeploy/on_premises_instances_test.go +++ b/services/codedeploy/on_premises_instances_test.go @@ -1,7 +1,7 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codedeploy" ) @@ -90,7 +91,7 @@ func TestHandler_BatchGetOnPremisesInstances(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) infos, ok := resp["instanceInfos"].([]any) require.True(t, ok) assert.Len(t, infos, tt.wantCount) @@ -120,7 +121,7 @@ func TestOnPremisesInstances_TagsAlwaysSlice(t *testing.T) { } `json:"instanceInfos"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.InstanceInfos, 1) // Tags should be an empty slice, not nil/missing assert.NotNil(t, resp.InstanceInfos[0].Tags) @@ -230,7 +231,7 @@ func TestOnPremisesInstances_IamValidation(t *testing.T) { if tt.wantErrType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) } }) @@ -290,7 +291,7 @@ func TestOnPremisesInstances_ListTagFilter(t *testing.T) { var resp struct { InstanceNames []string `json:"instanceNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.ElementsMatch(t, tt.wantNames, resp.InstanceNames) }) } @@ -337,7 +338,7 @@ func TestOnPremisesInstances_NotFoundErrorMapping(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) }) } @@ -354,7 +355,7 @@ func TestGetOnPremisesInstance_NotRegisteredNotDoesNotExist(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InstanceNotRegisteredException", resp["__type"]) assert.NotEqual(t, "InstanceDoesNotExistException", resp["__type"]) @@ -364,6 +365,6 @@ func TestGetOnPremisesInstance_NotRegisteredNotDoesNotExist(t *testing.T) { var listResp struct { InstanceNames []string `json:"instanceNames"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.InstanceNames) } diff --git a/services/codedeploy/persistence.go b/services/codedeploy/persistence.go index f996c46e61..63f92a36f0 100644 --- a/services/codedeploy/persistence.go +++ b/services/codedeploy/persistence.go @@ -2,7 +2,7 @@ package codedeploy import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -149,11 +149,11 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // identity-less set left unconverted (see store_setup.go) and is persisted // here directly, as before. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - GitHubTokens []string `json:"githubTokens,omitempty"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + GitHubTokens []string `json:"githubTokens,omitempty"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. Returns nil on marshal failure. @@ -275,7 +275,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // registerAllTables doc) from tables via the ephemeral DTO registry, // factored out of Restore to keep Restore's own cognitive complexity low. // Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("codedeploy: restore snapshot DTO tables: %w", err) diff --git a/services/codedeploy/store_setup.go b/services/codedeploy/store_setup.go index 0fba4b10c0..e22d1e97de 100644 --- a/services/codedeploy/store_setup.go +++ b/services/codedeploy/store_setup.go @@ -30,8 +30,9 @@ package codedeploy // NOT converted: there is no *T value for store.Table to key on. It remains // a plain map, unchanged by this refactor (see backend.go). import ( - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -61,7 +62,7 @@ func deploymentConfigTableKeyFn(v *DeploymentConfig) string { return v.Deploymen // which is exactly the identity real CodeDeploy uses to deduplicate // registrations of the same revision. func applicationRevisionKey(appName string, r RevisionLocation) string { - b, _ := json.Marshal(r) + b, _ := json.Marshal(r, jsonx.V1()) return appName + "\x00" + string(b) } diff --git a/services/codedeploy/tags_test.go b/services/codedeploy/tags_test.go index f5ea2af983..32dbe20c28 100644 --- a/services/codedeploy/tags_test.go +++ b/services/codedeploy/tags_test.go @@ -1,13 +1,15 @@ package codedeploy_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Tagging(t *testing.T) { @@ -69,7 +71,7 @@ func TestTags_SortedListTagsForResource(t *testing.T) { } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Tags, 3) assert.Equal(t, "aaa", resp.Tags[0].Key) assert.Equal(t, "mmm", resp.Tags[1].Key) @@ -104,7 +106,7 @@ func TestTags_OnDeploymentGroups(t *testing.T) { } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Tags, 1) assert.Equal(t, "env", resp.Tags[0].Key) assert.Equal(t, "prod", resp.Tags[0].Value) @@ -189,7 +191,7 @@ func TestTags_ResourceTagLimits(t *testing.T) { if tt.wantErrType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) } }) @@ -219,7 +221,7 @@ func TestTags_ResourceExceedsMaxTags(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // TagResource's own deserializer models only InvalidTagsToAddException for // tag content, not TagLimitExceededException (that code belongs to // AddTagsToOnPremisesInstances/RemoveTagsFromOnPremisesInstances/ diff --git a/services/codegurureviewer/persistence.go b/services/codegurureviewer/persistence.go index 0ca94e738d..5c7be4e197 100644 --- a/services/codegurureviewer/persistence.go +++ b/services/codegurureviewer/persistence.go @@ -2,7 +2,7 @@ package codegurureviewer import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/codepipeline/async_builtin_actions_test.go b/services/codepipeline/async_builtin_actions_test.go index a945dce975..166ee30585 100644 --- a/services/codepipeline/async_builtin_actions_test.go +++ b/services/codepipeline/async_builtin_actions_test.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "sync" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -198,7 +199,7 @@ func TestBuiltinActions_WaitForBackendState(t *testing.T) { details, lErr := h.Backend.ListActionExecutions(ctx, "p", exec.PipelineExecutionID) require.NoError(t, lErr) - raw, mErr := json.Marshal(details) + raw, mErr := json.Marshal(details, jsonx.V1()) require.NoError(t, mErr) assert.Contains(t, string(raw), tt.wantMsg) } diff --git a/services/codepipeline/custom_action_types_test.go b/services/codepipeline/custom_action_types_test.go index 1cda05da9d..faeb26b739 100644 --- a/services/codepipeline/custom_action_types_test.go +++ b/services/codepipeline/custom_action_types_test.go @@ -2,7 +2,8 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -80,7 +82,7 @@ func TestHandler_CreateCustomActionType(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) at, ok := out["actionType"].(map[string]any) require.True(t, ok) id, ok := at["id"].(map[string]any) @@ -130,7 +132,7 @@ func TestCreateCustomActionType_ActionConfigurationProperties_EmptyNotAbsent(t * require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) actionType, ok := raw["actionType"].(map[string]any) require.True(t, ok, "actionType must be an object") @@ -164,7 +166,7 @@ func TestCreateCustomActionType_ActionConfigurationProperties_RoundTrip(t *testi require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) actionType, ok := raw["actionType"].(map[string]any) require.True(t, ok) @@ -294,7 +296,7 @@ func TestHandler_DeleteCustomActionType_InUse(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) } }) @@ -358,7 +360,7 @@ func TestHandler_GetActionType(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) at, ok := out["actionType"].(map[string]any) require.True(t, ok) id, ok := at["id"].(map[string]any) @@ -402,7 +404,7 @@ func TestGetActionType_ActionTypeDeclarationShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) actionType, ok := raw["actionType"].(map[string]any) require.True(t, ok, "actionType must be an object") @@ -484,7 +486,7 @@ func TestGetActionType_AfterUpdateActionType_ReturnsDeclarationData(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) actionType, ok := raw["actionType"].(map[string]any) require.True(t, ok) @@ -507,7 +509,7 @@ func TestGetActionType_AfterUpdateActionType_ReturnsDeclarationData(t *testing.T require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) items, _ := listOut["actionTypes"].([]any) require.Len(t, items, 1) item0, _ := items[0].(map[string]any) @@ -627,7 +629,7 @@ func TestHandler_ListActionTypes_FullShape(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) items, _ := out["actionTypes"].([]any) assert.Len(t, items, tt.wantCount) @@ -667,7 +669,7 @@ func TestCPPagination_ListActionTypes(t *testing.T) { NextToken string `json:"nextToken"` ActionTypes []map[string]any `json:"actionTypes"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.ActionTypes, 5) assert.Empty(t, out.NextToken, "5 items fit within the 25-cap default page") @@ -707,7 +709,7 @@ func TestCPPagination_ListActionTypes_MultiPage(t *testing.T) { NextToken string `json:"nextToken"` ActionTypes []map[string]any `json:"actionTypes"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) total += len(out.ActionTypes) pages++ @@ -771,7 +773,7 @@ func TestHandler_UpdateActionType_FullFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) at, _ := out["actionType"].(map[string]any) inArt, _ := at["inputArtifactDetails"].(map[string]any) diff --git a/services/codepipeline/deploy_cloudformation.go b/services/codepipeline/deploy_cloudformation.go index bb52766062..8f8de5a2c3 100644 --- a/services/codepipeline/deploy_cloudformation.go +++ b/services/codepipeline/deploy_cloudformation.go @@ -2,11 +2,14 @@ package codepipeline import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -285,7 +288,7 @@ func (b *InMemoryBackend) cfnPublishOutputs( return } - body, err := json.Marshal(st.Outputs) + body, err := json.Marshal(st.Outputs, jsonx.V1()) if err == nil { err = b.putOutputFiles(ctx, p, execID, action, map[string][]byte{file: body}) } @@ -431,12 +434,12 @@ func (b *InMemoryBackend) applyTemplateConfiguration( } var doc struct { - Parameters map[string]any `json:"Parameters"` - Tags map[string]any `json:"Tags"` - StackPolicy json.RawMessage `json:"StackPolicy"` + Parameters map[string]any `json:"Parameters"` + Tags map[string]any `json:"Tags"` + StackPolicy jsontext.Value `json:"StackPolicy"` } - if err = json.Unmarshal(body, &doc); err != nil { + if err = json.Unmarshal(body, &doc, jsonx.V1()); err != nil { return fmt.Errorf("template configuration %q is not valid JSON: %w", ref, err) } @@ -469,7 +472,7 @@ func (b *InMemoryBackend) applyParameterOverrides( ctx context.Context, p *Pipeline, execID string, action Action, raw string, req *StackRequest, ) error { var overrides map[string]any - if err := json.Unmarshal([]byte(raw), &overrides); err != nil { + if err := json.Unmarshal([]byte(raw), &overrides, jsonx.V1()); err != nil { return fmt.Errorf("ParameterOverrides is not a valid JSON object: %w", err) } @@ -506,7 +509,7 @@ func (b *InMemoryBackend) resolveParameterFunction( } var doc map[string]any - if err = json.Unmarshal(body, &doc); err != nil { + if err = json.Unmarshal(body, &doc, jsonx.V1()); err != nil { return "", fmt.Errorf("%s is not a JSON object: %w", scalarString(args[1]), err) } diff --git a/services/codepipeline/deploy_ecs.go b/services/codepipeline/deploy_ecs.go index 3c49a802fd..d6ea044324 100644 --- a/services/codepipeline/deploy_ecs.go +++ b/services/codepipeline/deploy_ecs.go @@ -2,10 +2,12 @@ package codepipeline import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -97,7 +99,7 @@ func (b *InMemoryBackend) ecsStartDeployment( } var defs []imageDefinition - if err = json.Unmarshal(body, &defs); err != nil || len(defs) == 0 { + if err = json.Unmarshal(body, &defs, jsonx.V1()); err != nil || len(defs) == 0 { return "", fmt.Errorf( "%w: image definitions file %q must be a non-empty JSON array of name/imageUri", ErrValidation, diff --git a/services/codepipeline/handler.go b/services/codepipeline/handler.go index d2c660af97..03a8c42efe 100644 --- a/services/codepipeline/handler.go +++ b/services/codepipeline/handler.go @@ -2,7 +2,7 @@ package codepipeline import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -294,7 +295,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -346,10 +347,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err } } - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - - if errors.As(err, &syntaxErr) || errors.As(err, &typeErr) { + if jsonx.IsSyntaxError(err) || jsonx.IsTypeError(err) { return errorBlob(c, http.StatusBadRequest, errTypeValidation, err) } @@ -372,7 +370,7 @@ func errorBlob(c *echo.Context, status int, errType string, err error) error { payload, _ := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: stripExceptionPrefixes(err.Error()), - }) + }, jsonx.V1()) return c.JSONBlob(status, payload) } diff --git a/services/codepipeline/handler_test.go b/services/codepipeline/handler_test.go index e40334a94e..8489b9042c 100644 --- a/services/codepipeline/handler_test.go +++ b/services/codepipeline/handler_test.go @@ -3,7 +3,7 @@ package codepipeline_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -28,7 +29,7 @@ func doRequest(t *testing.T, h *codepipeline.Handler, action string, body any) * var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -174,7 +175,7 @@ func decodeBody(t *testing.T, data []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(data, &m)) + require.NoError(t, json.Unmarshal(data, &m, jsonx.V1())) return m } @@ -343,7 +344,7 @@ func TestHandler_UnknownAction(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "InvalidActionException", out["__type"]) } @@ -422,7 +423,7 @@ func TestHandler_ErrorEnvelopes(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) }) } @@ -445,7 +446,7 @@ func TestErrValidationMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ValidationException", out["__type"]) } diff --git a/services/codepipeline/jobs_test.go b/services/codepipeline/jobs_test.go index ebb2142fc2..2e5918725c 100644 --- a/services/codepipeline/jobs_test.go +++ b/services/codepipeline/jobs_test.go @@ -2,13 +2,14 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -78,7 +79,7 @@ func TestHandler_AcknowledgeJob(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out["status"]) } }) @@ -133,7 +134,7 @@ func TestHandler_GetJobDetails(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) details, ok := out["jobDetails"].(map[string]any) require.True(t, ok) assert.Equal(t, "jd-1", details["id"]) @@ -176,7 +177,7 @@ func TestGetJobDetails_DataPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) details, ok := out["jobDetails"].(map[string]any) require.True(t, ok, "jobDetails must be present") @@ -284,7 +285,7 @@ func TestHandler_PollForJobs_Filter(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobs, _ := out["jobs"].([]any) gotIDs := make([]string, len(jobs)) @@ -379,7 +380,7 @@ func TestHandler_PollForJobs_MaxBatchSize(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobs, _ := out["jobs"].([]any) assert.Len(t, jobs, tt.wantCount) diff --git a/services/codepipeline/persistence.go b/services/codepipeline/persistence.go index 8d53cd24c1..54c504bf97 100644 --- a/services/codepipeline/persistence.go +++ b/services/codepipeline/persistence.go @@ -2,7 +2,7 @@ package codepipeline import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -96,14 +96,14 @@ type conditionRunEntry struct { // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Executions []executionEntry `json:"executions"` - ActionExecutions []actionExecutionEntry `json:"actionExecutions"` - ActionRevisions []actionRevisionEntry `json:"actionRevisions"` - ConditionRuns []conditionRunEntry `json:"conditionRuns,omitempty"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Executions []executionEntry `json:"executions"` + ActionExecutions []actionExecutionEntry `json:"actionExecutions"` + ActionRevisions []actionRevisionEntry `json:"actionRevisions"` + ConditionRuns []conditionRunEntry `json:"conditionRuns,omitempty"` + Version int `json:"version"` } // customActionTypeKey.String returns a unique string for use as the DTO id @@ -352,7 +352,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds the five resource store.Table values from // snap's per-table JSON, factored out of Restore to keep Restore's own // cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, pipelineDTOs, catDTOs, jobDTOs, webhookDTOs, transitionDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/codepipeline/pipeline_declaration_test.go b/services/codepipeline/pipeline_declaration_test.go index e495533bbf..01ea61a3ca 100644 --- a/services/codepipeline/pipeline_declaration_test.go +++ b/services/codepipeline/pipeline_declaration_test.go @@ -1,13 +1,14 @@ package codepipeline_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -95,7 +96,7 @@ func TestHandler_PipelineDeclaration_V2Fields(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pl, _ := out["pipeline"].(map[string]any) require.NotNil(t, pl) @@ -219,7 +220,7 @@ func TestHandler_Action_ExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) pl := out["pipeline"].(map[string]any) stages := pl["stages"].([]any) diff --git a/services/codepipeline/pipeline_executions_test.go b/services/codepipeline/pipeline_executions_test.go index 6443c16ef8..000994f6d9 100644 --- a/services/codepipeline/pipeline_executions_test.go +++ b/services/codepipeline/pipeline_executions_test.go @@ -2,7 +2,8 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -100,7 +102,7 @@ func TestHandler_PipelineExecution_TerminalStatus(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) execID, _ := startOut["pipelineExecutionId"].(string) require.NotEmpty(t, execID) @@ -108,7 +110,7 @@ func TestHandler_PipelineExecution_TerminalStatus(t *testing.T) { require.Equal(t, tt.httpStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantType != "" { assert.Equal(t, tt.wantType, out["__type"]) @@ -247,7 +249,7 @@ func TestHandler_ListPipelineExecutions_StoresAndReturns(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, _ := out["pipelineExecutionSummaries"].([]any) assert.Len(t, summaries, tt.wantCount) } @@ -293,7 +295,7 @@ func TestListPipelineExecutions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.PipelineExecutionSummaries), 2, "page exceeds maxResults") for _, s := range resp.PipelineExecutionSummaries { @@ -392,7 +394,7 @@ func TestCPPagination_ListPipelineExecutions(t *testing.T) { NextToken string `json:"nextToken"` PipelineExecutionSummaries []map[string]any `json:"pipelineExecutionSummaries"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) total += len(out.PipelineExecutionSummaries) pages++ @@ -432,7 +434,7 @@ func TestListPipelineExecutions_TriggerObject(t *testing.T) { var out struct { PipelineExecutionSummaries []map[string]any `json:"pipelineExecutionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.PipelineExecutionSummaries, 2) for _, exec := range out.PipelineExecutionSummaries { @@ -598,7 +600,7 @@ func TestCPPagination_ListActionExecutions(t *testing.T) { NextToken string `json:"nextToken"` ActionExecutionDetails []map[string]any `json:"actionExecutionDetails"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) total += len(out.ActionExecutionDetails) pages++ diff --git a/services/codepipeline/pipeline_state_test.go b/services/codepipeline/pipeline_state_test.go index 1921f90070..15fcb2d71c 100644 --- a/services/codepipeline/pipeline_state_test.go +++ b/services/codepipeline/pipeline_state_test.go @@ -2,13 +2,14 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -321,7 +322,7 @@ func TestHandler_DisableStageTransition_StageValidation(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) } }) @@ -524,7 +525,7 @@ func TestHandler_GetPipelineState_ActionStates(t *testing.T) { if tt.checkFn != nil { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.checkFn(t, out) } }) @@ -611,7 +612,7 @@ func TestHandler_GetPipelineState_LatestExecution(t *testing.T) { if tt.checkFn != nil { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.checkFn(t, out) } }) @@ -655,7 +656,7 @@ func TestGetPipelineState_PipelineVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.InDelta(t, tt.wantVersion, out["pipelineVersion"], 0, "pipelineVersion mismatch") }) } diff --git a/services/codepipeline/pipeline_state_wire_test.go b/services/codepipeline/pipeline_state_wire_test.go index 8249eade4e..334510079c 100644 --- a/services/codepipeline/pipeline_state_wire_test.go +++ b/services/codepipeline/pipeline_state_wire_test.go @@ -1,7 +1,7 @@ package codepipeline_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -134,7 +135,7 @@ func TestGetPipelineState_OmitsOutboundTransitionState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) stageStates := out["stageStates"].([]any) require.Len(t, stageStates, 1) diff --git a/services/codepipeline/pipelines_test.go b/services/codepipeline/pipelines_test.go index 59c84f843d..021790e452 100644 --- a/services/codepipeline/pipelines_test.go +++ b/services/codepipeline/pipelines_test.go @@ -2,13 +2,14 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -72,7 +73,7 @@ func TestHandler_CreatePipeline(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pipeline, ok := out["pipeline"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, pipeline["name"]) @@ -131,8 +132,8 @@ func TestHandler_CreatePipeline_RoleArnRequired(t *testing.T) { input := tt.pipeline if input == nil { // marshal samplePipeline to map - b, _ := json.Marshal(samplePipeline("role-test")) - require.NoError(t, json.Unmarshal(b, &input)) + b, _ := json.Marshal(samplePipeline("role-test"), jsonx.V1()) + require.NoError(t, json.Unmarshal(b, &input, jsonx.V1())) } rec := doRequest(t, h, "CreatePipeline", map[string]any{"pipeline": input}) @@ -140,7 +141,7 @@ func TestHandler_CreatePipeline_RoleArnRequired(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) } }) @@ -177,7 +178,7 @@ func TestCreatePipeline_DuplicateName_PipelineNameInUseException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec2.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body, jsonx.V1())) errType, ok := body["__type"].(string) require.True(t, ok, "response must have __type field") @@ -200,7 +201,7 @@ func TestCreatePipeline_DuplicateName_NotInvalidStructureException(t *testing.T) require.Equal(t, http.StatusBadRequest, rec2.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &body, jsonx.V1())) errType, _ := body["__type"].(string) assert.NotEqual(t, "InvalidStructureException", errType, @@ -329,7 +330,7 @@ func TestHandler_GetPipeline_VersionHandling(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) } }) @@ -449,7 +450,7 @@ func TestHandler_UpdatePipeline_VersionIsIgnored(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pipeline, _ := out["pipeline"].(map[string]any) assert.InDelta( t, @@ -585,7 +586,7 @@ func TestInMemoryBackend_DeletePipeline_ClearsActionExecutions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var before map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before, jsonx.V1())) beforeDetails, _ := before["actionExecutionDetails"].([]any) require.NotEmpty(t, beforeDetails, "action executions must be recorded before delete") @@ -600,7 +601,7 @@ func TestInMemoryBackend_DeletePipeline_ClearsActionExecutions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var after map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after, jsonx.V1())) afterDetails, _ := after["actionExecutionDetails"].([]any) assert.Empty( t, @@ -683,7 +684,7 @@ func TestDeletePipeline_CascadeActionRevisions(t *testing.T) { require.Equal(t, http.StatusOK, stateRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(stateRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(stateRec.Body.Bytes(), &out, jsonx.V1())) stageStates, _ := out["stageStates"].([]any) require.Len(t, stageStates, 1) stage, _ := stageStates[0].(map[string]any) @@ -738,7 +739,7 @@ func TestHandler_ListPipelines(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pipelines, _ := out["pipelines"].([]any) assert.Len(t, pipelines, tt.wantCount) }) @@ -783,7 +784,7 @@ func TestHandler_ListPipelines_IncludesPipelineType(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pipelines, _ := out["pipelines"].([]any) require.Len(t, pipelines, 1) @@ -812,7 +813,7 @@ func TestSortedListPipelines(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["pipelines"].([]any) require.True(t, ok) @@ -836,7 +837,7 @@ func TestListPipelines_NonNilWhenEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // pipelines key must exist and be an array, not null. list, ok := out["pipelines"] @@ -861,7 +862,7 @@ func TestListPipelines_OmitsPipelineArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list := out["pipelines"].([]any) item := list[0].(map[string]any) @@ -871,7 +872,7 @@ func TestListPipelines_OmitsPipelineArn(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) metadata := getOut["metadata"].(map[string]any) arn, ok := metadata["pipelineArn"].(string) @@ -932,7 +933,7 @@ func TestListPipelines_Pagination(t *testing.T) { NextToken string `json:"nextToken"` Pipelines []map[string]any `json:"pipelines"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Pipelines, tt.wantLen) if tt.wantNextToken { assert.NotEmpty(t, out.NextToken) @@ -976,7 +977,7 @@ func TestListPipelines_FullPagination(t *testing.T) { NextToken string `json:"nextToken"` Pipelines []map[string]any `json:"pipelines"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.LessOrEqual(t, len(out.Pipelines), 2) for _, p := range out.Pipelines { diff --git a/services/codepipeline/rules_test.go b/services/codepipeline/rules_test.go index b32ae50801..4c963e138d 100644 --- a/services/codepipeline/rules_test.go +++ b/services/codepipeline/rules_test.go @@ -2,13 +2,15 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -110,7 +112,7 @@ func TestHandler_ListExecutionsAndRules(t *testing.T) { ActionExecutionID string `json:"actionExecutionId"` } `json:"actionExecutionDetails"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&actionExecs)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &actionExecs, jsonx.V1())) require.NotEmpty(t, actionExecs.ActionExecutionDetails) actionExecutionID := actionExecs.ActionExecutionDetails[0].ActionExecutionID require.NotEmpty(t, actionExecutionID) diff --git a/services/codepipeline/source_actions.go b/services/codepipeline/source_actions.go index db50e1ea98..f9f3c9dfb9 100644 --- a/services/codepipeline/source_actions.go +++ b/services/codepipeline/source_actions.go @@ -2,11 +2,12 @@ package codepipeline import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -65,7 +66,7 @@ func (b *InMemoryBackend) runECRSourceAction( RepositoryName: img.RepositoryName, ImageURI: uri, ImageTags: img.Tags, - }) + }, jsonx.V1()) if err == nil { err = b.putOutputFiles(ctx, p, execID, action, map[string][]byte{imageDetailFile: detail}) } diff --git a/services/codepipeline/tags_test.go b/services/codepipeline/tags_test.go index 6a210bcdc2..f2f6298783 100644 --- a/services/codepipeline/tags_test.go +++ b/services/codepipeline/tags_test.go @@ -2,13 +2,14 @@ package codepipeline_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -173,7 +174,7 @@ func TestHandler_ListTagsForResource_WebhookARN(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) } }) @@ -201,7 +202,7 @@ func TestSortedListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags, ok := out["tags"].([]any) require.True(t, ok) @@ -228,7 +229,7 @@ func TestListTagsForResource_EmptySlice(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags := out["tags"] assert.NotNil(t, tags) @@ -280,7 +281,7 @@ func TestWebhookTagging(t *testing.T) { // Get the webhook ARN. var putOut map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) webhookEntry := putOut["webhook"].(map[string]any) arn := webhookEntry["arn"].(string) require.NotEmpty(t, arn) @@ -308,7 +309,7 @@ func TestWebhookTagging(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) rawTags, _ := listOut["tags"].([]any) gotKeys := make([]string, 0, len(rawTags)) diff --git a/services/codepipeline/third_party_jobs_test.go b/services/codepipeline/third_party_jobs_test.go index ae993d186d..da89b3e178 100644 --- a/services/codepipeline/third_party_jobs_test.go +++ b/services/codepipeline/third_party_jobs_test.go @@ -1,13 +1,14 @@ package codepipeline_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -67,7 +68,7 @@ func TestHandler_AcknowledgeThirdPartyJob(t *testing.T) { if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantStatus, out["status"]) } }) @@ -166,7 +167,7 @@ func TestHandler_PollForThirdPartyJobs_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jobs, _ := out["jobs"].([]any) require.Len(t, jobs, 1) @@ -218,7 +219,7 @@ func TestGetThirdPartyJobDetails_DataPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) details, ok := out["jobDetails"].(map[string]any) require.True(t, ok, "jobDetails must be present") diff --git a/services/codepipeline/webhooks_test.go b/services/codepipeline/webhooks_test.go index 9ed32cbf20..95308746fe 100644 --- a/services/codepipeline/webhooks_test.go +++ b/services/codepipeline/webhooks_test.go @@ -1,7 +1,8 @@ package codepipeline_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codepipeline" ) @@ -107,7 +109,7 @@ func TestHandler_DeregisterWebhookWithThirdParty(t *testing.T) { if tt.httpStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out) } }) @@ -248,7 +250,7 @@ func TestHandler_Webhook_FullModel(t *testing.T) { if tt.checkFn != nil && tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.checkFn(t, out) } }) @@ -329,7 +331,7 @@ func TestHandler_ListWebhooks_DefinitionWrapper(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) webhooks, _ := out["webhooks"].([]any) assert.Len(t, webhooks, tt.wantCount) @@ -499,7 +501,7 @@ func TestCPPagination_ListWebhooks(t *testing.T) { NextToken string `json:"NextToken"` Webhooks []map[string]any `json:"webhooks"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) total += len(out.Webhooks) pages++ @@ -561,7 +563,7 @@ func TestPutWebhook_Filters_EmptyNotAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) webhook, ok := raw["webhook"].(map[string]any) require.True(t, ok, "webhook must be an object") @@ -604,7 +606,7 @@ func TestListWebhooks_Filters_EmptyNotAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) webhooks, ok := raw["webhooks"].([]any) require.True(t, ok, "webhooks must be an array") @@ -651,7 +653,7 @@ func TestListWebhooks_Filters_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) webhooks, ok := raw["webhooks"].([]any) require.True(t, ok) diff --git a/services/codestarconnections/handler.go b/services/codestarconnections/handler.go index 7e6c704228..ac1cb18e65 100644 --- a/services/codestarconnections/handler.go +++ b/services/codestarconnections/handler.go @@ -2,7 +2,7 @@ package codestarconnections import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -183,7 +184,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ResourceName string `json:"ResourceName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ConnectionArn != "": @@ -229,15 +230,12 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } const codestarContentType = "application/x-amz-json-1.0" func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var errType string var statusCode int @@ -256,7 +254,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err errType, statusCode = "LimitExceededException", http.StatusBadRequest case errors.Is(err, ErrValidation), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): errType, statusCode = errTypeInvalidInput, http.StatusBadRequest default: errType, statusCode = "InternalFailure", http.StatusInternalServerError @@ -265,7 +263,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err payload, marshalErr := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: wireMessage(err, errType), - }) + }, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } diff --git a/services/codestarconnections/handler_connections_test.go b/services/codestarconnections/handler_connections_test.go index 6aff1d4816..0e0c485444 100644 --- a/services/codestarconnections/handler_connections_test.go +++ b/services/codestarconnections/handler_connections_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -50,7 +51,7 @@ func TestHandler_CreateConnection(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out[tt.wantKey]) }) } @@ -119,7 +120,7 @@ func TestHandler_GetConnection(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) conn, ok := out["Connection"].(map[string]any) require.True(t, ok) assert.Equal(t, "test-conn", conn["ConnectionName"]) @@ -209,7 +210,7 @@ func TestHandler_ListConnections(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) conns, ok := out["Connections"].([]any) require.True(t, ok) assert.Len(t, conns, tt.wantCount) @@ -339,7 +340,7 @@ func TestSortedListConnections(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) conns := out["Connections"].([]any) require.Len(t, conns, 3) @@ -372,7 +373,7 @@ func TestGetConnectionIncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) conn := out["Connection"].(map[string]any) _, hasTags := conn["Tags"] assert.False(t, hasTags, "GetConnection should not include Tags in response") @@ -381,7 +382,7 @@ func TestGetConnectionIncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, recTags.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "env", tags[0].(map[string]any)["Key"].(string)) diff --git a/services/codestarconnections/handler_hosts_test.go b/services/codestarconnections/handler_hosts_test.go index 09decc3dc1..8a6670255e 100644 --- a/services/codestarconnections/handler_hosts_test.go +++ b/services/codestarconnections/handler_hosts_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -52,7 +53,7 @@ func TestHandler_CreateHost(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["HostArn"]) }) } @@ -132,7 +133,7 @@ func TestHandler_GetHost(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "test-host", out["Name"]) assert.Equal(t, "GitHubEnterpriseServer", out["ProviderType"]) assert.Equal(t, "https://example.com", out["ProviderEndpoint"]) @@ -152,7 +153,7 @@ func TestHandler_ListHosts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) hosts, ok := out["Hosts"].([]any) require.True(t, ok) assert.Len(t, hosts, 2) @@ -346,7 +347,7 @@ func TestSortedListHosts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) hosts := out["Hosts"].([]any) require.Len(t, hosts, 3) @@ -380,7 +381,7 @@ func TestGetHostIncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &out, jsonx.V1())) _, hasTags := out["Tags"] assert.False(t, hasTags, "GetHost should not include Tags in response") @@ -388,7 +389,7 @@ func TestGetHostIncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, recTags.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "platform", tags[0].(map[string]any)["Value"].(string)) diff --git a/services/codestarconnections/handler_repository_links_test.go b/services/codestarconnections/handler_repository_links_test.go index b8d700cd4b..c897b743d7 100644 --- a/services/codestarconnections/handler_repository_links_test.go +++ b/services/codestarconnections/handler_repository_links_test.go @@ -2,7 +2,7 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -69,7 +70,7 @@ func TestHandler_CreateRepositoryLink(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) info, ok := out["RepositoryLinkInfo"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, info["RepositoryLinkId"]) @@ -138,7 +139,7 @@ func TestHandler_GetRepositoryLink(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) info, ok := out["RepositoryLinkInfo"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-owner", info["OwnerId"]) @@ -226,7 +227,7 @@ func TestHandler_ListRepositoryLinks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) links, ok := out["RepositoryLinks"].([]any) require.True(t, ok) assert.Len(t, links, 2) diff --git a/services/codestarconnections/handler_sync_blockers_test.go b/services/codestarconnections/handler_sync_blockers_test.go index dfa7b2540d..c10c9f0230 100644 --- a/services/codestarconnections/handler_sync_blockers_test.go +++ b/services/codestarconnections/handler_sync_blockers_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -71,7 +72,7 @@ func TestHandler_GetSyncBlockerSummary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summary, ok := out["SyncBlockerSummary"].(map[string]any) require.True(t, ok) assert.Equal(t, "blocker-resource", summary["ResourceName"]) diff --git a/services/codestarconnections/handler_sync_configurations_test.go b/services/codestarconnections/handler_sync_configurations_test.go index 0ffea88f89..dcd64e375e 100644 --- a/services/codestarconnections/handler_sync_configurations_test.go +++ b/services/codestarconnections/handler_sync_configurations_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -84,7 +85,7 @@ func TestHandler_CreateSyncConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) cfg, ok := out["SyncConfiguration"].(map[string]any) require.True(t, ok) assert.Equal(t, "main", cfg["Branch"]) @@ -147,7 +148,7 @@ func TestHandler_GetSyncConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) cfg, ok := out["SyncConfiguration"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-stack", cfg["ResourceName"]) @@ -225,7 +226,7 @@ func TestHandler_RepositoryLink_SyncConfiguration_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recLink.Code) var linkOut map[string]any - require.NoError(t, json.Unmarshal(recLink.Body.Bytes(), &linkOut)) + require.NoError(t, json.Unmarshal(recLink.Body.Bytes(), &linkOut, jsonx.V1())) linkInfo := linkOut["RepositoryLinkInfo"].(map[string]any) linkID := linkInfo["RepositoryLinkId"].(string) require.NotEmpty(t, linkID) @@ -242,7 +243,7 @@ func TestHandler_RepositoryLink_SyncConfiguration_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recSync.Code) var syncOut map[string]any - require.NoError(t, json.Unmarshal(recSync.Body.Bytes(), &syncOut)) + require.NoError(t, json.Unmarshal(recSync.Body.Bytes(), &syncOut, jsonx.V1())) syncCfg := syncOut["SyncConfiguration"].(map[string]any) assert.Equal(t, "my-repo", syncCfg["RepositoryName"]) assert.Equal(t, "GitHub", syncCfg["ProviderType"]) @@ -267,7 +268,7 @@ func TestHandler_RepositoryLink_SyncConfiguration_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) links, ok := listOut["RepositoryLinks"].([]any) require.True(t, ok) assert.Len(t, links, 1) diff --git a/services/codestarconnections/handler_sync_statuses_test.go b/services/codestarconnections/handler_sync_statuses_test.go index bd72b5dfe7..7fe4f1e04b 100644 --- a/services/codestarconnections/handler_sync_statuses_test.go +++ b/services/codestarconnections/handler_sync_statuses_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -74,7 +75,7 @@ func TestHandler_GetRepositorySyncStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) latest, ok := out["LatestSync"].(map[string]any) require.True(t, ok) assert.Equal(t, "SUCCEEDED", latest["Status"]) @@ -136,7 +137,7 @@ func TestHandler_GetResourceSyncStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) latest, ok := out["LatestSync"].(map[string]any) require.True(t, ok) assert.Equal(t, "SUCCEEDED", latest["Status"]) diff --git a/services/codestarconnections/handler_tags_test.go b/services/codestarconnections/handler_tags_test.go index ea5663db45..6cd379bcb9 100644 --- a/services/codestarconnections/handler_tags_test.go +++ b/services/codestarconnections/handler_tags_test.go @@ -2,13 +2,14 @@ package codestarconnections_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -31,7 +32,7 @@ func TestHandler_TagResource_Connection(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) tags, ok := out["Tags"].([]any) require.True(t, ok) require.Len(t, tags, 1) @@ -93,7 +94,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags, ok := out["Tags"].([]any) require.True(t, ok) require.Len(t, tags, 1) @@ -125,7 +126,7 @@ func TestSortedTags(t *testing.T) { require.Equal(t, http.StatusOK, recTags.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) keys := make([]string, len(tags)) @@ -178,7 +179,7 @@ func TestTagResourceOnHost(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "ci", tags[0].(map[string]any)["Value"].(string)) @@ -211,7 +212,7 @@ func TestUntagResourceRemovesKeys(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "keep", tags[0].(map[string]any)["Key"].(string)) diff --git a/services/codestarconnections/handler_test.go b/services/codestarconnections/handler_test.go index a79bfaec9c..d2e7a9ee36 100644 --- a/services/codestarconnections/handler_test.go +++ b/services/codestarconnections/handler_test.go @@ -3,7 +3,7 @@ package codestarconnections_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/codestarconnections" ) @@ -28,7 +29,7 @@ func doRequest(t *testing.T, h *codestarconnections.Handler, action string, body var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -52,7 +53,7 @@ func parseResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -527,7 +528,7 @@ func TestErrValidationMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidInputException", resp["__type"]) } @@ -670,7 +671,7 @@ func TestExtractResource(t *testing.T) { h := newTestHandler(t) - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) e := echo.New() diff --git a/services/codestarconnections/persistence.go b/services/codestarconnections/persistence.go index cd78ca1a20..b224bb20c7 100644 --- a/services/codestarconnections/persistence.go +++ b/services/codestarconnections/persistence.go @@ -2,7 +2,7 @@ package codestarconnections import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -87,10 +87,10 @@ func resourceSyncStatusDTOKeyFn(d *resourceSyncStatusDTO) string { // an incompatible (older or newer) build of this backend as though it were // the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // persistenceDTOTables groups every ephemeral DTO table Snapshot/Restore @@ -246,7 +246,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // syncBlockers; see store_setup.go's registerAllTables doc) from tables via // the ephemeral DTO registry, factored out of Restore to keep Restore's own // cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("codestarconnections: restore snapshot DTO tables: %w", err) diff --git a/services/cognitoidentity/handler.go b/services/cognitoidentity/handler.go index b3c47be5d5..1a1d476a81 100644 --- a/services/cognitoidentity/handler.go +++ b/services/cognitoidentity/handler.go @@ -3,7 +3,7 @@ package cognitoidentity import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -119,7 +120,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { IdentityID string `json:"IdentityId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.IdentityPoolID != "" { return req.IdentityPoolID @@ -186,7 +187,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -252,11 +253,11 @@ func resolveErrorType(err error) (string, int) { } } - if _, ok := errors.AsType[*json.SyntaxError](err); ok { + if _, ok := jsonx.SyntaxError(err); ok { return ErrInvalidParameter.Error(), http.StatusBadRequest } - if _, ok := errors.AsType[*json.UnmarshalTypeError](err); ok { + if _, ok := jsonx.TypeError(err); ok { return ErrInvalidParameter.Error(), http.StatusBadRequest } diff --git a/services/cognitoidentity/handler_credentials_test.go b/services/cognitoidentity/handler_credentials_test.go index 4afb195016..62d5bc9e2f 100644 --- a/services/cognitoidentity/handler_credentials_test.go +++ b/services/cognitoidentity/handler_credentials_test.go @@ -1,13 +1,15 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aws-sdk-go-v2's cognitoidentity deserializer parses Credentials.Expiration with @@ -27,7 +29,7 @@ func TestGetCredentialsForIdentity_ExpirationIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rolesRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -44,7 +46,7 @@ func TestGetCredentialsForIdentity_ExpirationIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetCredentialsForIdentity", map[string]any{ "IdentityId": idOut["IdentityId"], @@ -52,7 +54,7 @@ func TestGetCredentialsForIdentity_ExpirationIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) creds := out["Credentials"].(map[string]any) expiration, ok := creds["Expiration"].(float64) @@ -118,7 +120,7 @@ func TestGetCredentialsForIdentity_CustomRoleArn(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rolesRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -135,7 +137,7 @@ func TestGetCredentialsForIdentity_CustomRoleArn(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) req := map[string]any{"IdentityId": idOut["IdentityId"]} if tt.customRoleArn != "" { @@ -166,7 +168,7 @@ func TestHandler_GetCredentialsForIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rolesRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -183,7 +185,7 @@ func TestHandler_GetCredentialsForIdentity(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetCredentialsForIdentity", map[string]any{ "IdentityId": idOut["IdentityId"], @@ -191,7 +193,7 @@ func TestHandler_GetCredentialsForIdentity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["IdentityId"]) creds, ok := out["Credentials"].(map[string]any) @@ -213,7 +215,7 @@ func TestHandler_GetOpenIDToken(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) idRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -222,7 +224,7 @@ func TestHandler_GetOpenIDToken(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetOpenIdToken", map[string]any{ "IdentityId": idOut["IdentityId"], @@ -230,7 +232,7 @@ func TestHandler_GetOpenIDToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["Token"]) assert.Equal(t, idOut["IdentityId"], out["IdentityId"]) } @@ -294,7 +296,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) body = map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -310,7 +312,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["IdentityId"]) assert.NotEmpty(t, out["Token"]) } @@ -331,7 +333,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity_ExistingIdentity(t *testing. require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) // First call creates a new identity. @@ -344,7 +346,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity_ExistingIdentity(t *testing. require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) identityID1 := out1["IdentityId"].(string) // Second call with same logins should re-use the same identity. @@ -357,7 +359,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity_ExistingIdentity(t *testing. require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) identityID2 := out2["IdentityId"].(string) assert.Equal(t, identityID1, identityID2) @@ -375,7 +377,7 @@ func TestHandler_GetOpenIdTokenForDeveloperIdentity_InvalidDuration(t *testing.T require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetOpenIdTokenForDeveloperIdentity", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], diff --git a/services/cognitoidentity/handler_developer_identities_test.go b/services/cognitoidentity/handler_developer_identities_test.go index bdb1316853..d21aaab442 100644 --- a/services/cognitoidentity/handler_developer_identities_test.go +++ b/services/cognitoidentity/handler_developer_identities_test.go @@ -1,12 +1,14 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_LookupDeveloperIdentity(t *testing.T) { @@ -52,7 +54,7 @@ func TestHandler_LookupDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID = created["IdentityPoolId"].(string) devRec := doCognitoIdentityRequest(t, h, "GetOpenIdTokenForDeveloperIdentity", map[string]any{ @@ -64,7 +66,7 @@ func TestHandler_LookupDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, devRec.Code) var devOut map[string]any - require.NoError(t, json.Unmarshal(devRec.Body.Bytes(), &devOut)) + require.NoError(t, json.Unmarshal(devRec.Body.Bytes(), &devOut, jsonx.V1())) identityID = devOut["IdentityId"].(string) } @@ -89,7 +91,7 @@ func TestHandler_LookupDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) body = map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -101,7 +103,7 @@ func TestHandler_LookupDeveloperIdentity(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["IdentityId"]) } }) @@ -145,7 +147,7 @@ func TestHandler_MergeDeveloperIdentities(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) for _, userID := range []string{"user-src", "user-dst"} { @@ -171,7 +173,7 @@ func TestHandler_MergeDeveloperIdentities(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["IdentityId"]) } }) @@ -226,7 +228,7 @@ func TestHandler_UnlinkIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) getIDRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ @@ -239,7 +241,7 @@ func TestHandler_UnlinkIdentity(t *testing.T) { require.Equal(t, http.StatusOK, getIDRec.Code) var getIDOut map[string]any - require.NoError(t, json.Unmarshal(getIDRec.Body.Bytes(), &getIDOut)) + require.NoError(t, json.Unmarshal(getIDRec.Body.Bytes(), &getIDOut, jsonx.V1())) identityID = getIDOut["IdentityId"].(string) body = map[string]any{ @@ -261,7 +263,7 @@ func TestHandler_UnlinkIdentity(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) logins, ok := descOut["Logins"].([]any) require.True(t, ok) assert.Equal(t, []any{"graph.facebook.com"}, logins) @@ -308,7 +310,7 @@ func TestHandler_UnlinkDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID = created["IdentityPoolId"].(string) tokenRec := doCognitoIdentityRequest(t, h, "GetOpenIdTokenForDeveloperIdentity", map[string]any{ @@ -320,7 +322,7 @@ func TestHandler_UnlinkDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, tokenRec.Code) var tokenOut map[string]any - require.NoError(t, json.Unmarshal(tokenRec.Body.Bytes(), &tokenOut)) + require.NoError(t, json.Unmarshal(tokenRec.Body.Bytes(), &tokenOut, jsonx.V1())) identityID = tokenOut["IdentityId"].(string) body = map[string]any{ @@ -343,7 +345,7 @@ func TestHandler_UnlinkDeveloperIdentity(t *testing.T) { require.Equal(t, http.StatusOK, lookupRec.Code) var lookupOut map[string]any - require.NoError(t, json.Unmarshal(lookupRec.Body.Bytes(), &lookupOut)) + require.NoError(t, json.Unmarshal(lookupRec.Body.Bytes(), &lookupOut, jsonx.V1())) ids, ok := lookupOut["DeveloperUserIdentifierList"].([]any) require.True(t, ok) assert.Empty(t, ids) @@ -364,7 +366,7 @@ func TestHandler_UnlinkIdentity_TokenMismatch_Returns403(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) idRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -376,7 +378,7 @@ func TestHandler_UnlinkIdentity_TokenMismatch_Returns403(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "UnlinkIdentity", map[string]any{ "IdentityId": idOut["IdentityId"], @@ -402,7 +404,7 @@ func TestHandler_UnlinkDeveloperIdentity_WrongUser_Returns403(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) devRec := doCognitoIdentityRequest(t, h, "GetOpenIdTokenForDeveloperIdentity", map[string]any{ @@ -414,7 +416,7 @@ func TestHandler_UnlinkDeveloperIdentity_WrongUser_Returns403(t *testing.T) { require.Equal(t, http.StatusOK, devRec.Code) var devOut map[string]any - require.NoError(t, json.Unmarshal(devRec.Body.Bytes(), &devOut)) + require.NoError(t, json.Unmarshal(devRec.Body.Bytes(), &devOut, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "UnlinkDeveloperIdentity", map[string]any{ "IdentityId": devOut["IdentityId"], diff --git a/services/cognitoidentity/handler_identities_test.go b/services/cognitoidentity/handler_identities_test.go index 24ee102ae1..d8d8bb5200 100644 --- a/services/cognitoidentity/handler_identities_test.go +++ b/services/cognitoidentity/handler_identities_test.go @@ -1,13 +1,16 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetID_UnauthDisabled_EmptyLogins(t *testing.T) { @@ -22,7 +25,7 @@ func TestHandler_GetID_UnauthDisabled_EmptyLogins(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -43,7 +46,7 @@ func TestHandler_ListIdentities_MaxResultsZero(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "ListIdentities", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -64,7 +67,7 @@ func TestHandler_GetID(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -74,7 +77,7 @@ func TestHandler_GetID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["IdentityId"]) } @@ -139,7 +142,7 @@ func TestHandler_DeleteIdentities(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) idRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -148,7 +151,7 @@ func TestHandler_DeleteIdentities(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) body = map[string]any{ "IdentityIdsToDelete": []string{idOut["IdentityId"].(string)}, @@ -160,7 +163,7 @@ func TestHandler_DeleteIdentities(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) unprocessed, _ := out["UnprocessedIdentityIds"].([]any) assert.Len(t, unprocessed, tt.wantLen) } @@ -195,7 +198,7 @@ func TestHandler_DescribeIdentity(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) idRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ "AccountId": "000000000000", @@ -207,7 +210,7 @@ func TestHandler_DescribeIdentity(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) identityID = idOut["IdentityId"].(string) } @@ -219,7 +222,7 @@ func TestHandler_DescribeIdentity(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, identityID, out["IdentityId"]) assert.NotEmpty(t, out["CreationDate"]) logins, _ := out["Logins"].([]any) @@ -257,7 +260,7 @@ func TestHandler_ListIdentities(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID = created["IdentityPoolId"].(string) for _, login := range []map[string]string{ @@ -282,7 +285,7 @@ func TestHandler_ListIdentities(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, poolID, out["IdentityPoolId"]) identities, _ := out["Identities"].([]any) @@ -304,7 +307,7 @@ func TestSortedListIdentities(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) for _, login := range []map[string]string{ @@ -327,7 +330,7 @@ func TestSortedListIdentities(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) identities, _ := listOut["Identities"].([]any) require.Len(t, identities, 3) @@ -389,7 +392,7 @@ func TestListIdentities_MaxResultsValidation(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "ListIdentities", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -425,7 +428,7 @@ func TestSortedLogins(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) idRec := doCognitoIdentityRequest(t, h, "GetId", map[string]any{ @@ -440,7 +443,7 @@ func TestSortedLogins(t *testing.T) { require.Equal(t, http.StatusOK, idRec.Code) var idOut map[string]any - require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut)) + require.NoError(t, json.Unmarshal(idRec.Body.Bytes(), &idOut, jsonx.V1())) identityID := idOut["IdentityId"].(string) rec := doCognitoIdentityRequest(t, h, "DescribeIdentity", map[string]any{ @@ -449,7 +452,7 @@ func TestSortedLogins(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) logins, _ := descOut["Logins"].([]any) require.Len(t, logins, 3) @@ -498,7 +501,7 @@ func TestHandler_GetID_MergesLoginProviders(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) poolID := createOut["IdentityPoolId"].(string) // First call: only google. @@ -509,7 +512,7 @@ func TestHandler_GetID_MergesLoginProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&out1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &out1, jsonx.V1())) id1 := out1["IdentityId"].(string) // Second call: same google + new facebook → must return same ID. @@ -523,7 +526,7 @@ func TestHandler_GetID_MergesLoginProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out2, jsonx.V1())) id2 := out2["IdentityId"].(string) assert.Equal(t, id1, id2, "GetId must return the same identity when any login provider matches") @@ -541,7 +544,7 @@ func TestHandler_ListIdentities_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) poolID := createOut["IdentityPoolId"].(string) for i := range 3 { @@ -559,7 +562,7 @@ func TestHandler_ListIdentities_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&out1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &out1, jsonx.V1())) assert.Len(t, out1["Identities"], 2) nextToken, _ := out1["NextToken"].(string) assert.NotEmpty(t, nextToken) @@ -573,6 +576,6 @@ func TestHandler_ListIdentities_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out2, jsonx.V1())) assert.Len(t, out2["Identities"], 1) } diff --git a/services/cognitoidentity/handler_identity_pool_roles_test.go b/services/cognitoidentity/handler_identity_pool_roles_test.go index ffc2fb37b5..602601f6e8 100644 --- a/services/cognitoidentity/handler_identity_pool_roles_test.go +++ b/services/cognitoidentity/handler_identity_pool_roles_test.go @@ -1,12 +1,15 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_SetGetIdentityPoolRoles_WithRoleMappings(t *testing.T) { @@ -21,7 +24,7 @@ func TestHandler_SetGetIdentityPoolRoles_WithRoleMappings(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) setRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ @@ -45,7 +48,7 @@ func TestHandler_SetGetIdentityPoolRoles_WithRoleMappings(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) roles, ok := out["Roles"].(map[string]any) require.True(t, ok) @@ -73,7 +76,7 @@ func TestHandler_SetGetIdentityPoolRoles(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) setRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -90,7 +93,7 @@ func TestHandler_SetGetIdentityPoolRoles(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) roles, ok := out["Roles"].(map[string]any) require.True(t, ok) @@ -186,7 +189,7 @@ func TestHandler_GetPrincipalTagAttributeMap(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID = created["IdentityPoolId"].(string) } @@ -199,7 +202,7 @@ func TestHandler_GetPrincipalTagAttributeMap(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantDefaults, out["UseDefaults"]) } }) @@ -244,7 +247,7 @@ func TestHandler_SetPrincipalTagAttributeMap(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) body = map[string]any{ @@ -262,7 +265,7 @@ func TestHandler_SetPrincipalTagAttributeMap(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, false, out["UseDefaults"]) tags, _ := out["PrincipalTags"].(map[string]any) @@ -284,7 +287,7 @@ func TestHandler_SetGetPrincipalTagAttributeMap_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) providerName := "cognito-idp.us-east-1.amazonaws.com/us-east-1_Pool1" @@ -307,7 +310,7 @@ func TestHandler_SetGetPrincipalTagAttributeMap_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, poolID, out["IdentityPoolId"]) assert.Equal(t, providerName, out["IdentityProviderName"]) assert.Equal(t, false, out["UseDefaults"]) @@ -329,7 +332,7 @@ func TestHandler_SetIdentityPoolRoles_SingleRole(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -352,7 +355,7 @@ func TestHandler_GetPrincipalTagAttributeMap_EmptyProvider_Returns400(t *testing require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "GetPrincipalTagAttributeMap", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -384,7 +387,7 @@ func TestHandler_GetIdentityPoolRoles_OmitsEmptyRoles(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) poolID := createOut["IdentityPoolId"].(string) setRec := doCognitoIdentityRequest(t, h, "SetIdentityPoolRoles", map[string]any{ @@ -401,7 +404,7 @@ func TestHandler_GetIdentityPoolRoles_OmitsEmptyRoles(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) roles := out["Roles"].(map[string]any) assert.Contains(t, roles, "authenticated") @@ -420,7 +423,7 @@ func TestHandler_SetIdentityPoolRoles_PreservesExistingRole(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) poolID := createOut["IdentityPoolId"].(string) // Set both roles. @@ -446,7 +449,7 @@ func TestHandler_SetIdentityPoolRoles_PreservesExistingRole(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) roles := out["Roles"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/AuthV2", roles["authenticated"]) diff --git a/services/cognitoidentity/handler_identity_pools_test.go b/services/cognitoidentity/handler_identity_pools_test.go index c2e85bc350..b88d6e73b1 100644 --- a/services/cognitoidentity/handler_identity_pools_test.go +++ b/services/cognitoidentity/handler_identity_pools_test.go @@ -1,12 +1,15 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateIdentityPool_OIDCProviderARNs(t *testing.T) { @@ -54,12 +57,12 @@ func TestCreateIdentityPool_OIDCProviderARNs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) // Verify ARNs in CreateIdentityPool response. - raw, _ := json.Marshal(created["OpenIdConnectProviderARNs"]) + raw, _ := json.Marshal(created["OpenIdConnectProviderARNs"], jsonx.V1()) var gotARNs []string - _ = json.Unmarshal(raw, &gotARNs) + _ = json.Unmarshal(raw, &gotARNs, jsonx.V1()) assert.Equal(t, tt.wantARNs, gotARNs, "create response OpenIdConnectProviderARNs mismatch") poolID, _ := created["IdentityPoolId"].(string) @@ -72,11 +75,11 @@ func TestCreateIdentityPool_OIDCProviderARNs(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) - raw, _ = json.Marshal(desc["OpenIdConnectProviderARNs"]) + raw, _ = json.Marshal(desc["OpenIdConnectProviderARNs"], jsonx.V1()) var descARNs []string - _ = json.Unmarshal(raw, &descARNs) + _ = json.Unmarshal(raw, &descARNs, jsonx.V1()) assert.Equal(t, tt.wantARNs, descARNs, "describe response OpenIdConnectProviderARNs mismatch") }) } @@ -118,11 +121,11 @@ func TestCreateIdentityPool_SAMLProviderARNs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) - raw, _ := json.Marshal(created["SamlProviderARNs"]) + raw, _ := json.Marshal(created["SamlProviderARNs"], jsonx.V1()) var gotARNs []string - _ = json.Unmarshal(raw, &gotARNs) + _ = json.Unmarshal(raw, &gotARNs, jsonx.V1()) assert.Equal(t, tt.wantARNs, gotARNs, "SamlProviderARNs mismatch in create response") }) } @@ -177,7 +180,7 @@ func TestUpdateIdentityPool_OIDCAndSAMLARNs(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID, _ := created["IdentityPoolId"].(string) require.NotEmpty(t, poolID) @@ -191,16 +194,16 @@ func TestUpdateIdentityPool_OIDCAndSAMLARNs(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code, "update: %s", updRec.Body.String()) var updated map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updated, jsonx.V1())) - raw, _ := json.Marshal(updated["OpenIdConnectProviderARNs"]) + raw, _ := json.Marshal(updated["OpenIdConnectProviderARNs"], jsonx.V1()) var gotOIDC []string - _ = json.Unmarshal(raw, &gotOIDC) + _ = json.Unmarshal(raw, &gotOIDC, jsonx.V1()) assert.Equal(t, tt.wantOIDC, gotOIDC, "OIDC ARNs after update") - raw, _ = json.Marshal(updated["SamlProviderARNs"]) + raw, _ = json.Marshal(updated["SamlProviderARNs"], jsonx.V1()) var gotSAML []string - _ = json.Unmarshal(raw, &gotSAML) + _ = json.Unmarshal(raw, &gotSAML, jsonx.V1()) assert.Equal(t, tt.wantSAML, gotSAML, "SAML ARNs after update") }) } @@ -267,7 +270,7 @@ func TestListIdentityPools_Pagination(t *testing.T) { IdentityPools []any `json:"IdentityPools"` NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pageCounts = append(pageCounts, len(out.IdentityPools)) token = out.NextToken @@ -304,7 +307,7 @@ func TestListIdentityPools_TokenPastEnd(t *testing.T) { IdentityPools []any `json:"IdentityPools"` NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.IdentityPools, "token past last pool must return empty list, not first page") assert.Empty(t, out.NextToken) @@ -344,7 +347,7 @@ func TestHandler_CreateIdentityPool(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.poolName, out["IdentityPoolName"]) assert.NotEmpty(t, out["IdentityPoolId"]) } @@ -380,7 +383,7 @@ func TestHandler_DeleteIdentityPool(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) poolID = out["IdentityPoolId"].(string) } else { poolID = "us-east-1:nonexistent" @@ -407,7 +410,7 @@ func TestHandler_DescribeIdentityPool(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "DescribeIdentityPool", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -415,7 +418,7 @@ func TestHandler_DescribeIdentityPool(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "desc-pool", out["IdentityPoolName"]) } @@ -438,7 +441,7 @@ func TestHandler_ListIdentityPools(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pools, ok := out["IdentityPools"].([]any) require.True(t, ok) @@ -457,7 +460,7 @@ func TestHandler_UpdateIdentityPool(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) rec := doCognitoIdentityRequest(t, h, "UpdateIdentityPool", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -467,7 +470,7 @@ func TestHandler_UpdateIdentityPool(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, false, out["AllowUnauthenticatedIdentities"]) } @@ -514,7 +517,7 @@ func TestHandler_WithProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "provider-pool", out["IdentityPoolName"]) providers, ok := out["CognitoIdentityProviders"].([]any) @@ -541,7 +544,7 @@ func TestSortedListIdentityPools(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pools, _ := out["IdentityPools"].([]any) require.Len(t, pools, 3) @@ -571,7 +574,7 @@ func TestTagsInIdentityPoolOutput(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) // Tags should be in the CreateIdentityPool response. tags, _ := created["IdentityPoolTags"].(map[string]any) @@ -585,7 +588,7 @@ func TestTagsInIdentityPoolOutput(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) descTags, _ := descOut["IdentityPoolTags"].(map[string]any) assert.Equal(t, "test", descTags["env"]) @@ -603,7 +606,7 @@ func TestDeleteIdentityPool_CleansTagsAndPrincipalTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) arn := "arn:aws:cognito-identity:us-east-1:000000000000:identitypool/" + poolID @@ -648,7 +651,7 @@ func TestHandler_DeveloperProviderName_InCreateAndDescribe(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, "developer.myapp.com", created["DeveloperProviderName"]) descRec := doCognitoIdentityRequest(t, h, "DescribeIdentityPool", map[string]any{ @@ -657,7 +660,7 @@ func TestHandler_DeveloperProviderName_InCreateAndDescribe(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "developer.myapp.com", descOut["DeveloperProviderName"]) } @@ -674,7 +677,7 @@ func TestHandler_UpdateIdentityPool_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) updateRec := doCognitoIdentityRequest(t, h, "UpdateIdentityPool", map[string]any{ @@ -686,7 +689,7 @@ func TestHandler_UpdateIdentityPool_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updated, jsonx.V1())) tags, _ := updated["IdentityPoolTags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "backend", tags["team"]) @@ -744,7 +747,7 @@ func TestHandler_ListIdentityPools_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&out1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &out1, jsonx.V1())) assert.Len(t, out1["IdentityPools"], 2) nextToken, _ := out1["NextToken"].(string) assert.NotEmpty(t, nextToken) @@ -757,6 +760,6 @@ func TestHandler_ListIdentityPools_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out2, jsonx.V1())) assert.Len(t, out2["IdentityPools"], 1) } diff --git a/services/cognitoidentity/handler_tags_test.go b/services/cognitoidentity/handler_tags_test.go index e122f31ddc..2b2689a511 100644 --- a/services/cognitoidentity/handler_tags_test.go +++ b/services/cognitoidentity/handler_tags_test.go @@ -1,12 +1,14 @@ package cognitoidentity_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListTagsForResource(t *testing.T) { @@ -43,7 +45,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) arn = "arn:aws:cognito-identity:us-east-1:000000000000:identitypool/" + poolID } @@ -56,7 +58,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags, _ := out["Tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) } @@ -76,7 +78,7 @@ func TestHandler_TagResource_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) arn := "arn:aws:cognito-identity:us-east-1:000000000000:identitypool/" + poolID @@ -97,7 +99,7 @@ func TestHandler_TagResource_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tags, _ := listOut["Tags"].(map[string]any) assert.Equal(t, "backend", tags["team"]) assert.Equal(t, "alice", tags["owner"]) @@ -116,7 +118,7 @@ func TestHandler_TagResource_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2, jsonx.V1())) tags2, _ := listOut2["Tags"].(map[string]any) assert.Equal(t, "backend", tags2["team"]) assert.NotContains(t, tags2, "owner") @@ -160,7 +162,7 @@ func TestListTagsForResource_EmptyTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) arn := "arn:aws:cognito-identity:us-east-1:000000000000:identitypool/" + poolID @@ -171,7 +173,7 @@ func TestListTagsForResource_EmptyTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Tags should always be present (even if empty), not omitted. tags, hasKey := out["Tags"] diff --git a/services/cognitoidentity/handler_test.go b/services/cognitoidentity/handler_test.go index d9c02b54f2..c644d91277 100644 --- a/services/cognitoidentity/handler_test.go +++ b/services/cognitoidentity/handler_test.go @@ -3,7 +3,7 @@ package cognitoidentity_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidentity" ) @@ -62,7 +63,7 @@ func doCognitoIdentityRequest( ) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -338,7 +339,7 @@ func TestNonNilSlices(t *testing.T) { require.Equal(t, http.StatusOK, listPoolsRec.Code) var listPoolsOut map[string]any - require.NoError(t, json.Unmarshal(listPoolsRec.Body.Bytes(), &listPoolsOut)) + require.NoError(t, json.Unmarshal(listPoolsRec.Body.Bytes(), &listPoolsOut, jsonx.V1())) pools, poolsOK := listPoolsOut["IdentityPools"].([]any) require.True(t, poolsOK, "IdentityPools should be a non-null array") assert.Empty(t, pools) @@ -351,7 +352,7 @@ func TestNonNilSlices(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) listIDsRec := doCognitoIdentityRequest(t, h, "ListIdentities", map[string]any{ "IdentityPoolId": created["IdentityPoolId"], @@ -360,7 +361,7 @@ func TestNonNilSlices(t *testing.T) { require.Equal(t, http.StatusOK, listIDsRec.Code) var listIDsOut map[string]any - require.NoError(t, json.Unmarshal(listIDsRec.Body.Bytes(), &listIDsOut)) + require.NoError(t, json.Unmarshal(listIDsRec.Body.Bytes(), &listIDsOut, jsonx.V1())) identities, idsOK := listIDsOut["Identities"].([]any) require.True(t, idsOK, "Identities should be a non-null array") assert.Empty(t, identities) @@ -389,7 +390,7 @@ func TestSeedHelpers(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "seeded-pool", descOut["IdentityPoolName"]) // Seed an identity. @@ -420,7 +421,7 @@ func TestCountHelpers(t *testing.T) { assert.Equal(t, 1, h.Backend.PoolCount()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) doCognitoIdentityRequest(t, h, "GetId", map[string]any{ @@ -470,7 +471,7 @@ func TestPersistenceRoundTripWithPrincipalTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) poolID := created["IdentityPoolId"].(string) setRec := doCognitoIdentityRequest(t, h, "SetPrincipalTagAttributeMap", map[string]any{ @@ -497,7 +498,7 @@ func TestPersistenceRoundTripWithPrincipalTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) tags, _ := getOut["PrincipalTags"].(map[string]any) assert.Equal(t, "user_id", tags["sub"]) diff --git a/services/cognitoidentity/login_tokens.go b/services/cognitoidentity/login_tokens.go index 2e2508b215..ace6363d06 100644 --- a/services/cognitoidentity/login_tokens.go +++ b/services/cognitoidentity/login_tokens.go @@ -5,10 +5,12 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const jwtSegments = 3 @@ -28,7 +30,7 @@ func parseLoginClaims(token string) (map[string]any, bool) { } var claims map[string]any - if json.Unmarshal(raw, &claims) != nil { + if json.Unmarshal(raw, &claims, jsonx.V1()) != nil { return nil, false } @@ -123,7 +125,7 @@ func (b *InMemoryBackend) verifyLoginSignature(token string, claims map[string]a Kid string `json:"kid"` } - if json.Unmarshal(rawHdr, &hdr) != nil { + if json.Unmarshal(rawHdr, &hdr, jsonx.V1()) != nil { return errBadSignature } diff --git a/services/cognitoidentity/open_id_token.go b/services/cognitoidentity/open_id_token.go index 0c3a422439..8fabc51b7b 100644 --- a/services/cognitoidentity/open_id_token.go +++ b/services/cognitoidentity/open_id_token.go @@ -2,9 +2,11 @@ package cognitoidentity import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -50,12 +52,12 @@ func issueOpenIDToken( claims[openIDTagsClaim] = tags } - header, err := json.Marshal(map[string]string{"alg": "RS256", "typ": "JWT"}) + header, err := json.Marshal(map[string]string{"alg": "RS256", "typ": "JWT"}, jsonx.V1()) if err != nil { return "", fmt.Errorf("encode token header: %w", err) } - payload, err := json.Marshal(claims) + payload, err := json.Marshal(claims, jsonx.V1()) if err != nil { return "", fmt.Errorf("encode token claims: %w", err) } diff --git a/services/cognitoidentity/open_id_token_test.go b/services/cognitoidentity/open_id_token_test.go index c58450d76f..0acec5afd5 100644 --- a/services/cognitoidentity/open_id_token_test.go +++ b/services/cognitoidentity/open_id_token_test.go @@ -3,13 +3,14 @@ package cognitoidentity_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidentity" ) @@ -23,7 +24,7 @@ func tokenClaims(t *testing.T, token string) map[string]any { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) return claims } diff --git a/services/cognitoidentity/persistence.go b/services/cognitoidentity/persistence.go index 77baf9ce8b..edab853440 100644 --- a/services/cognitoidentity/persistence.go +++ b/services/cognitoidentity/persistence.go @@ -2,7 +2,7 @@ package cognitoidentity import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -60,10 +60,10 @@ func principalTagDTOKeyFn(d *principalTagDTO) string { // decoding a snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // persistenceDTOTables groups every ephemeral DTO table buildPersistenceDTORegistry @@ -225,7 +225,7 @@ func unwrapPrincipalTagDTOs(dtos *store.Table[principalTagDTO]) []*PrincipalTagM // restoreResourceTables rebuilds every store.Table on b from snap's per-table JSON, // factored out of Restore to keep Restore's own cognitive complexity low. Callers must // hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/cognitoidentity/persistence_test.go b/services/cognitoidentity/persistence_test.go index 03e51cd828..2db79aa3a9 100644 --- a/services/cognitoidentity/persistence_test.go +++ b/services/cognitoidentity/persistence_test.go @@ -5,11 +5,13 @@ package cognitoidentity //nolint:testpackage // see comment above. import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // testRegionEast and testRegionWest are the two AWS regions exercised by the @@ -220,11 +222,11 @@ func tamperSnapshotVersion(t *testing.T, data []byte, version int) []byte { t.Helper() var snap backendSnapshot - require.NoError(t, json.Unmarshal(data, &snap)) + require.NoError(t, json.Unmarshal(data, &snap, jsonx.V1())) snap.Version = version - out, err := json.Marshal(snap) + out, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) return out diff --git a/services/cognitoidentity/role_selection_test.go b/services/cognitoidentity/role_selection_test.go index da45ff0814..d680153e95 100644 --- a/services/cognitoidentity/role_selection_test.go +++ b/services/cognitoidentity/role_selection_test.go @@ -7,7 +7,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "maps" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidentity" ) @@ -302,10 +303,10 @@ func signedLogin(t *testing.T, key *rsa.PrivateKey, claims map[string]any) strin t.Helper() enc := base64.RawURLEncoding - hdr, err := json.Marshal(map[string]string{"alg": "RS256", "kid": "k1"}) + hdr, err := json.Marshal(map[string]string{"alg": "RS256", "kid": "k1"}, jsonx.V1()) require.NoError(t, err) - body, err := json.Marshal(claims) + body, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) signing := enc.EncodeToString(hdr) + "." + enc.EncodeToString(body) diff --git a/services/cognitoidentity/sdk_login_token_flow_test.go b/services/cognitoidentity/sdk_login_token_flow_test.go index 5bfd506f30..4b311b909b 100644 --- a/services/cognitoidentity/sdk_login_token_flow_test.go +++ b/services/cognitoidentity/sdk_login_token_flow_test.go @@ -2,7 +2,7 @@ package cognitoidentity_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,16 +11,18 @@ import ( sdktypes "github.com/aws/aws-sdk-go-v2/service/cognitoidentity/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func loginJWT(t *testing.T, claims map[string]any) string { t.Helper() enc := base64.RawURLEncoding - hdr, err := json.Marshal(map[string]string{"alg": "RS256"}) + hdr, err := json.Marshal(map[string]string{"alg": "RS256"}, jsonx.V1()) require.NoError(t, err) - body, err := json.Marshal(claims) + body, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) return enc.EncodeToString(hdr) + "." + enc.EncodeToString(body) + ".sig" diff --git a/services/cognitoidp/attribute_custom_message_test.go b/services/cognitoidp/attribute_custom_message_test.go index 2995a2693e..ebdc03c0a1 100644 --- a/services/cognitoidp/attribute_custom_message_test.go +++ b/services/cognitoidp/attribute_custom_message_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -134,7 +135,7 @@ func TestUpdateUserAttributes_CustomMessageAndVerification(t *testing.T) { var resp struct { CodeDeliveryDetailsList []map[string]string `json:"CodeDeliveryDetailsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.CodeDeliveryDetailsList, 1) d := resp.CodeDeliveryDetailsList[0] @@ -216,7 +217,7 @@ func TestGetUserAttributeVerificationCode_FiresCustomMessage(t *testing.T) { var resp struct { CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "subj", resp.CodeDeliveryDetails["CustomMessageSubject"]) assert.Equal(t, "v", metadataOf(t, f.inv, "CustomMessage_VerifyUserAttribute", "clientMetadata")["k"]) } diff --git a/services/cognitoidp/attributes_management_test.go b/services/cognitoidp/attributes_management_test.go index 2e1e66fffb..427f854258 100644 --- a/services/cognitoidp/attributes_management_test.go +++ b/services/cognitoidp/attributes_management_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -133,7 +134,7 @@ func TestHandler_UpdateUserAttributes(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "attr-pool"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -141,7 +142,7 @@ func TestHandler_UpdateUserAttributes(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -168,7 +169,7 @@ func TestHandler_UpdateUserAttributes(t *testing.T) { require.Equal(t, http.StatusOK, authRec.Code) var authData map[string]map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authData)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authData, jsonx.V1())) accessToken := authData["AuthenticationResult"]["AccessToken"].(string) if tt.badToken { @@ -221,7 +222,7 @@ func TestHandler_AdminUpdateUserAttributes(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "admin-attr-pool"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -256,7 +257,7 @@ func TestHandler_AddCustomAttributes(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "test-pool"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["UserPool"].(map[string]any)["Id"].(string) }, @@ -334,7 +335,7 @@ func TestHandler_AdminDeleteUserAttributes(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "attr-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) username := "deleteattr-user" @@ -393,7 +394,7 @@ func TestAddCustomAttributes_RequiresCustomPrefix(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "prefix-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) rec := doCognitoRequest(t, h, "AddCustomAttributes", map[string]any{ @@ -413,7 +414,7 @@ func TestSortedAttributes(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "sorted-attrs-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -434,7 +435,7 @@ func TestSortedAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) attrs := resp["UserAttributes"].([]any) require.GreaterOrEqual(t, len(attrs), 3) @@ -462,7 +463,7 @@ func TestIDTokenUserAttributeClaims(t *testing.T) { require.Equal(t, http.StatusOK, poolRec.Code) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -476,7 +477,7 @@ func TestIDTokenUserAttributeClaims(t *testing.T) { require.Equal(t, http.StatusOK, clientRec.Code) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) signupRec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -490,7 +491,7 @@ func TestIDTokenUserAttributeClaims(t *testing.T) { require.Equal(t, http.StatusOK, signupRec.Code) var signupResp map[string]any - require.NoError(t, json.Unmarshal(signupRec.Body.Bytes(), &signupResp)) + require.NoError(t, json.Unmarshal(signupRec.Body.Bytes(), &signupResp, jsonx.V1())) // AutoVerifiedAttributes only selects which channel receives the confirmation // code; it does not skip confirmation itself (see TestSignUpWithValidation_AutoVerify). @@ -513,7 +514,7 @@ func TestIDTokenUserAttributeClaims(t *testing.T) { require.Equal(t, http.StatusOK, authRec.Code) var authResp map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) idToken := authResp["AuthenticationResult"].(map[string]any)["IdToken"].(string) claims := decodeJWTPayload(t, idToken) @@ -550,7 +551,7 @@ func TestAdminCreateUserSubInAttributes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) user := resp["User"].(map[string]any) attrs := user["Attributes"].([]any) diff --git a/services/cognitoidp/attributes_test.go b/services/cognitoidp/attributes_test.go index 6fbe0dd2c6..1d95583949 100644 --- a/services/cognitoidp/attributes_test.go +++ b/services/cognitoidp/attributes_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -50,7 +51,7 @@ func TestVerifyUserAttribute_RealCodeValidation(t *testing.T) { ID string `json:"Id"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -63,7 +64,7 @@ func TestVerifyUserAttribute_RealCodeValidation(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp.UserPoolClient.ClientID // Sign up and confirm a user with email attribute. @@ -117,7 +118,7 @@ func TestVerifyUserAttribute_RealCodeValidation(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) @@ -279,7 +280,7 @@ func TestAttrVerification_Handler_GetAndVerify(t *testing.T) { CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails,omitempty"` UserConfirmed bool `json:"UserConfirmed,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp, jsonx.V1())) if !signUpResp.UserConfirmed { confirmRec := doCognitoRequest(t, h, "ConfirmSignUp", map[string]any{ @@ -302,7 +303,7 @@ func TestAttrVerification_Handler_GetAndVerify(t *testing.T) { var codeOut struct { CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &codeOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &codeOut, jsonx.V1())) assert.Equal(t, "EMAIL", codeOut.CodeDeliveryDetails["DeliveryMedium"]) assert.NotEmpty(t, codeOut.CodeDeliveryDetails["Destination"]) @@ -449,7 +450,7 @@ func TestHandler_VerifyUserAttribute(t *testing.T) { CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails,omitempty"` UserConfirmed bool `json:"UserConfirmed,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp, jsonx.V1())) if !signUpResp.UserConfirmed { confirmRec := doCognitoRequest(t, h, "ConfirmSignUp", map[string]any{ diff --git a/services/cognitoidp/auth_events_test.go b/services/cognitoidp/auth_events_test.go index 542559a798..572835285a 100644 --- a/services/cognitoidp/auth_events_test.go +++ b/services/cognitoidp/auth_events_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAdminListUserAuthEvents_EmptyButValid(t *testing.T) { @@ -26,7 +28,7 @@ func TestAdminListUserAuthEvents_EmptyButValid(t *testing.T) { NextToken string `json:"NextToken,omitempty"` AuthEvents []map[string]any `json:"AuthEvents,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.AuthEvents) assert.Empty(t, resp.NextToken) @@ -122,7 +124,7 @@ func TestHandler_AdminListUserAuthEvents_Validation(t *testing.T) { var resp struct { AuthEvents []map[string]any `json:"AuthEvents"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.AuthEvents) }) diff --git a/services/cognitoidp/auth_tokens.go b/services/cognitoidp/auth_tokens.go index e6e1fe8a41..205fddc51e 100644 --- a/services/cognitoidp/auth_tokens.go +++ b/services/cognitoidp/auth_tokens.go @@ -4,7 +4,7 @@ import ( "crypto/rsa" "crypto/subtle" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -12,6 +12,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // findUserByAccessTokenLocked finds the live *User for a given access token. @@ -104,7 +106,7 @@ func tokenHeaderKID(token string) string { var hdr struct { Kid string `json:"kid"` } - if json.Unmarshal(raw, &hdr) != nil { + if json.Unmarshal(raw, &hdr, jsonx.V1()) != nil { return "" } diff --git a/services/cognitoidp/auth_tokens_test.go b/services/cognitoidp/auth_tokens_test.go index 7cdb43f65c..87595510c2 100644 --- a/services/cognitoidp/auth_tokens_test.go +++ b/services/cognitoidp/auth_tokens_test.go @@ -2,7 +2,7 @@ package cognitoidp_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -121,7 +122,7 @@ func initiateAuth(t *testing.T, h *cognitoidp.Handler, clientID, username string require.Equal(t, http.StatusOK, rec.Code, "InitiateAuth: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -137,7 +138,7 @@ func jwtClaims(t *testing.T, token string) map[string]any { require.NoError(t, err, "base64 decode JWT payload") var claims map[string]any - require.NoError(t, json.Unmarshal(payload, &claims)) + require.NoError(t, json.Unmarshal(payload, &claims, jsonx.V1())) return claims } @@ -217,7 +218,7 @@ func TestTokenValidity_StoredAndReturned(t *testing.T) { RefreshTokenValidity int32 `json:"RefreshTokenValidity"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, tt.accessTokenValidity, createResp.UserPoolClient.AccessTokenValidity) assert.Equal(t, tt.idTokenValidity, createResp.UserPoolClient.IDTokenValidity) assert.Equal(t, wantRefresh, createResp.UserPoolClient.RefreshTokenValidity) @@ -242,7 +243,7 @@ func TestTokenValidity_StoredAndReturned(t *testing.T) { RefreshTokenValidity int32 `json:"RefreshTokenValidity"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.accessTokenValidity, descResp.UserPoolClient.AccessTokenValidity) assert.Equal(t, tt.idTokenValidity, descResp.UserPoolClient.IDTokenValidity) assert.Equal(t, wantRefresh, descResp.UserPoolClient.RefreshTokenValidity) @@ -268,7 +269,7 @@ func TestTokenValidity_UpdateClient(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clientID := createResp.UserPoolClient.ClientID // Update with validity settings. @@ -291,7 +292,7 @@ func TestTokenValidity_UpdateClient(t *testing.T) { IDTokenValidity int32 `json:"IdTokenValidity"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) assert.Equal(t, int32(5), updResp.UserPoolClient.AccessTokenValidity) assert.Equal(t, int32(5), updResp.UserPoolClient.IDTokenValidity) assert.Equal(t, "minutes", updResp.UserPoolClient.TokenValidityUnits["AccessToken"]) @@ -349,7 +350,7 @@ func TestTokenValidity_HonoredInJWT(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clientID := createResp.UserPoolClient.ClientID // Create and confirm a user, then authenticate. @@ -374,7 +375,7 @@ func TestTokenValidity_HonoredInJWT(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &cr, jsonx.V1())) validClientID := cr.UserPoolClient.ClientID // Sign up user with the validity client. @@ -409,7 +410,7 @@ func TestTokenValidity_HonoredInJWT(t *testing.T) { ExpiresIn float64 `json:"ExpiresIn"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) assert.InDelta(t, tt.wantExpiresIn, authResp.AuthenticationResult.ExpiresIn, 1, "ExpiresIn mismatch") @@ -464,7 +465,7 @@ func TestAuthFlow_RefreshTokenAuth(t *testing.T) { require.Equal(t, http.StatusOK, refreshRec.Code, refreshRec.Body.String()) var refreshResp map[string]any - require.NoError(t, json.Unmarshal(refreshRec.Body.Bytes(), &refreshResp)) + require.NoError(t, json.Unmarshal(refreshRec.Body.Bytes(), &refreshResp, jsonx.V1())) result, ok := refreshResp["AuthenticationResult"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, result["AccessToken"]) @@ -587,7 +588,7 @@ func TestGetTokensFromRefreshToken(t *testing.T) { RefreshToken string `json:"RefreshToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) refreshToken := authResp.AuthenticationResult.RefreshToken require.NotEmpty(t, refreshToken) @@ -603,7 +604,7 @@ func TestGetTokensFromRefreshToken(t *testing.T) { AccessToken string `json:"AccessToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tokenResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tokenResp, jsonx.V1())) assert.NotEmpty(t, tokenResp.AuthenticationResult.AccessToken) } @@ -702,7 +703,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -710,7 +711,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) // Admin-create a confirmed user. @@ -737,7 +738,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { }, }) var authResp map[string]map[string]any - _ = json.Unmarshal(authRec.Body.Bytes(), &authResp) + _ = json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1()) rt := authResp["AuthenticationResult"]["RefreshToken"].(string) return clientID, rt @@ -749,7 +750,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p2"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -757,7 +758,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { "ClientName": "c2", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) return clientID, "totally-invalid-refresh-token" @@ -784,7 +785,7 @@ func TestHandler_RefreshTokenAuth(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["AuthenticationResult"]["AccessToken"]) assert.NotEmpty(t, resp["AuthenticationResult"]["IdToken"]) // After rotation the old token is gone; new one must differ. diff --git a/services/cognitoidp/branding_test.go b/services/cognitoidp/branding_test.go index b9068587fa..fc1fd999b0 100644 --- a/services/cognitoidp/branding_test.go +++ b/services/cognitoidp/branding_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestUICustomization_SetGet_WithImageFile(t *testing.T) { @@ -33,7 +35,7 @@ func TestUICustomization_SetGet_WithImageFile(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"UICustomization"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) require.NotNil(t, setOut.UICustomization) assert.Equal(t, poolID, setOut.UICustomization.UserPoolID) assert.Equal(t, clientID, setOut.UICustomization.ClientID) @@ -56,7 +58,7 @@ func TestUICustomization_SetGet_WithImageFile(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"UICustomization"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) require.NotNil(t, getOut.UICustomization) assert.Equal(t, ".banner { background: blue; }", getOut.UICustomization.CSS) assert.Empty(t, getOut.UICustomization.ImageURL) @@ -80,7 +82,7 @@ func TestUICustomization_Get_Empty(t *testing.T) { CSS string `json:"CSS,omitempty"` } `json:"UICustomization"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.UICustomization) assert.Empty(t, out.UICustomization.CSS) } @@ -155,7 +157,7 @@ func TestUICustomization(t *testing.T) { CSS string `json:"CSS,omitempty"` } `json:"UICustomization"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, ".logo { color: red; }", resp.UICustomization.CSS) } @@ -177,7 +179,7 @@ func TestManagedLoginBranding_CRUD(t *testing.T) { ManagedLoginBrandingID string `json:"ManagedLoginBrandingId,omitempty"` } `json:"ManagedLoginBranding"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) brandingID := createResp.ManagedLoginBranding.ManagedLoginBrandingID require.NotEmpty(t, brandingID) @@ -252,7 +254,7 @@ func TestManagedLoginBranding_SettingsAndAssets_AcceptedAndEchoed(t *testing.T) var createResp struct { ManagedLoginBranding managedLoginBrandingWire `json:"ManagedLoginBranding"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) componentClasses, ok := createResp.ManagedLoginBranding.Settings["componentClasses"].(map[string]any) require.True(t, ok) primaryButton, ok := componentClasses["primaryButton"].(map[string]any) @@ -277,7 +279,7 @@ func TestManagedLoginBranding_SettingsAndAssets_AcceptedAndEchoed(t *testing.T) var updResp struct { ManagedLoginBranding managedLoginBrandingWire `json:"ManagedLoginBranding"` } - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) assert.True(t, updResp.ManagedLoginBranding.UseCognitoProvidedValues) assert.NotEmpty(t, updResp.ManagedLoginBranding.Settings, "omitting Settings on update must not clear it") } diff --git a/services/cognitoidp/delivery_test.go b/services/cognitoidp/delivery_test.go index c675ba3506..4e71afa573 100644 --- a/services/cognitoidp/delivery_test.go +++ b/services/cognitoidp/delivery_test.go @@ -2,7 +2,7 @@ package cognitoidp_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -76,7 +77,7 @@ func newDeliveryEnv(t *testing.T, poolExtra map[string]any) *deliveryEnv { } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pool)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pool, jsonx.V1())) rec = doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ "UserPoolId": pool.UserPool.ID, "ClientName": "app", @@ -89,7 +90,7 @@ func newDeliveryEnv(t *testing.T, poolExtra map[string]any) *deliveryEnv { } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &client)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &client, jsonx.V1())) return &deliveryEnv{h: h, snd: snd, poolID: pool.UserPool.ID, clientID: client.UserPoolClient.ClientID} } @@ -102,7 +103,7 @@ func (e *deliveryEnv) call(t *testing.T, action string, body map[string]any) map var out map[string]any if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out diff --git a/services/cognitoidp/devices_test.go b/services/cognitoidp/devices_test.go index d4c2b484e2..662d044d2d 100644 --- a/services/cognitoidp/devices_test.go +++ b/services/cognitoidp/devices_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -33,7 +34,7 @@ func TestDevices_CRUD(t *testing.T) { var confirmResp struct { UserConfirmationNecessary bool `json:"UserConfirmationNecessary,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &confirmResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &confirmResp, jsonx.V1())) assert.False(t, confirmResp.UserConfirmationNecessary) // ListDevices — discover the generated device key. @@ -43,7 +44,7 @@ func TestDevices_CRUD(t *testing.T) { var listResp struct { Devices []deviceWire `json:"Devices,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Devices, 1) assert.NotContains(t, rec.Body.String(), "DeviceStatus") deviceKey := listResp.Devices[0].DeviceKey @@ -59,7 +60,7 @@ func TestDevices_CRUD(t *testing.T) { var getResp struct { Device deviceWire `json:"Device"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, deviceKey, getResp.Device.DeviceKey) // UpdateDeviceStatus -> remembered @@ -75,7 +76,7 @@ func TestDevices_CRUD(t *testing.T) { "DeviceKey": deviceKey, }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, rec.Body.String(), "DeviceStatus") // ForgetDevice @@ -101,7 +102,7 @@ func TestDevices_CRUD(t *testing.T) { var listResp2 struct { Devices []deviceWire `json:"Devices,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2, jsonx.V1())) assert.Empty(t, listResp2.Devices) } @@ -144,7 +145,7 @@ func TestDevices_NotFoundAndValidation(t *testing.T) { var listResp struct { Devices []deviceWire `json:"Devices,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Devices, 1) rec = doCognitoRequest(t, h, "UpdateDeviceStatus", map[string]any{ @@ -179,7 +180,7 @@ func TestAdminDevices_CRUD(t *testing.T) { var listResp struct { Devices []deviceWire `json:"Devices,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Devices, 1) assert.Equal(t, "device-abc", listResp.Devices[0].DeviceKey) @@ -210,7 +211,7 @@ func TestAdminDevices_CRUD(t *testing.T) { var getResp struct { Device deviceWire `json:"Device"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, rec.Body.String(), "DeviceStatus") // AdminForgetDevice — real deletion when the device exists. @@ -395,7 +396,7 @@ func TestHandler_AdminForgetDevice(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "device-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) username := "device-user" diff --git a/services/cognitoidp/domains_test.go b/services/cognitoidp/domains_test.go index 8bf070a1a2..017b878e72 100644 --- a/services/cognitoidp/domains_test.go +++ b/services/cognitoidp/domains_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -79,7 +80,7 @@ func TestCreateUserPoolDomain_ManagedDomain_EmptyCloudFrontDomain(t *testing.T) var out struct { CloudFrontDomain string `json:"CloudFrontDomain,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantCF { assert.Contains( @@ -114,7 +115,7 @@ func TestUserPoolDomain_Managed(t *testing.T) { var out struct { CloudFrontDomain string `json:"CloudFrontDomain,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Managed domains: AWS returns empty CloudFrontDomain (no CloudFront distribution). assert.Empty(t, out.CloudFrontDomain) } @@ -139,7 +140,7 @@ func TestUserPoolDomain_Custom_WithCertArn(t *testing.T) { var createOut struct { CloudFrontDomain string `json:"CloudFrontDomain,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.Contains(t, createOut.CloudFrontDomain, "cloudfront.net") // Describe should return the domain with status. @@ -159,7 +160,7 @@ func TestUserPoolDomain_Custom_WithCertArn(t *testing.T) { CloudFrontDistribution string `json:"CloudFrontDistribution,omitempty"` } `json:"DomainDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.DomainDescription) assert.Equal(t, "auth.mycompany.com", descOut.DomainDescription.Domain) assert.Equal(t, "ACTIVE", descOut.DomainDescription.Status) @@ -197,7 +198,7 @@ func TestUserPoolDomain_Managed_NoCustomDomainConfig(t *testing.T) { } `json:"CustomDomainConfig,omitempty"` } `json:"DomainDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.DomainDescription) assert.Nil(t, descOut.DomainDescription.CustomDomainConfig) } @@ -231,7 +232,7 @@ func TestUserPoolDomain_Update_WithCertArn(t *testing.T) { var out struct { CloudFrontDomain string `json:"CloudFrontDomain,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out.CloudFrontDomain, "cloudfront.net") } @@ -282,7 +283,7 @@ func TestUserPoolDomain_Describe_AWSAccountIdManagedLoginVersionS3Bucket(t *test var createOut struct { ManagedLoginVersion *int32 `json:"ManagedLoginVersion,omitempty"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) require.NotNil(t, createOut.ManagedLoginVersion, "CreateUserPoolDomain must echo ManagedLoginVersion") assert.Equal(t, tt.wantMLV, *createOut.ManagedLoginVersion) @@ -290,7 +291,7 @@ func TestUserPoolDomain_Describe_AWSAccountIdManagedLoginVersionS3Bucket(t *test require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out domainDescriptionWire - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.DomainDescription) assert.NotEmpty(t, out.DomainDescription.AWSAccountID) @@ -323,7 +324,7 @@ func TestUserPoolDomain_UpdateManagedLoginVersion_OmittedRetainsPrior(t *testing var updateOut struct { ManagedLoginVersion *int32 `json:"ManagedLoginVersion,omitempty"` } - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) require.NotNil(t, updateOut.ManagedLoginVersion, "UpdateUserPoolDomain must echo ManagedLoginVersion") assert.EqualValues(t, 2, *updateOut.ManagedLoginVersion, "omitting the field on update must not reset it") } @@ -429,7 +430,7 @@ func TestDomain_FullCRUDLifecycle(t *testing.T) { var createResp struct { CloudFrontDomain string `json:"CloudFrontDomain,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) // Managed domains: AWS returns empty CloudFrontDomain in the create response. assert.Empty(t, createResp.CloudFrontDomain) @@ -445,7 +446,7 @@ func TestDomain_FullCRUDLifecycle(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"DomainDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "myapp", descResp.DomainDescription.Domain) assert.Equal(t, "ACTIVE", descResp.DomainDescription.Status) @@ -475,6 +476,6 @@ func TestDomain_FullCRUDLifecycle(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"DomainDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterDeleteResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterDeleteResp, jsonx.V1())) assert.Empty(t, afterDeleteResp.DomainDescription.Domain) } diff --git a/services/cognitoidp/federation_providers_test.go b/services/cognitoidp/federation_providers_test.go index 70be4d4e4c..2ae4ee5831 100644 --- a/services/cognitoidp/federation_providers_test.go +++ b/services/cognitoidp/federation_providers_test.go @@ -5,7 +5,8 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "net/http" "net/http/httptest" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -48,7 +50,10 @@ func newOAuth2OnlyIDP(t *testing.T, profile map[string]any) *oauth2OnlyIDP { return } - _ = json.NewEncoder(w).Encode(map[string]string{"access_token": "opaque-token", "token_type": "bearer"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"access_token": "opaque-token", "token_type": "bearer"}, + ) case "/me": if r.Header.Get("Authorization") != "Bearer opaque-token" { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -56,7 +61,7 @@ func newOAuth2OnlyIDP(t *testing.T, profile map[string]any) *oauth2OnlyIDP { return } - _ = json.NewEncoder(w).Encode(idp.profile) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), idp.profile) default: http.NotFound(w, r) } diff --git a/services/cognitoidp/groups_membership_test.go b/services/cognitoidp/groups_membership_test.go index 25c01a9d76..47843fd197 100644 --- a/services/cognitoidp/groups_membership_test.go +++ b/services/cognitoidp/groups_membership_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -236,7 +237,7 @@ func TestHandler_Groups_CRUD(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "grp-pool"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) // Create a group. @@ -283,7 +284,7 @@ func TestHandler_Groups_CRUD(t *testing.T) { assert.Equal(t, http.StatusOK, listRec2.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp, jsonx.V1())) groups := listResp["Groups"].([]any) assert.Empty(t, groups) } @@ -334,7 +335,7 @@ func TestHandler_AdminGroupMembership(t *testing.T) { // Set up pool, user, and group. poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "grp-pool"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -405,7 +406,7 @@ func TestHandler_AdminGroupMembership(t *testing.T) { }) assert.Equal(t, http.StatusOK, listRec2.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp, jsonx.V1())) groups := listResp["Groups"].([]any) assert.Empty(t, groups) } @@ -419,7 +420,7 @@ func TestSortedListGroups(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "sorted-groups-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) for _, name := range []string{"zeta", "admin", "moderator"} { @@ -433,7 +434,7 @@ func TestSortedListGroups(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) groups := listResp["Groups"].([]any) require.Len(t, groups, 3) assert.Equal(t, "admin", groups[0].(map[string]any)["GroupName"]) @@ -447,7 +448,7 @@ func TestSortedAdminListGroupsForUser(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "algfu-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) for _, g := range []string{"zeta", "alpha", "beta"} { @@ -478,7 +479,7 @@ func TestSortedAdminListGroupsForUser(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["Groups"].([]any) require.Len(t, groups, 3) assert.Equal(t, "alpha", groups[0].(map[string]any)["GroupName"]) @@ -537,7 +538,7 @@ func TestAccessTokenGroupsClaim(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) authResult := resp["AuthenticationResult"].(map[string]any) accessToken := authResult["AccessToken"].(string) @@ -593,7 +594,7 @@ func TestAdminListGroupsForUser_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body) var page1 listOut - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Groups, 2) require.NotEmpty(t, page1.NextToken, "first page must return a cursor when more groups remain") @@ -606,7 +607,7 @@ func TestAdminListGroupsForUser_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body) var page2 listOut - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.Groups, 1) require.Empty(t, page2.NextToken) diff --git a/services/cognitoidp/groups_test.go b/services/cognitoidp/groups_test.go index 90926953b9..bb35148c7f 100644 --- a/services/cognitoidp/groups_test.go +++ b/services/cognitoidp/groups_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -186,7 +187,7 @@ func TestGroup_LastModifiedDate_Present(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Greater(t, out.Group.LastModifiedDate, float64(0), "CreateGroup must include LastModifiedDate") @@ -202,7 +203,7 @@ func TestGroup_LastModifiedDate_Present(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Greater(t, out.Group.LastModifiedDate, float64(0), "GetGroup must include LastModifiedDate") @@ -219,7 +220,7 @@ func TestGroup_LastModifiedDate_Present(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Greater(t, out.Group.LastModifiedDate, float64(0), "UpdateGroup must include LastModifiedDate") @@ -235,7 +236,7 @@ func TestGroup_LastModifiedDate_Present(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Groups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Groups, 1) assert.Greater( t, @@ -273,7 +274,7 @@ func TestGroup_Create_WithRoleArn(t *testing.T) { CreationDate float64 `json:"CreationDate,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Equal(t, "admins", out.Group.GroupName) assert.Equal(t, roleArn, out.Group.RoleArn) @@ -310,7 +311,7 @@ func TestGroup_Update_WithRoleArn(t *testing.T) { Precedence int32 `json:"Precedence,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Equal(t, roleArn, out.Group.RoleArn) assert.Equal(t, int32(10), out.Group.Precedence) @@ -344,7 +345,7 @@ func TestGroup_GetGroup_WithRoleArn(t *testing.T) { Precedence int32 `json:"Precedence,omitempty"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Group) assert.Equal(t, "viewers", out.Group.GroupName) assert.Equal(t, roleArn, out.Group.RoleArn) @@ -378,7 +379,7 @@ func TestGroup_ListGroups_Pagination(t *testing.T) { GroupName string `json:"GroupName,omitempty"` } `json:"Groups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Groups, 3) assert.NotEmpty(t, page1.NextToken) @@ -396,7 +397,7 @@ func TestGroup_ListGroups_Pagination(t *testing.T) { GroupName string `json:"GroupName,omitempty"` } `json:"Groups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Groups, 2) assert.Empty(t, page2.NextToken) } @@ -439,7 +440,7 @@ func TestGroup_ListUsersInGroup_Pagination(t *testing.T) { Username string `json:"Username,omitempty"` } `json:"Users"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Users, 2) assert.NotEmpty(t, page1.NextToken) @@ -458,7 +459,7 @@ func TestGroup_ListUsersInGroup_Pagination(t *testing.T) { Username string `json:"Username,omitempty"` } `json:"Users"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Users, 2) assert.Empty(t, page2.NextToken) } diff --git a/services/cognitoidp/handler.go b/services/cognitoidp/handler.go index 6a8436f438..9a42222061 100644 --- a/services/cognitoidp/handler.go +++ b/services/cognitoidp/handler.go @@ -2,7 +2,7 @@ package cognitoidp import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -321,7 +322,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { Username string `json:"Username,omitempty"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.UserPoolID != "" { return req.UserPoolID @@ -421,7 +422,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -483,11 +484,11 @@ func resolveErrorType(err error) (string, int) { } } - if _, ok := errors.AsType[*json.SyntaxError](err); ok { + if _, ok := jsonx.SyntaxError(err); ok { return errInvalidParameterException, http.StatusBadRequest } - if _, ok := errors.AsType[*json.UnmarshalTypeError](err); ok { + if _, ok := jsonx.TypeError(err); ok { return errInvalidParameterException, http.StatusBadRequest } diff --git a/services/cognitoidp/handler_auth_challenges_test.go b/services/cognitoidp/handler_auth_challenges_test.go index 9f4e911a55..7397225c0a 100644 --- a/services/cognitoidp/handler_auth_challenges_test.go +++ b/services/cognitoidp/handler_auth_challenges_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -153,7 +154,7 @@ func TestHandler_AdminRespondToAuthChallenge(t *testing.T) { var authResp struct { Session string `json:"Session,omitempty"` } - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) session = authResp.Session } @@ -234,7 +235,7 @@ func TestHandler_RespondToAuthChallenge_NewPassword(t *testing.T) { Session string `json:"Session,omitempty"` } - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) session = authResp.Session } @@ -259,7 +260,7 @@ func TestHandler_ForgotPasswordConfirmForgotPassword(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "fp-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -267,7 +268,7 @@ func TestHandler_ForgotPasswordConfirmForgotPassword(t *testing.T) { "ClientName": "fp-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -288,7 +289,7 @@ func TestHandler_ForgotPasswordConfirmForgotPassword(t *testing.T) { assert.Equal(t, http.StatusOK, fpRec.Code) var fpResp map[string]any - require.NoError(t, json.Unmarshal(fpRec.Body.Bytes(), &fpResp)) + require.NoError(t, json.Unmarshal(fpRec.Body.Bytes(), &fpResp, jsonx.V1())) details := fpResp["CodeDeliveryDetails"].(map[string]any) code := details["ConfirmationCode"].(string) require.NotEmpty(t, code) @@ -330,7 +331,7 @@ func TestHandler_GetUser_ChangePassword(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "gu-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -338,7 +339,7 @@ func TestHandler_GetUser_ChangePassword(t *testing.T) { "ClientName": "gu-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -366,7 +367,7 @@ func TestHandler_GetUser_ChangePassword(t *testing.T) { assert.Equal(t, http.StatusOK, authRec.Code) var authResp map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) authResult := authResp["AuthenticationResult"].(map[string]any) accessToken := authResult["AccessToken"].(string) @@ -377,7 +378,7 @@ func TestHandler_GetUser_ChangePassword(t *testing.T) { assert.Equal(t, http.StatusOK, guRec.Code) var guResp map[string]any - require.NoError(t, json.Unmarshal(guRec.Body.Bytes(), &guResp)) + require.NoError(t, json.Unmarshal(guRec.Body.Bytes(), &guResp, jsonx.V1())) assert.Equal(t, "guuser", guResp["Username"]) // ChangePassword with wrong old password must fail. @@ -497,7 +498,7 @@ func TestForgotPassword_UserStateValidation(t *testing.T) { if tt.wantType != "" { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) } }) @@ -558,7 +559,7 @@ func TestResendConfirmationCodeAlreadyConfirmed(t *testing.T) { if tt.wantType != "" { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) } }) @@ -607,7 +608,7 @@ func TestConfirmForgotPasswordExpiryCheckedFirst(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fpResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fpResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fpResp, jsonx.V1())) code := tt.useCode if code == "" { @@ -626,7 +627,7 @@ func TestConfirmForgotPasswordExpiryCheckedFirst(t *testing.T) { if tt.wantType != "" { var errResp map[string]any - require.NoError(t, json.Unmarshal(confirmRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(confirmRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) } }) diff --git a/services/cognitoidp/handler_auth_flows_test.go b/services/cognitoidp/handler_auth_flows_test.go index 3b46f04439..8e717ea352 100644 --- a/services/cognitoidp/handler_auth_flows_test.go +++ b/services/cognitoidp/handler_auth_flows_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -32,7 +33,7 @@ func TestHandler_SignUp_PolicyEnforced(t *testing.T) { ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &poolResp, jsonx.V1())) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ "UserPoolId": poolResp.UserPool.ID, @@ -45,7 +46,7 @@ func TestHandler_SignUp_PolicyEnforced(t *testing.T) { ClientID string `json:"ClientId,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp.UserPoolClient.ClientID rec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -100,7 +101,7 @@ func TestSignUp_ConfirmSignUp(t *testing.T) { ID string `json:"Id"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID if tt.policy != nil { @@ -121,7 +122,7 @@ func TestSignUp_ConfirmSignUp(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp.UserPoolClient.ClientID rec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -153,7 +154,7 @@ func TestHandler_SignUp(t *testing.T) { setup: func(h *cognitoidp.Handler) string { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -161,7 +162,7 @@ func TestHandler_SignUp(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) return clientResp["UserPoolClient"]["ClientId"].(string) }, @@ -225,7 +226,7 @@ func TestHandler_ConfirmSignUp(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -233,7 +234,7 @@ func TestHandler_ConfirmSignUp(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) signupRec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -245,7 +246,7 @@ func TestHandler_ConfirmSignUp(t *testing.T) { // Extract confirm code from the CodeDeliveryDetails in the SignUp response. var signupResp map[string]any - _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp) + _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp, jsonx.V1()) code := "" if details, ok := signupResp["CodeDeliveryDetails"].(map[string]any); ok { code, _ = details["ConfirmationCode"].(string) @@ -260,7 +261,7 @@ func TestHandler_ConfirmSignUp(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -268,7 +269,7 @@ func TestHandler_ConfirmSignUp(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) return clientResp["UserPoolClient"]["ClientId"].(string), "nobody", "123456" }, @@ -308,7 +309,7 @@ func TestHandler_InitiateAuth(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -316,7 +317,7 @@ func TestHandler_InitiateAuth(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) signupRec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -328,7 +329,7 @@ func TestHandler_InitiateAuth(t *testing.T) { // Extract the confirmation code from the SignUp response. var signupResp map[string]any - _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp) + _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp, jsonx.V1()) code := "" if details, ok := signupResp["CodeDeliveryDetails"].(map[string]any); ok { code, _ = details["ConfirmationCode"].(string) @@ -352,7 +353,7 @@ func TestHandler_InitiateAuth(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -360,7 +361,7 @@ func TestHandler_InitiateAuth(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) signupRec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -372,7 +373,7 @@ func TestHandler_InitiateAuth(t *testing.T) { // Extract the confirmation code from the SignUp response. var signupResp map[string]any - _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp) + _ = json.Unmarshal(signupRec.Body.Bytes(), &signupResp, jsonx.V1()) code := "" if details, ok := signupResp["CodeDeliveryDetails"].(map[string]any); ok { code, _ = details["ConfirmationCode"].(string) @@ -431,7 +432,7 @@ func TestHandler_AdminInitiateAuth(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -439,7 +440,7 @@ func TestHandler_AdminInitiateAuth(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) clientID := clientResp["UserPoolClient"]["ClientId"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -495,7 +496,7 @@ func TestHandler_AdminConfirmSignUp(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "admin-confirm-pool"}) var poolData map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolData) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolData, jsonx.V1()) poolID := poolData["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -503,7 +504,7 @@ func TestHandler_AdminConfirmSignUp(t *testing.T) { "ClientName": "c", }) var clientData map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientData) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientData, jsonx.V1()) clientID := clientData["UserPoolClient"]["ClientId"].(string) signupRec := doCognitoRequest(t, h, "SignUp", map[string]any{ @@ -552,7 +553,7 @@ func TestAdminNoSRPAuth(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code, "ADMIN_NO_SRP_AUTH should succeed") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasAuth := resp["AuthenticationResult"] assert.True(t, hasAuth, "must have AuthenticationResult") } diff --git a/services/cognitoidp/handler_auth_tokens_test.go b/services/cognitoidp/handler_auth_tokens_test.go index b0d1bc8e1e..30aa011e0c 100644 --- a/services/cognitoidp/handler_auth_tokens_test.go +++ b/services/cognitoidp/handler_auth_tokens_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_SecretHash_InitiateAuth_Via_HTTP(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_SecretHash_InitiateAuth_Via_HTTP(t *testing.T) { ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -39,7 +41,7 @@ func TestHandler_SecretHash_InitiateAuth_Via_HTTP(t *testing.T) { ClientSecret string `json:"ClientSecret,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp.UserPoolClient.ClientID secret := clientResp.UserPoolClient.ClientSecret require.NotEmpty(t, secret) @@ -56,7 +58,7 @@ func TestHandler_SecretHash_InitiateAuth_Via_HTTP(t *testing.T) { var signUpResp struct { CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails,omitempty"` } - require.NoError(t, json.Unmarshal(signUpRec.Body.Bytes(), &signUpResp)) + require.NoError(t, json.Unmarshal(signUpRec.Body.Bytes(), &signUpResp, jsonx.V1())) code := signUpResp.CodeDeliveryDetails["ConfirmationCode"] confirmRec := doCognitoRequest(t, h, "ConfirmSignUp", map[string]any{ @@ -147,7 +149,7 @@ func TestTokenExpiryFor_Backend(t *testing.T) { RefreshTokenValidity int32 `json:"RefreshTokenValidity"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, tt.validity, createResp.UserPoolClient.AccessTokenValidity) assert.Equal(t, tt.unit, createResp.UserPoolClient.TokenValidityUnits[unitKey]) }) @@ -251,12 +253,12 @@ func TestHandler_RevokeToken(t *testing.T) { // Set up pool, client and confirmed user. poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "revoke-pool"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolRec.Body.String() _ = poolID var poolData map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolData) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolData, jsonx.V1()) pID := poolData["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -264,7 +266,7 @@ func TestHandler_RevokeToken(t *testing.T) { "ClientName": "revoke-client", }) var clientData map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientData) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientData, jsonx.V1()) clientID := clientData["UserPoolClient"]["ClientId"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -292,7 +294,7 @@ func TestHandler_RevokeToken(t *testing.T) { require.Equal(t, http.StatusOK, authRec.Code) var authData map[string]map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authData)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authData, jsonx.V1())) refreshToken := authData["AuthenticationResult"]["RefreshToken"].(string) // RevokeToken should succeed (200). diff --git a/services/cognitoidp/handler_test.go b/services/cognitoidp/handler_test.go index a2098b0f0a..6410a5cd4e 100644 --- a/services/cognitoidp/handler_test.go +++ b/services/cognitoidp/handler_test.go @@ -2,7 +2,7 @@ package cognitoidp_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -191,7 +192,7 @@ func TestHandler_JWKS(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -354,7 +355,7 @@ func TestReset(t *testing.T) { ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID assert.Equal(t, 1, b.UserPoolCount()) @@ -423,7 +424,7 @@ func TestNonNilSlices(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "nil-slices-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) // Empty ListUsers must return [] not null. @@ -484,7 +485,7 @@ func TestExportCountHelpers(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "count-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) assert.Equal(t, 1, h.Backend.UserPoolCount()) @@ -593,7 +594,7 @@ func TestHandler_ProvisionedLimits(t *testing.T) { } var resp provisionedLimitResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "API_CATEGORY", resp.Limit.LimitDefinition.LimitClass) assert.EqualValues(t, 120, resp.Limit.FreeLimitValue) @@ -627,7 +628,7 @@ func TestHandler_ProvisionedLimits_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp provisionedLimitResp - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.EqualValues(t, 200, resp.Limit.ProvisionedLimitValue) assert.EqualValues(t, 50, resp.Limit.FreeLimitValue) } diff --git a/services/cognitoidp/handler_users_lifecycle_test.go b/services/cognitoidp/handler_users_lifecycle_test.go index 45ee339057..c330269275 100644 --- a/services/cognitoidp/handler_users_lifecycle_test.go +++ b/services/cognitoidp/handler_users_lifecycle_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -25,7 +26,7 @@ func TestHandler_AdminGetUser(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) poolID := resp["UserPool"]["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -43,7 +44,7 @@ func TestHandler_AdminGetUser(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string), "nobody" }, @@ -95,7 +96,7 @@ func TestHandler_ListUsers(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "test-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -103,7 +104,7 @@ func TestHandler_ListUsers(t *testing.T) { "ClientName": "test-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) if tt.wantCount > 0 { @@ -120,7 +121,7 @@ func TestHandler_ListUsers(t *testing.T) { assert.Equal(t, tt.wantHTTP, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) users := resp["Users"].([]any) assert.Len(t, users, tt.wantCount) }) @@ -155,7 +156,7 @@ func TestHandler_AdminDeleteUser(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "test-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -163,7 +164,7 @@ func TestHandler_AdminDeleteUser(t *testing.T) { "ClientName": "test-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) // Create the user to delete in the first case. @@ -224,7 +225,7 @@ func TestHandler_AdminDisableEnableUser(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "dis-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) username := "dis-user" @@ -252,7 +253,7 @@ func TestHandler_AdminDisableUser_BlocksAuth(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "block-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -260,7 +261,7 @@ func TestHandler_AdminDisableUser_BlocksAuth(t *testing.T) { "ClientName": "block-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -341,7 +342,7 @@ func TestHandler_ListUsers_ReturnsCorrectEnabledState(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "enabled-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) const username = "state-user" @@ -362,7 +363,7 @@ func TestHandler_ListUsers_ReturnsCorrectEnabledState(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) users := listResp["Users"].([]any) require.Len(t, users, 1) assert.Equal(t, tt.wantEnabled, users[0].(map[string]any)["Enabled"].(bool)) @@ -376,7 +377,7 @@ func TestSortedListUsers(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "sorted-users-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) for _, username := range []string{"zeus", "alice", "bob"} { @@ -391,7 +392,7 @@ func TestSortedListUsers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) users := listResp["Users"].([]any) require.Len(t, users, 3) assert.Equal(t, "alice", users[0].(map[string]any)["Username"]) @@ -405,7 +406,7 @@ func TestAdminGetUser_IncludesEnabledAndModifiedDate(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "agu-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -421,7 +422,7 @@ func TestAdminGetUser_IncludesEnabledAndModifiedDate(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.True(t, resp["Enabled"].(bool)) assert.NotZero(t, resp["UserCreateDate"]) assert.NotZero(t, resp["UserLastModifiedDate"]) @@ -433,7 +434,7 @@ func TestAdminCreateUser_IncludesEnabled(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "acu-enabled-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) rec := doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -444,7 +445,7 @@ func TestAdminCreateUser_IncludesEnabled(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.True(t, resp["User"].(map[string]any)["Enabled"].(bool)) } @@ -454,7 +455,7 @@ func TestRefreshToken_DisabledUserBlocked(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "rt-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -462,7 +463,7 @@ func TestRefreshToken_DisabledUserBlocked(t *testing.T) { "ClientName": "rt-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -488,7 +489,7 @@ func TestRefreshToken_DisabledUserBlocked(t *testing.T) { require.Equal(t, http.StatusOK, authRec.Code) var authResp map[string]any - require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp)) + require.NoError(t, json.Unmarshal(authRec.Body.Bytes(), &authResp, jsonx.V1())) refreshToken := authResp["AuthenticationResult"].(map[string]any)["RefreshToken"].(string) // Disable the user. @@ -525,7 +526,7 @@ func TestListUsers_Pagination(t *testing.T) { ID string `json:"Id"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID for i := range 5 { @@ -555,7 +556,7 @@ func TestListUsers_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.Users), 2) for _, u := range resp.Users { diff --git a/services/cognitoidp/handler_users_test.go b/services/cognitoidp/handler_users_test.go index 51b71a4727..755627cd76 100644 --- a/services/cognitoidp/handler_users_test.go +++ b/services/cognitoidp/handler_users_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -36,7 +37,7 @@ func TestHandler_UserSRPAuth_ViaHTTP(t *testing.T) { Session *string `json:"Session,omitempty"` ChallengeParameters map[string]string `json:"ChallengeParameters,omitempty"` } - require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp, jsonx.V1())) require.NotNil(t, initResp.ChallengeName) assert.Equal(t, "PASSWORD_VERIFIER", *initResp.ChallengeName) @@ -55,7 +56,7 @@ func TestHandler_UserSRPAuth_ViaHTTP(t *testing.T) { AccessToken string `json:"AccessToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(respRec.Body.Bytes(), &tokenResp)) + require.NoError(t, json.Unmarshal(respRec.Body.Bytes(), &tokenResp, jsonx.V1())) require.NotNil(t, tokenResp.AuthenticationResult) assert.NotEmpty(t, tokenResp.AuthenticationResult.AccessToken) } @@ -81,7 +82,7 @@ func TestHandler_AdminCreateUser_PolicyEnforced(t *testing.T) { ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID rec := doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ @@ -144,7 +145,7 @@ func TestAuthFlow_UserPasswordAuth(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) authResult, ok := resp["AuthenticationResult"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, authResult["AccessToken"]) @@ -173,7 +174,7 @@ func TestAdminCreateUser(t *testing.T) { Username string `json:"Username"` } `json:"User"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "adminuser", resp.User.Username) } @@ -200,7 +201,7 @@ func TestAdminCreateUser_Suppress(t *testing.T) { UserStatus string `json:"UserStatus,omitempty"` } `json:"User"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.User) assert.Equal(t, "suppressed-user", out.User.Username) assert.Equal(t, "FORCE_CHANGE_PASSWORD", out.User.UserStatus) @@ -234,7 +235,7 @@ func TestAdminCreateUser_Resend(t *testing.T) { UserStatus string `json:"UserStatus,omitempty"` } `json:"User"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.User) assert.Equal(t, "resend-user", out.User.Username) } @@ -261,7 +262,7 @@ func TestAdminCreateUser_DeliveryMediums(t *testing.T) { Username string `json:"Username,omitempty"` } `json:"User"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.User) assert.Equal(t, "delivery-user", out.User.Username) } @@ -299,7 +300,7 @@ func TestAdminSetUserPassword_Permanent(t *testing.T) { var out struct { UserStatus string `json:"UserStatus,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "CONFIRMED", out.UserStatus) } @@ -335,7 +336,7 @@ func TestAdminSetUserPassword_Temporary(t *testing.T) { var out struct { UserStatus string `json:"UserStatus,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "FORCE_CHANGE_PASSWORD", out.UserStatus) } @@ -355,7 +356,7 @@ func TestUserSettings_And_AuthFactors(t *testing.T) { Username string `json:"Username,omitempty"` ConfiguredUserAuthFactors []string `json:"ConfiguredUserAuthFactors,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp, jsonx.V1())) assert.Equal(t, "settings-user", factorsResp.Username) assert.Contains(t, factorsResp.ConfiguredUserAuthFactors, "PASSWORD") assert.NotContains(t, factorsResp.ConfiguredUserAuthFactors, "SMS_OTP") @@ -372,7 +373,7 @@ func TestUserSettings_And_AuthFactors(t *testing.T) { rec = doCognitoRequest(t, h, "GetUserAuthFactors", map[string]any{"AccessToken": accessToken}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp, jsonx.V1())) assert.Contains(t, factorsResp.ConfiguredUserAuthFactors, "SMS_OTP") // Register a WebAuthn credential -> WEB_AUTHN factor appears too. @@ -384,7 +385,7 @@ func TestUserSettings_And_AuthFactors(t *testing.T) { rec = doCognitoRequest(t, h, "GetUserAuthFactors", map[string]any{"AccessToken": accessToken}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &factorsResp, jsonx.V1())) assert.Contains(t, factorsResp.ConfiguredUserAuthFactors, "WEB_AUTHN") // AdminSetUserSettings — admin variant, same persisted state. @@ -486,7 +487,7 @@ func TestHandler_AdminGetUserAuthFactors(t *testing.T) { PreferredMfaSetting string `json:"PreferredMfaSetting,omitempty"` UserMFASettingList []string `json:"UserMFASettingList,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, username, resp.Username) assert.Contains(t, resp.ConfiguredUserAuthFactors, "PASSWORD") assert.Contains(t, resp.ConfiguredUserAuthFactors, "SMS_OTP") @@ -570,7 +571,7 @@ func TestHandler_AdminCreateUser(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -622,7 +623,7 @@ func TestHandler_AdminSetUserPassword(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) poolID := resp["UserPool"]["Id"].(string) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ diff --git a/services/cognitoidp/identity_providers_test.go b/services/cognitoidp/identity_providers_test.go index bc4abaed74..d47f168706 100644 --- a/services/cognitoidp/identity_providers_test.go +++ b/services/cognitoidp/identity_providers_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -48,7 +49,7 @@ func TestCreateIdentityProvider_Duplicate_ReturnsDuplicateProviderException(t *t var errResp struct { Code string `json:"__type,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "DuplicateProviderException", errResp.Code) } else { b := newTestBackend() @@ -123,7 +124,7 @@ func TestUpdateIdentityProvider_ReplacesProviderDetails(t *testing.T) { ProviderDetails map[string]string `json:"ProviderDetails,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.IdentityProvider) assert.Equal(t, "https://new.example.com/metadata", out.IdentityProvider.ProviderDetails["MetadataURL"]) @@ -195,7 +196,7 @@ func TestIdentityProvider_SAML_WithAttributeMapping(t *testing.T) { CreationDate float64 `json:"CreationDate,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) require.NotNil(t, createOut.IdentityProvider) assert.Equal(t, "MySAML", createOut.IdentityProvider.ProviderName) assert.Equal(t, "SAML", createOut.IdentityProvider.ProviderType) @@ -235,7 +236,7 @@ func TestIdentityProvider_OIDC_WithScopes(t *testing.T) { ProviderType string `json:"ProviderType,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.IdentityProvider) assert.Equal(t, "MyOIDC", out.IdentityProvider.ProviderName) assert.Equal(t, "email", out.IdentityProvider.AttributeMapping["email"]) @@ -280,7 +281,7 @@ func TestIdentityProvider_Social_Google(t *testing.T) { ProviderDetails map[string]string `json:"ProviderDetails,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.IdentityProvider) assert.Equal(t, "email", descOut.IdentityProvider.AttributeMapping["email"]) assert.Equal(t, "google-app-id", descOut.IdentityProvider.ProviderDetails["client_id"]) @@ -324,7 +325,7 @@ func TestIdentityProvider_Update_AttributeMapping(t *testing.T) { IdpIdentifiers []string `json:"IdpIdentifiers,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updOut, jsonx.V1())) require.NotNil(t, updOut.IdentityProvider) assert.Equal(t, "id", updOut.IdentityProvider.AttributeMapping["username"]) assert.Contains(t, updOut.IdentityProvider.IdpIdentifiers, "facebook.com") @@ -364,7 +365,7 @@ func TestIdentityProvider_GetByIdentifier(t *testing.T) { CreationDate float64 `json:"CreationDate,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.IdentityProvider) assert.Equal(t, "LoginWithAmazon", out.IdentityProvider.ProviderName) // AttributeMapping/IdpIdentifiers/CreationDate only appear on the "Full" @@ -414,7 +415,7 @@ func TestIdentityProvider_List_WithTimestamps(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Providers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Providers, 3) for _, p := range out.Providers { @@ -544,7 +545,7 @@ func TestIdentityProvider_CRUD(t *testing.T) { ProviderType string `json:"ProviderType,omitempty"` } `json:"IdentityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, "Google", createResp.IdentityProvider.ProviderName) assert.Equal(t, "Google", createResp.IdentityProvider.ProviderType) @@ -573,7 +574,7 @@ func TestIdentityProvider_CRUD(t *testing.T) { ProviderName string `json:"ProviderName,omitempty"` } `json:"Providers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Providers, 1) assert.Equal(t, "Google", listResp.Providers[0].ProviderName) @@ -597,7 +598,7 @@ func TestIdentityProvider_CRUD(t *testing.T) { "UserPoolId": poolID, }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.Providers) } @@ -689,7 +690,7 @@ func TestHandler_AdminDisableProviderForUser(t *testing.T) { if poolID == "" { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "provider-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID = poolResp["UserPool"].(map[string]any)["Id"].(string) createProviderViaHandler(t, h, poolID, "Google", "Google") _, err := h.Backend.CreateFederatedUserForTest(poolID, "Google", "google-123") @@ -743,7 +744,7 @@ func TestListIdentityProviders_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body) var page1 listOut - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Providers, 2) require.NotEmpty(t, page1.NextToken, "first page must return a cursor when more providers remain") @@ -755,7 +756,7 @@ func TestListIdentityProviders_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body) var page2 listOut - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.Providers, 1) require.Empty(t, page2.NextToken) diff --git a/services/cognitoidp/input_validation.go b/services/cognitoidp/input_validation.go index 161b59f11b..dd96df3a15 100644 --- a/services/cognitoidp/input_validation.go +++ b/services/cognitoidp/input_validation.go @@ -1,9 +1,11 @@ package cognitoidp import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -24,7 +26,7 @@ type commonIdentifiers struct { // validateCommonIdentifiers applies the SDK's userPoolId/clientId pattern constraints before dispatch. func validateCommonIdentifiers(body []byte) error { var ids commonIdentifiers - if err := json.Unmarshal(body, &ids); err != nil { + if err := json.Unmarshal(body, &ids, jsonx.V1()); err != nil { return nil //nolint:nilerr // malformed bodies are reported by the op's own decoder } diff --git a/services/cognitoidp/input_validation_test.go b/services/cognitoidp/input_validation_test.go index 213515aee1..275586dc55 100644 --- a/services/cognitoidp/input_validation_test.go +++ b/services/cognitoidp/input_validation_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestInputValidation_ErrorTypes(t *testing.T) { @@ -80,7 +82,7 @@ func TestInputValidation_ErrorTypes(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tc.wantType, errResp.Type) }) } diff --git a/services/cognitoidp/mfa_test.go b/services/cognitoidp/mfa_test.go index db8ebff292..297b31e365 100644 --- a/services/cognitoidp/mfa_test.go +++ b/services/cognitoidp/mfa_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -116,7 +117,7 @@ func TestHandler_GetUser_WithMFAFields(t *testing.T) { PreferredMfaSetting string `json:"PreferredMfaSetting,omitempty"` UserMFASettingList []string `json:"UserMFASettingList,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, []string{"SOFTWARE_TOKEN_MFA"}, resp.UserMFASettingList) assert.Equal(t, "SOFTWARE_TOKEN_MFA", resp.PreferredMfaSetting) } @@ -140,7 +141,7 @@ func TestHandler_RespondToAuthChallenge_SMSMFAFlow(t *testing.T) { var assocResp struct { SecretCode string `json:"SecretCode,omitempty"` } - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) require.NotEmpty(t, assocResp.SecretCode) setupCode, err := cognitoidp.GenerateTOTPCode(assocResp.SecretCode, time.Now()) @@ -173,7 +174,7 @@ func TestHandler_RespondToAuthChallenge_SMSMFAFlow(t *testing.T) { ChallengeName *string `json:"ChallengeName,omitempty"` Session *string `json:"Session,omitempty"` } - require.NoError(t, json.Unmarshal(setMFARec.Body.Bytes(), &mfaInitResp)) + require.NoError(t, json.Unmarshal(setMFARec.Body.Bytes(), &mfaInitResp, jsonx.V1())) require.NotNil(t, mfaInitResp.ChallengeName) require.Equal(t, "SOFTWARE_TOKEN_MFA", *mfaInitResp.ChallengeName) @@ -197,7 +198,7 @@ func TestHandler_RespondToAuthChallenge_SMSMFAFlow(t *testing.T) { AccessToken string `json:"AccessToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(respondRec.Body.Bytes(), &tokenResp)) + require.NoError(t, json.Unmarshal(respondRec.Body.Bytes(), &tokenResp, jsonx.V1())) require.NotNil(t, tokenResp.AuthenticationResult) assert.NotEmpty(t, tokenResp.AuthenticationResult.AccessToken) } @@ -217,7 +218,7 @@ func TestHandler_AssociateSoftwareToken_Accurate(t *testing.T) { var resp struct { SecretCode string `json:"SecretCode,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.SecretCode) assert.Greater(t, len(resp.SecretCode), 10) } @@ -266,7 +267,7 @@ func TestHandler_VerifySoftwareToken_Accurate(t *testing.T) { var assocResp struct { SecretCode string `json:"SecretCode,omitempty"` } - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) require.NotEmpty(t, assocResp.SecretCode) code, err := cognitoidp.GenerateTOTPCode(assocResp.SecretCode, time.Now()) @@ -281,7 +282,7 @@ func TestHandler_VerifySoftwareToken_Accurate(t *testing.T) { var resp struct { Status string `json:"Status,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SUCCESS", resp.Status) } @@ -384,7 +385,7 @@ func TestSetGetMfaConfig_SmsMfaConfiguration(t *testing.T) { } `json:"SmsMfaConfiguration"` MfaConfiguration string `json:"MfaConfiguration,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) assert.Equal(t, "ON", setOut.MfaConfiguration) require.NotNil(t, setOut.SmsMfaConfiguration) assert.Equal(t, "Your code is {####}", setOut.SmsMfaConfiguration.SmsAuthenticationMessage) @@ -403,7 +404,7 @@ func TestSetGetMfaConfig_SmsMfaConfiguration(t *testing.T) { } `json:"SmsMfaConfiguration"` MfaConfiguration string `json:"MfaConfiguration,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "ON", getOut.MfaConfiguration) require.NotNil(t, getOut.SmsMfaConfiguration) assert.Equal(t, "Your code is {####}", getOut.SmsMfaConfiguration.SmsAuthenticationMessage) @@ -430,7 +431,7 @@ func TestSetGetMfaConfig_SoftwareTokenMfa(t *testing.T) { } `json:"SoftwareTokenMfaConfiguration"` MfaConfiguration string `json:"MfaConfiguration,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) assert.Equal(t, "OPTIONAL", setOut.MfaConfiguration) require.NotNil(t, setOut.SoftwareTokenMfaConfiguration) assert.True(t, setOut.SoftwareTokenMfaConfiguration.Enabled) @@ -446,7 +447,7 @@ func TestSetGetMfaConfig_SoftwareTokenMfa(t *testing.T) { Enabled bool `json:"Enabled,omitempty"` } `json:"SoftwareTokenMfaConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) require.NotNil(t, getOut.SoftwareTokenMfaConfiguration) assert.True(t, getOut.SoftwareTokenMfaConfiguration.Enabled) } @@ -473,7 +474,7 @@ func TestSetGetMfaConfig_EmailMfa(t *testing.T) { Subject string `json:"Subject,omitempty"` } `json:"EmailMfaConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) require.NotNil(t, setOut.EmailMfaConfiguration) assert.Equal(t, "Your login code is {####}", setOut.EmailMfaConfiguration.Message) assert.Equal(t, "Login verification", setOut.EmailMfaConfiguration.Subject) @@ -701,7 +702,7 @@ func TestAdminGetUserMFAFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Fields should be present (even if empty/nil for non-MFA user). // The response JSON should decode without error and have the base fields. diff --git a/services/cognitoidp/multi_region_test.go b/services/cognitoidp/multi_region_test.go index 4188d176b6..fa12ceb066 100644 --- a/services/cognitoidp/multi_region_test.go +++ b/services/cognitoidp/multi_region_test.go @@ -2,7 +2,7 @@ package cognitoidp_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -54,7 +55,7 @@ func idpCall(t *testing.T, h *cognitoidp.Handler, region, op, body string) map[s out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out diff --git a/services/cognitoidp/oauth_federation_backend.go b/services/cognitoidp/oauth_federation_backend.go index b0060234ba..6aeb0115f1 100644 --- a/services/cognitoidp/oauth_federation_backend.go +++ b/services/cognitoidp/oauth_federation_backend.go @@ -3,7 +3,7 @@ package cognitoidp import ( "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -308,7 +309,7 @@ func addIdentity(existing string, idp *IdentityProvider, subject, issuer string) func parseIdentities(raw string) []identityRecord { records := make([]identityRecord, 0, 1) if raw != "" { - _ = json.Unmarshal([]byte(raw), &records) + _ = json.Unmarshal([]byte(raw), &records, jsonx.V1()) } return records @@ -328,7 +329,7 @@ func appendIdentity(existing string, rec identityRecord) string { rec.Primary = strconv.FormatBool(len(records) == 0) } - out, err := json.Marshal(append(records, rec)) + out, err := json.Marshal(append(records, rec), jsonx.V1()) if err != nil { return existing } @@ -345,7 +346,7 @@ func removeIdentity(existing, providerName, userID string) string { return "" } - out, err := json.Marshal(records) + out, err := json.Marshal(records, jsonx.V1()) if err != nil { return existing } diff --git a/services/cognitoidp/oauth_federation_helpers_test.go b/services/cognitoidp/oauth_federation_helpers_test.go index 6a0912758f..697ee6539b 100644 --- a/services/cognitoidp/oauth_federation_helpers_test.go +++ b/services/cognitoidp/oauth_federation_helpers_test.go @@ -4,7 +4,8 @@ import ( "crypto/rand" "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "math/big" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -62,7 +64,7 @@ func newFakeIDP(t *testing.T) *fakeIDP { func (f *fakeIDP) serve(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/.well-known/openid-configuration": - _ = json.NewEncoder(w).Encode(map[string]string{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{ "issuer": f.srv.URL, "authorization_endpoint": f.srv.URL + "/authorize", "token_endpoint": f.srv.URL + "/token", "jwks_uri": f.srv.URL + "/jwks", }) @@ -72,7 +74,7 @@ func (f *fakeIDP) serve(w http.ResponseWriter, r *http.Request) { f.token(w, r) case "/jwks": pub := f.key.PublicKey - _ = json.NewEncoder(w).Encode(map[string]any{"keys": []map[string]string{{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{"keys": []map[string]string{{ "kty": "RSA", "kid": fedKID, "alg": "RS256", "use": "sig", "n": base64.RawURLEncoding.EncodeToString(pub.N.Bytes()), "e": base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes()), @@ -150,7 +152,7 @@ func (f *fakeIDP) token(w http.ResponseWriter, r *http.Request) { return } - _ = json.NewEncoder(w).Encode(map[string]string{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]string{ "id_token": signed, "access_token": "idp-access", "token_type": "Bearer", }) } diff --git a/services/cognitoidp/oauth_federation_idp.go b/services/cognitoidp/oauth_federation_idp.go index 26d0e580a3..add496e292 100644 --- a/services/cognitoidp/oauth_federation_idp.go +++ b/services/cognitoidp/oauth_federation_idp.go @@ -5,7 +5,8 @@ import ( "crypto/rsa" "crypto/subtle" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -19,6 +20,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -201,7 +203,11 @@ func idpFetchJSON( return idpErrorf("%s returned status %d", req.URL.Host, resp.StatusCode) } - if err = json.NewDecoder(io.LimitReader(resp.Body, maxIDPDocBytes)).Decode(out); err != nil { + if err = json.UnmarshalDecode( + jsontext.NewDecoder(io.LimitReader(resp.Body, maxIDPDocBytes)), + out, + jsonx.V1(), + ); err != nil { return idpErrorf("invalid JSON from %s: %v", req.URL.Host, err) } diff --git a/services/cognitoidp/oauth_federation_test.go b/services/cognitoidp/oauth_federation_test.go index c6f9ac278b..b016bbe2ac 100644 --- a/services/cognitoidp/oauth_federation_test.go +++ b/services/cognitoidp/oauth_federation_test.go @@ -3,7 +3,7 @@ package cognitoidp_test import ( "crypto/rand" "crypto/rsa" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -77,7 +78,7 @@ func TestFederatedOIDCCodeFlow(t *testing.T) { assert.Equal(t, "true", user.Attributes["email_verified"]) var stored []map[string]string - require.NoError(t, json.Unmarshal([]byte(user.Attributes["identities"]), &stored)) + require.NoError(t, json.Unmarshal([]byte(user.Attributes["identities"]), &stored, jsonx.V1())) assert.Equal(t, fedSubject, stored[0]["userId"]) }) } diff --git a/services/cognitoidp/oauth_helpers_test.go b/services/cognitoidp/oauth_helpers_test.go index 759a5861e3..4189694c51 100644 --- a/services/cognitoidp/oauth_helpers_test.go +++ b/services/cognitoidp/oauth_helpers_test.go @@ -4,7 +4,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "math/big" "net/http" @@ -18,6 +18,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -259,7 +260,7 @@ func jsonMap(t *testing.T, body []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m), string(body)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1()), string(body)) return m } @@ -277,7 +278,7 @@ func (e *oauthEnv) verifyAgainstJWKS(t *testing.T, tok string) jwt.MapClaims { E string `json:"e"` } `json:"keys"` } - require.NoError(t, json.Unmarshal(body, &set)) + require.NoError(t, json.Unmarshal(body, &set, jsonx.V1())) require.NotEmpty(t, set.Keys) parsed, err := jwt.Parse(tok, func(tk *jwt.Token) (any, error) { diff --git a/services/cognitoidp/oauth_token.go b/services/cognitoidp/oauth_token.go index ee9aed4249..a03abeda56 100644 --- a/services/cognitoidp/oauth_token.go +++ b/services/cognitoidp/oauth_token.go @@ -4,7 +4,7 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -12,6 +12,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -339,7 +341,7 @@ func clientMetadataParam(form url.Values) (map[string]string, *oauthError) { } var metadata map[string]string - if err := json.Unmarshal([]byte(raw), &metadata); err != nil { + if err := json.Unmarshal([]byte(raw), &metadata, jsonx.V1()); err != nil { return nil, newOAuthError(errInvalidRequest, "aws_client_metadata must be a JSON object of strings") } diff --git a/services/cognitoidp/persistence.go b/services/cognitoidp/persistence.go index 8ed0530171..3d48336470 100644 --- a/services/cognitoidp/persistence.go +++ b/services/cognitoidp/persistence.go @@ -4,13 +4,15 @@ import ( "context" "crypto/rsa" "crypto/x509" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "errors" "fmt" "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -133,7 +135,7 @@ type backendSnapshot struct { TokenRevokedBeforeSeq map[string]int64 `json:"tokenRevokedBeforeSeq,omitempty"` TokenRevokedBefore map[string]time.Time `json:"tokenRevokedBefore,omitempty"` RevokedChains map[string]time.Time `json:"revokedChains,omitempty"` - Tables map[string]json.RawMessage `json:"tables,omitempty"` + Tables map[string]jsontext.Value `json:"tables,omitempty"` ResourceTags map[string]map[string]string `json:"resourceTags,omitempty"` RiskConfigurations map[string]*RiskConfiguration `json:"riskConfigurations,omitempty"` LogDeliveryConfigs map[string]*LogDeliveryConfig `json:"logDeliveryConfigs,omitempty"` @@ -345,7 +347,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Endpoint: b.endpoint, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "cognitoidp: failed to marshal backend snapshot", "error", err) @@ -425,7 +427,7 @@ func (b *InMemoryBackend) resetForIncompatibleSnapshotLocked() { // every secondary index registered on it, e.g. clientsByPool/groupsByPool, so // no separate index rebuild step is needed for these). Caller must hold b.mu // in write mode. -func (b *InMemoryBackend) restoreTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreTablesLocked(tables map[string]jsontext.Value) error { dtoReg := store.NewRegistry() poolDTOs := store.Register(dtoReg, "pools", store.New(poolSnapshotKeyFn)) @@ -468,7 +470,7 @@ func (b *InMemoryBackend) restoreTablesLocked(tables map[string]json.RawMessage) // a shape from some other future change) is likewise dropped rather than // failing the whole Restore, unlike dtoReg.RestoreAll's other tables. Caller // must hold b.mu in write mode. -func (b *InMemoryBackend) restoreTermsLocked(ctx context.Context, raw json.RawMessage) { +func (b *InMemoryBackend) restoreTermsLocked(ctx context.Context, raw jsontext.Value) { if len(raw) == 0 { b.terms.Restore(nil) @@ -477,7 +479,7 @@ func (b *InMemoryBackend) restoreTermsLocked(ctx context.Context, raw json.RawMe var items []*Terms - if err := json.Unmarshal(raw, &items); err != nil { + if err := json.Unmarshal(raw, &items, jsonx.V1()); err != nil { logger.Load(ctx).WarnContext(ctx, "cognitoidp: dropping unparseable terms snapshot", "error", err) b.terms.Restore(nil) diff --git a/services/cognitoidp/persistence_internal_test.go b/services/cognitoidp/persistence_internal_test.go index 3b1410442f..f0ac8b87d1 100644 --- a/services/cognitoidp/persistence_internal_test.go +++ b/services/cognitoidp/persistence_internal_test.go @@ -1,12 +1,15 @@ package cognitoidp import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestSnapshotRoundTrip_NonUTCZone guards against the bare-'Z' layout bug: Format's @@ -128,19 +131,19 @@ func TestGlobalSignOut_RestoreFromV2Snapshot(t *testing.T) { data := b.Snapshot(t.Context()) require.NotEmpty(t, data) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(data, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(data, &raw, jsonx.V1())) delete(raw, "tokenSeq") delete(raw, "tokenRevokedBeforeSeq") key := pool.ID + ":" + user.Username - revoked, err := json.Marshal(map[string]time.Time{key: baseTime}) + revoked, err := json.Marshal(map[string]time.Time{key: baseTime}, jsonx.V1()) require.NoError(t, err) raw["tokenRevokedBefore"] = revoked - raw["version"] = json.RawMessage(`2`) + raw["version"] = jsontext.Value(`2`) - patched, err := json.Marshal(raw) + patched, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) b2 := NewInMemoryBackend("000000000000", "us-east-1", "http://localhost:8000") diff --git a/services/cognitoidp/persistence_test.go b/services/cognitoidp/persistence_test.go index 126dfcbab2..4e74060591 100644 --- a/services/cognitoidp/persistence_test.go +++ b/services/cognitoidp/persistence_test.go @@ -2,13 +2,14 @@ package cognitoidp_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -391,7 +392,7 @@ func TestPersistenceRoundTrip(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "persist-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -399,7 +400,7 @@ func TestPersistenceRoundTrip(t *testing.T) { "ClientName": "persist-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) doCognitoRequest(t, h, "AdminCreateUser", map[string]any{ "UserPoolId": poolID, @@ -427,7 +428,7 @@ func TestPersistenceRoundTrip(t *testing.T) { "Username": "persist-user", }) var getUserResp map[string]any - require.NoError(t, json.Unmarshal(getUserRec.Body.Bytes(), &getUserResp)) + require.NoError(t, json.Unmarshal(getUserRec.Body.Bytes(), &getUserResp, jsonx.V1())) assert.False(t, getUserResp["Enabled"].(bool)) } @@ -542,7 +543,7 @@ func TestInMemoryBackend_RestoreDropsPreRedesignTerms(t *testing.T) { require.NotEmpty(t, data) var raw map[string]any - require.NoError(t, json.Unmarshal(data, &raw)) + require.NoError(t, json.Unmarshal(data, &raw, jsonx.V1())) tables, ok := raw["tables"].(map[string]any) require.True(t, ok) @@ -557,7 +558,7 @@ func TestInMemoryBackend_RestoreDropsPreRedesignTerms(t *testing.T) { {"userPoolID": pool.ID, "text": "accept these terms"}, } - spliced, err := json.Marshal(raw) + spliced, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) restored := newTestBackend() diff --git a/services/cognitoidp/realclient_managed_login_and_provisioning_test.go b/services/cognitoidp/realclient_managed_login_and_provisioning_test.go index 9a141afaa0..9878dada6c 100644 --- a/services/cognitoidp/realclient_managed_login_and_provisioning_test.go +++ b/services/cognitoidp/realclient_managed_login_and_provisioning_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -54,7 +55,7 @@ func decodeIdpDocument(t *testing.T, doc interface{ UnmarshalSmithyDocument(v an var v any require.NoError(t, doc.UnmarshalSmithyDocument(&v)) - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/cognitoidp/regions.go b/services/cognitoidp/regions.go index b7002fc752..4022a6fcd0 100644 --- a/services/cognitoidp/regions.go +++ b/services/cognitoidp/regions.go @@ -3,7 +3,7 @@ package cognitoidp import ( "context" "crypto/rsa" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" ) @@ -265,7 +266,7 @@ func targetHints(r *http.Request) ownerHints { Session string `json:"Session"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return ownerHints{ poolID: req.UserPoolID, arn: req.ResourceArn, clientID: req.ClientID, diff --git a/services/cognitoidp/resource_servers_test.go b/services/cognitoidp/resource_servers_test.go index 2962326c0d..8be9eb75ff 100644 --- a/services/cognitoidp/resource_servers_test.go +++ b/services/cognitoidp/resource_servers_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -66,7 +67,7 @@ func TestHandler_ResourceServers_Accurate(t *testing.T) { } `json:"Scopes"` } `json:"ResourceServer"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "My API", resp.ResourceServer.Name) assert.Len(t, resp.ResourceServer.Scopes, 1) assert.Equal(t, "read", resp.ResourceServer.Scopes[0].ScopeName) @@ -98,7 +99,7 @@ func TestResourceServer_CRUD(t *testing.T) { Identifier string `json:"Identifier"` } `json:"ResourceServers"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.ResourceServers, 1) assert.Equal(t, "https://api.example.com", listResp.ResourceServers[0].Identifier) } @@ -279,7 +280,7 @@ func TestHandler_ListResourceServers(t *testing.T) { var resp struct { ResourceServers []map[string]any `json:"ResourceServers,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceServers, tt.count) }) } @@ -399,7 +400,7 @@ func TestListResourceServers_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body) var page1 listOut - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.ResourceServers, 2) require.NotEmpty(t, page1.NextToken, "first page must return a cursor when more resource servers remain") @@ -411,7 +412,7 @@ func TestListResourceServers_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body) var page2 listOut - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.ResourceServers, 1) require.Empty(t, page2.NextToken) diff --git a/services/cognitoidp/saml_federation_test.go b/services/cognitoidp/saml_federation_test.go index 2d939a6e88..9b74116cda 100644 --- a/services/cognitoidp/saml_federation_test.go +++ b/services/cognitoidp/saml_federation_test.go @@ -10,7 +10,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "maps" "math/big" @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -481,7 +482,7 @@ func TestSAMLFederation(t *testing.T) { require.Equal(t, http.StatusOK, form.StatusCode, string(body)) var tok map[string]any - require.NoError(t, json.Unmarshal(body, &tok)) + require.NoError(t, json.Unmarshal(body, &tok, jsonx.V1())) claims := env.verifyAgainstJWKS(t, tok["id_token"].(string)) assert.Equal(t, fedProvider+"_"+samlNameID, claims["cognito:username"]) @@ -527,7 +528,7 @@ func TestSAMLMultiValuedAttributes(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode, string(body)) var tok map[string]any - require.NoError(t, json.Unmarshal(body, &tok)) + require.NoError(t, json.Unmarshal(body, &tok, jsonx.V1())) claims := env.verifyAgainstJWKS(t, tok["id_token"].(string)) assert.Equal(t, tt.want, claims["email"]) diff --git a/services/cognitoidp/saml_slo_test.go b/services/cognitoidp/saml_slo_test.go index 77adbc26dc..99e5094015 100644 --- a/services/cognitoidp/saml_slo_test.go +++ b/services/cognitoidp/saml_slo_test.go @@ -8,7 +8,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "regexp" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -53,7 +54,7 @@ func (e *samlEnv) loginSAML(t *testing.T) string { require.Equal(t, http.StatusOK, resp.StatusCode, string(body)) var tok map[string]any - require.NoError(t, json.Unmarshal(body, &tok)) + require.NoError(t, json.Unmarshal(body, &tok, jsonx.V1())) return tok["access_token"].(string) } diff --git a/services/cognitoidp/security_config_test.go b/services/cognitoidp/security_config_test.go index 74bf1951ce..8a7d338a2c 100644 --- a/services/cognitoidp/security_config_test.go +++ b/services/cognitoidp/security_config_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -60,7 +61,7 @@ func TestSetRiskConfiguration_PersistsRiskExceptionConfiguration(t *testing.T) { } `json:"RiskExceptionConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) require.NotNil(t, setOut.RiskConfiguration) require.NotNil( t, @@ -100,7 +101,7 @@ func TestSetRiskConfiguration_PersistsRiskExceptionConfiguration(t *testing.T) { } `json:"RiskExceptionConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.RiskConfiguration) require.NotNil( t, @@ -138,7 +139,7 @@ func TestRiskConfiguration_CompromisedCredentials(t *testing.T) { } `json:"CompromisedCredentialsRiskConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &setOut, jsonx.V1())) require.NotNil(t, setOut.RiskConfiguration) require.NotNil(t, setOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration) assert.Contains(t, setOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration.EventFilter, "SIGN_IN") @@ -160,7 +161,7 @@ func TestRiskConfiguration_CompromisedCredentials(t *testing.T) { } `json:"CompromisedCredentialsRiskConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.RiskConfiguration) require.NotNil(t, descOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration) assert.Equal(t, "BLOCK", descOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration.Actions.EventAction) @@ -226,7 +227,7 @@ func TestRiskConfiguration_AccountTakeover(t *testing.T) { } `json:"AccountTakeoverRiskConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.RiskConfiguration) require.NotNil(t, out.RiskConfiguration.AccountTakeoverRiskConfiguration) require.NotNil(t, out.RiskConfiguration.AccountTakeoverRiskConfiguration.Actions) @@ -274,7 +275,7 @@ func TestRiskConfiguration_PerClient(t *testing.T) { CompromisedCredentialsRiskConfiguration *struct{} `json:"CompromisedCredentialsRiskConfiguration,omitempty"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &poolOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &poolOut, jsonx.V1())) assert.Nil(t, poolOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration) // Client-level should be set. @@ -293,7 +294,7 @@ func TestRiskConfiguration_PerClient(t *testing.T) { } `json:"CompromisedCredentialsRiskConfiguration"` } `json:"RiskConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &clientOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &clientOut, jsonx.V1())) require.NotNil(t, clientOut.RiskConfiguration.CompromisedCredentialsRiskConfiguration) assert.Equal( t, @@ -414,7 +415,7 @@ func TestSetLogDeliveryConfiguration_LogConfigurationsPersistAndEcho(t *testing. LogConfigurations []map[string]any `json:"LogConfigurations,omitempty"` } `json:"LogDeliveryConfiguration"` } - require.NoError(t, json.Unmarshal(setRec.Body.Bytes(), &setOut)) + require.NoError(t, json.Unmarshal(setRec.Body.Bytes(), &setOut, jsonx.V1())) require.Len(t, setOut.LogDeliveryConfiguration.LogConfigurations, 1, "LogConfigurations is a required accepted field and must not be silently dropped", ) @@ -428,7 +429,7 @@ func TestSetLogDeliveryConfiguration_LogConfigurationsPersistAndEcho(t *testing. LogConfigurations []map[string]any `json:"LogConfigurations,omitempty"` } `json:"LogDeliveryConfiguration"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) require.Len(t, getOut.LogDeliveryConfiguration.LogConfigurations, 1, "Set must actually persist for Get to read back", ) diff --git a/services/cognitoidp/tags_test.go b/services/cognitoidp/tags_test.go index 4508ddee48..0edf0654ec 100644 --- a/services/cognitoidp/tags_test.go +++ b/services/cognitoidp/tags_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags(t *testing.T) { @@ -31,7 +33,7 @@ func TestTags(t *testing.T) { var listResp struct { Tags map[string]string `json:"Tags,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Equal(t, "prod", listResp.Tags["env"]) assert.Equal(t, "platform", listResp.Tags["team"]) @@ -49,7 +51,7 @@ func TestTags(t *testing.T) { var afterUntagResp struct { Tags map[string]string `json:"Tags,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterUntagResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterUntagResp, jsonx.V1())) assert.NotContains(t, afterUntagResp.Tags, "env") assert.Equal(t, "platform", afterUntagResp.Tags["team"]) } diff --git a/services/cognitoidp/temp_password_sdk_test.go b/services/cognitoidp/temp_password_sdk_test.go index d46ef744bf..e766608687 100644 --- a/services/cognitoidp/temp_password_sdk_test.go +++ b/services/cognitoidp/temp_password_sdk_test.go @@ -1,7 +1,8 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -9,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const tempPwd = "Tmp-Secret-9876!x" @@ -108,7 +111,7 @@ func TestTemporaryPassword_NotReturnedByUserOps(t *testing.T) { require.NoError(t, err) attrs := tt.op(t, c, poolID) - raw, err := json.Marshal(attrs) + raw, err := json.Marshal(attrs, jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(raw), tempPwd) assert.NotContains(t, attrNames(attrs), "custom:temporaryPassword") @@ -196,25 +199,25 @@ func TestTemporaryPassword_LegacySnapshotMigrates(t *testing.T) { }) require.NoError(t, err) - var snap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &snap)) + var snap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &snap, jsonx.V1())) - var tables map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap["tables"], &tables)) + var tables map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap["tables"], &tables, jsonx.V1())) var users []map[string]any - require.NoError(t, json.Unmarshal(tables["users"], &users)) + require.NoError(t, json.Unmarshal(tables["users"], &users, jsonx.V1())) require.Len(t, users, 1) delete(users[0], "temporaryPassword") users[0]["attributes"].(map[string]any)["custom:temporaryPassword"] = tempPwd - tables["users"], err = json.Marshal(users) + tables["users"], err = json.Marshal(users, jsonx.V1()) require.NoError(t, err) - snap["tables"], err = json.Marshal(tables) + snap["tables"], err = json.Marshal(tables, jsonx.V1()) require.NoError(t, err) - old, err := json.Marshal(snap) + old, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) fresh := newTestHandler(t) diff --git a/services/cognitoidp/terms_test.go b/services/cognitoidp/terms_test.go index 95edf1255f..aca924818e 100644 --- a/services/cognitoidp/terms_test.go +++ b/services/cognitoidp/terms_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func validTermsBody(poolID, clientID string) map[string]any { @@ -51,7 +53,7 @@ func TestCreateTerms_Validation(t *testing.T) { var errResp struct { Type string `json:"__type,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) }) } @@ -69,7 +71,7 @@ func TestCreateTerms_UnknownClient(t *testing.T) { var errResp struct { Type string `json:"__type,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type) } @@ -88,7 +90,7 @@ func TestCreateTerms_Duplicate(t *testing.T) { var errResp struct { Type string `json:"__type,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "TermsExistsException", errResp.Type) } @@ -109,7 +111,7 @@ func TestTerms_CRUD(t *testing.T) { ClientID string `json:"ClientId,omitempty"` } `json:"Terms"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.Terms) rec = doCognitoRequest(t, h, "CreateTerms", validTermsBody(poolID, clientID)) @@ -128,7 +130,7 @@ func TestTerms_CRUD(t *testing.T) { LastModifiedDate float64 `json:"LastModifiedDate,omitempty"` } `json:"Terms"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) require.NotEmpty(t, createResp.Terms.TermsID) assert.Equal(t, clientID, createResp.Terms.ClientID) assert.Equal(t, poolID, createResp.Terms.UserPoolID) @@ -144,7 +146,7 @@ func TestTerms_CRUD(t *testing.T) { // List returns TermsDescriptionType, which has no ClientId field on the real wire. rec = doCognitoRequest(t, h, "ListTerms", map[string]any{"UserPoolId": poolID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Terms, 1) assert.Equal(t, termsID, listResp.Terms[0].TermsID) assert.Empty(t, listResp.Terms[0].ClientID, "TermsDescriptionType has no ClientId field") @@ -155,7 +157,7 @@ func TestTerms_CRUD(t *testing.T) { "TermsName": "terms-of-use-v2", }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, "terms-of-use-v2", createResp.Terms.TermsName) assert.Equal(t, "NONE", createResp.Terms.Enforcement, "unset fields on update are left unchanged") @@ -163,7 +165,7 @@ func TestTerms_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = doCognitoRequest(t, h, "ListTerms", map[string]any{"UserPoolId": poolID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.Terms) } @@ -205,7 +207,7 @@ func TestTerms_NotFound(t *testing.T) { var errResp struct { Type string `json:"__type,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type) }) } diff --git a/services/cognitoidp/testhelpers_test.go b/services/cognitoidp/testhelpers_test.go index 3365f18849..a1487da2c7 100644 --- a/services/cognitoidp/testhelpers_test.go +++ b/services/cognitoidp/testhelpers_test.go @@ -5,7 +5,7 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -28,7 +29,7 @@ func decodeJWTPayload(t *testing.T, token string) map[string]any { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(payload, &claims)) + require.NoError(t, json.Unmarshal(payload, &claims, jsonx.V1())) return claims } @@ -79,7 +80,7 @@ func setupHandlerPoolAndClient(t *testing.T, h *cognitoidp.Handler, poolName str ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp.UserPool.ID clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -93,7 +94,7 @@ func setupHandlerPoolAndClient(t *testing.T, h *cognitoidp.Handler, poolName str ClientID string `json:"ClientId,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) return poolID, clientResp.UserPoolClient.ClientID } @@ -114,7 +115,7 @@ func signUpAndConfirmViaHandler(t *testing.T, h *cognitoidp.Handler, clientID, u CodeDeliveryDetails map[string]string `json:"CodeDeliveryDetails,omitempty"` UserConfirmed bool `json:"UserConfirmed,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signUpResp, jsonx.V1())) if signUpResp.UserConfirmed { return @@ -147,7 +148,7 @@ func loginViaHandler(t *testing.T, h *cognitoidp.Handler, clientID, username str AccessToken string `json:"AccessToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.AuthenticationResult) return resp.AuthenticationResult.AccessToken @@ -175,7 +176,7 @@ func newTestHandler(t *testing.T) *cognitoidp.Handler { func doCognitoRequest(t *testing.T, h *cognitoidp.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -214,7 +215,7 @@ func setupPoolAndClientNamed(t *testing.T, h *cognitoidp.Handler, poolName, clie require.Equal(t, http.StatusOK, poolRec.Code) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -224,7 +225,7 @@ func setupPoolAndClientNamed(t *testing.T, h *cognitoidp.Handler, poolName, clie require.Equal(t, http.StatusOK, clientRec.Code) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) return poolID, clientID diff --git a/services/cognitoidp/tokens.go b/services/cognitoidp/tokens.go index fb1139d59a..0616fc0669 100644 --- a/services/cognitoidp/tokens.go +++ b/services/cognitoidp/tokens.go @@ -7,7 +7,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "math/big" @@ -19,6 +19,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // rsaKeyBits is the size of the RSA key generated for JWT signing. @@ -373,7 +375,7 @@ func (t *tokenIssuer) signIDToken(p TokenParams, now time.Time, idExpiry time.Du if raw := p.Attributes[attrIdentities]; raw != "" { var identities []any - if json.Unmarshal([]byte(raw), &identities) == nil { + if json.Unmarshal([]byte(raw), &identities, jsonx.V1()) == nil { idClaims[attrIdentities] = identities } } @@ -547,5 +549,5 @@ func (t *tokenIssuer) ParseAccessToken(tokenString string) (jwt.MapClaims, error // jwksResponseJSON serializes JWKSResponse as JSON bytes. func jwksResponseJSON(r JWKSResponse) ([]byte, error) { - return json.Marshal(r) + return json.Marshal(r, jsonx.V1()) } diff --git a/services/cognitoidp/user_import_test.go b/services/cognitoidp/user_import_test.go index eb67177334..ff5d778d40 100644 --- a/services/cognitoidp/user_import_test.go +++ b/services/cognitoidp/user_import_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -32,7 +33,7 @@ func TestUserImportJob_CRUD(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp.UserImportJob.JobID require.NotEmpty(t, jobID) assert.Equal(t, "my-import", createResp.UserImportJob.JobName) @@ -55,7 +56,7 @@ func TestUserImportJob_CRUD(t *testing.T) { JobID string `json:"JobId,omitempty"` } `json:"UserImportJobs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.UserImportJobs, 1) // GetCSVHeader @@ -73,7 +74,7 @@ func TestUserImportJob_CRUD(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.Equal(t, "InProgress", startResp.UserImportJob.Status) // Stop @@ -87,7 +88,7 @@ func TestUserImportJob_CRUD(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Equal(t, "Stopped", stopResp.UserImportJob.Status) // Start again on stopped job — error @@ -125,7 +126,7 @@ func TestUserImportJob_SelfCompletesToSucceeded(t *testing.T) { JobID string `json:"JobId,omitempty"` } `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp.UserImportJob.JobID require.NotEmpty(t, jobID) @@ -153,7 +154,7 @@ func TestUserImportJob_SelfCompletesToSucceeded(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) return desc.UserImportJob.Status == "Succeeded" }, 2*time.Second, 10*time.Millisecond, @@ -193,7 +194,7 @@ func TestUserImportJob_EchoesCloudWatchLogsRoleArnAndPasswordHashingAlgorithm(t var createResp struct { UserImportJob userImportJobWire `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, "arn:aws:iam::000000000000:role/CognitoImportRole", createResp.UserImportJob.CloudWatchLogsRoleArn, @@ -213,7 +214,7 @@ func TestUserImportJob_EchoesCloudWatchLogsRoleArnAndPasswordHashingAlgorithm(t var startResp struct { UserImportJob userImportJobWire `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) assert.NotZero(t, startResp.UserImportJob.StartDate) assert.Zero(t, startResp.UserImportJob.CompletionDate) @@ -223,7 +224,7 @@ func TestUserImportJob_EchoesCloudWatchLogsRoleArnAndPasswordHashingAlgorithm(t var stopResp struct { UserImportJob userImportJobWire `json:"UserImportJob"` } - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) assert.NotZero(t, stopResp.UserImportJob.CompletionDate) } @@ -257,7 +258,7 @@ func TestListUserImportJobs_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body) var page1 listOut - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.UserImportJobs, 2) require.NotEmpty(t, page1.PaginationToken, "first page must return a cursor when more import jobs remain") @@ -269,7 +270,7 @@ func TestListUserImportJobs_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body) var page2 listOut - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.UserImportJobs, 1) require.Empty(t, page2.PaginationToken) diff --git a/services/cognitoidp/user_pool_clients_handler_test.go b/services/cognitoidp/user_pool_clients_handler_test.go index 17f5975af8..c62eae2c9c 100644 --- a/services/cognitoidp/user_pool_clients_handler_test.go +++ b/services/cognitoidp/user_pool_clients_handler_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -56,7 +57,7 @@ func TestHandler_UpdateUserPoolClient_WithOpts(t *testing.T) { AllowedOAuthScopes []string `json:"AllowedOAuthScopes,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "renamed-client", resp.UserPoolClient.ClientName) }) } @@ -75,7 +76,7 @@ func TestHandler_CreateUserPoolClient(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -119,7 +120,7 @@ func TestHandler_DescribeUserPoolClient(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -127,7 +128,7 @@ func TestHandler_DescribeUserPoolClient(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) return poolID, clientResp["UserPoolClient"]["ClientId"].(string) }, @@ -138,7 +139,7 @@ func TestHandler_DescribeUserPoolClient(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string), "nonexistent-client" }, @@ -204,7 +205,7 @@ func TestHandler_DeleteUserPoolClient(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -212,7 +213,7 @@ func TestHandler_DeleteUserPoolClient(t *testing.T) { "ClientName": "c", }) var clientResp map[string]map[string]any - _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp) + _ = json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1()) return poolID, clientResp["UserPoolClient"]["ClientId"].(string) }, @@ -223,7 +224,7 @@ func TestHandler_DeleteUserPoolClient(t *testing.T) { setup: func(h *cognitoidp.Handler) (string, string) { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string), "nonexistent-client" }, @@ -275,7 +276,7 @@ func TestHandler_ListUserPoolClients(t *testing.T) { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var poolResp map[string]map[string]any - _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp) + _ = json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1()) poolID := poolResp["UserPool"]["Id"].(string) for i := range tt.numClients { @@ -290,7 +291,7 @@ func TestHandler_ListUserPoolClients(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clients := resp["UserPoolClients"].([]any) assert.Len(t, clients, tt.numClients) }) @@ -337,7 +338,7 @@ func TestHandler_AddUserPoolClientSecret(t *testing.T) { if tt.poolExists { poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "sec-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID = poolResp["UserPool"].(map[string]any)["Id"].(string) } @@ -347,7 +348,7 @@ func TestHandler_AddUserPoolClientSecret(t *testing.T) { "ClientName": "test-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID = clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) } @@ -369,7 +370,7 @@ func TestSortedListUserPoolClients(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "sorted-clients-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) for _, name := range []string{"web-client", "android-client", "ios-client"} { @@ -383,7 +384,7 @@ func TestSortedListUserPoolClients(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) clients := listResp["UserPoolClients"].([]any) require.Len(t, clients, 3) assert.Equal(t, "android-client", clients[0].(map[string]any)["ClientName"]) @@ -397,7 +398,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "secret-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) clientRec := doCognitoRequest(t, h, "CreateUserPoolClient", map[string]any{ @@ -405,7 +406,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { "ClientName": "sec-client", }) var clientResp map[string]any - require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp)) + require.NoError(t, json.Unmarshal(clientRec.Body.Bytes(), &clientResp, jsonx.V1())) clientID := clientResp["UserPoolClient"].(map[string]any)["ClientId"].(string) // No secret yet. @@ -414,7 +415,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { "ClientId": clientID, }) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Empty(t, descResp["UserPoolClient"].(map[string]any)["ClientSecret"]) // AddUserPoolClientSecret creates an independently ClientSecretId-keyed @@ -427,7 +428,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { "ClientId": clientID, }) var addResp map[string]any - require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp)) + require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addResp, jsonx.V1())) descriptor, ok := addResp["ClientSecretDescriptor"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, descriptor["ClientSecretId"]) @@ -438,7 +439,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { "ClientId": clientID, }) var descResp2 map[string]any - require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2)) + require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descResp2, jsonx.V1())) assert.Empty(t, descResp2["UserPoolClient"].(map[string]any)["ClientSecret"]) listRec := doCognitoRequest(t, h, "ListUserPoolClientSecrets", map[string]any{ @@ -446,7 +447,7 @@ func TestDescribeUserPoolClient_IncludesClientSecret(t *testing.T) { "ClientId": clientID, }) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) secrets, ok := listResp["ClientSecrets"].([]any) require.True(t, ok) require.Len(t, secrets, 1) @@ -460,14 +461,14 @@ func TestListUserPoolClients_NonNilWhenEmpty(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "empty-clients-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) rec := doCognitoRequest(t, h, "ListUserPoolClients", map[string]any{"UserPoolId": poolID}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) clients := listResp["UserPoolClients"].([]any) assert.Empty(t, clients) } @@ -486,7 +487,7 @@ func TestClientLastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) client := descResp["UserPoolClient"].(map[string]any) creationDate := client["CreationDate"].(float64) lastModBefore := client["LastModifiedDate"].(float64) @@ -501,7 +502,7 @@ func TestClientLastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, upRec.Code) var upResp map[string]any - require.NoError(t, json.Unmarshal(upRec.Body.Bytes(), &upResp)) + require.NoError(t, json.Unmarshal(upRec.Body.Bytes(), &upResp, jsonx.V1())) updatedClient := upResp["UserPoolClient"].(map[string]any) lastModAfter := updatedClient["LastModifiedDate"].(float64) assert.GreaterOrEqual(t, lastModAfter, creationDate, "LastModifiedDate must be >= CreationDate after update") @@ -526,7 +527,7 @@ func TestClientOAuthRedirectFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) client := resp["UserPoolClient"].(map[string]any) callbackURLs := client["CallbackURLs"].([]any) diff --git a/services/cognitoidp/user_pool_clients_test.go b/services/cognitoidp/user_pool_clients_test.go index ccf9ee1e21..ddd0f02824 100644 --- a/services/cognitoidp/user_pool_clients_test.go +++ b/services/cognitoidp/user_pool_clients_test.go @@ -1,13 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -55,7 +56,7 @@ func TestHandler_CreateUserPoolClient_WithOAuthFields(t *testing.T) { ExplicitAuthFlows []string `json:"ExplicitAuthFlows,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, []string{"code"}, resp.UserPoolClient.AllowedOAuthFlows) assert.ElementsMatch(t, []string{"email", "openid"}, resp.UserPoolClient.AllowedOAuthScopes) assert.Equal(t, []string{"ALLOW_USER_PASSWORD_AUTH"}, resp.UserPoolClient.ExplicitAuthFlows) @@ -81,7 +82,7 @@ func TestHandler_DescribeUserPoolClient_IncludesOAuthFields(t *testing.T) { ClientID string `json:"ClientId,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doCognitoRequest(t, h, "DescribeUserPoolClient", map[string]any{ "UserPoolId": poolID, @@ -96,7 +97,7 @@ func TestHandler_DescribeUserPoolClient_IncludesOAuthFields(t *testing.T) { ExplicitAuthFlows []string `json:"ExplicitAuthFlows,omitempty"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, []string{"code"}, resp.UserPoolClient.AllowedOAuthFlows) assert.Equal(t, []string{"openid"}, resp.UserPoolClient.AllowedOAuthScopes) assert.Equal(t, []string{"ALLOW_USER_PASSWORD_AUTH"}, resp.UserPoolClient.ExplicitAuthFlows) @@ -121,7 +122,7 @@ func TestAppClient_CRUD(t *testing.T) { ClientName string `json:"ClientName"` } `json:"UserPoolClient"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, "my-app-client", createResp.UserPoolClient.ClientName) clientID := createResp.UserPoolClient.ClientID assert.NotEmpty(t, clientID) @@ -135,7 +136,7 @@ func TestAppClient_CRUD(t *testing.T) { ClientID string `json:"ClientId"` } `json:"UserPoolClients"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) var found bool for _, c := range listResp.UserPoolClients { if c.ClientID == clientID { @@ -418,7 +419,7 @@ func TestClientSecrets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp clientSecretsListResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.ClientSecrets) // Add a secret; its ClientSecretId is required to delete it later @@ -436,7 +437,7 @@ func TestClientSecrets(t *testing.T) { ClientSecretValue string `json:"ClientSecretValue"` } `json:"ClientSecretDescriptor"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &addResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &addResp, jsonx.V1())) secretID := addResp.ClientSecretDescriptor.ClientSecretID require.NotEmpty(t, secretID) require.NotEmpty(t, addResp.ClientSecretDescriptor.ClientSecretValue) @@ -446,7 +447,7 @@ func TestClientSecrets(t *testing.T) { "UserPoolId": poolID, "ClientId": clientID, }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.ClientSecrets, 1) assert.Equal(t, secretID, listResp.ClientSecrets[0].ClientSecretID) assert.Empty(t, listResp.ClientSecrets[0].ClientSecretValue) @@ -472,7 +473,7 @@ func TestClientSecrets(t *testing.T) { "UserPoolId": poolID, "ClientId": clientID, }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.ClientSecrets) } @@ -592,7 +593,7 @@ func TestListUserPoolClients_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body) var page1 listOut - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.UserPoolClients, 2) require.NotEmpty(t, page1.NextToken, "first page must return a cursor when more clients remain") @@ -604,7 +605,7 @@ func TestListUserPoolClients_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body) var page2 listOut - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.UserPoolClients, 2) require.Empty(t, page2.NextToken) diff --git a/services/cognitoidp/user_pools_config_test.go b/services/cognitoidp/user_pools_config_test.go index 33940ea1d8..24caabc970 100644 --- a/services/cognitoidp/user_pools_config_test.go +++ b/services/cognitoidp/user_pools_config_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -172,7 +173,7 @@ func TestHandler_ListUserPools(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pools, ok := resp["UserPools"].([]any) require.True(t, ok) @@ -194,7 +195,7 @@ func TestHandler_DescribeUserPool(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "p"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -266,7 +267,7 @@ func TestHandler_DeleteUserPool(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "del-pool"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -307,7 +308,7 @@ func TestHandler_GetUserPoolMfaConfig(t *testing.T) { setup: func(h *cognitoidp.Handler) string { rec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "mfa-pool"}) var resp map[string]map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["UserPool"]["Id"].(string) }, @@ -351,7 +352,7 @@ func TestSortedListUserPools(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pools := resp["UserPools"].([]any) require.Len(t, pools, 3) @@ -366,7 +367,7 @@ func TestDescribeUserPool_IncludesSchemaAttributes(t *testing.T) { h := newTestHandler(t) poolRec := doCognitoRequest(t, h, "CreateUserPool", map[string]any{"PoolName": "schema-pool"}) var poolResp map[string]any - require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp)) + require.NoError(t, json.Unmarshal(poolRec.Body.Bytes(), &poolResp, jsonx.V1())) poolID := poolResp["UserPool"].(map[string]any)["Id"].(string) doCognitoRequest(t, h, "AddCustomAttributes", map[string]any{ @@ -380,7 +381,7 @@ func TestDescribeUserPool_IncludesSchemaAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schema := resp["UserPool"].(map[string]any)["SchemaAttributes"] assert.NotNil(t, schema) schemaList := schema.([]any) @@ -396,7 +397,7 @@ func TestListUserPools_NonNilWhenEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) pools, ok := listResp["UserPools"].([]any) assert.True(t, ok, "UserPools should be an array, not nil") assert.Empty(t, pools) @@ -436,7 +437,7 @@ func TestPasswordPolicyDefaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pool := resp["UserPool"].(map[string]any) policies := pool["Policies"].(map[string]any) @@ -457,7 +458,7 @@ func TestUserPoolLastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pool := createResp["UserPool"].(map[string]any) poolID := pool["Id"].(string) createDate := pool["CreationDate"].(float64) @@ -478,7 +479,7 @@ func TestUserPoolLastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) lastModAfter := descResp["UserPool"].(map[string]any)["LastModifiedDate"].(float64) assert.GreaterOrEqual(t, lastModAfter, createDate, "LastModifiedDate must be >= CreationDate after update") } @@ -493,7 +494,7 @@ func TestUpdateUserPoolLambdaConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) poolID := createResp["UserPool"].(map[string]any)["Id"].(string) lambdaConfig := map[string]any{ @@ -510,7 +511,7 @@ func TestUpdateUserPoolLambdaConfig(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) returnedPool := descResp["UserPool"].(map[string]any) returnedLambda := returnedPool["LambdaConfig"].(map[string]any) assert.Equal(t, "arn:aws:lambda:us-east-1:000000000000:function:pre-signup", returnedLambda["PreSignUp"]) @@ -545,7 +546,7 @@ func TestListUserPools_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.UserPools), 2, "page must not exceed MaxResults") for _, p := range resp.UserPools { diff --git a/services/cognitoidp/user_pools_test.go b/services/cognitoidp/user_pools_test.go index b7a41fd261..7c00f5ce25 100644 --- a/services/cognitoidp/user_pools_test.go +++ b/services/cognitoidp/user_pools_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -179,7 +180,7 @@ func TestHandler_CreateUserPool_WithPasswordPolicy(t *testing.T) { AutoVerifiedAttributes []string `json:"AutoVerifiedAttributes,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.UserPool.Policies) require.NotNil(t, resp.UserPool.Policies.PasswordPolicy) assert.Equal(t, 10, resp.UserPool.Policies.PasswordPolicy.MinimumLength) @@ -205,7 +206,7 @@ func TestHandler_DescribeUserPool_IncludesPolicy(t *testing.T) { ID string `json:"Id,omitempty"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doCognitoRequest(t, h, "DescribeUserPool", map[string]any{"UserPoolId": createResp.UserPool.ID}) require.Equal(t, http.StatusOK, rec.Code) @@ -219,7 +220,7 @@ func TestHandler_DescribeUserPool_IncludesPolicy(t *testing.T) { } `json:"Policies"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.UserPool.Policies) require.NotNil(t, resp.UserPool.Policies.PasswordPolicy) assert.Equal(t, 12, resp.UserPool.Policies.PasswordPolicy.MinimumLength) @@ -251,7 +252,7 @@ func TestUserPool_CRUD(t *testing.T) { Name string `json:"Name"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) poolID := createResp.UserPool.ID assert.Equal(t, tt.poolName, createResp.UserPool.Name) assert.NotEmpty(t, poolID) @@ -265,7 +266,7 @@ func TestUserPool_CRUD(t *testing.T) { ID string `json:"Id"` } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, poolID, descResp.UserPool.ID) // Delete. @@ -588,7 +589,7 @@ func TestHandler_CreateUserPool_MfaConfiguration(t *testing.T) { Id string `json:"Id"` //nolint:revive,staticcheck // matches wire field name. } `json:"UserPool"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec = doCognitoRequest(t, h, "GetUserPoolMfaConfig", map[string]any{ "UserPoolId": createResp.UserPool.Id, @@ -598,7 +599,7 @@ func TestHandler_CreateUserPool_MfaConfiguration(t *testing.T) { var mfaResp struct { MfaConfiguration string `json:"MfaConfiguration,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mfaResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mfaResp, jsonx.V1())) assert.Equal(t, "ON", mfaResp.MfaConfiguration) } @@ -616,7 +617,7 @@ func TestGetUserPoolMfaConfig_DefaultsToOFF(t *testing.T) { var out struct { MfaConfiguration string `json:"MfaConfiguration,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "OFF", out.MfaConfiguration) } @@ -844,7 +845,7 @@ func TestHandler_UserPoolReplicas(t *testing.T) { UserPoolArn string `json:"UserPoolArn,omitempty"` } `json:"UserPoolReplica"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, tt.regionName, createResp.UserPoolReplica.RegionName) assert.Equal(t, "SECONDARY", createResp.UserPoolReplica.Role) assert.Equal(t, "INACTIVE", createResp.UserPoolReplica.Status) @@ -860,7 +861,7 @@ func TestHandler_UserPoolReplicas(t *testing.T) { RegionName string `json:"RegionName,omitempty"` } `json:"UserPoolReplicas,omitempty"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.UserPoolReplicas, 1) assert.Equal(t, tt.regionName, listResp.UserPoolReplicas[0].RegionName) @@ -876,7 +877,7 @@ func TestHandler_UserPoolReplicas(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserPoolReplica"` } - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "ACTIVE", updateResp.UserPoolReplica.Status) deleteRec := doCognitoRequest(t, h, "DeleteUserPoolReplica", map[string]any{ @@ -890,7 +891,7 @@ func TestHandler_UserPoolReplicas(t *testing.T) { Status string `json:"Status,omitempty"` } `json:"UserPoolReplica"` } - require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp, jsonx.V1())) assert.Equal(t, "DELETING", deleteResp.UserPoolReplica.Status) }) } diff --git a/services/cognitoidp/users_test.go b/services/cognitoidp/users_test.go index 860bb98eb9..521fb193d1 100644 --- a/services/cognitoidp/users_test.go +++ b/services/cognitoidp/users_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cognitoidp" ) @@ -866,7 +867,7 @@ func TestDeleteUser_ClearsGroupAndWebAuthnStateOnRecreate(t *testing.T) { func unmarshalBody(t *testing.T, rec *httptest.ResponseRecorder, v any) error { t.Helper() - return json.Unmarshal(rec.Body.Bytes(), v) + return json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1()) } func TestAdminSetUserPassword_PolicyEnforced(t *testing.T) { diff --git a/services/cognitoidp/webauthn_test.go b/services/cognitoidp/webauthn_test.go index e7b7476b4e..2417ec442f 100644 --- a/services/cognitoidp/webauthn_test.go +++ b/services/cognitoidp/webauthn_test.go @@ -1,12 +1,14 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestWebAuthn_CRUD(t *testing.T) { @@ -24,7 +26,7 @@ func TestWebAuthn_CRUD(t *testing.T) { var startResp struct { CredentialCreationOptions map[string]any `json:"CredentialCreationOptions,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) require.NotEmpty(t, startResp.CredentialCreationOptions) assert.NotEmpty(t, startResp.CredentialCreationOptions["challenge"]) rp, ok := startResp.CredentialCreationOptions["rp"].(map[string]any) @@ -59,7 +61,7 @@ func TestWebAuthn_CRUD(t *testing.T) { AuthenticatorTransports []string `json:"AuthenticatorTransports"` } `json:"Credentials,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Credentials, 1) assert.Equal(t, "cred-id-1", listResp.Credentials[0].CredentialID) assert.Equal(t, "platform", listResp.Credentials[0].AuthenticatorAttachment) @@ -88,7 +90,7 @@ func TestWebAuthn_CRUD(t *testing.T) { CredentialID string `json:"CredentialId,omitempty"` } `json:"Credentials,omitempty"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2, jsonx.V1())) assert.Empty(t, listResp2.Credentials) // Delete again — not found. @@ -139,7 +141,7 @@ func TestHandler_CompleteWebAuthnRegistration_Validation(t *testing.T) { AccessToken string `json:"AccessToken,omitempty"` } `json:"AuthenticationResult"` } - require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp, jsonx.V1())) require.NotNil(t, initResp.AuthenticationResult) accessToken := initResp.AuthenticationResult.AccessToken require.NotEmpty(t, accessToken) diff --git a/services/cognitoidp/wire_field_fixes_test.go b/services/cognitoidp/wire_field_fixes_test.go index 6c5ecd8320..ba6d1c424a 100644 --- a/services/cognitoidp/wire_field_fixes_test.go +++ b/services/cognitoidp/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package cognitoidp_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListUserPoolClients_SummaryShape proves ListUserPoolClients returns the @@ -56,7 +58,7 @@ func TestListUserPoolClients_SummaryShape(t *testing.T) { // assert directly on the wire body that no such key is emitted. rec := doCognitoRequest(t, h, "ListUserPoolClients", map[string]any{"UserPoolId": poolID}) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) rawClients, ok := raw["UserPoolClients"].([]any) require.True(t, ok) require.Len(t, rawClients, 1) diff --git a/services/comprehend/handler.go b/services/comprehend/handler.go index 8ddfaf6662..ab2292b253 100644 --- a/services/comprehend/handler.go +++ b/services/comprehend/handler.go @@ -2,7 +2,7 @@ package comprehend import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -155,7 +156,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } var values map[string]any - if unmarshalErr := json.Unmarshal(body, &values); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &values, jsonx.V1()); unmarshalErr != nil { return "" } for _, key := range []string{ @@ -202,7 +203,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt var input map[string]any if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } } @@ -214,7 +215,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt return nil, err } - return json.Marshal(output) + return json.Marshal(output, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -241,7 +242,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err code, status = "KmsKeyValidationException", http.StatusBadRequest } - payload, marshalErr := json.Marshal(service.JSONErrorResponse{Type: code, Message: errMessage(err)}) + payload, marshalErr := json.Marshal(service.JSONErrorResponse{Type: code, Message: errMessage(err)}, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } diff --git a/services/comprehend/handler_test.go b/services/comprehend/handler_test.go index e6fdcc10ca..c49c2e5b2d 100644 --- a/services/comprehend/handler_test.go +++ b/services/comprehend/handler_test.go @@ -2,7 +2,7 @@ package comprehend_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/comprehend" ) @@ -67,7 +68,7 @@ func mergedBody(base, extra map[string]any) map[string]any { func request(t *testing.T, handler *comprehend.Handler, operation string, input map[string]any) map[string]any { t.Helper() - payload, err := json.Marshal(input) + payload, err := json.Marshal(input, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(payload)) req.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -78,7 +79,7 @@ func request(t *testing.T, handler *comprehend.Handler, operation string, input require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var output map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &output)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &output, jsonx.V1())) return output } @@ -107,7 +108,7 @@ func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } @@ -118,7 +119,7 @@ func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { func toJSON(t *testing.T, input map[string]any) string { t.Helper() - payload, err := json.Marshal(input) + payload, err := json.Marshal(input, jsonx.V1()) require.NoError(t, err) return string(payload) @@ -892,7 +893,7 @@ func TestProtocolOpList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.NotEmpty(t, ops, "GET / must return non-empty operation list") assert.Contains(t, ops, "DetectSentiment") assert.Contains(t, ops, "DetectEntities") diff --git a/services/comprehend/persistence.go b/services/comprehend/persistence.go index 4555857377..ab0a89612c 100644 --- a/services/comprehend/persistence.go +++ b/services/comprehend/persistence.go @@ -2,7 +2,7 @@ package comprehend import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -41,7 +41,7 @@ const comprehendSnapshotVersion = 1 // nothing for store.Table to key on (see store_setup.go's file doc comment). // They are persisted directly here. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` Policies map[string]string `json:"policies"` PolicyRevisions map[string]string `json:"policyRevisions"` diff --git a/services/comprehend/s3_output.go b/services/comprehend/s3_output.go index 1a0ec15aa7..44ba63abfb 100644 --- a/services/comprehend/s3_output.go +++ b/services/comprehend/s3_output.go @@ -5,7 +5,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "fmt" "path" "slices" @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awss3 "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" s3backend "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -172,12 +173,12 @@ func outputFiles(job *Job) map[string][]byte { case jobTypeClassification: line, _ := json.Marshal(map[string]any{ "File": input, "Classes": []map[string]any{{"Name": "DEFAULT", "Score": defaultScore}}, - }) + }, jsonx.V1()) return map[string][]byte{"predictions.jsonl": append(line, '\n')} } - line, _ := json.Marshal(jobResultLine(job, input)) + line, _ := json.Marshal(jobResultLine(job, input), jsonx.V1()) return map[string][]byte{"output": append(line, '\n')} } diff --git a/services/connect/contacts.go b/services/connect/contacts.go index eebb94684c..fb8aa74503 100644 --- a/services/connect/contacts.go +++ b/services/connect/contacts.go @@ -2,7 +2,7 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" "maps" "slices" "strings" @@ -40,8 +40,8 @@ type StartOutboundVoiceContactInput struct { Description string RelatedContactID string `json:"RelatedContactId"` ClientToken string - OutboundStrategy json.RawMessage - References json.RawMessage + OutboundStrategy jsontext.Value + References jsontext.Value } // ParticipantDetails names a chat participant. @@ -73,7 +73,7 @@ type StartChatContactInput struct { CustomerID string `json:"CustomerId"` RelatedContactID string `json:"RelatedContactId"` ClientToken string - SegmentAttributes json.RawMessage + SegmentAttributes jsontext.Value } // StartTaskContactInput is the StartTaskContact request. @@ -89,9 +89,9 @@ type StartTaskContactInput struct { QuickConnectID string `json:"QuickConnectId"` TaskTemplateID string `json:"TaskTemplateId"` ClientToken string - SegmentAttributes json.RawMessage - Attachments json.RawMessage - References json.RawMessage + SegmentAttributes jsontext.Value + Attachments jsontext.Value + References jsontext.Value } // ContactRefInput addresses one contact in body-addressed contact operations. @@ -114,8 +114,8 @@ type ContactRefInput struct { ContactID string `json:"ContactId" path:"ContactId"` InstanceID string `json:"InstanceId" path:"InstanceId"` InitialContactID string `json:"InitialContactId"` - SegmentAttributes json.RawMessage - References json.RawMessage + SegmentAttributes jsontext.Value + References jsontext.Value } // ContactPathInput addresses one contact by path. diff --git a/services/connect/datatables.go b/services/connect/datatables.go index ba40dfa647..1157cacb49 100644 --- a/services/connect/datatables.go +++ b/services/connect/datatables.go @@ -2,7 +2,8 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "net/http" @@ -11,6 +12,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -211,11 +213,11 @@ func (t *dataTableRec) primaryAttrs() []*DataTableAttribute { } func (t *dataTableRec) clone() *dataTableRec { - raw, _ := json.Marshal(t) + raw, _ := json.Marshal(t, jsonx.V1()) var out dataTableRec - _ = json.Unmarshal(raw, &out) + _ = json.Unmarshal(raw, &out, jsonx.V1()) return &out } @@ -779,8 +781,8 @@ func checkDTText(v *DataTableValidation, s string) error { } func checkDTList(a *DataTableAttribute, s string) error { - var items []json.RawMessage - if err := json.Unmarshal([]byte(s), &items); err != nil { + var items []jsontext.Value + if err := json.Unmarshal([]byte(s), &items, jsonx.V1()); err != nil { return valueErr("value must be a JSON array") } @@ -798,7 +800,7 @@ func checkDTList(a *DataTableAttribute, s string) error { for _, raw := range items { if a.ValueType == dtTypeNumberList { var f float64 - if json.Unmarshal(raw, &f) != nil { + if json.Unmarshal(raw, &f, jsonx.V1()) != nil { return valueErr("list item %s is not a number", raw) } @@ -810,7 +812,7 @@ func checkDTList(a *DataTableAttribute, s string) error { } var str string - if json.Unmarshal(raw, &str) != nil { + if json.Unmarshal(raw, &str, jsonx.V1()) != nil { return valueErr("list item %s is not text", raw) } diff --git a/services/connect/evaluationforms.go b/services/connect/evaluationforms.go index 1ae1c508dc..a6dce6a97c 100644 --- a/services/connect/evaluationforms.go +++ b/services/connect/evaluationforms.go @@ -2,11 +2,13 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -54,15 +56,15 @@ type evalFormVersion struct { } type formContent struct { - ScoringStrategy json.RawMessage `json:"ScoringStrategy,omitempty"` - AutoEvaluationConfiguration json.RawMessage `json:"AutoEvaluationConfiguration,omitempty"` - ReviewConfiguration json.RawMessage `json:"ReviewConfiguration,omitempty"` - TargetConfiguration json.RawMessage `json:"TargetConfiguration,omitempty"` - LanguageConfiguration json.RawMessage `json:"LanguageConfiguration,omitempty"` - Title string `json:"Title"` - Description string `json:"Description,omitempty"` - AIVersion string `json:"AIVersion,omitempty"` - Items []json.RawMessage `json:"Items"` + ScoringStrategy jsontext.Value `json:"ScoringStrategy,omitempty"` + AutoEvaluationConfiguration jsontext.Value `json:"AutoEvaluationConfiguration,omitempty"` + ReviewConfiguration jsontext.Value `json:"ReviewConfiguration,omitempty"` + TargetConfiguration jsontext.Value `json:"TargetConfiguration,omitempty"` + LanguageConfiguration jsontext.Value `json:"LanguageConfiguration,omitempty"` + Title string `json:"Title"` + Description string `json:"Description,omitempty"` + AIVersion string `json:"AIVersion,omitempty"` + Items []jsontext.Value `json:"Items"` } type evalFormRec struct { @@ -212,9 +214,9 @@ func (w *formWalk) problem(sev, code, desc string) { w.findings = append(w.findings, FormFinding{Description: desc, IssueCode: code, Severity: sev}) } -func (w *formWalk) item(raw json.RawMessage, depth int) { - var u map[string]json.RawMessage - if err := json.Unmarshal(raw, &u); err != nil || len(u) != 1 { +func (w *formWalk) item(raw jsontext.Value, depth int) { + var u map[string]jsontext.Value + if err := json.Unmarshal(raw, &u, jsonx.V1()); err != nil || len(u) != 1 { w.problem(findingError, "InvalidItem", "an item must be an object with exactly one of Section or Question") return @@ -248,14 +250,14 @@ func (w *formWalk) ref(ref string) { } } -func (w *formWalk) section(raw json.RawMessage, depth int) { +func (w *formWalk) section(raw jsontext.Value, depth int) { var s struct { - RefID string `json:"RefId"` - Title string `json:"Title"` - Items []json.RawMessage `json:"Items"` + RefID string `json:"RefId"` + Title string `json:"Title"` + Items []jsontext.Value `json:"Items"` } - if json.Unmarshal(raw, &s) != nil { + if json.Unmarshal(raw, &s, jsonx.V1()) != nil { w.problem(findingError, "InvalidSection", "a Section is not valid") return @@ -278,15 +280,15 @@ func (w *formWalk) section(raw json.RawMessage, depth int) { } } -func (w *formWalk) question(raw json.RawMessage) { +func (w *formWalk) question(raw jsontext.Value) { var q struct { - Props map[string]json.RawMessage `json:"QuestionTypeProperties"` - RefID string `json:"RefId"` - Title string `json:"Title"` - QuestionType string `json:"QuestionType"` + Props map[string]jsontext.Value `json:"QuestionTypeProperties"` + RefID string `json:"RefId"` + Title string `json:"Title"` + QuestionType string `json:"QuestionType"` } - if json.Unmarshal(raw, &q) != nil { + if json.Unmarshal(raw, &q, jsonx.V1()) != nil { w.problem(findingError, "InvalidQuestion", "a Question is not valid") return @@ -313,7 +315,7 @@ func (w *formWalk) question(raw json.RawMessage) { } // inspectForm walks the items and reports structural problems and warnings. -func inspectForm(items []json.RawMessage) *formWalk { +func inspectForm(items []jsontext.Value) *formWalk { w := &formWalk{refs: map[string]bool{}} for _, it := range items { w.item(it, 0) @@ -346,7 +348,7 @@ func checkFormContent(c *formContent) error { Mode string `json:"Mode"` Status string `json:"Status"` } - if json.Unmarshal(c.ScoringStrategy, &s) != nil { + if json.Unmarshal(c.ScoringStrategy, &s, jsonx.V1()) != nil { return invalidParamErr("ScoringStrategy is not valid") } @@ -730,13 +732,13 @@ func (b *Backend) SearchEvaluationForms(in *SearchRequest) (*SearchResult, error } // rejectCriteria refuses criteria members the emulator does not evaluate, at any nesting level. -func rejectCriteria(raw json.RawMessage, members ...string) error { +func rejectCriteria(raw jsontext.Value, members ...string) error { if len(raw) == 0 { return nil } - var node map[string]json.RawMessage - if json.Unmarshal(raw, &node) != nil { + var node map[string]jsontext.Value + if json.Unmarshal(raw, &node, jsonx.V1()) != nil { return invalidParamErr("SearchCriteria is not valid") } @@ -747,8 +749,8 @@ func rejectCriteria(raw json.RawMessage, members ...string) error { } for _, key := range []string{"AndConditions", "OrConditions"} { - var subs []json.RawMessage - if json.Unmarshal(node[key], &subs) != nil { + var subs []jsontext.Value + if json.Unmarshal(node[key], &subs, jsonx.V1()) != nil { continue } diff --git a/services/connect/flows.go b/services/connect/flows.go index d77980fb5f..fb1bc4837b 100644 --- a/services/connect/flows.go +++ b/services/connect/flows.go @@ -4,12 +4,15 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "slices" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -57,7 +60,7 @@ func validateFlowContent(code, content string) error { } var doc flowDoc - if err := json.Unmarshal([]byte(content), &doc); err != nil { + if err := json.Unmarshal([]byte(content), &doc, jsonx.V1()); err != nil { return invalidFlowErr(code, "content is not valid flow language JSON: "+err.Error()) } @@ -402,7 +405,7 @@ type CreateContactFlowModuleInput struct { Settings string ClientToken string instPath - ExternalInvocationConfiguration json.RawMessage + ExternalInvocationConfiguration jsontext.Value } func validateModule(content, settings string) error { @@ -410,7 +413,7 @@ func validateModule(content, settings string) error { return err } - if settings != "" && !json.Valid([]byte(settings)) { + if settings != "" && !jsontext.Value([]byte(settings)).IsValid(jsonx.V1()) { return invalidFlowErr("InvalidContactFlowModuleException", "Settings is not valid JSON") } diff --git a/services/connect/metrics.go b/services/connect/metrics.go index e190e792b2..4db70874da 100644 --- a/services/connect/metrics.go +++ b/services/connect/metrics.go @@ -1,7 +1,7 @@ package connect import ( - "encoding/json" + "encoding/json/jsontext" "slices" "strings" "time" @@ -572,10 +572,10 @@ func sortMetricResults(results []CurrentMetricResult, criteria []CurrentMetricSo // HistoricalMetric names one historical metric and statistic. type HistoricalMetric struct { - Name string `json:"Name"` - Statistic string `json:"Statistic"` - Unit string `json:"Unit"` - Threshold json.RawMessage `json:"Threshold,omitempty"` + Name string `json:"Name"` + Statistic string `json:"Statistic"` + Unit string `json:"Unit"` + Threshold jsontext.Value `json:"Threshold,omitempty"` } // GetMetricDataInput is the GetMetricData request. diff --git a/services/connect/models.go b/services/connect/models.go index 72b4359acc..725ae00a74 100644 --- a/services/connect/models.go +++ b/services/connect/models.go @@ -1,7 +1,7 @@ package connect import ( - "encoding/json" + "encoding/json/jsontext" "github.com/blackbirdworks/gopherstack/pkgs/awstime" ) @@ -167,11 +167,11 @@ type User struct { RoutingProfileID string `json:"RoutingProfileId"` Username string `json:"Username"` SecurityProfileIDs []string `json:"SecurityProfileIds"` - AfterContactWorkConfigs json.RawMessage `json:"AfterContactWorkConfigs,omitempty"` - AutoAcceptConfigs json.RawMessage `json:"AutoAcceptConfigs,omitempty"` - PersistentConnectionConfigs json.RawMessage `json:"PersistentConnectionConfigs,omitempty"` - PhoneNumberConfigs json.RawMessage `json:"PhoneNumberConfigs,omitempty"` - VoiceEnhancementConfigs json.RawMessage `json:"VoiceEnhancementConfigs,omitempty"` + AfterContactWorkConfigs jsontext.Value `json:"AfterContactWorkConfigs,omitempty"` + AutoAcceptConfigs jsontext.Value `json:"AutoAcceptConfigs,omitempty"` + PersistentConnectionConfigs jsontext.Value `json:"PersistentConnectionConfigs,omitempty"` + PhoneNumberConfigs jsontext.Value `json:"PhoneNumberConfigs,omitempty"` + VoiceEnhancementConfigs jsontext.Value `json:"VoiceEnhancementConfigs,omitempty"` } // Summary is the list shape shared by most resources. @@ -192,10 +192,10 @@ type UserSummary struct { // MediaConcurrency limits concurrent contacts of one channel. type MediaConcurrency struct { - Channel string `json:"Channel"` - CrossChannelBehavior json.RawMessage `json:"CrossChannelBehavior,omitempty"` - WorkloadTypeConcurrencies json.RawMessage `json:"WorkloadTypeConcurrencies,omitempty"` - Concurrency int32 `json:"Concurrency"` + Channel string `json:"Channel"` + CrossChannelBehavior jsontext.Value `json:"CrossChannelBehavior,omitempty"` + WorkloadTypeConcurrencies jsontext.Value `json:"WorkloadTypeConcurrencies,omitempty"` + Concurrency int32 `json:"Concurrency"` } // QueueReference names a queue and channel in a routing profile. @@ -246,7 +246,7 @@ type SecurityProfile struct { Stamp AllowedAccessControlTags map[string]string `json:"AllowedAccessControlTags,omitempty"` Tags map[string]string `json:"Tags,omitempty"` - GranularAccessControlConfiguration json.RawMessage `json:"GranularAccessControlConfiguration,omitempty"` + GranularAccessControlConfiguration jsontext.Value `json:"GranularAccessControlConfiguration,omitempty"` AllowedAccessControlHierarchyGroupID string `json:"AllowedAccessControlHierarchyGroupId,omitempty"` Arn string `json:"Arn"` Description string `json:"Description,omitempty"` @@ -385,8 +385,8 @@ type QuickConnectConfig struct { ContactFlowID string `json:"ContactFlowId"` UserID string `json:"UserId"` } `json:"UserConfig,omitempty"` - QuickConnectType string `json:"QuickConnectType"` - FlowConfig json.RawMessage `json:"FlowConfig,omitempty"` + QuickConnectType string `json:"QuickConnectType"` + FlowConfig jsontext.Value `json:"FlowConfig,omitempty"` } // QuickConnect is a saved transfer destination. @@ -441,7 +441,7 @@ type ContactFlowModule struct { State string `json:"State"` Status string `json:"Status"` VersionDescription string `json:"VersionDescription,omitempty"` - ExternalInvocationConfiguration json.RawMessage `json:"ExternalInvocationConfiguration,omitempty"` + ExternalInvocationConfiguration jsontext.Value `json:"ExternalInvocationConfiguration,omitempty"` } // ContactFlowModuleSummary is the list shape of a ContactFlowModule. @@ -580,6 +580,6 @@ type Contact struct { Name string `json:"Name,omitempty"` PreviousContactID string `json:"PreviousContactId,omitempty"` RelatedContactID string `json:"RelatedContactId,omitempty"` - OutboundStrategy json.RawMessage `json:"OutboundStrategy,omitempty"` - SegmentAttributes json.RawMessage `json:"SegmentAttributes,omitempty"` + OutboundStrategy jsontext.Value `json:"OutboundStrategy,omitempty"` + SegmentAttributes jsontext.Value `json:"SegmentAttributes,omitempty"` } diff --git a/services/connect/persistence.go b/services/connect/persistence.go index 5536f01256..66da04122e 100644 --- a/services/connect/persistence.go +++ b/services/connect/persistence.go @@ -2,7 +2,7 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/connect/proficiencies.go b/services/connect/proficiencies.go index e3fcf04154..2f63a96f22 100644 --- a/services/connect/proficiencies.go +++ b/services/connect/proficiencies.go @@ -2,11 +2,13 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "slices" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -210,17 +212,17 @@ func (b *Backend) ListUserProficiencies(in *ProficienciesInput) (*ProficienciesO type UpdateUserConfigInput struct { UserID string `json:"-" path:"UserId"` instPath - AfterContactWorkConfigs json.RawMessage - AutoAcceptConfigs json.RawMessage - PersistentConnectionConfigs json.RawMessage - PhoneNumberConfigs json.RawMessage - VoiceEnhancementConfigs json.RawMessage + AfterContactWorkConfigs jsontext.Value + AutoAcceptConfigs jsontext.Value + PersistentConnectionConfigs jsontext.Value + PhoneNumberConfigs jsontext.Value + VoiceEnhancementConfigs jsontext.Value } var userChannels = []string{"VOICE", "CHAT", "TASK", emailValue} //nolint:gochecknoglobals // immutable lookup table // checkChannelConfigs validates that every entry names a known channel and returns the normalised list. -func checkChannelConfigs(field string, raw json.RawMessage) (json.RawMessage, error) { +func checkChannelConfigs(field string, raw jsontext.Value) (jsontext.Value, error) { if len(raw) == 0 || string(raw) == jsonNull { return nil, nil } @@ -229,7 +231,7 @@ func checkChannelConfigs(field string, raw json.RawMessage) (json.RawMessage, er Channel string `json:"Channel"` } - if err := json.Unmarshal(raw, &items); err != nil { + if err := json.Unmarshal(raw, &items, jsonx.V1()); err != nil { return nil, invalidParamErr("%s is not valid", field) } @@ -251,30 +253,30 @@ func checkChannelConfigs(field string, raw json.RawMessage) (json.RawMessage, er } type userConfigField struct { - dst func(*User) *json.RawMessage + dst func(*User) *jsontext.Value name string - raw json.RawMessage + raw jsontext.Value } func userConfigFields(in *UpdateUserConfigInput) []userConfigField { return []userConfigField{ {name: "AfterContactWorkConfigs", raw: in.AfterContactWorkConfigs, - dst: func(u *User) *json.RawMessage { return &u.AfterContactWorkConfigs }}, + dst: func(u *User) *jsontext.Value { return &u.AfterContactWorkConfigs }}, {name: "AutoAcceptConfigs", raw: in.AutoAcceptConfigs, - dst: func(u *User) *json.RawMessage { return &u.AutoAcceptConfigs }}, + dst: func(u *User) *jsontext.Value { return &u.AutoAcceptConfigs }}, {name: "PersistentConnectionConfigs", raw: in.PersistentConnectionConfigs, - dst: func(u *User) *json.RawMessage { return &u.PersistentConnectionConfigs }}, + dst: func(u *User) *jsontext.Value { return &u.PersistentConnectionConfigs }}, {name: "PhoneNumberConfigs", raw: in.PhoneNumberConfigs, - dst: func(u *User) *json.RawMessage { return &u.PhoneNumberConfigs }}, + dst: func(u *User) *jsontext.Value { return &u.PhoneNumberConfigs }}, {name: "VoiceEnhancementConfigs", raw: in.VoiceEnhancementConfigs, - dst: func(u *User) *json.RawMessage { return &u.VoiceEnhancementConfigs }}, + dst: func(u *User) *jsontext.Value { return &u.VoiceEnhancementConfigs }}, } } // UpdateUserConfig replaces the supplied per-channel settings of a user. func (b *Backend) UpdateUserConfig(ctx context.Context, in *UpdateUserConfigInput) (*empty, error) { fields := userConfigFields(in) - checked := make([]json.RawMessage, len(fields)) + checked := make([]jsontext.Value, len(fields)) for i, f := range fields { raw, err := checkChannelConfigs(f.name, f.raw) diff --git a/services/connect/records.go b/services/connect/records.go index 6eb4a53ce8..4e412b527d 100644 --- a/services/connect/records.go +++ b/services/connect/records.go @@ -1,7 +1,7 @@ package connect import ( - "encoding/json" + "encoding/json/jsontext" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -90,13 +90,13 @@ type promptRec struct { } type contactRec struct { - InstanceID string `json:"instanceId"` - ParticipantID string `json:"participantId,omitempty"` - ParticipantToken string `json:"participantToken,omitempty"` - FlowID string `json:"flowId,omitempty"` - TaskTemplateID string `json:"taskTemplateId,omitempty"` - Contact Contact `json:"contact"` - References json.RawMessage `json:"references,omitempty"` + InstanceID string `json:"instanceId"` + ParticipantID string `json:"participantId,omitempty"` + ParticipantToken string `json:"participantToken,omitempty"` + FlowID string `json:"flowId,omitempty"` + TaskTemplateID string `json:"taskTemplateId,omitempty"` + Contact Contact `json:"contact"` + References jsontext.Value `json:"references,omitempty"` } type integrationRec struct { diff --git a/services/connect/rules.go b/services/connect/rules.go index b4ae08aaa6..9a20d281d2 100644 --- a/services/connect/rules.go +++ b/services/connect/rules.go @@ -2,10 +2,12 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -57,8 +59,8 @@ type Rule struct { PublishStatus string `json:"PublishStatus"` RuleArn string `json:"RuleArn"` RuleID string `json:"RuleId"` - Actions []json.RawMessage `json:"Actions"` - PreEvaluationFilters json.RawMessage `json:"PreEvaluationFilters,omitempty"` + Actions []jsontext.Value `json:"Actions"` + PreEvaluationFilters jsontext.Value `json:"PreEvaluationFilters,omitempty"` } // ActionSummary is the list shape of a rule action. @@ -92,8 +94,8 @@ type CreateRuleInput struct { PublishStatus string ClientToken string instPath - PreEvaluationFilters json.RawMessage - Actions []json.RawMessage + PreEvaluationFilters jsontext.Value + Actions []jsontext.Value } // UpdateRuleInput is the UpdateRule request. @@ -103,8 +105,8 @@ type UpdateRuleInput struct { PublishStatus string RuleID string `json:"-" path:"RuleId"` instPath - PreEvaluationFilters json.RawMessage - Actions []json.RawMessage + PreEvaluationFilters jsontext.Value + Actions []jsontext.Value } // RuleOut is the wire shape of a rule. @@ -113,14 +115,14 @@ type RuleOut struct { RuleID string `json:"RuleId"` } -func ruleActionType(raw json.RawMessage) (string, error) { - var a map[string]json.RawMessage - if err := json.Unmarshal(raw, &a); err != nil { +func ruleActionType(raw jsontext.Value) (string, error) { + var a map[string]jsontext.Value + if err := json.Unmarshal(raw, &a, jsonx.V1()); err != nil { return "", invalidParamErr("Actions contains an invalid action") } var t string - if err := json.Unmarshal(a["ActionType"], &t); err != nil { + if err := json.Unmarshal(a["ActionType"], &t, jsonx.V1()); err != nil { return "", invalidParamErr("every action needs an ActionType") } @@ -136,7 +138,7 @@ func ruleActionType(raw json.RawMessage) (string, error) { return t, nil } -func (b *Backend) checkRuleActions(inst string, actions []json.RawMessage) error { +func (b *Backend) checkRuleActions(inst string, actions []jsontext.Value) error { if len(actions) == 0 || len(actions) > maxRuleActions { return invalidParamErr("Actions must contain 1-%d actions", maxRuleActions) } @@ -157,14 +159,14 @@ func (b *Backend) checkRuleActions(inst string, actions []json.RawMessage) error return nil } -func (b *Backend) checkTaskActionFlow(inst string, raw json.RawMessage) error { +func (b *Backend) checkTaskActionFlow(inst string, raw jsontext.Value) error { var a struct { TaskAction struct { ContactFlowID string `json:"ContactFlowId"` } `json:"TaskAction"` } - if err := json.Unmarshal(raw, &a); err != nil { + if err := json.Unmarshal(raw, &a, jsonx.V1()); err != nil { return invalidParamErr("TaskAction is not valid") } diff --git a/services/connect/search.go b/services/connect/search.go index d45f34779e..755290af2b 100644 --- a/services/connect/search.go +++ b/services/connect/search.go @@ -1,11 +1,13 @@ package connect import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TimeRange bounds a contact search. @@ -70,23 +72,23 @@ type SearchContactsInput struct { TimeRange TimeRange InstanceID string `json:"InstanceId"` NextToken string - SearchCriteria json.RawMessage + SearchCriteria jsontext.Value MaxResults int32 } -func parseCriteria(raw json.RawMessage) (*ContactSearchCriteria, error) { +func parseCriteria(raw jsontext.Value) (*ContactSearchCriteria, error) { var crit ContactSearchCriteria if len(raw) == 0 { return &crit, nil } - if err := json.Unmarshal(raw, &crit); err != nil { + if err := json.Unmarshal(raw, &crit, jsonx.V1()); err != nil { return nil, invalidParamErr("SearchCriteria is not valid") } - var members map[string]json.RawMessage - if err := json.Unmarshal(raw, &members); err != nil { + var members map[string]jsontext.Value + if err := json.Unmarshal(raw, &members, jsonx.V1()); err != nil { return nil, invalidParamErr("SearchCriteria is not valid") } @@ -291,7 +293,7 @@ type ContactSearchSummary struct { InitiationMethod string `json:"InitiationMethod"` Name string `json:"Name,omitempty"` PreviousContactID string `json:"PreviousContactId,omitempty"` - SegmentAttributes json.RawMessage `json:"SegmentAttributes,omitempty"` + SegmentAttributes jsontext.Value `json:"SegmentAttributes,omitempty"` } func summarizeContact(c *contactRec) ContactSearchSummary { diff --git a/services/connect/searchengine.go b/services/connect/searchengine.go index 63d391048b..e27096ffb2 100644 --- a/services/connect/searchengine.go +++ b/services/connect/searchengine.go @@ -1,10 +1,12 @@ package connect import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -124,17 +126,17 @@ type searchSpec struct { fields []string } -func parseSearch(rawCriteria, rawFilter json.RawMessage, spec searchSpec) (*searchCriteria, *searchFilter, error) { +func parseSearch(rawCriteria, rawFilter jsontext.Value, spec searchSpec) (*searchCriteria, *searchFilter, error) { var c searchCriteria var f searchFilter - for raw, dst := range map[*json.RawMessage]any{&rawCriteria: &c, &rawFilter: &f} { + for raw, dst := range map[*jsontext.Value]any{&rawCriteria: &c, &rawFilter: &f} { if len(*raw) == 0 || string(*raw) == jsonNull { continue } - if err := json.Unmarshal(*raw, dst); err != nil { + if err := json.Unmarshal(*raw, dst, jsonx.V1()); err != nil { return nil, nil, invalidParamErr("SearchCriteria or SearchFilter is not valid") } } diff --git a/services/connect/searches.go b/services/connect/searches.go index d628b95bee..5ad78bcbc6 100644 --- a/services/connect/searches.go +++ b/services/connect/searches.go @@ -2,18 +2,21 @@ package connect import ( "cmp" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // SearchRequest is the body shared by the Search* operations. type SearchRequest struct { InstanceID string `json:"InstanceId"` NextToken string - SearchCriteria json.RawMessage - SearchFilter json.RawMessage + SearchCriteria jsontext.Value + SearchFilter jsontext.Value ResourceTypes []string MaxResults int32 } @@ -435,10 +438,10 @@ type TagSet struct { Value string `json:"value"` } -func parseTagSearch(raw json.RawMessage) (func(k, v string) bool, error) { +func parseTagSearch(raw jsontext.Value) (func(k, v string) bool, error) { var c resourceTagsCriteria if len(raw) > 0 && string(raw) != jsonNull { - if err := json.Unmarshal(raw, &c); err != nil { + if err := json.Unmarshal(raw, &c, jsonx.V1()); err != nil { return nil, invalidParamErr("SearchCriteria is not valid") } } diff --git a/services/connect/security.go b/services/connect/security.go index 4809c00633..c729d59969 100644 --- a/services/connect/security.go +++ b/services/connect/security.go @@ -2,7 +2,7 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" "slices" "github.com/google/uuid" @@ -18,13 +18,13 @@ type CreateSecurityProfileInput struct { instPath AllowedAccessControlHierarchyGroupID string `json:"AllowedAccessControlHierarchyGroupId"` Description string - AllowedAIAgents json.RawMessage - Applications json.RawMessage - AllowedFlowModules json.RawMessage + AllowedAIAgents jsontext.Value + Applications jsontext.Value + AllowedFlowModules jsontext.Value HierarchyRestrictedResources []string Permissions []string TagRestrictedResources []string - GranularAccessControlConfiguration json.RawMessage + GranularAccessControlConfiguration jsontext.Value } // UpdateSecurityProfileInput is the UpdateSecurityProfile request. @@ -34,10 +34,10 @@ type UpdateSecurityProfileInput struct { AllowedAccessControlHierarchyGroupID *string `json:"AllowedAccessControlHierarchyGroupId"` ProfileID string `json:"-" path:"SecurityProfileId"` instPath - GranularAccessControlConfiguration json.RawMessage - AllowedAIAgents json.RawMessage - AllowedFlowModules json.RawMessage - Applications json.RawMessage + GranularAccessControlConfiguration jsontext.Value + AllowedAIAgents jsontext.Value + AllowedFlowModules jsontext.Value + Applications jsontext.Value HierarchyRestrictedResources []string Permissions []string TagRestrictedResources []string diff --git a/services/connect/securityextras.go b/services/connect/securityextras.go index f3189ef3a7..0c43c45535 100644 --- a/services/connect/securityextras.go +++ b/services/connect/securityextras.go @@ -2,18 +2,21 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxSecurityExtras = 100 // securityExtras are the Applications, AllowedFlowModules and AllowedAIAgents of a security profile. type securityExtras struct { - AIAgents json.RawMessage `json:"aiAgents,omitempty"` - Applications json.RawMessage `json:"applications,omitempty"` - FlowModules json.RawMessage `json:"flowModules,omitempty"` + AIAgents jsontext.Value `json:"aiAgents,omitempty"` + Applications jsontext.Value `json:"applications,omitempty"` + FlowModules jsontext.Value `json:"flowModules,omitempty"` } type entityProfilesRec struct { @@ -23,7 +26,7 @@ type entityProfilesRec struct { } // checkSecurityExtras validates the three lists and returns them ready to store; nil inputs stay nil. -func (b *Backend) checkSecurityExtras(inst string, aiAgents, apps, modules json.RawMessage) (securityExtras, error) { +func (b *Backend) checkSecurityExtras(inst string, aiAgents, apps, modules jsontext.Value) (securityExtras, error) { var out securityExtras if present(aiAgents) { @@ -53,9 +56,9 @@ func (b *Backend) checkSecurityExtras(inst string, aiAgents, apps, modules json. return out, nil } -func present(raw json.RawMessage) bool { return len(raw) > 0 && string(raw) != jsonNull } +func present(raw jsontext.Value) bool { return len(raw) > 0 && string(raw) != jsonNull } -func checkAIAgents(raw json.RawMessage) error { +func checkAIAgents(raw jsontext.Value) error { var items []struct { Arn string `json:"Arn"` Type string `json:"Type"` @@ -77,7 +80,7 @@ func checkAIAgents(raw json.RawMessage) error { return nil } -func checkApplications(raw json.RawMessage) error { +func checkApplications(raw jsontext.Value) error { var items []struct { Namespace string `json:"Namespace"` Type string `json:"Type"` @@ -100,7 +103,7 @@ func checkApplications(raw json.RawMessage) error { return nil } -func (b *Backend) checkFlowModules(inst string, raw json.RawMessage) error { +func (b *Backend) checkFlowModules(inst string, raw jsontext.Value) error { var items []struct { FlowModuleID string `json:"FlowModuleId"` Type string `json:"Type"` @@ -123,13 +126,13 @@ func (b *Backend) checkFlowModules(inst string, raw json.RawMessage) error { return nil } -func decodeBounded(raw json.RawMessage, dst any, field string) error { - if err := json.Unmarshal(raw, dst); err != nil { +func decodeBounded(raw jsontext.Value, dst any, field string) error { + if err := json.Unmarshal(raw, dst, jsonx.V1()); err != nil { return invalidParamErr("%s is not valid", field) } - var list []json.RawMessage - if json.Unmarshal(raw, &list) == nil && len(list) > maxSecurityExtras { + var list []jsontext.Value + if json.Unmarshal(raw, &list, jsonx.V1()) == nil && len(list) > maxSecurityExtras { return invalidParamErr("%s can hold at most %d entries", field, maxSecurityExtras) } @@ -172,10 +175,10 @@ type SecurityListInput struct { type SecurityListOut map[string]any func (b *Backend) listSecurityExtra( - op, key string, in *SecurityListInput, pick func(*securityExtras) json.RawMessage, + op, key string, in *SecurityListInput, pick func(*securityExtras) jsontext.Value, ) (*SecurityListOut, error) { var ( - items []json.RawMessage + items []jsontext.Value stamp Stamp ) @@ -188,7 +191,7 @@ func (b *Backend) listSecurityExtra( stamp = p.Profile.Stamp if raw := pick(&p.Extras); raw != nil { - err = json.Unmarshal(raw, &items) + err = json.Unmarshal(raw, &items, jsonx.V1()) } return &empty{}, err @@ -216,19 +219,19 @@ func (b *Backend) listSecurityExtra( // ListSecurityProfileApplications lists the applications a security profile grants. func (b *Backend) ListSecurityProfileApplications(in *SecurityListInput) (*SecurityListOut, error) { return b.listSecurityExtra("ListSecurityProfileApplications", "Applications", in, - func(e *securityExtras) json.RawMessage { return e.Applications }) + func(e *securityExtras) jsontext.Value { return e.Applications }) } // ListSecurityProfileFlowModules lists the flow modules a security profile allows. func (b *Backend) ListSecurityProfileFlowModules(in *SecurityListInput) (*SecurityListOut, error) { return b.listSecurityExtra("ListSecurityProfileFlowModules", "AllowedFlowModules", in, - func(e *securityExtras) json.RawMessage { return e.FlowModules }) + func(e *securityExtras) jsontext.Value { return e.FlowModules }) } // ListSecurityProfileAIAgents lists the AI agents a security profile allows. func (b *Backend) ListSecurityProfileAIAgents(in *SecurityListInput) (*SecurityListOut, error) { return b.listSecurityExtra("ListSecurityProfileAIAgents", "AllowedAIAgents", in, - func(e *securityExtras) json.RawMessage { return e.AIAgents }) + func(e *securityExtras) jsontext.Value { return e.AIAgents }) } // EntityProfilesInput is the Associate/Disassociate/ListEntitySecurityProfiles request. diff --git a/services/connect/users.go b/services/connect/users.go index 8fa30a0c57..1a080973ff 100644 --- a/services/connect/users.go +++ b/services/connect/users.go @@ -2,7 +2,7 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" "slices" "unicode" @@ -29,12 +29,12 @@ type CreateUserInput struct { HierarchyGroupID string `json:"HierarchyGroupId"` DirectoryUserID string `json:"DirectoryUserId"` Username string - AfterContactWorkConfigs json.RawMessage - VoiceEnhancementConfigs json.RawMessage - PhoneNumberConfigs json.RawMessage - PersistentConnectionConfigs json.RawMessage + AfterContactWorkConfigs jsontext.Value + VoiceEnhancementConfigs jsontext.Value + PhoneNumberConfigs jsontext.Value + PersistentConnectionConfigs jsontext.Value SecurityProfileIDs []string `json:"SecurityProfileIds"` - AutoAcceptConfigs json.RawMessage + AutoAcceptConfigs jsontext.Value } // UpdateUserInput carries the user update requests; each op reads only its own members. diff --git a/services/connect/views.go b/services/connect/views.go index 5bd749287a..d082f35405 100644 --- a/services/connect/views.go +++ b/services/connect/views.go @@ -2,13 +2,15 @@ package connect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "slices" "strconv" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -113,7 +115,7 @@ func checkViewContent(c *ViewInputContent) (*ViewContent, error) { return nil, invalidParamErr("Content.Template is required") } - if !json.Valid([]byte(c.Template)) { + if !jsontext.Value([]byte(c.Template)).IsValid(jsonx.V1()) { return nil, invalidParamErr("Content.Template must be valid JSON") } @@ -137,7 +139,7 @@ func viewSchema(template string) string { raw, _ := json.Marshal(map[string]any{ "$schema": "http://json-schema.org/draft-07/schema#", fieldType: "object", "properties": props, - }) + }, jsonx.V1()) return string(raw) } diff --git a/services/connect/workspaces.go b/services/connect/workspaces.go index 1f08156e35..cb02205d87 100644 --- a/services/connect/workspaces.go +++ b/services/connect/workspaces.go @@ -3,11 +3,13 @@ package connect import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -59,13 +61,13 @@ type MediaItem struct { type Workspace struct { Tags map[string]string `json:"Tags,omitempty"` Stamp - Arn string `json:"Arn"` - ID string `json:"Id"` - Name string `json:"Name"` - Description string `json:"Description,omitempty"` - Title string `json:"Title,omitempty"` - Visibility string `json:"Visibility"` - Theme json.RawMessage `json:"Theme,omitempty"` + Arn string `json:"Arn"` + ID string `json:"Id"` + Name string `json:"Name"` + Description string `json:"Description,omitempty"` + Title string `json:"Title,omitempty"` + Visibility string `json:"Visibility"` + Theme jsontext.Value `json:"Theme,omitempty"` } // WorkspaceSummary is the list shape of a workspace. @@ -112,19 +114,19 @@ type wsRef struct { instPath } -func checkTheme(raw json.RawMessage) error { +func checkTheme(raw jsontext.Value) error { if len(raw) == 0 || string(raw) == jsonNull { return nil } - var modes map[string]json.RawMessage - if err := json.Unmarshal(raw, &modes); err != nil { + var modes map[string]jsontext.Value + if err := json.Unmarshal(raw, &modes, jsonx.V1()); err != nil { return invalidParamErr("Theme must be an object") } for k, v := range modes { - var cfg map[string]json.RawMessage - if k != "Light" && k != "Dark" || json.Unmarshal(v, &cfg) != nil { + var cfg map[string]jsontext.Value + if k != "Light" && k != "Dark" || json.Unmarshal(v, &cfg, jsonx.V1()) != nil { return invalidParamErr("Theme may only hold Light and Dark theme configurations") } } @@ -151,7 +153,7 @@ type CreateWorkspaceInput struct { Description string Title string instPath - Theme json.RawMessage + Theme jsontext.Value } type createWorkspaceOut struct { @@ -261,7 +263,7 @@ func (b *Backend) UpdateWorkspaceMetadata(ctx context.Context, in *UpdateWorkspa // UpdateWorkspaceThemeInput is the UpdateWorkspaceTheme request. type UpdateWorkspaceThemeInput struct { wsRef - Theme json.RawMessage + Theme jsontext.Value } // UpdateWorkspaceTheme replaces the theme of a workspace. @@ -371,8 +373,8 @@ func (b *Backend) checkPageFields(inst string, in *pageFields) error { } if in.InputData != nil { - var obj map[string]json.RawMessage - if json.Unmarshal([]byte(*in.InputData), &obj) != nil { + var obj map[string]jsontext.Value + if json.Unmarshal([]byte(*in.InputData), &obj, jsonx.V1()) != nil { return invalidParamErr("InputData must be a JSON object") } } diff --git a/services/cosmosdb/account_ops_test.go b/services/cosmosdb/account_ops_test.go index e651ad8e5b..93126cb2bb 100644 --- a/services/cosmosdb/account_ops_test.go +++ b/services/cosmosdb/account_ops_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_AccountRoot covers the database-account root resource ("" or @@ -72,7 +74,7 @@ func TestHandler_AccountRoot_EndpointEchoesRequestHost(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) const wantEndpoint = "http://localhost:32771/" diff --git a/services/cosmosdb/batch.go b/services/cosmosdb/batch.go index a3810d3704..578977b7ca 100644 --- a/services/cosmosdb/batch.go +++ b/services/cosmosdb/batch.go @@ -2,7 +2,8 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxBatchOperations is the Cosmos limit on operations in one transactional batch. @@ -33,10 +35,10 @@ func isBatchRequest(r *http.Request) bool { } type batchOpWire struct { - OperationType string `json:"operationType"` - ID string `json:"id"` - IfMatch string `json:"ifMatch"` - ResourceBody json.RawMessage `json:"resourceBody"` + OperationType string `json:"operationType"` + ID string `json:"id"` + IfMatch string `json:"ifMatch"` + ResourceBody jsontext.Value `json:"resourceBody"` } // batchOp is a decoded batch operation. @@ -59,8 +61,8 @@ type BatchResult struct { func parseBatchOps(raw []byte) ([]batchOp, error) { var wire []batchOpWire - dec := json.NewDecoder(bytes.NewReader(raw)) - if err := dec.Decode(&wire); err != nil || rejectTrailingJSON(dec) != nil { + dec := jsontext.NewDecoder(bytes.NewReader(raw), jsonx.V1()) + if err := json.UnmarshalDecode(dec, &wire, jsonx.V1()); err != nil || rejectTrailingJSON(dec) != nil { return nil, fmt.Errorf("%w: the batch body is not a JSON array of operations", ErrInvalidDocument) } @@ -190,7 +192,7 @@ func (tx *containerTx) runBatchOp(pk string, op batchOp) (BatchResult, error) { } body := documentAsMap(info) - size, _ := json.Marshal(body) + size, _ := json.Marshal(body, jsonx.V1()) return BatchResult{ StatusCode: status, ETag: info.ETag, Body: body, diff --git a/services/cosmosdb/changelog.go b/services/cosmosdb/changelog.go index b26d14e64f..64be63997b 100644 --- a/services/cosmosdb/changelog.go +++ b/services/cosmosdb/changelog.go @@ -1,9 +1,11 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -125,10 +127,10 @@ func partitionKeyObject(paths []string, pkJSON string) map[string]any { var vals []any if len(paths) == 1 { var v any - if json.Unmarshal([]byte(pkJSON), &v) == nil { + if json.Unmarshal([]byte(pkJSON), &v, jsonx.V1()) == nil { vals = []any{v} } - } else if json.Unmarshal([]byte(pkJSON), &vals) != nil { + } else if json.Unmarshal([]byte(pkJSON), &vals, jsonx.V1()) != nil { vals = nil } diff --git a/services/cosmosdb/computed_properties_test.go b/services/cosmosdb/computed_properties_test.go index a6aeefaad9..167ed15110 100644 --- a/services/cosmosdb/computed_properties_test.go +++ b/services/cosmosdb/computed_properties_test.go @@ -1,7 +1,8 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -20,10 +22,10 @@ func computedBackend(t *testing.T, indexing string, props string) *cosmosdb.InMe require.NoError(t, err) spec := cosmosdb.ContainerSpec{ - ID: "c", PartitionKeyPath: "/pk", ComputedProperties: json.RawMessage(props), + ID: "c", PartitionKeyPath: "/pk", ComputedProperties: jsontext.Value(props), } if indexing != "" { - spec.IndexingPolicy = json.RawMessage(indexing) + spec.IndexingPolicy = jsontext.Value(indexing) } _, err = b.CreateContainer("d", spec) @@ -77,7 +79,7 @@ func TestComputedProperties_IndexSelection(t *testing.T) { name: "group by a computed property", indexing: defaultPolicy, query: `SELECT VALUE COUNT(1) FROM c GROUP BY c.cp_lower`, - want: []any{json.Number("1"), json.Number("1"), json.Number("1")}, + want: []any{jsonx.Number("1"), jsonx.Number("1"), jsonx.Number("1")}, }, { name: "select star leaves computed properties out", indexing: defaultPolicy, @@ -207,7 +209,7 @@ func TestComputedProperties_DefinitionRules(t *testing.T) { props = append(props, map[string]string{"name": fmt.Sprintf("p%d", i), "query": "SELECT VALUE 1 FROM c"}) } - raw, err := json.Marshal(props) + raw, err := json.Marshal(props, jsonx.V1()) require.NoError(t, err) s := newLiveServer(t) diff --git a/services/cosmosdb/computed_rules.go b/services/cosmosdb/computed_rules.go index d60e14b0ea..e39a932da9 100644 --- a/services/cosmosdb/computed_rules.go +++ b/services/cosmosdb/computed_rules.go @@ -1,7 +1,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" ) @@ -133,7 +133,7 @@ func (p indexingPolicy) isComputed(path string) bool { } // checkComputedIndexes rejects a spatial index on a computed property. -func checkComputedIndexes(pol indexingPolicy, raw json.RawMessage) error { +func checkComputedIndexes(pol indexingPolicy, raw jsontext.Value) error { props, err := parseComputedProperties(raw) if err != nil || len(props) == 0 { return err diff --git a/services/cosmosdb/conflicts.go b/services/cosmosdb/conflicts.go index 848c2c9c28..bd0c86ff7a 100644 --- a/services/cosmosdb/conflicts.go +++ b/services/cosmosdb/conflicts.go @@ -1,11 +1,14 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Conflict resolution modes (azcosmos ConflictResolutionMode) and the default last-writer-wins path. @@ -24,11 +27,11 @@ type conflictPolicy struct { Procedure string `json:"conflictResolutionProcedure"` } -func parseConflictPolicy(raw json.RawMessage) conflictPolicy { +func parseConflictPolicy(raw jsontext.Value) conflictPolicy { p := conflictPolicy{Mode: conflictModeLWW, Path: conflictPathTS} if len(raw) > 0 { - _ = json.Unmarshal(raw, &p) + _ = json.Unmarshal(raw, &p, jsonx.V1()) } if p.Mode == "" { @@ -76,7 +79,7 @@ func conflictValue(d *storedDocument, path string) float64 { } switch n := cur.(type) { - case json.Number: + case jsonx.Number: if f, err := n.Float64(); err == nil { return f } @@ -279,13 +282,13 @@ func (b *InMemoryBackend) resolveRegion(region string) string { // validateConflictPolicy checks a container's conflictResolutionPolicy: mode LastWriterWins or Custom, a path that // starts with /. -func validateConflictPolicy(raw json.RawMessage) error { +func validateConflictPolicy(raw jsontext.Value) error { if len(raw) == 0 || isJSONNull(raw) { return nil } var p conflictPolicy - if err := json.Unmarshal(raw, &p); err != nil { + if err := json.Unmarshal(raw, &p, jsonx.V1()); err != nil { return fmt.Errorf("%w: conflictResolutionPolicy is malformed", ErrInvalidContainerPolicy) } @@ -304,7 +307,7 @@ func validateConflictPolicy(raw json.RawMessage) error { // conflictPolicyChanged reports whether a replace request names a policy different from the stored one; the policy // can only be set at container creation (Learn, "Manage conflicts between regions"). -func conflictPolicyChanged(stored, requested json.RawMessage) bool { +func conflictPolicyChanged(stored, requested jsontext.Value) bool { return len(requested) > 0 && !isJSONNull(requested) && parseConflictPolicy(stored) != parseConflictPolicy(requested) } diff --git a/services/cosmosdb/container_ops.go b/services/cosmosdb/container_ops.go index b196eb69cc..aa7325ce77 100644 --- a/services/cosmosdb/container_ops.go +++ b/services/cosmosdb/container_ops.go @@ -1,7 +1,8 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // partitionKeyDefSpec is the "partitionKey" sub-object of a container definition: @@ -24,9 +26,9 @@ type containerBodyRequest struct { DefaultTTL *int `json:"defaultTtl"` ID string `json:"id"` PartitionKey partitionKeyDefSpec `json:"partitionKey"` - IndexingPolicy json.RawMessage `json:"indexingPolicy"` + IndexingPolicy jsontext.Value `json:"indexingPolicy"` - ConflictResolutionPolicy json.RawMessage `json:"conflictResolutionPolicy"` + ConflictResolutionPolicy jsontext.Value `json:"conflictResolutionPolicy"` ContainerPolicies } @@ -111,7 +113,7 @@ func (h *Handler) readContainerRequest(c *echo.Context) (containerBodyRequest, b return req, false } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { _ = h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") return req, false @@ -288,12 +290,12 @@ func resourceTS(modified time.Time) int64 { } // echoOrDefault returns raw, or def when raw is unset. -func echoOrDefault(raw json.RawMessage, def string) json.RawMessage { +func echoOrDefault(raw jsontext.Value, def string) jsontext.Value { if len(raw) > 0 { return raw } - return json.RawMessage(def) + return jsontext.Value(def) } // addPolicyFields echoes the container policies; unset unique-key and geospatial settings show their defaults. @@ -301,7 +303,7 @@ func addPolicyFields(body map[string]any, p ContainerPolicies) { body["uniqueKeyPolicy"] = echoOrDefault(p.UniqueKeyPolicy, defaultUniqueKeyPol) body["geospatialConfig"] = echoOrDefault(p.GeospatialConfig, defaultGeoConfig) - for key, raw := range map[string]json.RawMessage{ + for key, raw := range map[string]jsontext.Value{ "vectorEmbeddingPolicy": p.VectorEmbeddingPolicy, "fullTextPolicy": p.FullTextPolicy, "computedProperties": p.ComputedProperties, } { @@ -331,7 +333,7 @@ func containerBody(info ContainerInfo) map[string]any { kind = partitionKindMultiHash } - indexing := json.RawMessage(defaultIndexingPolicy) + indexing := jsontext.Value(defaultIndexingPolicy) if len(info.IndexingPolicy) > 0 { indexing = info.IndexingPolicy } @@ -357,7 +359,7 @@ func containerBody(info ContainerInfo) map[string]any { addPolicyFields(body, info.ContainerPolicies) - body["conflictResolutionPolicy"] = json.RawMessage(defaultConflictResolutionPolicy) + body["conflictResolutionPolicy"] = jsontext.Value(defaultConflictResolutionPolicy) if len(info.ConflictResolutionPolicy) > 0 { body["conflictResolutionPolicy"] = info.ConflictResolutionPolicy } diff --git a/services/cosmosdb/container_policy.go b/services/cosmosdb/container_policy.go index 299d4d190b..b27e0708e3 100644 --- a/services/cosmosdb/container_policy.go +++ b/services/cosmosdb/container_policy.go @@ -2,11 +2,14 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "reflect" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -22,11 +25,11 @@ const ( // ContainerPolicies are the optional container settings that are stored and echoed verbatim; the unique-key // policy is enforced on writes and computed properties are evaluated by queries. type ContainerPolicies struct { - UniqueKeyPolicy json.RawMessage `json:"uniqueKeyPolicy"` - GeospatialConfig json.RawMessage `json:"geospatialConfig"` - VectorEmbeddingPolicy json.RawMessage `json:"vectorEmbeddingPolicy"` - FullTextPolicy json.RawMessage `json:"fullTextPolicy"` - ComputedProperties json.RawMessage `json:"computedProperties"` + UniqueKeyPolicy jsontext.Value `json:"uniqueKeyPolicy"` + GeospatialConfig jsontext.Value `json:"geospatialConfig"` + VectorEmbeddingPolicy jsontext.Value `json:"vectorEmbeddingPolicy"` + FullTextPolicy jsontext.Value `json:"fullTextPolicy"` + ComputedProperties jsontext.Value `json:"computedProperties"` } type uniqueKeyPolicy struct { @@ -40,14 +43,14 @@ type computedProperty struct { Query string `json:"query"` } -func isJSONNull(raw json.RawMessage) bool { +func isJSONNull(raw jsontext.Value) bool { t := bytes.TrimSpace(raw) return len(t) == 0 || bytes.Equal(t, []byte(nullText)) } func (p ContainerPolicies) stored() ContainerPolicies { - for _, f := range []*json.RawMessage{ + for _, f := range []*jsontext.Value{ &p.UniqueKeyPolicy, &p.GeospatialConfig, &p.VectorEmbeddingPolicy, &p.FullTextPolicy, &p.ComputedProperties, } { if isJSONNull(*f) { @@ -81,13 +84,13 @@ func (p ContainerPolicies) validate() error { // validateSearchIndexes checks the indexing policy's vector and full-text indexes against the policies they // refer to. -func validateSearchIndexes(indexing json.RawMessage, p ContainerPolicies) error { +func validateSearchIndexes(indexing jsontext.Value, p ContainerPolicies) error { if len(indexing) == 0 { return nil } var pol indexingPolicy - if err := json.Unmarshal(indexing, &pol); err != nil { + if err := json.Unmarshal(indexing, &pol, jsonx.V1()); err != nil { return fmt.Errorf("%w: indexingPolicy is malformed", ErrInvalidContainerPolicy) } @@ -148,26 +151,27 @@ func (c *storedContainer) checkReplaceIndexes(spec ContainerSpec) error { } // sameJSON reports whether two raw JSON values are semantically equal. -func sameJSON(a, b json.RawMessage) bool { +func sameJSON(a, b jsontext.Value) bool { var va, vb any - return json.Unmarshal(a, &va) == nil && json.Unmarshal(b, &vb) == nil && reflect.DeepEqual(va, vb) + return json.Unmarshal(a, &va, jsonx.V1()) == nil && json.Unmarshal(b, &vb, jsonx.V1()) == nil && + reflect.DeepEqual(va, vb) } -func parseUniqueKeyPolicy(raw json.RawMessage) (uniqueKeyPolicy, error) { +func parseUniqueKeyPolicy(raw jsontext.Value) (uniqueKeyPolicy, error) { var pol uniqueKeyPolicy if isJSONNull(raw) { return pol, nil } - if err := json.Unmarshal(raw, &pol); err != nil { + if err := json.Unmarshal(raw, &pol, jsonx.V1()); err != nil { return pol, fmt.Errorf("%w: uniqueKeyPolicy is malformed", ErrInvalidContainerPolicy) } return pol, nil } -func validateUniqueKeyPolicy(raw json.RawMessage) error { +func validateUniqueKeyPolicy(raw jsontext.Value) error { pol, err := parseUniqueKeyPolicy(raw) if err != nil { return err @@ -192,7 +196,7 @@ func validateUniqueKeyPolicy(raw json.RawMessage) error { return nil } -func validateGeospatialConfig(raw json.RawMessage) error { +func validateGeospatialConfig(raw jsontext.Value) error { if isJSONNull(raw) { return nil } @@ -201,7 +205,12 @@ func validateGeospatialConfig(raw json.RawMessage) error { Type string `json:"type"` } - if err := json.Unmarshal(raw, &cfg); err != nil || (cfg.Type != geoTypeGeography && cfg.Type != geoTypeGeometry) { + if err := json.Unmarshal( + raw, + &cfg, + jsonx.V1(), + ); err != nil || + (cfg.Type != geoTypeGeography && cfg.Type != geoTypeGeometry) { return fmt.Errorf("%w: geospatialConfig.type must be %s or %s", ErrInvalidContainerPolicy, geoTypeGeography, geoTypeGeometry) } @@ -211,13 +220,13 @@ func validateGeospatialConfig(raw json.RawMessage) error { // parseComputedProperties decodes and validates computedProperties: each query must be // SELECT VALUE FROM with nothing else. -func parseComputedProperties(raw json.RawMessage) ([]computedProperty, error) { +func parseComputedProperties(raw jsontext.Value) ([]computedProperty, error) { if isJSONNull(raw) { return nil, nil } var props []computedProperty - if err := json.Unmarshal(raw, &props); err != nil { + if err := json.Unmarshal(raw, &props, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: computedProperties is malformed", ErrInvalidContainerPolicy) } @@ -273,7 +282,7 @@ func isBareSelectValue(stmt *sqlSelectStmt) bool { } // policiesEqualUnique reports whether two raw unique-key policies are semantically identical. -func policiesEqualUnique(a, b json.RawMessage) bool { +func policiesEqualUnique(a, b jsontext.Value) bool { pa, errA := parseUniqueKeyPolicy(a) pb, errB := parseUniqueKeyPolicy(b) @@ -309,7 +318,7 @@ func (c *storedContainer) applyPolicies(p ContainerPolicies, replacing bool) err c.UniqueKeyPolicy = p.UniqueKeyPolicy } - for _, f := range []struct{ dst, src *json.RawMessage }{ + for _, f := range []struct{ dst, src *jsontext.Value }{ {&c.GeospatialConfig, &p.GeospatialConfig}, {&c.VectorEmbeddingPolicy, &p.VectorEmbeddingPolicy}, {&c.FullTextPolicy, &p.FullTextPolicy}, @@ -324,7 +333,7 @@ func (c *storedContainer) applyPolicies(p ContainerPolicies, replacing bool) err } // policyChanged reports whether a replace request carries a setting different from the stored one. -func policyChanged(stored, requested json.RawMessage) bool { +func policyChanged(stored, requested jsontext.Value) bool { return requested != nil && !sameJSON(stored, requested) } diff --git a/services/cosmosdb/container_policy_test.go b/services/cosmosdb/container_policy_test.go index 2eb021bf59..58e9b79231 100644 --- a/services/cosmosdb/container_policy_test.go +++ b/services/cosmosdb/container_policy_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -357,7 +358,7 @@ func TestComputedProperties_UsableInQueries(t *testing.T) { func mustJSON(t *testing.T, v any) []byte { t.Helper() - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return out diff --git a/services/cosmosdb/database_ops.go b/services/cosmosdb/database_ops.go index 950b06c581..e6bd97a137 100644 --- a/services/cosmosdb/database_ops.go +++ b/services/cosmosdb/database_ops.go @@ -1,13 +1,14 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createDatabaseBody is the request body shape for POST /dbs. @@ -58,7 +59,7 @@ func (h *Handler) createDatabase(c *echo.Context) error { } var req createDatabaseBody - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } diff --git a/services/cosmosdb/document_ops.go b/services/cosmosdb/document_ops.go index d82601a695..6ec30ce5ef 100644 --- a/services/cosmosdb/document_ops.go +++ b/services/cosmosdb/document_ops.go @@ -2,7 +2,8 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -75,11 +77,10 @@ func partitionKeyFromHeader(r *http.Request) (string, error) { } func parsePartitionKeyHeader(raw string) (string, error) { - dec := json.NewDecoder(bytes.NewReader([]byte(raw))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(raw)), jsonx.V1()) var arr []any - if err := dec.Decode(&arr); err != nil { + if err := json.UnmarshalDecode(dec, &arr, jsonx.V1(), jsonx.UseNumber()); err != nil { return "", fmt.Errorf("%w: malformed %s header: %w", ErrInvalidDocument, headerPartitionKey, err) } @@ -106,7 +107,7 @@ func parsePartitionKeyHeader(raw string) (string, error) { return canonicalPartitionKeyJSON(arr[0]) } - data, err := json.Marshal(arr) + data, err := json.Marshal(arr, jsonx.V1()) if err != nil { return "", fmt.Errorf("cosmosdb: encode partition key: %w", err) } @@ -370,10 +371,15 @@ func (h *Handler) queryDocuments(c *echo.Context, dbID, collID string) error { var req queryRequestBody - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body), jsonx.V1()) - if decErr := dec.Decode(&req); decErr != nil || rejectTrailingJSON(dec) != nil { + if decErr := json.UnmarshalDecode( + dec, + &req, + jsonx.V1(), + jsonx.UseNumber(), + ); decErr != nil || + rejectTrailingJSON(dec) != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } diff --git a/services/cosmosdb/fulltext.go b/services/cosmosdb/fulltext.go index aa241c758c..24c3288529 100644 --- a/services/cosmosdb/fulltext.go +++ b/services/cosmosdb/fulltext.go @@ -1,12 +1,15 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -32,20 +35,20 @@ type fullTextPolicy struct { FullTextPaths []fullTextPath `json:"fullTextPaths"` } -func parseFullTextPolicy(raw json.RawMessage) (fullTextPolicy, error) { +func parseFullTextPolicy(raw jsontext.Value) (fullTextPolicy, error) { var pol fullTextPolicy if isJSONNull(raw) { return pol, nil } - if err := json.Unmarshal(raw, &pol); err != nil { + if err := json.Unmarshal(raw, &pol, jsonx.V1()); err != nil { return pol, fmt.Errorf("%w: fullTextPolicy is malformed", ErrInvalidContainerPolicy) } return pol, nil } -func validateFullTextPolicy(raw json.RawMessage) error { +func validateFullTextPolicy(raw jsontext.Value) error { pol, err := parseFullTextPolicy(raw) if err != nil { return err diff --git a/services/cosmosdb/fulltext_analysis_test.go b/services/cosmosdb/fulltext_analysis_test.go index d9ad640a26..ff6fa5a61c 100644 --- a/services/cosmosdb/fulltext_analysis_test.go +++ b/services/cosmosdb/fulltext_analysis_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "testing" @@ -21,10 +21,10 @@ func ftBackend(t *testing.T, lang string, texts ...string) *cosmosdb.InMemoryBac policy := fmt.Sprintf(`{"defaultLanguage":%q,"fullTextPaths":[{"path":"/text","language":%q}]}`, lang, lang) _, err = b.CreateContainer("d", cosmosdb.ContainerSpec{ ID: "c", PartitionKeyPath: "/pk", - IndexingPolicy: json.RawMessage( + IndexingPolicy: jsontext.Value( `{"indexingMode":"consistent","includedPaths":[{"path":"/*"}],"excludedPaths":[],` + `"fullTextIndexes":[{"path":"/text"}]}`), - FullTextPolicy: json.RawMessage(policy), + FullTextPolicy: jsontext.Value(policy), }) require.NoError(t, err) diff --git a/services/cosmosdb/handler.go b/services/cosmosdb/handler.go index 33d7407a26..adf55f58c0 100644 --- a/services/cosmosdb/handler.go +++ b/services/cosmosdb/handler.go @@ -4,7 +4,7 @@ import ( "context" "crypto/rand" "crypto/tls" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/devtls" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/odatatable" @@ -827,7 +828,7 @@ func (h *Handler) writeJSON(c *echo.Context, status int, v any) error { return c.Blob(status, "application/json", body) } -func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) } +func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v, jsonx.V1()) } // cosmosErrorBody is Cosmos DB's JSON error envelope shape: // {"code":"NotFound","message":"..."}. @@ -846,7 +847,7 @@ func (h *Handler) writeError(c *echo.Context, status int, code, message string) // itself somehow failed to marshal, though it never does in practice since // it's built from plain strings). func (h *Handler) writeErrorNoRecurse(c *echo.Context, status int, code, message string) error { - body, _ := json.Marshal(cosmosErrorBody{Code: code, Message: message}) + body, _ := json.Marshal(cosmosErrorBody{Code: code, Message: message}, jsonx.V1()) return c.Blob(status, "application/json", body) } diff --git a/services/cosmosdb/handler_test.go b/services/cosmosdb/handler_test.go index e6237d9eda..841c6a8257 100644 --- a/services/cosmosdb/handler_test.go +++ b/services/cosmosdb/handler_test.go @@ -2,7 +2,7 @@ package cosmosdb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -54,7 +55,7 @@ func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -107,7 +108,7 @@ func TestHandler_DatabaseLifecycle(t *testing.T) { h := newTestHandler(t) - createBody, err := json.Marshal(map[string]string{"id": "mydb"}) + createBody, err := json.Marshal(map[string]string{"id": "mydb"}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs", nil, createBody) @@ -176,7 +177,7 @@ func TestHandler_CreateDatabase_InvalidInput(t *testing.T) { func createTestDatabase(t *testing.T, h *cosmosdb.Handler) { t.Helper() - body, err := json.Marshal(map[string]string{"id": "mydb"}) + body, err := json.Marshal(map[string]string{"id": "mydb"}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs", nil, body) @@ -189,7 +190,7 @@ func createTestContainer(t *testing.T, h *cosmosdb.Handler) { body, err := json.Marshal(map[string]any{ "id": "mycoll", "partitionKey": map[string]any{"paths": []string{"/pk"}, "kind": "Hash"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls", nil, body) @@ -205,7 +206,7 @@ func TestHandler_ContainerLifecycle(t *testing.T) { body, err := json.Marshal(map[string]any{ "id": "mycoll", "partitionKey": map[string]any{"paths": []string{"/pk"}, "kind": "Hash"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls", nil, body) @@ -255,7 +256,7 @@ func TestHandler_CreateContainer_InvalidPartitionKey(t *testing.T) { body, err := json.Marshal(map[string]any{ "id": "mycoll", "partitionKey": map[string]any{"paths": []string{"/a", "/b"}, "kind": "Hash"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls", nil, body) @@ -276,7 +277,7 @@ func TestHandler_CreateContainer_EmptyPartitionKeyPath(t *testing.T) { body, err := json.Marshal(map[string]any{ "id": "mycoll", "partitionKey": map[string]any{"paths": []string{""}, "kind": "Hash"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls", nil, body) @@ -309,7 +310,7 @@ func TestHandler_DocumentLifecycle(t *testing.T) { createTestDatabase(t, h) createTestContainer(t, h) - docBody, err := json.Marshal(map[string]any{"id": "doc1", "pk": "partA", "value": 42}) + docBody, err := json.Marshal(map[string]any{"id": "doc1", "pk": "partA", "value": 42}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls/mycoll/docs", nil, docBody) @@ -349,7 +350,7 @@ func TestHandler_DocumentLifecycle(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) // Replace with wrong If-Match -> 412. - replaceBody, err := json.Marshal(map[string]any{"pk": "partA", "value": 100}) + replaceBody, err := json.Marshal(map[string]any{"pk": "partA", "value": 100}, jsonx.V1()) require.NoError(t, err) wrongEtagHeaders := map[string]string{ @@ -436,7 +437,7 @@ func TestHandler_ReadFeed(t *testing.T) { createTestContainer(t, h) for _, id := range []string{"a", "b"} { - body, err := json.Marshal(map[string]any{"id": id, "pk": "x"}) + body, err := json.Marshal(map[string]any{"id": id, "pk": "x"}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls/mycoll/docs", nil, body) @@ -458,14 +459,14 @@ func TestHandler_Query(t *testing.T) { createTestContainer(t, h) for i, name := range []string{"alice", "bob"} { - body, err := json.Marshal(map[string]any{"id": name, "pk": "x", "n": i}) + body, err := json.Marshal(map[string]any{"id": name, "pk": "x", "n": i}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls/mycoll/docs", nil, body) require.Equal(t, http.StatusCreated, rec.Code) } - queryBody, err := json.Marshal(map[string]any{"query": "SELECT * FROM c WHERE c.id = 'alice'"}) + queryBody, err := json.Marshal(map[string]any{"query": "SELECT * FROM c WHERE c.id = 'alice'"}, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, http.MethodPost, "/dbs/mydb/colls/mycoll/docs", @@ -476,7 +477,7 @@ func TestHandler_Query(t *testing.T) { assert.InDelta(t, 1, result["_count"], 0) // A malformed query yields 400, never a panic. - badBody, err := json.Marshal(map[string]any{"query": "NOT VALID SQL AT ALL ((("}) + badBody, err := json.Marshal(map[string]any{"query": "NOT VALID SQL AT ALL ((("}, jsonx.V1()) require.NoError(t, err) rec = doRequest(t, h, http.MethodPost, "/dbs/mydb/colls/mycoll/docs", diff --git a/services/cosmosdb/helpers_test.go b/services/cosmosdb/helpers_test.go index 36e43d88b6..d4a6c310ef 100644 --- a/services/cosmosdb/helpers_test.go +++ b/services/cosmosdb/helpers_test.go @@ -2,13 +2,16 @@ package cosmosdb_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // newEchoContextForRequest wraps req in an *echo.Context for tests that call @@ -32,12 +35,11 @@ func newEchoContextForRequest(t *testing.T, req *http.Request) *echo.Context { func decodeJSONBody(t *testing.T, raw string) map[string]any { t.Helper() - dec := json.NewDecoder(bytes.NewReader([]byte(raw))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(raw))) var m map[string]any - require.NoError(t, dec.Decode(&m)) + require.NoError(t, json.UnmarshalDecode(dec, &m, jsonx.V1(), jsonx.UseNumber())) return m } diff --git a/services/cosmosdb/indexing.go b/services/cosmosdb/indexing.go index 66e034d22a..ace0946244 100644 --- a/services/cosmosdb/indexing.go +++ b/services/cosmosdb/indexing.go @@ -1,9 +1,12 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -48,14 +51,14 @@ type indexingPolicy struct { } // parseIndexingPolicy decodes the stored policy; an unset or undecodable one yields the default (index all). -func parseIndexingPolicy(raw json.RawMessage) indexingPolicy { +func parseIndexingPolicy(raw jsontext.Value) indexingPolicy { if len(raw) == 0 { - raw = json.RawMessage(defaultIndexingPolicy) + raw = jsontext.Value(defaultIndexingPolicy) } var pol indexingPolicy - if err := json.Unmarshal(raw, &pol); err != nil { - _ = json.Unmarshal([]byte(defaultIndexingPolicy), &pol) + if err := json.Unmarshal(raw, &pol, jsonx.V1()); err != nil { + _ = json.Unmarshal([]byte(defaultIndexingPolicy), &pol, jsonx.V1()) } return pol @@ -64,7 +67,7 @@ func parseIndexingPolicy(raw json.RawMessage) indexingPolicy { // checkQueryIndexing applies the indexing policy to stmt: multi-property ORDER BY needs a composite index, and a // query whose filter cannot use the index needs the scan switch. func checkQueryIndexing( - stmt *sqlSelectStmt, raw json.RawMessage, opts QueryOptions, computed map[string]sqlExpr, + stmt *sqlSelectStmt, raw jsontext.Value, opts QueryOptions, computed map[string]sqlExpr, ) error { pol := parseIndexingPolicy(raw) pol.computed = make(map[string]struct{}, len(computed)) diff --git a/services/cosmosdb/live_server_test.go b/services/cosmosdb/live_server_test.go index 056b26c1a9..23c5be7917 100644 --- a/services/cosmosdb/live_server_test.go +++ b/services/cosmosdb/live_server_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -35,7 +36,7 @@ func (r rawResponse) object(t *testing.T) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(r.body, &m), string(r.body)) + require.NoError(t, json.Unmarshal(r.body, &m, jsonx.V1()), string(r.body)) return m } @@ -134,13 +135,13 @@ func stripSystem[T ~string | ~[]byte](t *testing.T, raw T) string { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(raw), &m)) + require.NoError(t, json.Unmarshal([]byte(raw), &m, jsonx.V1())) for _, k := range []string{"_rid", "_self", "_etag", "_ts", "_attachments", "_lsn"} { delete(m, k) } - out, err := json.Marshal(m) + out, err := json.Marshal(m, jsonx.V1()) require.NoError(t, err) return string(out) diff --git a/services/cosmosdb/models.go b/services/cosmosdb/models.go index 0650f84281..dd5a37f60c 100644 --- a/services/cosmosdb/models.go +++ b/services/cosmosdb/models.go @@ -4,7 +4,8 @@ import ( "bytes" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -12,6 +13,8 @@ import ( "strconv" "sync/atomic" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // System property names auto-populated on every Cosmos resource. Named @@ -108,9 +111,9 @@ type ContainerSpec struct { ID string PartitionKeyPath string PartitionKeyPaths []string - IndexingPolicy json.RawMessage + IndexingPolicy jsontext.Value - ConflictResolutionPolicy json.RawMessage + ConflictResolutionPolicy jsontext.Value ContainerPolicies } @@ -122,10 +125,10 @@ type ContainerInfo struct { RID string PartitionKeyPath string PartitionKeyPaths []string - IndexingPolicy json.RawMessage + IndexingPolicy jsontext.Value Modified time.Time - ConflictResolutionPolicy json.RawMessage + ConflictResolutionPolicy jsontext.Value ContainerPolicies } @@ -168,16 +171,16 @@ type storedContainer struct { Sprocs map[string]*storedScript `json:"Sprocs,omitempty"` PartitionKeyPath string ID string - GeospatialConfig json.RawMessage `json:"GeospatialConfig,omitempty"` - UniqueKeyPolicy json.RawMessage `json:"UniqueKeyPolicy,omitempty"` - ConflictResolutionPolicy json.RawMessage `json:"ConflictResolutionPolicy,omitempty"` + GeospatialConfig jsontext.Value `json:"GeospatialConfig,omitempty"` + UniqueKeyPolicy jsontext.Value `json:"UniqueKeyPolicy,omitempty"` + ConflictResolutionPolicy jsontext.Value `json:"ConflictResolutionPolicy,omitempty"` Conflicts []*conflictRecord `json:"Conflicts,omitempty"` PartitionKeyPaths []string `json:"PartitionKeyPaths,omitempty"` - VectorEmbeddingPolicy json.RawMessage `json:"VectorEmbeddingPolicy,omitempty"` - FullTextPolicy json.RawMessage `json:"FullTextPolicy,omitempty"` - ComputedProperties json.RawMessage `json:"ComputedProperties,omitempty"` + VectorEmbeddingPolicy jsontext.Value `json:"VectorEmbeddingPolicy,omitempty"` + FullTextPolicy jsontext.Value `json:"FullTextPolicy,omitempty"` + ComputedProperties jsontext.Value `json:"ComputedProperties,omitempty"` ChangeLog []*changeRecord `json:"ChangeLog,omitempty"` - IndexingPolicy json.RawMessage `json:"IndexingPolicy,omitempty"` + IndexingPolicy jsontext.Value `json:"IndexingPolicy,omitempty"` keep time.Duration ChangeFloor int64 `json:"ChangeFloor,omitempty"` ChangeSeq int64 `json:"ChangeSeq,omitempty"` @@ -203,18 +206,18 @@ type storedDocument struct { // snapshot/restore cycle -- exactly the bug class AZURE.md's process rules // call out. type storedDocumentWire struct { - Timestamp time.Time `json:"Timestamp"` - ID string `json:"ID"` - PartitionKeyJSON string `json:"PartitionKeyJSON"` - Origin string `json:"Origin,omitempty"` - Body json.RawMessage `json:"Body"` - Seq int64 `json:"Seq,omitempty"` - GLSN int64 `json:"GLSN,omitempty"` + Timestamp time.Time `json:"Timestamp"` + ID string `json:"ID"` + PartitionKeyJSON string `json:"PartitionKeyJSON"` + Origin string `json:"Origin,omitempty"` + Body jsontext.Value `json:"Body"` + Seq int64 `json:"Seq,omitempty"` + GLSN int64 `json:"GLSN,omitempty"` } // MarshalJSON implements json.Marshaler for persistence snapshots. func (d *storedDocument) MarshalJSON() ([]byte, error) { - bodyBytes, err := json.Marshal(d.Body) + bodyBytes, err := json.Marshal(d.Body, jsonx.V1()) if err != nil { return nil, fmt.Errorf("cosmosdb: marshal stored document body: %w", err) } @@ -222,7 +225,7 @@ func (d *storedDocument) MarshalJSON() ([]byte, error) { return json.Marshal(storedDocumentWire{ Body: bodyBytes, Timestamp: d.Timestamp, ID: d.ID, PartitionKeyJSON: d.PartitionKeyJSON, Seq: d.Seq, GLSN: d.GLSN, Origin: d.Origin, - }) + }, jsonx.V1()) } // UnmarshalJSON implements json.Unmarshaler for persistence snapshots. See @@ -230,7 +233,7 @@ func (d *storedDocument) MarshalJSON() ([]byte, error) { func (d *storedDocument) UnmarshalJSON(data []byte) error { var wire storedDocumentWire - if err := json.Unmarshal(data, &wire); err != nil { + if err := json.Unmarshal(data, &wire, jsonx.V1()); err != nil { return fmt.Errorf("cosmosdb: unmarshal stored document: %w", err) } @@ -247,11 +250,10 @@ func (d *storedDocument) UnmarshalJSON(data []byte) error { return fmt.Errorf("%w: id %q", ErrSnapshotDocumentNullBody, wire.ID) } - dec := json.NewDecoder(bytes.NewReader(wire.Body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(wire.Body)) var body map[string]any - if err := dec.Decode(&body); err != nil { + if err := json.UnmarshalDecode(dec, &body, jsonx.V1(), jsonx.UseNumber()); err != nil { return fmt.Errorf("cosmosdb: unmarshal stored document body: %w", err) } @@ -282,7 +284,7 @@ type documentCompositeKey struct { // rather than delimiter-joined, for the same collision-avoidance reason // documented above and on services/azuretable's identical MarshalText. func (k documentCompositeKey) MarshalText() ([]byte, error) { - return json.Marshal([2]string{k.PartitionKeyJSON, k.ID}) + return json.Marshal([2]string{k.PartitionKeyJSON, k.ID}, jsonx.V1()) } // UnmarshalText implements encoding.TextUnmarshaler, the inverse of @@ -290,7 +292,7 @@ func (k documentCompositeKey) MarshalText() ([]byte, error) { func (k *documentCompositeKey) UnmarshalText(text []byte) error { var pair [2]string - if err := json.Unmarshal(text, &pair); err != nil { + if err := json.Unmarshal(text, &pair, jsonx.V1()); err != nil { return fmt.Errorf("cosmosdb: malformed document composite key %q: %w", text, err) } @@ -318,7 +320,7 @@ func (k *documentCompositeKey) UnmarshalText(text []byte) error { // data never intended to be a key at all. func canonicalPartitionKeyJSON(v any) (string, error) { switch v.(type) { - case nil, string, bool, json.Number, float64, float32, int, int32, int64: + case nil, string, bool, jsonx.Number, float64, float32, int, int32, int64: default: return "", fmt.Errorf( "%w: partition key value must be a scalar (string, number, bool, or null), got %T", @@ -326,7 +328,7 @@ func canonicalPartitionKeyJSON(v any) (string, error) { ) } - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return "", fmt.Errorf("cosmosdb: encode partition key value: %w", err) } @@ -342,11 +344,10 @@ func canonicalPartitionKeyJSON(v any) (string, error) { // them. json.Number round-trips back out through json.Marshal as the exact // original digit sequence. func decodeJSONObject(body []byte) (map[string]any, error) { - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body), jsonx.V1()) var m map[string]any - if err := dec.Decode(&m); err != nil { + if err := json.UnmarshalDecode(dec, &m, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidDocument, err) } @@ -371,9 +372,9 @@ func decodeJSONObject(body []byte) (map[string]any, error) { // *current* enclosing structure -- there isn't one here. Decoding a second // value and requiring io.EOF instead makes the JSON scanner itself validate // what remains, catching those unmatched-delimiter cases too. -func rejectTrailingJSON(dec *json.Decoder) error { +func rejectTrailingJSON(dec *jsontext.Decoder) error { var extra any - if err := dec.Decode(&extra); !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode(dec, &extra, jsonx.V1(), jsonx.UseNumber()); !errors.Is(err, io.EOF) { if err == nil { return ErrUnexpectedTrailingJSON } @@ -393,16 +394,15 @@ func rejectTrailingJSON(dec *json.Decoder) error { // recursion, since it must handle arbitrarily nested maps/slices/scalars // uniformly and preserve json.Number fidelity throughout. func deepCopyJSONValue(v any) (any, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, fmt.Errorf("cosmosdb: deep copy: marshal: %w", err) } - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(data)) var out any - if decodeErr := dec.Decode(&out); decodeErr != nil { + if decodeErr := json.UnmarshalDecode(dec, &out, jsonx.V1(), jsonx.UseNumber()); decodeErr != nil { return nil, fmt.Errorf("cosmosdb: deep copy: unmarshal: %w", decodeErr) } @@ -464,7 +464,7 @@ func documentAsMap(info DocumentInfo) map[string]any { // walk; anything else takes the JSON round-trip so results stay identical. func copyStoredValue(v any) any { switch t := v.(type) { - case nil, string, bool, json.Number: + case nil, string, bool, jsonx.Number: return t case map[string]any: out := make(map[string]any, len(t)) diff --git a/services/cosmosdb/offer_ops.go b/services/cosmosdb/offer_ops.go index 5032d3b538..d2b9221e1c 100644 --- a/services/cosmosdb/offer_ops.go +++ b/services/cosmosdb/offer_ops.go @@ -1,7 +1,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -79,7 +80,7 @@ func specFromHeaders(r *http.Request) (ThroughputSpec, bool, error) { } var raw autopilotRaw - if jsonErr := json.Unmarshal([]byte(auto), &raw); jsonErr != nil { + if jsonErr := json.Unmarshal([]byte(auto), &raw, jsonx.V1()); jsonErr != nil { return ThroughputSpec{}, true, fmt.Errorf("%w: %w", ErrInvalidThroughput, jsonErr) } @@ -232,7 +233,7 @@ func (h *Handler) replaceOffer(c *echo.Context, tb ThroughputBackend, rid string } var wire offerWire - if jsonErr := json.Unmarshal(body, &wire); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &wire, jsonx.V1()); jsonErr != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } diff --git a/services/cosmosdb/pagination_test.go b/services/cosmosdb/pagination_test.go index 77c20ff74e..4e855e1efe 100644 --- a/services/cosmosdb/pagination_test.go +++ b/services/cosmosdb/pagination_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // pageIDs walks every page of a feed, returning each page's ids and asserting the continuation protocol. @@ -149,7 +151,7 @@ func TestPagination_Feeds(t *testing.T) { rec := doRequest(t, h, method, tt.path, headers, body) var decoded map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &decoded) + _ = json.Unmarshal(rec.Body.Bytes(), &decoded, jsonx.V1()) return rec.Code, decoded, rec.Header() } @@ -221,7 +223,7 @@ func TestPagination_QueryContinuationIsStateless(t *testing.T) { var body struct { Documents []string `json:"Documents"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) return append(body.Documents, "next="+rec.Header().Get("X-Ms-Continuation")) } diff --git a/services/cosmosdb/partition_epk.go b/services/cosmosdb/partition_epk.go index 287ed577d3..0324af6dd4 100644 --- a/services/cosmosdb/partition_epk.go +++ b/services/cosmosdb/partition_epk.go @@ -3,10 +3,13 @@ package cosmosdb import ( "bytes" "encoding/binary" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "math/big" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Effective partition key hashing for partition key version 2. Ported from the hashing azcosmos v1.5.0 uses to @@ -157,7 +160,7 @@ func appendEPKComponent(buf []byte, v any) []byte { } return append(buf, epkComponentFalse) - case json.Number: + case jsonx.Number: f, _ := x.Float64() return appendEPKNumber(buf, f) @@ -193,11 +196,10 @@ func epkOfValues(vals []any) u128 { // epkHash is the routing hash of a canonical partition key: a scalar hashes as a whole; a hierarchical key (a JSON // array) routes by its first component, whose hash opens its effective partition key. func epkHash(pkJSON string) u128 { - dec := json.NewDecoder(bytes.NewReader([]byte(pkJSON))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(pkJSON))) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return epkOfValues([]any{pkJSON}) } diff --git a/services/cosmosdb/partitionkey.go b/services/cosmosdb/partitionkey.go index 488b153d90..04d2846c04 100644 --- a/services/cosmosdb/partitionkey.go +++ b/services/cosmosdb/partitionkey.go @@ -1,9 +1,12 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxPartitionKeyPaths is the hierarchical (MultiHash) partition key depth limit. @@ -66,7 +69,7 @@ func partitionKeyForBody(body map[string]any, paths []string) (string, error) { vals[i] = v } - data, err := json.Marshal(vals) + data, err := json.Marshal(vals, jsonx.V1()) if err != nil { return "", fmt.Errorf("cosmosdb: encode partition key: %w", err) } @@ -90,8 +93,8 @@ func partitionKeyArity(pkJSON string) int { return 1 } - var arr []json.RawMessage - if err := json.Unmarshal([]byte(pkJSON), &arr); err != nil { + var arr []jsontext.Value + if err := json.Unmarshal([]byte(pkJSON), &arr, jsonx.V1()); err != nil { return 0 } diff --git a/services/cosmosdb/patch.go b/services/cosmosdb/patch.go index e7bcd282e8..4f81e3afde 100644 --- a/services/cosmosdb/patch.go +++ b/services/cosmosdb/patch.go @@ -2,11 +2,14 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxPatchOperations is the Cosmos limit on operations in one patch request. @@ -24,10 +27,10 @@ const ( ) type patchOp struct { - Op string `json:"op"` - Path string `json:"path"` - From string `json:"from,omitempty"` - Value json.RawMessage `json:"value,omitempty"` + Op string `json:"op"` + Path string `json:"path"` + From string `json:"from,omitempty"` + Value jsontext.Value `json:"value,omitempty"` } type patchRequest struct { @@ -43,8 +46,8 @@ func invalidPatchf(format string, args ...any) error { func parsePatchRequest(raw []byte) (patchRequest, error) { var req patchRequest - dec := json.NewDecoder(bytes.NewReader(raw)) - if err := dec.Decode(&req); err != nil || rejectTrailingJSON(dec) != nil { + dec := jsontext.NewDecoder(bytes.NewReader(raw), jsonx.V1()) + if err := json.UnmarshalDecode(dec, &req, jsonx.V1()); err != nil || rejectTrailingJSON(dec) != nil { return patchRequest{}, invalidPatchf("the patch body is not valid JSON") } @@ -80,16 +83,15 @@ func patchPath(path string) ([]string, error) { return segs, nil } -func decodePatchValue(raw json.RawMessage) (any, error) { +func decodePatchValue(raw jsontext.Value) (any, error) { if len(raw) == 0 { return nil, invalidPatchf("the operation requires a value") } - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, invalidPatchf("invalid value: %v", err) } @@ -342,14 +344,14 @@ func incrValue(cur any, exists bool, delta any) (any, error) { return addNumbers(c, d) } -func addNumbers(a, b json.Number) (json.Number, error) { +func addNumbers(a, b jsonx.Number) (jsonx.Number, error) { x, errX := a.Int64() y, errY := b.Int64() if errX == nil && errY == nil { sum := x + y if (x >= 0) != (y >= 0) || (sum >= 0) == (x >= 0) { - return json.Number(strconv.FormatInt(sum, 10)), nil + return jsonx.Number(strconv.FormatInt(sum, 10)), nil } } @@ -360,7 +362,7 @@ func addNumbers(a, b json.Number) (json.Number, error) { return "", invalidPatchf("incr result is not a finite number") } - return json.Number(strconv.FormatFloat(fx+fy, 'g', -1, 64)), nil + return jsonx.Number(strconv.FormatFloat(fx+fy, 'g', -1, 64)), nil } // evalPatchCondition runs a "from c where ..." predicate against one document. diff --git a/services/cosmosdb/permission_ops.go b/services/cosmosdb/permission_ops.go index e53e86398c..0c760927fd 100644 --- a/services/cosmosdb/permission_ops.go +++ b/services/cosmosdb/permission_ops.go @@ -1,7 +1,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "time" @@ -10,10 +10,10 @@ import ( // permissionWire is the request body of a permission create or replace. type permissionWire struct { - ID string `json:"id"` - Mode string `json:"permissionMode"` - Resource string `json:"resource"` - PartitionKey json.RawMessage `json:"resourcePartitionKey"` + ID string `json:"id"` + Mode string `json:"permissionMode"` + Resource string `json:"resource"` + PartitionKey jsontext.Value `json:"resourcePartitionKey"` } func (h *Handler) handlePermissions(c *echo.Context, dbID, userID string) error { diff --git a/services/cosmosdb/persistence.go b/services/cosmosdb/persistence.go index 749e1ee803..fa7f3d36d3 100644 --- a/services/cosmosdb/persistence.go +++ b/services/cosmosdb/persistence.go @@ -2,7 +2,7 @@ package cosmosdb import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" @@ -21,7 +21,7 @@ const cosmosdbSnapshotVersion = 1 type backendSnapshot struct { Databases map[string]*storedDatabase `json:"databases"` Regions *AccountRegions `json:"regions,omitempty"` - TableAPI json.RawMessage `json:"tableApi,omitempty"` + TableAPI jsontext.Value `json:"tableApi,omitempty"` Version int `json:"version"` } @@ -107,7 +107,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { } // restoreTableAPI restores the attached Table API state; a snapshot without it empties the Table API. -func (b *InMemoryBackend) restoreTableAPI(ctx context.Context, data json.RawMessage) error { +func (b *InMemoryBackend) restoreTableAPI(ctx context.Context, data jsontext.Value) error { if b.tableAPI == nil { return nil } diff --git a/services/cosmosdb/persistence_test.go b/services/cosmosdb/persistence_test.go index 4d86e4cdf1..91505a1b6f 100644 --- a/services/cosmosdb/persistence_test.go +++ b/services/cosmosdb/persistence_test.go @@ -1,12 +1,12 @@ package cosmosdb_test import ( - "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -77,7 +77,7 @@ func TestBackend_SnapshotRestore_Int64PrecisionRoundTrip(t *testing.T) { info, getErr := b2.GetDocument("mydb", "mycoll", pk, tt.name) require.NoError(t, getErr, tt.name) - gotNum, ok := info.Body["big"].(json.Number) + gotNum, ok := info.Body["big"].(jsonx.Number) require.True(t, ok, tt.name) assert.Equal(t, tt.raw, gotNum.String(), tt.name) } diff --git a/services/cosmosdb/query_resources.go b/services/cosmosdb/query_resources.go index 85142c311c..baa2654fc4 100644 --- a/services/cosmosdb/query_resources.go +++ b/services/cosmosdb/query_resources.go @@ -2,12 +2,14 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // queryResources runs the SQL query in the request body over resource bodies (databases or containers), as @@ -25,10 +27,15 @@ func (h *Handler) queryResources(c *echo.Context, listKey string, bodies []map[s var req queryRequestBody - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw), jsonx.V1()) - if decErr := dec.Decode(&req); decErr != nil || rejectTrailingJSON(dec) != nil { + if decErr := json.UnmarshalDecode( + dec, + &req, + jsonx.V1(), + jsonx.UseNumber(), + ); decErr != nil || + rejectTrailingJSON(dec) != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } diff --git a/services/cosmosdb/query_rows.go b/services/cosmosdb/query_rows.go index cf294c6b9b..40b5cfa9eb 100644 --- a/services/cosmosdb/query_rows.go +++ b/services/cosmosdb/query_rows.go @@ -1,9 +1,11 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" "sort" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxParsedQueries bounds the parsed-statement cache. @@ -32,7 +34,7 @@ func (r sqlRow) lookup(seg string) (any, bool) { case "id": return r.doc.ID, true case sysPropTS: - return json.Number(strconv.FormatInt(r.doc.Timestamp.Unix(), 10)), true + return jsonx.Number(strconv.FormatInt(r.doc.Timestamp.Unix(), 10)), true case sysPropETag: return r.src.b.etagFunc(r.doc.Timestamp), true case sysPropAttachments: @@ -56,7 +58,7 @@ func (r sqlRow) lookup(seg string) (any, bool) { v, ok := r.m[seg] if i, isInt := v.(int64); isInt { - return json.Number(strconv.FormatInt(i, 10)), true + return jsonx.Number(strconv.FormatInt(i, 10)), true } return v, ok @@ -221,7 +223,7 @@ func (b *InMemoryBackend) queryLocked( // loadedCharge sums the read cost of the documents the query had to retrieve: those its filter kept when the // indexing policy can serve the filter, every examined document otherwise (or when there is no filter). -func loadedCharge(stmt *sqlSelectStmt, indexing json.RawMessage, res QueryResult) float64 { +func loadedCharge(stmt *sqlSelectStmt, indexing jsontext.Value, res QueryResult) float64 { docs := res.Matched if stmt.where == nil || parseIndexingPolicy(indexing).needsScan(stmt.where) { docs = res.Visited diff --git a/services/cosmosdb/region_http.go b/services/cosmosdb/region_http.go index 969094bded..dd1d4683fe 100644 --- a/services/cosmosdb/region_http.go +++ b/services/cosmosdb/region_http.go @@ -3,7 +3,7 @@ package cosmosdb import ( "context" "crypto/tls" - "encoding/json" + "encoding/json/v2" "fmt" "net" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/telemetry" ) @@ -398,7 +399,7 @@ func (h *Handler) putAdminTopology(c *echo.Context) error { } var req adminTopology - if jsonErr := json.Unmarshal(raw, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(raw, &req, jsonx.V1()); jsonErr != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } diff --git a/services/cosmosdb/regions_conflicts_test.go b/services/cosmosdb/regions_conflicts_test.go index f3d3503624..6f074dc0bc 100644 --- a/services/cosmosdb/regions_conflicts_test.go +++ b/services/cosmosdb/regions_conflicts_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -127,7 +128,7 @@ func jsonRank(t *testing.T, doc string) float64 { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(doc), &m)) + require.NoError(t, json.Unmarshal([]byte(doc), &m, jsonx.V1())) r, _ := m["rank"].(float64) diff --git a/services/cosmosdb/script_engine.go b/services/cosmosdb/script_engine.go index 3ba73a7a62..c3a7a8a2f6 100644 --- a/services/cosmosdb/script_engine.go +++ b/services/cosmosdb/script_engine.go @@ -1,7 +1,6 @@ package cosmosdb import ( - "encoding/json" "errors" "fmt" "math" @@ -10,6 +9,7 @@ import ( "github.com/dop251/goja" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/jsruntime" ) @@ -90,7 +90,7 @@ func toJS(vm *goja.Runtime, v any) goja.Value { return goja.Null() case undef: return goja.Undefined() - case json.Number: + case jsonx.Number: return numberToJS(vm, t) case map[string]any: o := vm.NewObject() @@ -111,7 +111,7 @@ func toJS(vm *goja.Runtime, v any) goja.Value { } } -func numberToJS(vm *goja.Runtime, n json.Number) goja.Value { +func numberToJS(vm *goja.Runtime, n jsonx.Number) goja.Value { if i, err := n.Int64(); err == nil { return vm.ToValue(i) } @@ -137,15 +137,15 @@ func fromJS(v goja.Value) any { func normalizeExport(v any) any { switch t := v.(type) { case int64: - return json.Number(strconv.FormatInt(t, 10)) + return jsonx.Number(strconv.FormatInt(t, 10)) case int: - return json.Number(strconv.Itoa(t)) + return jsonx.Number(strconv.Itoa(t)) case float64: if math.IsNaN(t) || math.IsInf(t, 0) { return nil } - return json.Number(strconv.FormatFloat(t, 'g', -1, 64)) + return jsonx.Number(strconv.FormatFloat(t, 'g', -1, 64)) case map[string]any: out := make(map[string]any, len(t)) for k, e := range t { diff --git a/services/cosmosdb/script_ops.go b/services/cosmosdb/script_ops.go index 06d18cb2d6..1a56c9b39e 100644 --- a/services/cosmosdb/script_ops.go +++ b/services/cosmosdb/script_ops.go @@ -2,7 +2,8 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -224,7 +226,7 @@ func (h *Handler) writeScript( } var req scriptBody - if jsonErr := json.Unmarshal(raw, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(raw, &req, jsonx.V1()); jsonErr != nil { return h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") } @@ -356,10 +358,15 @@ func sprocArgs(raw []byte) ([]any, error) { var args []any - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw), jsonx.V1()) - if err := dec.Decode(&args); err != nil || rejectTrailingJSON(dec) != nil { + if err := json.UnmarshalDecode( + dec, + &args, + jsonx.V1(), + jsonx.UseNumber(), + ); err != nil || + rejectTrailingJSON(dec) != nil { return nil, ErrInvalidDocument } diff --git a/services/cosmosdb/scripts_test.go b/services/cosmosdb/scripts_test.go index 1264a03bf8..485ec9c77c 100644 --- a/services/cosmosdb/scripts_test.go +++ b/services/cosmosdb/scripts_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -22,7 +23,7 @@ const pkA = `["a"]` func scriptJSON(t *testing.T, fields map[string]string) string { t.Helper() - raw, err := json.Marshal(fields) + raw, err := json.Marshal(fields, jsonx.V1()) require.NoError(t, err) return string(raw) @@ -562,7 +563,7 @@ func TestUDF_InQueries(t *testing.T) { s.putScript("udfs", "boom", `function boom(){ throw new Error("x"); }`, nil).status, ) - body, err := json.Marshal(map[string]any{"query": tt.query}) + body, err := json.Marshal(map[string]any{"query": tt.query}, jsonx.V1()) require.NoError(t, err) resp := s.do(http.MethodPost, "/dbs/d/colls/c/docs", map[string]string{ @@ -577,7 +578,7 @@ func TestUDF_InQueries(t *testing.T) { require.Equal(t, want, resp.status, string(resp.body)) if tt.want != "" { - docs, marshalErr := json.Marshal(resp.object(t)["Documents"]) + docs, marshalErr := json.Marshal(resp.object(t)["Documents"], jsonx.V1()) require.NoError(t, marshalErr) assert.JSONEq(t, tt.want, string(docs)) } diff --git a/services/cosmosdb/sdk_test.go b/services/cosmosdb/sdk_test.go index 9a85b0b09a..afffce5e87 100644 --- a/services/cosmosdb/sdk_test.go +++ b/services/cosmosdb/sdk_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -132,7 +133,7 @@ func TestSDK_QueriesPagingTTLAndHierarchicalKeys(t *testing.T) { body, mErr := json.Marshal(map[string]any{ "id": fmt.Sprintf("i%d", i), "tenant": tenant, "user": "u", "n": i, "tags": []string{"x", strconv.Itoa(i)}, - }) + }, jsonx.V1()) require.NoError(t, mErr) _, err = c.CreateItem(ctx, azcosmos.NewPartitionKeyString(tenant).AppendString("u"), body, nil) diff --git a/services/cosmosdb/search_funcs_test.go b/services/cosmosdb/search_funcs_test.go index eee0f0b6e3..f447a35b75 100644 --- a/services/cosmosdb/search_funcs_test.go +++ b/services/cosmosdb/search_funcs_test.go @@ -1,12 +1,13 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -98,7 +99,7 @@ func TestSearchFunctions_ParseErrors(t *testing.T) { func jsonString(t *testing.T, v any) string { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/cosmosdb/search_sdk_test.go b/services/cosmosdb/search_sdk_test.go index ff4bd41892..f61a6d3313 100644 --- a/services/cosmosdb/search_sdk_test.go +++ b/services/cosmosdb/search_sdk_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -66,7 +67,7 @@ func seedSearchContainer(t *testing.T, props azcosmos.ContainerProperties) *azco } for _, d := range docs { - body, mErr := json.Marshal(d) + body, mErr := json.Marshal(d, jsonx.V1()) require.NoError(t, mErr) _, err = c.CreateItem(ctx, azcosmos.NewPartitionKeyString("p"), body, nil) @@ -94,7 +95,7 @@ func queryIDs(t *testing.T, c *azcosmos.ContainerClient, query string) ([]string ID string `json:"id"` } - require.NoError(t, json.Unmarshal(item, &v)) + require.NoError(t, json.Unmarshal(item, &v, jsonx.V1())) ids = append(ids, v.ID) } diff --git a/services/cosmosdb/spatial_index.go b/services/cosmosdb/spatial_index.go index f8f0008ca6..1a6a282bae 100644 --- a/services/cosmosdb/spatial_index.go +++ b/services/cosmosdb/spatial_index.go @@ -1,10 +1,13 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Spatial index types (azcosmos SpatialType, azcosmos v1.5.0 spatial_type.go; Learn "Indexing policies"). @@ -30,12 +33,12 @@ func (b boundingBox) valid() bool { } // geospatialType is the container's geospatial data type; Geography unless the config says Geometry. -func geospatialType(raw json.RawMessage) string { +func geospatialType(raw jsontext.Value) string { var cfg struct { Type string `json:"type"` } - if json.Unmarshal(raw, &cfg) == nil && cfg.Type == geoTypeGeometry { + if json.Unmarshal(raw, &cfg, jsonx.V1()) == nil && cfg.Type == geoTypeGeometry { return geoTypeGeometry } diff --git a/services/cosmosdb/sql_agg.go b/services/cosmosdb/sql_agg.go index 63f0bc35d8..e0d3af152c 100644 --- a/services/cosmosdb/sql_agg.go +++ b/services/cosmosdb/sql_agg.go @@ -1,9 +1,10 @@ package cosmosdb import ( - "encoding/json" "math" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -52,7 +53,7 @@ func (a *aggAcc) add(v any) { } } -func (a *aggAcc) addNumber(n json.Number) { +func (a *aggAcc) addNumber(n jsonx.Number) { a.numCount++ a.sumFloat += numFloat(n) @@ -101,7 +102,7 @@ func (a *aggAcc) result() any { case a.floatSum: return floatResult(a.sumFloat) default: - return json.Number(strconv.FormatInt(a.sumInt, 10)) + return jsonx.Number(strconv.FormatInt(a.sumInt, 10)) } case aggAvg: if a.numCount == 0 { diff --git a/services/cosmosdb/sql_engine_test.go b/services/cosmosdb/sql_engine_test.go index e80a085d70..cc6500322f 100644 --- a/services/cosmosdb/sql_engine_test.go +++ b/services/cosmosdb/sql_engine_test.go @@ -1,12 +1,13 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -137,8 +138,8 @@ func TestExecuteQuery_Engine(t *testing.T) { name: "offset limit params", query: "SELECT VALUE c.id FROM c ORDER BY c.age OFFSET @o LIMIT @l", params: []cosmosdb.QueryParameter{ - {Name: "@o", Value: json.Number("0")}, - {Name: "@l", Value: json.Number("2")}, + {Name: "@o", Value: jsonx.Number("0")}, + {Name: "@l", Value: jsonx.Number("2")}, }, want: `["2","1"]`, }, @@ -194,7 +195,7 @@ func TestExecuteQuery_Engine(t *testing.T) { rows, err := cosmosdb.ExecuteQuery(tt.query, tt.params, docs) require.NoError(t, err) - got, err := json.Marshal(rows) + got, err := json.Marshal(rows, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(got)) diff --git a/services/cosmosdb/sql_eval.go b/services/cosmosdb/sql_eval.go index dc9db26031..5ebe9cf108 100644 --- a/services/cosmosdb/sql_eval.go +++ b/services/cosmosdb/sql_eval.go @@ -1,7 +1,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "fmt" "math" "regexp" @@ -9,6 +9,8 @@ import ( "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // undef is the Cosmos SQL "undefined" value (a missing property, an unbound parameter, a failed operation). @@ -247,7 +249,7 @@ func (ec *evalCtx) evalUnary(n *exprUnary, env []any) any { return undef{} } - return numArith(sqlTokMinus, json.Number("0"), num) + return numArith(sqlTokMinus, jsonx.Number("0"), num) default: if num, ok := toNumber(v); ok { return num @@ -445,16 +447,16 @@ func (ec *evalCtx) evalSubquery(n *exprSubquery, env []any) any { // --- numbers --- -func toNumber(v any) (json.Number, bool) { +func toNumber(v any) (jsonx.Number, bool) { switch t := v.(type) { - case json.Number: + case jsonx.Number: return t, true case int: - return json.Number(strconv.Itoa(t)), true + return jsonx.Number(strconv.Itoa(t)), true case int32: - return json.Number(strconv.FormatInt(int64(t), 10)), true + return jsonx.Number(strconv.FormatInt(int64(t), 10)), true case int64: - return json.Number(strconv.FormatInt(t, 10)), true + return jsonx.Number(strconv.FormatInt(t, 10)), true case float32: return formatFloatNumber(float64(t)) case float64: @@ -464,7 +466,7 @@ func toNumber(v any) (json.Number, bool) { } } -func formatFloatNumber(f float64) (json.Number, bool) { +func formatFloatNumber(f float64) (jsonx.Number, bool) { if math.IsNaN(f) || math.IsInf(f, 0) { return "", false } @@ -474,10 +476,10 @@ func formatFloatNumber(f float64) (json.Number, bool) { format = 'e' } - return json.Number(strconv.FormatFloat(f, format, -1, 64)), true + return jsonx.Number(strconv.FormatFloat(f, format, -1, 64)), true } -func numFloat(n json.Number) float64 { +func numFloat(n jsonx.Number) float64 { f, _ := n.Float64() return f @@ -493,7 +495,7 @@ func floatResult(f float64) any { } // numArith applies + - * / % to a and b, staying in int64 when exact and falling back to float64. -func numArith(op sqlTokenType, a, b json.Number) any { +func numArith(op sqlTokenType, a, b jsonx.Number) any { ai, aErr := a.Int64() bi, bErr := b.Int64() @@ -554,7 +556,7 @@ func intArith(op sqlTokenType, a, b int64) (any, bool) { return nil, false } - return json.Number(strconv.FormatInt(v, 10)), true + return jsonx.Number(strconv.FormatInt(v, 10)), true } func intAdd(a, b int64) (int64, bool) { @@ -720,7 +722,7 @@ func objectsEqual(a, b map[string]any) bool { } // compareJSONNumbers compares as int64 when both are exact integers, else as float64. -func compareJSONNumbers(a, b json.Number) int { +func compareJSONNumbers(a, b jsonx.Number) int { ai, aErr := a.Int64() bi, bErr := b.Int64() @@ -856,7 +858,7 @@ func compareArrays(a, b []any) int { } func canonicalJSON(v any) string { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Sprint(v) } diff --git a/services/cosmosdb/sql_exec_test.go b/services/cosmosdb/sql_exec_test.go index e8986b8c3d..bfa2fcd003 100644 --- a/services/cosmosdb/sql_exec_test.go +++ b/services/cosmosdb/sql_exec_test.go @@ -1,7 +1,6 @@ package cosmosdb_test import ( - "encoding/json" "strings" "testing" "time" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -60,7 +60,7 @@ func TestExecuteQuery_ProjectionWithAlias(t *testing.T) { require.NoError(t, err) require.Len(t, rows, 1) assert.Equal(t, "alice", rowObj(t, rows[0])["n"]) - assert.Equal(t, json.Number("30"), rowObj(t, rows[0])["age"]) + assert.Equal(t, jsonx.Number("30"), rowObj(t, rows[0])["age"]) } func TestExecuteQuery_WhereComparisons(t *testing.T) { diff --git a/services/cosmosdb/sql_ext_test.go b/services/cosmosdb/sql_ext_test.go index 4db3cfddbf..356d500548 100644 --- a/services/cosmosdb/sql_ext_test.go +++ b/services/cosmosdb/sql_ext_test.go @@ -1,12 +1,13 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -115,7 +116,7 @@ func TestExecuteQuery_SpatialAndConversionFunctions(t *testing.T) { require.NoError(t, err) require.Len(t, rows, 1) - got, err := json.Marshal(rows[0]) + got, err := json.Marshal(rows[0], jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/cosmosdb/sql_funcs.go b/services/cosmosdb/sql_funcs.go index 7f33040387..574a4566cc 100644 --- a/services/cosmosdb/sql_funcs.go +++ b/services/cosmosdb/sql_funcs.go @@ -1,13 +1,14 @@ package cosmosdb import ( - "encoding/json" "math" "regexp" "strconv" "strings" "time" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -270,7 +271,7 @@ func intArg(v any) (int, bool) { return int(i), true } -func intValue(i int) any { return json.Number(strconv.Itoa(i)) } +func intValue(i int) any { return jsonx.Number(strconv.Itoa(i)) } func boolArg(args []any, i int) bool { if i >= len(args) { @@ -459,7 +460,7 @@ func parseStringValue(name string, v any) any { } if i, iErr := strconv.ParseInt(s, 10, 64); iErr == nil { - return json.Number(strconv.FormatInt(i, 10)) + return jsonx.Number(strconv.FormatInt(i, 10)) } return floatResult(f) @@ -549,13 +550,13 @@ func callMath(name string, args []any) any { } } -func mathRounding(name string, n json.Number) any { +func mathRounding(name string, n jsonx.Number) any { if i, err := n.Int64(); err == nil { switch { case name == biSign: return intValue(cmpSign(i)) case name == biAbs && i < 0 && i != math.MinInt64: - return json.Number(strconv.FormatInt(-i, 10)) + return jsonx.Number(strconv.FormatInt(-i, 10)) default: return n } @@ -590,7 +591,7 @@ func cmpSign(i int64) int { } } -func mathLog(n json.Number, args []any) any { +func mathLog(n jsonx.Number, args []any) any { f := numFloat(n) if f <= 0 { return undef{} @@ -823,9 +824,9 @@ func (ec *evalCtx) callDate(name string, args []any) any { case biGetcurrentdatetime, biGetcurrentdatetimestatic: return ec.now.Format(dateTimeLayout) case biGetcurrenttimestamp, biGetcurrenttimestampstatic: - return json.Number(strconv.FormatInt(ec.now.UnixMilli(), 10)) + return jsonx.Number(strconv.FormatInt(ec.now.UnixMilli(), 10)) case biGetcurrentticks, biGetcurrentticksstatic: - return json.Number(strconv.FormatInt(ec.now.UnixNano()/ticksPerNano, 10)) + return jsonx.Number(strconv.FormatInt(ec.now.UnixNano()/ticksPerNano, 10)) case biTimestamptodatetime: ms, ok := toNumber(args[0]) if !ok { @@ -841,7 +842,7 @@ func (ec *evalCtx) callDate(name string, args []any) any { return undef{} } - return json.Number(strconv.FormatInt(t.UnixMilli(), 10)) + return jsonx.Number(strconv.FormatInt(t.UnixMilli(), 10)) default: return dateTimeOp(name, args) } diff --git a/services/cosmosdb/sql_funcs_ext.go b/services/cosmosdb/sql_funcs_ext.go index 8f850c67e6..b374ae7a66 100644 --- a/services/cosmosdb/sql_funcs_ext.go +++ b/services/cosmosdb/sql_funcs_ext.go @@ -2,10 +2,13 @@ package cosmosdb import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -18,11 +21,16 @@ const ( // parseJSONContainer implements StringToArray and StringToObject: the string must hold exactly that JSON kind. func parseJSONContainer(name, s string) any { - dec := json.NewDecoder(bytes.NewReader([]byte(s))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(s)), jsonx.V1()) var v any - if err := dec.Decode(&v); err != nil || rejectTrailingJSON(dec) != nil { + if err := json.UnmarshalDecode( + dec, + &v, + jsonx.V1(), + jsonx.UseNumber(), + ); err != nil || + rejectTrailingJSON(dec) != nil { return undef{} } @@ -86,7 +94,7 @@ func dateTicksOp(name string, args []any) any { return undef{} } - return json.Number(strconv.FormatInt(t.Unix()*ticksPerSecond+int64(t.Nanosecond())/ticksPerNano, 10)) + return jsonx.Number(strconv.FormatInt(t.Unix()*ticksPerSecond+int64(t.Nanosecond())/ticksPerNano, 10)) case biTickstodatetime: n, ok := toNumber(args[0]) if !ok { diff --git a/services/cosmosdb/sql_funcs_more.go b/services/cosmosdb/sql_funcs_more.go index 4b5bfdc0a5..864d9b83f5 100644 --- a/services/cosmosdb/sql_funcs_more.go +++ b/services/cosmosdb/sql_funcs_more.go @@ -3,7 +3,6 @@ package cosmosdb import ( "crypto/rand" "encoding/binary" - "encoding/json" "math" "math/big" "regexp" @@ -12,6 +11,8 @@ import ( "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Built-in functions of the Cosmos DB query language reference (Learn, "Functions") beyond the first set: @@ -253,7 +254,7 @@ func trigonometry(name string, x float64) any { // numberBin rounds a down to a multiple of the bin size (default 1): floor(a / size) * size, so a negative size // bins upward as the documented example shows. A zero or non-numeric size has no result. -func numberBin(a json.Number, args []any) any { +func numberBin(a jsonx.Number, args []any) any { size := 1.0 if len(args) > 1 { @@ -277,7 +278,7 @@ func numberBin(a json.Number, args []any) any { q-- } - return json.Number(strconv.FormatInt(q*si, 10)) + return jsonx.Number(strconv.FormatInt(q*si, 10)) } return floatResult(math.Floor(numFloat(a)/size) * size) @@ -311,7 +312,7 @@ func intMath(name string, args []any) any { } if name == biIntbitnot { - return json.Number(strconv.FormatInt(^a, 10)) + return jsonx.Number(strconv.FormatInt(^a, 10)) } b, bok := int64Arg(args[1]) @@ -324,7 +325,7 @@ func intMath(name string, args []any) any { return undef{} } - return json.Number(strconv.FormatInt(r, 10)) + return jsonx.Number(strconv.FormatInt(r, 10)) } func intBinary(name string, a, b int64) (int64, bool) { @@ -406,8 +407,8 @@ func moreCollection(name string, args []any) any { return args[arityPair] case biChoose: - i, ok := int64Arg(args[0]) - if !ok || i < 1 || int(i) >= len(args) { + i, ok := intArg(args[0]) + if !ok || i < 1 || i >= len(args) { return undef{} } @@ -605,12 +606,12 @@ func (ec *evalCtx) regexOp(name string, args []any) any { group := 0 if len(args) > regexGroupArg { - g, gok := int64Arg(args[regexGroupArg]) - if !gok || g < 0 || int(g) > re.NumSubexp() { + g, gok := intArg(args[regexGroupArg]) + if !gok || g < 0 || g > re.NumSubexp() { return undef{} } - group = int(g) + group = g } return extractRegex(re, subject, group, name == biRegexextractall) @@ -810,7 +811,7 @@ func documentIDOf(n *exprCall, env []any) any { return undef{} } - return json.Number(strconv.FormatInt(row.doc.Seq, 10)) + return jsonx.Number(strconv.FormatInt(row.doc.Seq, 10)) } // randomUnit is a uniform number in [0, 1). diff --git a/services/cosmosdb/sql_functions_doc_test.go b/services/cosmosdb/sql_functions_doc_test.go index 795506e23b..e08acff93e 100644 --- a/services/cosmosdb/sql_functions_doc_test.go +++ b/services/cosmosdb/sql_functions_doc_test.go @@ -1,12 +1,13 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -203,7 +204,7 @@ func TestSQLFunctions_DocumentedExamples(t *testing.T) { require.NoError(t, err) require.Len(t, rows, 1) - got, err := json.Marshal(rows[0]) + got, err := json.Marshal(rows[0], jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(got)) }) @@ -343,7 +344,7 @@ func TestSQL_DocumentedStatementForms(t *testing.T) { require.NoError(t, err) - got, marshalErr := json.Marshal(rows) + got, marshalErr := json.Marshal(rows, jsonx.V1()) require.NoError(t, marshalErr) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/cosmosdb/sql_parser.go b/services/cosmosdb/sql_parser.go index 6b8a6a6eda..80d2bce226 100644 --- a/services/cosmosdb/sql_parser.go +++ b/services/cosmosdb/sql_parser.go @@ -1,10 +1,11 @@ package cosmosdb import ( - "encoding/json" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sql_parser.go is a recursive-descent parser for Cosmos DB SQL. @@ -267,7 +268,7 @@ func (p *sqlParser) parseCountOperand(what string) (sqlExpr, error) { p.advance() - return &exprLit{v: json.Number(strconv.Itoa(n))}, nil + return &exprLit{v: jsonx.Number(strconv.Itoa(n))}, nil case sqlTokParam: x := &exprParam{name: p.cur.lit} @@ -883,7 +884,7 @@ func (p *sqlParser) parseUnary() (sqlExpr, error) { p.advance() - return &exprLit{v: json.Number(lit)}, nil + return &exprLit{v: jsonx.Number(lit)}, nil } inner, err := p.parseUnary() @@ -937,7 +938,7 @@ func (p *sqlParser) parsePrimary() (sqlExpr, error) { case sqlTokNumber: p.advance() - return &exprLit{v: json.Number(tok.lit)}, nil + return &exprLit{v: jsonx.Number(tok.lit)}, nil case sqlTokString: p.advance() diff --git a/services/cosmosdb/sql_spatial_doc_test.go b/services/cosmosdb/sql_spatial_doc_test.go index 6387ff30a5..15e021ede7 100644 --- a/services/cosmosdb/sql_spatial_doc_test.go +++ b/services/cosmosdb/sql_spatial_doc_test.go @@ -1,12 +1,14 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -33,7 +35,7 @@ func evalOne(t *testing.T, expr string) any { func asFloat(t *testing.T, v any) float64 { t.Helper() - n, ok := v.(json.Number) + n, ok := v.(jsonx.Number) require.True(t, ok, "%T", v) f, err := n.Float64() @@ -187,7 +189,7 @@ func TestSpatial_GeographySemantics(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := json.Marshal(evalOne(t, tt.expr)) + got, err := json.Marshal(evalOne(t, tt.expr), jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(got)) }) @@ -243,7 +245,7 @@ func TestSpatial_GeometryContainersUsePlanarMath(t *testing.T) { _, err = b.CreateContainer("d", cosmosdb.ContainerSpec{ ID: "c", PartitionKeyPath: "/pk", - GeospatialConfig: json.RawMessage(`{"type":"` + tt.geo + `"}`), + GeospatialConfig: jsontext.Value(`{"type":"` + tt.geo + `"}`), }) require.NoError(t, err) _, err = b.CreateDocument("d", "c", map[string]any{"id": "1", "pk": "k"}, false) @@ -253,7 +255,7 @@ func TestSpatial_GeometryContainersUsePlanarMath(t *testing.T) { require.NoError(t, err) require.Len(t, res.Rows, 1) - got, err := json.Marshal(res.Rows[0]) + got, err := json.Marshal(res.Rows[0], jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/cosmosdb/store_test.go b/services/cosmosdb/store_test.go index 8bc610eb96..e1fffbda71 100644 --- a/services/cosmosdb/store_test.go +++ b/services/cosmosdb/store_test.go @@ -1,7 +1,6 @@ package cosmosdb_test import ( - "encoding/json" "math" "testing" "time" @@ -9,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -322,7 +322,7 @@ func TestInMemoryBackend_Int64PrecisionPreservedThroughStorage(t *testing.T) { info, err := b.CreateDocument("mydb", "mycoll", body, false) require.NoError(t, err) - gotNum, ok := info.Body["big"].(json.Number) + gotNum, ok := info.Body["big"].(jsonx.Number) require.True(t, ok, "big field must remain a json.Number, not a lossy float64") assert.Equal(t, tt.raw, gotNum.String()) }) diff --git a/services/cosmosdb/table_api.go b/services/cosmosdb/table_api.go index 95c390ac54..11ea881135 100644 --- a/services/cosmosdb/table_api.go +++ b/services/cosmosdb/table_api.go @@ -23,7 +23,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -31,6 +31,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/odatatable" ) @@ -362,7 +363,7 @@ func (h *Handler) createTable(c *echo.Context) error { } var req createTableBody - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return h.writeTableError(c, http.StatusBadRequest, "InvalidInput", "The input is not valid.") } @@ -514,7 +515,7 @@ type tableODataErrorEnvelope struct { func (h *Handler) writeTableJSON(c *echo.Context, status int, v any) error { level := tableODataLevelFromAccept(c.Request().Header.Get("Accept")) - body, err := json.Marshal(v) + body, err := json.Marshal(v, jsonx.V1()) if err != nil { return h.writeTableErrorNoRecurse(c, http.StatusInternalServerError, "InternalError", "Failed to marshal response.") @@ -545,7 +546,7 @@ func (h *Handler) writeTableErrorNoRecurse(c *echo.Context, status int, code, me c.Response().Header().Set("X-Ms-Error-Code", code) body, _ := json.Marshal(tableODataErrorEnvelope{ Error: tableODataErrorDetail{Code: code, Message: tableODataErrorMessage{Lang: tableErrorLang, Value: message}}, - }) + }, jsonx.V1()) return c.Blob(status, "application/json;odata=minimalmetadata;streaming=true;charset=utf-8", body) } diff --git a/services/cosmosdb/throughput_test.go b/services/cosmosdb/throughput_test.go index 75d432c8f6..5b8d1a2021 100644 --- a/services/cosmosdb/throughput_test.go +++ b/services/cosmosdb/throughput_test.go @@ -2,7 +2,7 @@ package cosmosdb_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -480,7 +481,7 @@ func TestThroughput_PersistenceRoundTrip(t *testing.T) { snap := src.Snapshot(t.Context()) var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) dst := cosmosdb.NewInMemoryBackend() require.NoError(t, dst.Restore(t.Context(), snap)) diff --git a/services/cosmosdb/token.go b/services/cosmosdb/token.go index 92bf81bfd0..95f84a145e 100644 --- a/services/cosmosdb/token.go +++ b/services/cosmosdb/token.go @@ -4,13 +4,15 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -93,7 +95,7 @@ func signTokenPayload(masterKey string, payload []byte) []byte { // issueResourceToken builds "type=resource&ver=1&sig=;", the value of a permission's _token. func issueResourceToken(masterKey string, claims tokenClaims) (string, error) { - payload, err := json.Marshal(claims) + payload, err := json.Marshal(claims, jsonx.V1()) if err != nil { return "", fmt.Errorf("cosmosdb: marshal token claims: %w", err) } @@ -126,7 +128,7 @@ func parseResourceToken(masterKey, sig string) (tokenClaims, bool) { } var claims tokenClaims - if json.Unmarshal(payload, &claims) != nil { + if json.Unmarshal(payload, &claims, jsonx.V1()) != nil { return tokenClaims{}, false } diff --git a/services/cosmosdb/triggers.go b/services/cosmosdb/triggers.go index 6b035b20ab..42bf0c680b 100644 --- a/services/cosmosdb/triggers.go +++ b/services/cosmosdb/triggers.go @@ -1,9 +1,11 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // docOp is a document write with the triggers requested for it. @@ -226,7 +228,7 @@ func triggerNames(header string) []string { // patchWithBody applies a patch whose request body (the operations, after any pre-trigger edit) is body. func (tx *containerTx) patchWithBody(op docOp, body map[string]any) (DocumentInfo, error) { - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) if err != nil { return DocumentInfo{}, invalidPatchf("the patch body is not valid JSON") } diff --git a/services/cosmosdb/ttl_test.go b/services/cosmosdb/ttl_test.go index 5b450b5257..591c7284a5 100644 --- a/services/cosmosdb/ttl_test.go +++ b/services/cosmosdb/ttl_test.go @@ -1,7 +1,6 @@ package cosmosdb_test import ( - "encoding/json" "net/http" "sync/atomic" "testing" @@ -10,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -143,7 +143,7 @@ func TestTTL_ExpiredReadsAsAbsentForWrites(t *testing.T) { info, err := b.GetDocument("d", "c", `"a"`, "x") require.NoError(t, err) - assert.Equal(t, json.Number("2"), info.Body["v"]) + assert.Equal(t, jsonx.Number("2"), info.Body["v"]) } func TestTTL_SweepRemovesExpired(t *testing.T) { diff --git a/services/cosmosdb/users.go b/services/cosmosdb/users.go index 360b211e7c..cc4ab07d2a 100644 --- a/services/cosmosdb/users.go +++ b/services/cosmosdb/users.go @@ -1,7 +1,7 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "sort" @@ -46,7 +46,7 @@ type PermissionSpec struct { ID string Mode string Resource string - PartitionKey json.RawMessage + PartitionKey jsontext.Value } // PermissionInfo is a read-only snapshot of a permission. Link is the canonical id-based resource path. @@ -60,7 +60,7 @@ type PermissionInfo struct { Resource string Link string PartitionKeyJSON string - PartitionKey json.RawMessage + PartitionKey jsontext.Value } type storedUser struct { @@ -75,8 +75,8 @@ type storedPermission struct { Mode string Resource string Link string - PartitionKeyJSON string `json:"PartitionKeyJSON,omitempty"` - PartitionKey json.RawMessage `json:"PartitionKey,omitempty"` + PartitionKeyJSON string `json:"PartitionKeyJSON,omitempty"` + PartitionKey jsontext.Value `json:"PartitionKey,omitempty"` } func userInfo(dbID string, u *storedUser) UserInfo { diff --git a/services/cosmosdb/users_ops.go b/services/cosmosdb/users_ops.go index d3cb7a433e..c472e1409a 100644 --- a/services/cosmosdb/users_ops.go +++ b/services/cosmosdb/users_ops.go @@ -1,13 +1,14 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -122,7 +123,7 @@ func (h *Handler) readJSONBody(c *echo.Context, dst any) bool { return false } - if json.Unmarshal(raw, dst) != nil { + if json.Unmarshal(raw, dst, jsonx.V1()) != nil { _ = h.writeError(c, http.StatusBadRequest, "BadRequest", "The input is not valid JSON.") return false diff --git a/services/cosmosdb/users_test.go b/services/cosmosdb/users_test.go index 0e0637f369..132f42bc61 100644 --- a/services/cosmosdb/users_test.go +++ b/services/cosmosdb/users_test.go @@ -1,7 +1,7 @@ package cosmosdb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/cosmosdb" ) @@ -65,7 +66,7 @@ func decodeMap(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/cosmosdb/vector.go b/services/cosmosdb/vector.go index a4940dc3ed..bc93bdb11f 100644 --- a/services/cosmosdb/vector.go +++ b/services/cosmosdb/vector.go @@ -1,11 +1,14 @@ package cosmosdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -50,20 +53,20 @@ func validVectorIndexType(s string) bool { return slices.Contains([]string{vectorIdxFlat, vectorIdxQuantizedFlat, vectorIdxDiskANN}, s) } -func parseVectorPolicy(raw json.RawMessage) (vectorEmbeddingPolicy, error) { +func parseVectorPolicy(raw jsontext.Value) (vectorEmbeddingPolicy, error) { var pol vectorEmbeddingPolicy if isJSONNull(raw) { return pol, nil } - if err := json.Unmarshal(raw, &pol); err != nil { + if err := json.Unmarshal(raw, &pol, jsonx.V1()); err != nil { return pol, fmt.Errorf("%w: vectorEmbeddingPolicy is malformed", ErrInvalidContainerPolicy) } return pol, nil } -func validateVectorPolicy(raw json.RawMessage) error { +func validateVectorPolicy(raw jsontext.Value) error { pol, err := parseVectorPolicy(raw) if err != nil { return err diff --git a/services/cur/delivery.go b/services/cur/delivery.go index bca09016aa..a055f0a667 100644 --- a/services/cur/delivery.go +++ b/services/cur/delivery.go @@ -2,7 +2,7 @@ package cur import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "path" "slices" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/billingexport" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -224,7 +225,7 @@ func writeManifests( periodDir, dataDir string, athena bool, ) error { - mb, err := json.Marshal(m) + mb, err := json.Marshal(m, jsonx.V1()) if err != nil { return fmt.Errorf("encode manifest: %w", err) } @@ -303,7 +304,7 @@ func redshiftManifest(dataURI string) []byte { "globalUploadSettings": map[string]string{ "format": "CSV", "delimiter": ",", "textqualifier": "\"", "containsHeader": "true", }, - }) + }, jsonx.V1()) return b } @@ -314,7 +315,7 @@ func quicksightManifest(dataURI string) []byte { "globalUploadSettings": map[string]string{ "format": "CSV", "delimiter": ",", "textqualifier": "\"", "containsHeader": "true", }, - }) + }, jsonx.V1()) return b } diff --git a/services/cur/delivery_test.go b/services/cur/delivery_test.go index e7f2c5dffe..16797c198c 100644 --- a/services/cur/delivery_test.go +++ b/services/cur/delivery_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "encoding/csv" - "encoding/json" + "encoding/json/v2" "io" "slices" "testing" @@ -16,6 +16,8 @@ import ( sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (f *fixture) object(t *testing.T, key string) []byte { @@ -109,7 +111,7 @@ func TestDelivery_CSVGzip(t *testing.T) { } var m map[string]any - require.NoError(t, json.Unmarshal(f.object(t, manifestKey), &m)) + require.NoError(t, json.Unmarshal(f.object(t, manifestKey), &m, jsonx.V1())) assert.Equal(t, "daily", m["reportName"]) assert.Equal(t, "20260301T000000.000Z", m["billingPeriod"].(map[string]any)["start"]) assert.Equal(t, []any{dataKey}, m["reportKeys"]) diff --git a/services/cur/handler.go b/services/cur/handler.go index fc7e4bd429..a9c3b53a95 100644 --- a/services/cur/handler.go +++ b/services/cur/handler.go @@ -2,7 +2,7 @@ package cur import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/worker" @@ -39,7 +40,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrValidation, "malformed request body: %v", err) } } diff --git a/services/cur/persistence.go b/services/cur/persistence.go index e4b13d9968..dcf27103b0 100644 --- a/services/cur/persistence.go +++ b/services/cur/persistence.go @@ -2,7 +2,7 @@ package cur import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/databrew/datasets_test.go b/services/databrew/datasets_test.go index c9442e1a40..dd6a2986ea 100644 --- a/services/databrew/datasets_test.go +++ b/services/databrew/datasets_test.go @@ -2,13 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -298,7 +299,7 @@ func TestHandlerCreateDataset(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-ds", resp["Name"]) } @@ -326,7 +327,7 @@ func TestHandlerDescribeDataset(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/datasets/ds1", nil) assert.Equal(t, http.StatusOK, rec.Code) var ds map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ds)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ds, jsonx.V1())) assert.Equal(t, "ds1", ds["Name"]) } @@ -347,7 +348,7 @@ func TestHandlerListDatasets(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/datasets", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Datasets"]) } @@ -453,7 +454,7 @@ func TestHandlerCreateDataset_PathOptions(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/datasets/po-wire-ds", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pathOptions, ok := resp["PathOptions"].(map[string]any) require.True(t, ok, "PathOptions must round-trip as an object") filesLimit, ok := pathOptions["FilesLimit"].(map[string]any) diff --git a/services/databrew/handler.go b/services/databrew/handler.go index 5eeb9c3ee4..1b01fea1ca 100644 --- a/services/databrew/handler.go +++ b/services/databrew/handler.go @@ -2,7 +2,8 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -377,18 +379,18 @@ var onceSimpleQueryParamToJSONKey = sync.OnceValue(func() map[string]string { // mergeSimpleQueryParams copies every onceSimpleQueryParamToJSONKey entry // present on the request into m. -func mergeSimpleQueryParams(c *echo.Context, m map[string]json.RawMessage) { +func mergeSimpleQueryParams(c *echo.Context, m map[string]jsontext.Value) { for param, jsonKey := range onceSimpleQueryParamToJSONKey() { if v := c.QueryParam(param); v != "" { - m[jsonKey], _ = json.Marshal(v) + m[jsonKey], _ = json.Marshal(v, jsonx.V1()) } } } func enrichDataBrewBody(c *echo.Context, _, name string, body []byte) ([]byte, error) { - m := make(map[string]json.RawMessage) + m := make(map[string]jsontext.Value) if len(body) > 0 { - if err := json.Unmarshal(body, &m); err != nil { + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -396,11 +398,11 @@ func enrichDataBrewBody(c *echo.Context, _, name string, body []byte) ([]byte, e if name != "" { if existingName, ok := m["Name"]; ok { var parsedName string - if err := json.Unmarshal(existingName, &parsedName); err == nil && parsedName != name { + if err := json.Unmarshal(existingName, &parsedName, jsonx.V1()); err == nil && parsedName != name { return nil, fmt.Errorf("%w: name in body does not match path", errInvalidRequest) } } - nameJSON, _ := json.Marshal(name) + nameJSON, _ := json.Marshal(name, jsonx.V1()) m["Name"] = nameJSON } @@ -413,7 +415,7 @@ func enrichDataBrewBody(c *echo.Context, _, name string, body []byte) ([]byte, e // encoder.AddQuery("tagKeys") per key). Without this, UntagResource always // silently no-ops. if tagKeys := c.QueryParams()["tagKeys"]; len(tagKeys) > 0 { - m["TagKeys"], _ = json.Marshal(tagKeys) + m["TagKeys"], _ = json.Marshal(tagKeys, jsonx.V1()) } // ListRecipeVersions is GET /recipeVersions?name=... (top-level, no path // segment for the recipe name); only set when name isn't already known @@ -421,20 +423,20 @@ func enrichDataBrewBody(c *echo.Context, _, name string, body []byte) ([]byte, e // alias already populated "Name" above). if _, ok := m["Name"]; !ok { if v := c.QueryParam("name"); v != "" { - m["Name"], _ = json.Marshal(v) + m["Name"], _ = json.Marshal(v, jsonx.V1()) } } - result, _ := json.Marshal(m) + result, _ := json.Marshal(m, jsonx.V1()) return result, nil } func enrichDataBrewSubOpBody(path string, body []byte) []byte { segments := strings.Split(path, "/") - m := make(map[string]json.RawMessage) + m := make(map[string]jsontext.Value) if len(body) > 0 { - _ = json.Unmarshal(body, &m) + _ = json.Unmarshal(body, &m, jsonx.V1()) } for i, seg := range segments { @@ -442,26 +444,26 @@ func enrichDataBrewSubOpBody(path string, body []byte) []byte { case "jobRun": // e.g. /jobs/{Name}/jobRun/{RunId} — with or without /databrew/v1/ prefix if i+1 < len(segments) { - runIDJSON, _ := json.Marshal(segments[i+1]) + runIDJSON, _ := json.Marshal(segments[i+1], jsonx.V1()) m["RunId"] = runIDJSON } case "recipeVersion": // e.g. /recipes/{Name}/recipeVersion/{RecipeVersion} if i+1 < len(segments) { - versionJSON, _ := json.Marshal(segments[i+1]) + versionJSON, _ := json.Marshal(segments[i+1], jsonx.V1()) m["RecipeVersion"] = versionJSON } case "tags": // e.g. /databrew/v1/tags/{resourceArn} or /tags/{resourceArn} if i+1 < len(segments) { arn := strings.Join(segments[i+1:], "/") - arnJSON, _ := json.Marshal(arn) + arnJSON, _ := json.Marshal(arn, jsonx.V1()) m["ResourceArn"] = arnJSON } } } - result, _ := json.Marshal(m) + result, _ := json.Marshal(m, jsonx.V1()) return result } diff --git a/services/databrew/handler_datasets.go b/services/databrew/handler_datasets.go index f1c9512dae..9391c42334 100644 --- a/services/databrew/handler_datasets.go +++ b/services/databrew/handler_datasets.go @@ -2,9 +2,11 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseDatasetOp(method, name string) string { @@ -72,7 +74,7 @@ func (h *Handler) handleCreateDataset(ctx context.Context, body []byte) ([]byte, Name string `json:"Name"` Format string `json:"Format"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } ds, err := h.Backend.CreateDataset( @@ -82,14 +84,14 @@ func (h *Handler) handleCreateDataset(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: ds.Name}) + return json.Marshal(map[string]string{keyName: ds.Name}, jsonx.V1()) } func (h *Handler) handleDescribeDataset(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } ds, err := h.Backend.DescribeDataset(ctx, req.Name) @@ -100,7 +102,7 @@ func (h *Handler) handleDescribeDataset(ctx context.Context, body []byte) ([]byt cp := *ds cp.AccountID = "" - return json.Marshal(cp) + return json.Marshal(cp, jsonx.V1()) } func (h *Handler) handleListDatasets(ctx context.Context, body []byte) ([]byte, error) { @@ -108,7 +110,7 @@ func (h *Handler) handleListDatasets(ctx context.Context, body []byte) ([]byte, MaxResults string `json:"MaxResults"` NextToken string `json:"NextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -127,7 +129,7 @@ func (h *Handler) handleUpdateDataset(ctx context.Context, body []byte) ([]byte, Name string `json:"Name"` Format string `json:"Format"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateDataset( @@ -136,19 +138,19 @@ func (h *Handler) handleUpdateDataset(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteDataset(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteDataset(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } diff --git a/services/databrew/handler_jobs.go b/services/databrew/handler_jobs.go index 13921b141d..152646c9b2 100644 --- a/services/databrew/handler_jobs.go +++ b/services/databrew/handler_jobs.go @@ -2,10 +2,12 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseProfileJobOp(method, name string) string { @@ -143,7 +145,7 @@ func (h *Handler) handleCreateProfileJob(ctx context.Context, body []byte) ([]by MaxRetries int `json:"MaxRetries"` Timeout int `json:"Timeout"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } j, err := h.Backend.CreateJob( @@ -172,7 +174,7 @@ func (h *Handler) handleCreateProfileJob(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]string{keyName: j.Name}) + return json.Marshal(map[string]string{keyName: j.Name}, jsonx.V1()) } // outputLocationToOutputs converts a CreateProfileJobInput/UpdateProfileJobInput @@ -204,7 +206,7 @@ func (h *Handler) handleCreateRecipeJob(ctx context.Context, body []byte) ([]byt MaxRetries int `json:"MaxRetries"` Timeout int `json:"Timeout"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } recipeName, recipeVersion := "", "" @@ -238,14 +240,14 @@ func (h *Handler) handleCreateRecipeJob(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]string{keyName: j.Name}) + return json.Marshal(map[string]string{keyName: j.Name}, jsonx.V1()) } func (h *Handler) handleDescribeJob(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } j, err := h.Backend.DescribeJob(ctx, req.Name) @@ -256,7 +258,7 @@ func (h *Handler) handleDescribeJob(ctx context.Context, body []byte) ([]byte, e cp := *j cp.AccountID = "" - return json.Marshal(cp) + return json.Marshal(cp, jsonx.V1()) } func (h *Handler) handleListJobs(ctx context.Context, body []byte) ([]byte, error) { @@ -266,7 +268,7 @@ func (h *Handler) handleListJobs(ctx context.Context, body []byte) ([]byte, erro DatasetName string `json:"DatasetName"` ProjectName string `json:"ProjectName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -295,7 +297,7 @@ func (h *Handler) handleUpdateProfileJob(ctx context.Context, body []byte) ([]by MaxRetries int `json:"MaxRetries"` Timeout int `json:"Timeout"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateJob( @@ -313,7 +315,7 @@ func (h *Handler) handleUpdateProfileJob(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } // handleUpdateRecipeJob handles UpdateRecipeJob, whose wire field for the @@ -332,7 +334,7 @@ func (h *Handler) handleUpdateRecipeJob(ctx context.Context, body []byte) ([]byt MaxRetries int `json:"MaxRetries"` Timeout int `json:"Timeout"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateJob( @@ -348,28 +350,28 @@ func (h *Handler) handleUpdateRecipeJob(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteJob(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteJob(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleStartJobRun(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } run, err := h.Backend.StartJobRun(ctx, req.Name) @@ -377,7 +379,7 @@ func (h *Handler) handleStartJobRun(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]string{"RunId": run.RunID}) + return json.Marshal(map[string]string{"RunId": run.RunID}, jsonx.V1()) } func (h *Handler) handleListJobRuns(ctx context.Context, body []byte) ([]byte, error) { @@ -386,7 +388,7 @@ func (h *Handler) handleListJobRuns(ctx context.Context, body []byte) ([]byte, e MaxResults string `json:"MaxResults"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } maxResults, token, err := listParams(req.MaxResults, req.NextToken) @@ -407,7 +409,7 @@ func (h *Handler) handleDescribeJobRun(ctx context.Context, body []byte) ([]byte Name string `json:"Name"` RunID string `json:"RunId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } run, err := h.Backend.DescribeJobRun(ctx, req.Name, req.RunID) @@ -415,7 +417,7 @@ func (h *Handler) handleDescribeJobRun(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(run) + return json.Marshal(run, jsonx.V1()) } func (h *Handler) handleStopJobRun(ctx context.Context, body []byte) ([]byte, error) { @@ -423,7 +425,7 @@ func (h *Handler) handleStopJobRun(ctx context.Context, body []byte) ([]byte, er Name string `json:"Name"` RunID string `json:"RunId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } run, err := h.Backend.StopJobRun(ctx, req.Name, req.RunID) @@ -431,5 +433,5 @@ func (h *Handler) handleStopJobRun(ctx context.Context, body []byte) ([]byte, er return nil, err } - return json.Marshal(map[string]string{"RunId": run.RunID}) + return json.Marshal(map[string]string{"RunId": run.RunID}, jsonx.V1()) } diff --git a/services/databrew/handler_projects.go b/services/databrew/handler_projects.go index cd55898518..e614ecc9f0 100644 --- a/services/databrew/handler_projects.go +++ b/services/databrew/handler_projects.go @@ -2,11 +2,13 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseProjectOp(method, name, subOp string) string { @@ -88,7 +90,7 @@ func (h *Handler) handleCreateProject(ctx context.Context, body []byte) ([]byte, RoleArn string `json:"RoleArn"` Sample Sample `json:"Sample"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } p, err := h.Backend.CreateProject( @@ -104,14 +106,14 @@ func (h *Handler) handleCreateProject(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: p.Name}) + return json.Marshal(map[string]string{keyName: p.Name}, jsonx.V1()) } func (h *Handler) handleDescribeProject(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } p, err := h.Backend.DescribeProject(ctx, req.Name) @@ -122,7 +124,7 @@ func (h *Handler) handleDescribeProject(ctx context.Context, body []byte) ([]byt cp := *p cp.AccountID = "" - return json.Marshal(cp) + return json.Marshal(cp, jsonx.V1()) } func (h *Handler) handleListProjects(ctx context.Context, body []byte) ([]byte, error) { @@ -130,7 +132,7 @@ func (h *Handler) handleListProjects(ctx context.Context, body []byte) ([]byte, MaxResults string `json:"MaxResults"` NextToken string `json:"NextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -149,28 +151,28 @@ func (h *Handler) handleUpdateProject(ctx context.Context, body []byte) ([]byte, RoleArn string `json:"RoleArn"` Sample Sample `json:"Sample"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateProject(ctx, req.Name, req.RoleArn, req.Sample); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteProject(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteProject(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } // handleStartProjectSession handles StartProjectSession. It cannot emulate a @@ -182,7 +184,7 @@ func (h *Handler) handleStartProjectSession(ctx context.Context, body []byte) ([ Name string `json:"Name"` AssumeControl bool `json:"AssumeControl"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if _, err := h.Backend.OpenProjectSession(ctx, req.Name); err != nil { @@ -192,7 +194,7 @@ func (h *Handler) handleStartProjectSession(ctx context.Context, body []byte) ([ return json.Marshal(map[string]string{ keyName: req.Name, "ClientSessionId": uuid.New().String(), - }) + }, jsonx.V1()) } // handleSendProjectSessionAction handles SendProjectSessionAction. Applying @@ -203,12 +205,12 @@ func (h *Handler) handleSendProjectSessionAction(ctx context.Context, body []byt var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if _, err := h.Backend.DescribeProject(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } diff --git a/services/databrew/handler_recipes.go b/services/databrew/handler_recipes.go index c095156655..380ba1d65a 100644 --- a/services/databrew/handler_recipes.go +++ b/services/databrew/handler_recipes.go @@ -2,10 +2,12 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseRecipeSubOp(method, subOp string) string { @@ -115,7 +117,7 @@ func (h *Handler) handleCreateRecipe(ctx context.Context, body []byte) ([]byte, Name string `json:"Name"` Description string `json:"Description"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -132,7 +134,7 @@ func (h *Handler) handleCreateRecipe(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: r.Name}) + return json.Marshal(map[string]string{keyName: r.Name}, jsonx.V1()) } func (h *Handler) handleDescribeRecipe(ctx context.Context, body []byte) ([]byte, error) { @@ -140,7 +142,7 @@ func (h *Handler) handleDescribeRecipe(ctx context.Context, body []byte) ([]byte Name string `json:"Name"` RecipeVersion string `json:"RecipeVersion"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } r, err := h.Backend.DescribeRecipe(ctx, req.Name, req.RecipeVersion) @@ -148,7 +150,7 @@ func (h *Handler) handleDescribeRecipe(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(r) + return json.Marshal(r, jsonx.V1()) } func (h *Handler) handleListRecipes(ctx context.Context, body []byte) ([]byte, error) { @@ -157,7 +159,7 @@ func (h *Handler) handleListRecipes(ctx context.Context, body []byte) ([]byte, e NextToken string `json:"NextToken"` RecipeVersion string `json:"RecipeVersion"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -173,14 +175,14 @@ func (h *Handler) handlePublishRecipe(ctx context.Context, body []byte) ([]byte, Name string `json:"Name"` Description string `json:"Description"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.PublishRecipe(ctx, req.Name, req.Description); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleUpdateRecipe(ctx context.Context, body []byte) ([]byte, error) { @@ -189,7 +191,7 @@ func (h *Handler) handleUpdateRecipe(ctx context.Context, body []byte) ([]byte, Description string `json:"Description"` Steps []RecipeStep `json:"Steps"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := validateRecipeStepMembers(req.Steps); err != nil { @@ -200,21 +202,21 @@ func (h *Handler) handleUpdateRecipe(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteRecipe(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteRecipe(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleBatchDeleteRecipeVersion(ctx context.Context, body []byte) ([]byte, error) { @@ -222,7 +224,7 @@ func (h *Handler) handleBatchDeleteRecipeVersion(ctx context.Context, body []byt Name string `json:"Name"` RecipeVersions []string `json:"RecipeVersions"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } errs, err := h.Backend.BatchDeleteRecipeVersion(ctx, req.Name, req.RecipeVersions) @@ -230,7 +232,7 @@ func (h *Handler) handleBatchDeleteRecipeVersion(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyName: req.Name, "Errors": errs}) + return json.Marshal(map[string]any{keyName: req.Name, "Errors": errs}, jsonx.V1()) } func (h *Handler) handleDeleteRecipeVersion(ctx context.Context, body []byte) ([]byte, error) { @@ -238,14 +240,14 @@ func (h *Handler) handleDeleteRecipeVersion(ctx context.Context, body []byte) ([ Name string `json:"Name"` RecipeVersion string `json:"RecipeVersion"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteRecipeVersion(ctx, req.Name, req.RecipeVersion); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name, "RecipeVersion": req.RecipeVersion}) + return json.Marshal(map[string]string{keyName: req.Name, "RecipeVersion": req.RecipeVersion}, jsonx.V1()) } func (h *Handler) handleListRecipeVersions(ctx context.Context, body []byte) ([]byte, error) { @@ -254,7 +256,7 @@ func (h *Handler) handleListRecipeVersions(ctx context.Context, body []byte) ([] MaxResults string `json:"MaxResults"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } maxResults, token, err := listParams(req.MaxResults, req.NextToken) diff --git a/services/databrew/handler_rulesets.go b/services/databrew/handler_rulesets.go index 7628821e64..d72d0c4070 100644 --- a/services/databrew/handler_rulesets.go +++ b/services/databrew/handler_rulesets.go @@ -2,9 +2,11 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseRulesetOp(method, name string) string { @@ -65,7 +67,7 @@ func (h *Handler) handleCreateRuleset(ctx context.Context, body []byte) ([]byte, TargetArn string `json:"TargetArn"` Rules []Rule `json:"Rules"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.TargetArn == "" || req.Rules == nil { @@ -81,14 +83,14 @@ func (h *Handler) handleCreateRuleset(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: rs.Name}) + return json.Marshal(map[string]string{keyName: rs.Name}, jsonx.V1()) } func (h *Handler) handleDescribeRuleset(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } rs, err := h.Backend.DescribeRuleset(ctx, req.Name) @@ -96,7 +98,7 @@ func (h *Handler) handleDescribeRuleset(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(newRulesetDescribeView(rs)) + return json.Marshal(newRulesetDescribeView(rs), jsonx.V1()) } func (h *Handler) handleListRulesets(ctx context.Context, body []byte) ([]byte, error) { @@ -105,7 +107,7 @@ func (h *Handler) handleListRulesets(ctx context.Context, body []byte) ([]byte, NextToken string `json:"NextToken"` TargetArn string `json:"TargetArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -126,7 +128,7 @@ func (h *Handler) handleUpdateRuleset(ctx context.Context, body []byte) ([]byte, Description string `json:"Description"` Rules []Rule `json:"Rules"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.Rules == nil { @@ -141,19 +143,19 @@ func (h *Handler) handleUpdateRuleset(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteRuleset(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteRuleset(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } diff --git a/services/databrew/handler_schedules.go b/services/databrew/handler_schedules.go index 683225c9a0..f861ea9816 100644 --- a/services/databrew/handler_schedules.go +++ b/services/databrew/handler_schedules.go @@ -2,9 +2,11 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseScheduleOp(method, name string) string { @@ -64,7 +66,7 @@ func (h *Handler) handleCreateSchedule(ctx context.Context, body []byte) ([]byte CronExpression string `json:"CronExpression"` JobNames []string `json:"JobNames"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } sc, err := h.Backend.CreateSchedule(ctx, req.Name, req.JobNames, req.CronExpression, req.Tags) @@ -72,14 +74,14 @@ func (h *Handler) handleCreateSchedule(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(map[string]string{keyName: sc.Name}) + return json.Marshal(map[string]string{keyName: sc.Name}, jsonx.V1()) } func (h *Handler) handleDescribeSchedule(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } sc, err := h.Backend.DescribeSchedule(ctx, req.Name) @@ -90,7 +92,7 @@ func (h *Handler) handleDescribeSchedule(ctx context.Context, body []byte) ([]by cp := *sc cp.AccountID = "" - return json.Marshal(cp) + return json.Marshal(cp, jsonx.V1()) } func (h *Handler) handleListSchedules(ctx context.Context, body []byte) ([]byte, error) { @@ -99,7 +101,7 @@ func (h *Handler) handleListSchedules(ctx context.Context, body []byte) ([]byte, NextToken string `json:"NextToken"` JobName string `json:"JobName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) maxResults, token, err := listParams(req.MaxResults, req.NextToken) if err != nil { return nil, err @@ -116,26 +118,26 @@ func (h *Handler) handleUpdateSchedule(ctx context.Context, body []byte) ([]byte CronExpression string `json:"CronExpression"` JobNames []string `json:"JobNames"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateSchedule(ctx, req.Name, req.JobNames, req.CronExpression); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } func (h *Handler) handleDeleteSchedule(ctx context.Context, body []byte) ([]byte, error) { var req struct { Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.DeleteSchedule(ctx, req.Name); err != nil { return nil, err } - return json.Marshal(map[string]string{keyName: req.Name}) + return json.Marshal(map[string]string{keyName: req.Name}, jsonx.V1()) } diff --git a/services/databrew/handler_tags.go b/services/databrew/handler_tags.go index e988ed5516..6b1321b3cb 100644 --- a/services/databrew/handler_tags.go +++ b/services/databrew/handler_tags.go @@ -2,9 +2,11 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseTagsOp(method, _ string) string { @@ -47,14 +49,14 @@ func (h *Handler) handleTagResource(ctx context.Context, body []byte) ([]byte, e Tags map[string]string `json:"Tags"` ResourceArn string `json:"ResourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateTagsByArn(ctx, req.ResourceArn, req.Tags, nil); err != nil { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, error) { @@ -62,21 +64,21 @@ func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, ResourceArn string `json:"ResourceArn"` TagKeys []string `json:"TagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if err := h.Backend.UpdateTagsByArn(ctx, req.ResourceArn, nil, req.TagKeys); err != nil { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([]byte, error) { var req struct { ResourceArn string `json:"ResourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } tags, err := h.Backend.FindTagsByArn(ctx, req.ResourceArn) @@ -84,5 +86,5 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(map[string]any{"Tags": tags}) + return json.Marshal(map[string]any{"Tags": tags}, jsonx.V1()) } diff --git a/services/databrew/handler_test.go b/services/databrew/handler_test.go index ac3cfd13d3..4b749e3455 100644 --- a/services/databrew/handler_test.go +++ b/services/databrew/handler_test.go @@ -1,7 +1,7 @@ package databrew_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Handler metadata ---- @@ -30,7 +32,7 @@ func TestHandlerReset(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/datasets", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) datasets := resp["Datasets"].([]any) assert.Empty(t, datasets) } diff --git a/services/databrew/jobs_test.go b/services/databrew/jobs_test.go index cb673e183c..cf3c2ee8ac 100644 --- a/services/databrew/jobs_test.go +++ b/services/databrew/jobs_test.go @@ -2,7 +2,7 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -738,7 +739,7 @@ func TestHandlerUpdateProfileJob(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "upd-profile-j", resp["Name"]) } @@ -811,7 +812,7 @@ func TestHandlerDescribeJobRun(t *testing.T) { runRec := databrewReq(t, h, http.MethodPost, "/databrew/v1/jobs/djr-job/startJobRun", nil) require.Equal(t, http.StatusOK, runRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &startResp, jsonx.V1())) runID, _ := startResp["RunId"].(string) require.NotEmpty(t, runID) @@ -832,7 +833,7 @@ func TestHandlerStopJobRun(t *testing.T) { runRec := databrewReq(t, h, http.MethodPost, "/databrew/v1/jobs/sjr-job/startJobRun", nil) require.Equal(t, http.StatusOK, runRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &startResp, jsonx.V1())) runID, _ := startResp["RunId"].(string) require.NotEmpty(t, runID) @@ -854,7 +855,7 @@ func TestStartJobRun_RunId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["RunID"], "RunID (uppercase D) must not appear — AWS SDK uses RunId") runID, ok := resp["RunId"].(string) assert.True(t, ok, "response must have RunId string field") @@ -935,7 +936,7 @@ func TestListJobs_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs := resp["Jobs"].([]any) assert.Len(t, jobs, tc.wantCount, "query=%v", tc.queryParams) }) @@ -955,7 +956,7 @@ func TestJobRunIdField_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) runID, ok := startResp["RunId"].(string) require.True(t, ok) require.NotEmpty(t, runID) @@ -967,13 +968,13 @@ func TestJobRunIdField_RoundTrip(t *testing.T) { descRec := databrewReq(t, h, http.MethodGet, "/databrew/v1/jobs/rt-job/jobRun/"+runID, nil) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, runID, descResp["RunId"]) stopRec := databrewReq(t, h, http.MethodPost, "/databrew/v1/jobs/rt-job/jobRun/"+runID, nil) require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Equal(t, runID, stopResp["RunId"]) }) } @@ -1394,7 +1395,7 @@ func TestHandlerCreateProfileJob_Extras(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/jobs/pj-extras", nil) require.Equal(t, http.StatusOK, rec.Code) var job map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &job, jsonx.V1())) jobSample, ok := job["JobSample"].(map[string]any) require.True(t, ok, "JobSample must round-trip as an object") assert.Equal(t, "CUSTOM_ROWS", jobSample["Mode"]) @@ -1428,7 +1429,7 @@ func TestHandlerCreateRecipeJob_Extras(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/jobs/rj-extras", nil) require.Equal(t, http.StatusOK, rec.Code) var job map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &job, jsonx.V1())) assert.Equal(t, "SSE-KMS", job["EncryptionMode"]) assert.Equal(t, "ENABLE", job["LogSubscription"]) outputs, ok := job["DataCatalogOutputs"].([]any) diff --git a/services/databrew/paginate_helper.go b/services/databrew/paginate_helper.go index 762b051097..7781059dcd 100644 --- a/services/databrew/paginate_helper.go +++ b/services/databrew/paginate_helper.go @@ -2,10 +2,12 @@ package databrew import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func paginateKeys(keys []string, maxResults int, nextToken string) ([]string, string) { @@ -69,5 +71,5 @@ func marshalPage(key string, items any, next string) ([]byte, error) { out[nextTokenKey] = base64.RawURLEncoding.EncodeToString([]byte(pageTokenPrefix + next)) } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } diff --git a/services/databrew/persistence.go b/services/databrew/persistence.go index 3205f05d08..45e09bcada 100644 --- a/services/databrew/persistence.go +++ b/services/databrew/persistence.go @@ -2,7 +2,7 @@ package databrew import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" @@ -43,7 +43,7 @@ const databrewSnapshotVersion = 1 // directly here, nested the same way it is held on InMemoryBackend // (region -> job name -> run history). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` JobRuns map[string]map[string][]*JobRun `json:"jobRuns"` RecipeVersions map[string]map[string][]*Recipe `json:"recipeVersions"` AccountID string `json:"accountID"` @@ -90,7 +90,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { // "/" (see store_setup.go's *Table accessors); // splitting on the first "/" recovers both parts (region names never // contain "/"). -func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]json.RawMessage) { +func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]jsontext.Value) { for key := range tables { name, region, found := strings.Cut(key, "/") if !found { @@ -144,7 +144,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { } if snap.Tables == nil { - snap.Tables = make(map[string]json.RawMessage) + snap.Tables = make(map[string]jsontext.Value) } b.preRegisterSnapshotTables(snap.Tables) diff --git a/services/databrew/projects_test.go b/services/databrew/projects_test.go index 255f6f9cec..5745c1dcfe 100644 --- a/services/databrew/projects_test.go +++ b/services/databrew/projects_test.go @@ -2,13 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -242,7 +243,7 @@ func TestHandlerUpdateProject(t *testing.T) { desc := databrewReq(t, h, http.MethodGet, "/databrew/v1/projects/upd-proj", nil) require.Equal(t, http.StatusOK, desc.Code) var proj map[string]any - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &proj)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &proj, jsonx.V1())) assert.Equal(t, "orig-ds", proj["DatasetName"], "DatasetName must be immutable via UpdateProject") assert.Equal(t, "arn:aws:iam::123456789012:role/new", proj["RoleArn"]) } @@ -276,7 +277,7 @@ func TestHandlerStartProjectSession(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "sess-proj", resp["Name"]) } @@ -297,7 +298,7 @@ func TestHandlerSendProjectSessionAction(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "action-proj", resp["Name"]) } @@ -314,7 +315,7 @@ func TestHandlerStartProjectSession_ClientSessionID(t *testing.T) { rec := databrewReq(t, h, http.MethodPut, "/databrew/v1/projects/sess-id-proj/startProjectSession", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ClientSessionId"]) } diff --git a/services/databrew/recipes_test.go b/services/databrew/recipes_test.go index 6f0d069734..7602ebc4c5 100644 --- a/services/databrew/recipes_test.go +++ b/services/databrew/recipes_test.go @@ -2,13 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -193,7 +194,7 @@ func TestHandlerCreateRecipe(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-recipe", resp["Name"]) } @@ -239,7 +240,7 @@ func TestHandlerListRecipes(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/recipes", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Recipes"]) } @@ -280,7 +281,7 @@ func TestHandlerListRecipeVersions(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/recipes/ver-r/recipeVersions", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Recipes"]) } @@ -293,7 +294,7 @@ func TestHandlerListRecipeVersions_UnknownRecipe(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/recipes/no-such/recipeVersions", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Recipes"]) } @@ -356,7 +357,7 @@ func TestHandlerBatchDeleteRecipeVersion_LatestWorkingOnly(t *testing.T) { var out struct { Errors []map[string]string `json:"Errors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Errors, "deleting LATEST_WORKING as the only version must succeed with no partial failures") desc := databrewReq(t, h, http.MethodGet, "/databrew/v1/recipes/bdrv-lw-only", nil) @@ -394,7 +395,7 @@ func TestHandlerBatchDeleteRecipeVersion_LatestWorkingBlockedByPublished(t *test ErrorCode string `json:"ErrorCode"` } `json:"Errors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Errors, 1) assert.Equal(t, "LATEST_WORKING", out.Errors[0].RecipeVersion) assert.Equal(t, "ValidationException", out.Errors[0].ErrorCode) @@ -429,7 +430,7 @@ func TestRecipeReference(t *testing.T) { require.Equal(t, http.StatusOK, desc.Code) var job map[string]any - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &job, jsonx.V1())) assert.Nil(t, job["RecipeName"], "RecipeName must not appear in JSON — AWS SDK uses RecipeReference") ref, ok := job["RecipeReference"].(map[string]any) @@ -463,7 +464,7 @@ func TestRecipeReference_HonorsNumericVersion(t *testing.T) { require.Equal(t, http.StatusOK, desc.Code) var job map[string]any - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &job, jsonx.V1())) ref, ok := job["RecipeReference"].(map[string]any) require.True(t, ok, "RecipeReference must be an object") diff --git a/services/databrew/rulesets_test.go b/services/databrew/rulesets_test.go index afcbe72660..83825b6371 100644 --- a/services/databrew/rulesets_test.go +++ b/services/databrew/rulesets_test.go @@ -2,13 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -177,7 +178,7 @@ func TestHandlerCreateRuleset(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-ruleset", resp["Name"]) } @@ -204,7 +205,7 @@ func TestHandlerListRulesets(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/rulesets", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Rulesets"]) } @@ -283,7 +284,7 @@ func TestHandlerListRulesets_RuleCount(t *testing.T) { var resp struct { Rulesets []map[string]any `json:"Rulesets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Rulesets, 1) assert.InDelta(t, float64(1), resp.Rulesets[0]["RuleCount"], 0) } @@ -308,7 +309,7 @@ func TestHandlerListRulesets_NoRulesLeak(t *testing.T) { var resp struct { Rulesets []map[string]any `json:"Rulesets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Rulesets, 1) _, hasRules := resp.Rulesets[0]["Rules"] assert.False(t, hasRules, "ListRulesets item leaked Rules; real types.RulesetItem has no Rules member") @@ -331,7 +332,7 @@ func TestHandlerDescribeRuleset_NoAccountIDOrRuleCountLeak(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/rulesets/no-leak-describe-rs", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasAccountID := resp["AccountId"] assert.False( t, diff --git a/services/databrew/schedules_test.go b/services/databrew/schedules_test.go index ef5173790c..29870ec226 100644 --- a/services/databrew/schedules_test.go +++ b/services/databrew/schedules_test.go @@ -2,12 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Schedule backend ---- @@ -164,7 +166,7 @@ func TestHandlerCreateSchedule(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-schedule", resp["Name"]) } @@ -191,7 +193,7 @@ func TestHandlerListSchedules(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/schedules", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Schedules"]) } diff --git a/services/databrew/store_test.go b/services/databrew/store_test.go index e927d15c09..198896d43c 100644 --- a/services/databrew/store_test.go +++ b/services/databrew/store_test.go @@ -3,7 +3,8 @@ package databrew_test import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -43,7 +45,7 @@ func databrewReq(t *testing.T, h *databrew.Handler, method, path string, body an var buf bytes.Buffer if body != nil { - require.NoError(t, json.NewEncoder(&buf).Encode(body)) + require.NoError(t, json.MarshalEncode(jsontext.NewEncoder(&buf, jsonx.V1()), body)) } req := httptest.NewRequest(method, path, &buf) @@ -198,7 +200,7 @@ func TestHandlerDescribe_NoAccountIDLeak(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasAccountID := resp["AccountId"] assert.False(t, hasAccountID, "Describe leaked AccountId; the real Describe*Output has no AccountId member") }) @@ -224,7 +226,7 @@ func TestHandlerDescribeProject_NoSessionStatusFabrication(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasSessionStatus := resp["SessionStatus"] assert.False(t, hasSessionStatus, "DescribeProject fabricated SessionStatus; types.Project has no such member") } diff --git a/services/databrew/tags_test.go b/services/databrew/tags_test.go index 5f77ef74eb..da8836c869 100644 --- a/services/databrew/tags_test.go +++ b/services/databrew/tags_test.go @@ -2,13 +2,14 @@ package databrew_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/databrew" ) @@ -320,7 +321,7 @@ func TestHandlerListTagsForResource(t *testing.T) { rec := databrewReq(t, h, http.MethodGet, "/databrew/v1/tags/"+ds.Arn, nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Tags"]) } @@ -375,7 +376,7 @@ func TestTagsOps(t *testing.T) { listRec := databrewReq(t, h, http.MethodGet, prefix, nil) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) diff --git a/services/datasync/handler.go b/services/datasync/handler.go index 9cca34a962..15a61d4265 100644 --- a/services/datasync/handler.go +++ b/services/datasync/handler.go @@ -2,7 +2,7 @@ package datasync import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -226,13 +227,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, describeNotFound(err, body) } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { // ErrNotFound/ErrAlreadyExists deliberately map to the SAME wire code as // ErrInvalidParameter below: every one of datasync's 53 @@ -249,8 +247,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyType: "InvalidRequestException", keyMessage: publicMessage(err), diff --git a/services/datasync/handler_agents_test.go b/services/datasync/handler_agents_test.go index 1f3b549478..53b280983f 100644 --- a/services/datasync/handler_agents_test.go +++ b/services/datasync/handler_agents_test.go @@ -1,13 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -33,7 +34,7 @@ func TestDataSync_Agent(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["AgentArn"], "arn:aws:datasync:us-east-1:000000000000:agent/") }, }, @@ -54,7 +55,7 @@ func TestDataSync_Agent(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ONLINE", resp["Status"]) }, }, @@ -93,7 +94,7 @@ func TestDataSync_Agent(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) agents, ok := resp["Agents"].([]any) require.True(t, ok) assert.Empty(t, agents) @@ -138,7 +139,7 @@ func TestDataSync_AgentCRUD(t *testing.T) { rec := doRequest(t, h, "DescribeAgent", map[string]any{"AgentArn": agentArn}) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-agent", descResp["Name"]) // Update @@ -147,14 +148,14 @@ func TestDataSync_AgentCRUD(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeAgent", map[string]any{"AgentArn": agentArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "updated-agent", descResp["Name"]) // List rec = doRequest(t, h, "ListAgents", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Agents"], 1) // Delete diff --git a/services/datasync/handler_locations_agentarns_test.go b/services/datasync/handler_locations_agentarns_test.go index 6c46772e1d..0929560627 100644 --- a/services/datasync/handler_locations_agentarns_test.go +++ b/services/datasync/handler_locations_agentarns_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // agentArnsLocationCase covers one of the six location types that accept @@ -179,7 +181,7 @@ func TestDataSync_AgentArns_RejectsPhantomAgent(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["__type"]) }) } @@ -206,7 +208,7 @@ func TestDataSync_AgentArns_UpdateRejectsPhantomAgent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -214,7 +216,7 @@ func TestDataSync_AgentArns_UpdateRejectsPhantomAgent(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, "update: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["__type"]) // The rejected update must not have applied: AgentArns on the @@ -223,7 +225,7 @@ func TestDataSync_AgentArns_UpdateRejectsPhantomAgent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.ElementsMatch(t, []any{realAgentArn}, tc.agentArns(t, descResp)) }) } @@ -246,7 +248,7 @@ func TestDataSync_AgentArns_AcceptsRealAgent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -254,7 +256,7 @@ func TestDataSync_AgentArns_AcceptsRealAgent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.ElementsMatch(t, []any{agentArn}, tc.agentArns(t, descResp)) }) } diff --git a/services/datasync/handler_locations_azureblob_test.go b/services/datasync/handler_locations_azureblob_test.go index 4d744fa50a..e52b021b7f 100644 --- a/services/datasync/handler_locations_azureblob_test.go +++ b/services/datasync/handler_locations_azureblob_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_AzureBlob covers the AzureBlob location lifecycle. @@ -31,7 +33,7 @@ func TestDataSync_AzureBlob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) require.NotEmpty(t, locArn) @@ -41,7 +43,7 @@ func TestDataSync_AzureBlob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, locArn, descResp["LocationArn"]) assert.Equal(t, "BLOCK", descResp["BlobType"]) assert.Equal(t, "HOT", descResp["AccessTier"]) @@ -62,7 +64,7 @@ func TestDataSync_AzureBlob(t *testing.T) { // Describe after update rec = doRequest(t, h, "DescribeLocationAzureBlob", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "COOL", descResp["AccessTier"]) // Not found diff --git a/services/datasync/handler_locations_efs_test.go b/services/datasync/handler_locations_efs_test.go index 9c60568625..249b671ad2 100644 --- a/services/datasync/handler_locations_efs_test.go +++ b/services/datasync/handler_locations_efs_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_Efs covers the EFS location lifecycle. @@ -28,7 +30,7 @@ func TestDataSync_Efs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -36,7 +38,7 @@ func TestDataSync_Efs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, locArn, descResp["LocationArn"]) assert.Equal(t, "TLS1_2", descResp["InTransitEncryption"]) assert.Contains(t, descResp["LocationUri"].(string), "efs://") diff --git a/services/datasync/handler_locations_fsxlustre_test.go b/services/datasync/handler_locations_fsxlustre_test.go index 6b9081886b..cdb33839bf 100644 --- a/services/datasync/handler_locations_fsxlustre_test.go +++ b/services/datasync/handler_locations_fsxlustre_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_FsxLustre covers the FSx Lustre location lifecycle. @@ -24,7 +26,7 @@ func TestDataSync_FsxLustre(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -32,7 +34,7 @@ func TestDataSync_FsxLustre(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) // "fsxl://", not the bare "lustre://" real AWS's LocationUri pattern // (`^(efs|nfs|s3|smb|hdfs|fsx[a-z0-9-]+)://...$`) rules out -- see // fsxLustreURIScheme. diff --git a/services/datasync/handler_locations_fsxontap_test.go b/services/datasync/handler_locations_fsxontap_test.go index 92917d6b1d..91e5d06262 100644 --- a/services/datasync/handler_locations_fsxontap_test.go +++ b/services/datasync/handler_locations_fsxontap_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_FsxOntap covers the FSx ONTAP location lifecycle. @@ -29,7 +31,7 @@ func TestDataSync_FsxOntap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -37,7 +39,7 @@ func TestDataSync_FsxOntap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) // NFS protocol -> "nfs://" scheme (ONTAP has no distinct "ontap://" or // "fsxn://" scheme; DataSync reuses the underlying access protocol's own // scheme, matching how FSx Windows reuses "smb://" -- see @@ -57,7 +59,7 @@ func TestDataSync_FsxOntap(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) rec = doRequest(t, h, "DescribeLocationFsxOntap", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "smb://") // Missing StorageVirtualMachineArn diff --git a/services/datasync/handler_locations_fsxopenzfs_test.go b/services/datasync/handler_locations_fsxopenzfs_test.go index 8e8430085d..a0c2678ec2 100644 --- a/services/datasync/handler_locations_fsxopenzfs_test.go +++ b/services/datasync/handler_locations_fsxopenzfs_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_FsxOpenZfs covers the FSx OpenZFS location lifecycle. @@ -29,7 +31,7 @@ func TestDataSync_FsxOpenZfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -37,7 +39,7 @@ func TestDataSync_FsxOpenZfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "fsxz://") // The real DescribeLocationFsxOpenZfsOutput has no FsxFilesystemArn field. assert.Nil(t, descResp["FsxFilesystemArn"]) diff --git a/services/datasync/handler_locations_fsxwindows_test.go b/services/datasync/handler_locations_fsxwindows_test.go index 450a5feb79..dafa9f0059 100644 --- a/services/datasync/handler_locations_fsxwindows_test.go +++ b/services/datasync/handler_locations_fsxwindows_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_FsxWindows covers the FSx Windows location lifecycle. @@ -27,7 +29,7 @@ func TestDataSync_FsxWindows(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -35,7 +37,7 @@ func TestDataSync_FsxWindows(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "smb://") assert.Equal(t, "CORP", descResp["Domain"]) assert.Equal(t, "svcuser", descResp["User"]) @@ -56,7 +58,7 @@ func TestDataSync_FsxWindows(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeLocationFsxWindows", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "NEWDOMAIN", descResp["Domain"]) // Missing FsxFilesystemArn diff --git a/services/datasync/handler_locations_hdfs_test.go b/services/datasync/handler_locations_hdfs_test.go index 5d95a0739b..8604ab7dc4 100644 --- a/services/datasync/handler_locations_hdfs_test.go +++ b/services/datasync/handler_locations_hdfs_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_Hdfs covers the HDFS location lifecycle. @@ -35,7 +37,7 @@ func TestDataSync_Hdfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -43,7 +45,7 @@ func TestDataSync_Hdfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "hdfs://") assert.Equal(t, "SIMPLE", descResp["AuthenticationType"]) assert.Equal(t, "hadoop", descResp["SimpleUser"]) @@ -62,7 +64,7 @@ func TestDataSync_Hdfs(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeLocationHdfs", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "newuser", descResp["SimpleUser"]) nameNodes := descResp["NameNodes"].([]any) require.Len(t, nameNodes, 1) diff --git a/services/datasync/handler_locations_nfs_test.go b/services/datasync/handler_locations_nfs_test.go index d757968c05..65d40f36ed 100644 --- a/services/datasync/handler_locations_nfs_test.go +++ b/services/datasync/handler_locations_nfs_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_Nfs covers the NFS location lifecycle. @@ -29,7 +31,7 @@ func TestDataSync_Nfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -37,7 +39,7 @@ func TestDataSync_Nfs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "nfs://") assert.NotNil(t, descResp["MountOptions"]) assert.NotNil(t, descResp["OnPremConfig"]) @@ -58,7 +60,7 @@ func TestDataSync_Nfs(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeLocationNfs", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) mo := descResp["MountOptions"].(map[string]any) assert.Equal(t, "NFS4_1", mo["Version"]) @@ -120,7 +122,7 @@ func TestDataSync_UpdateLocationNfs_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -132,7 +134,7 @@ func TestDataSync_UpdateLocationNfs_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantURI, descResp["LocationUri"]) }) } diff --git a/services/datasync/handler_locations_objectstorage_test.go b/services/datasync/handler_locations_objectstorage_test.go index de2fe86594..3584f4adbd 100644 --- a/services/datasync/handler_locations_objectstorage_test.go +++ b/services/datasync/handler_locations_objectstorage_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_ObjectStorage covers the ObjectStorage location lifecycle. @@ -30,7 +32,7 @@ func TestDataSync_ObjectStorage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -38,7 +40,7 @@ func TestDataSync_ObjectStorage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "object-storage://") assert.Equal(t, "HTTPS", descResp["ServerProtocol"]) // SecretKey not returned @@ -59,7 +61,7 @@ func TestDataSync_ObjectStorage(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeLocationObjectStorage", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "HTTP", descResp["ServerProtocol"]) // Missing ServerHostname @@ -127,7 +129,7 @@ func TestDataSync_UpdateLocationObjectStorage_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -139,7 +141,7 @@ func TestDataSync_UpdateLocationObjectStorage_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantURI, descResp["LocationUri"]) if proto, isSet := tt.update["ServerProtocol"]; isSet { diff --git a/services/datasync/handler_locations_secretconfig_test.go b/services/datasync/handler_locations_secretconfig_test.go index 659d399069..ee7201640a 100644 --- a/services/datasync/handler_locations_secretconfig_test.go +++ b/services/datasync/handler_locations_secretconfig_test.go @@ -1,7 +1,7 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -126,7 +127,7 @@ func TestDataSync_SecretConfig_CmkRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -134,7 +135,7 @@ func TestDataSync_SecretConfig_CmkRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "describe: %s", rec.Body.String()) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) cmk, ok := descResp["CmkSecretConfig"].(map[string]any) require.True(t, ok, "CmkSecretConfig missing from describe response: %v", descResp) @@ -166,7 +167,7 @@ func TestDataSync_SecretConfig_CustomRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -174,7 +175,7 @@ func TestDataSync_SecretConfig_CustomRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "describe: %s", rec.Body.String()) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) custom, ok := descResp["CustomSecretConfig"].(map[string]any) require.True(t, ok, "CustomSecretConfig missing from describe response: %v", descResp) @@ -239,7 +240,7 @@ func TestDataSync_Smb_KerberosAndDNS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -247,7 +248,7 @@ func TestDataSync_Smb_KerberosAndDNS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "KERBEROS", descResp["AuthenticationType"]) assert.Equal(t, "HOST/kerberosuser@MYDOMAIN.ORG", descResp["KerberosPrincipal"]) @@ -269,7 +270,7 @@ func TestDataSync_Smb_KerberosAndDNS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) rec = doRequest(t, h, "DescribeLocationSmb", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "HOST/other@MYDOMAIN.ORG", descResp["KerberosPrincipal"]) dns, ok = descResp["DnsIpAddresses"].([]any) @@ -322,12 +323,12 @@ func TestDataSync_Smb_AuthenticationTypeEnum(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) rec = doRequest(t, h, "DescribeLocationSmb", map[string]any{"LocationArn": locArn}) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.expectEchoedAs, descResp["AuthenticationType"]) }) } diff --git a/services/datasync/handler_locations_smb_test.go b/services/datasync/handler_locations_smb_test.go index 2200920fa9..8cf9c62d82 100644 --- a/services/datasync/handler_locations_smb_test.go +++ b/services/datasync/handler_locations_smb_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDataSync_Smb covers the SMB location lifecycle. @@ -29,7 +31,7 @@ func TestDataSync_Smb(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) // Describe @@ -37,7 +39,7 @@ func TestDataSync_Smb(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "smb://") assert.Equal(t, "CORP", descResp["Domain"]) assert.Equal(t, "smbuser", descResp["User"]) @@ -62,7 +64,7 @@ func TestDataSync_Smb(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeLocationSmb", map[string]any{"LocationArn": locArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "NEWDOMAIN", descResp["Domain"]) mo := descResp["MountOptions"].(map[string]any) assert.Equal(t, "AUTOMATIC", mo["Version"]) @@ -129,7 +131,7 @@ func TestDataSync_UpdateLocationSmb_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn, ok := createResp["LocationArn"].(string) require.True(t, ok) @@ -141,7 +143,7 @@ func TestDataSync_UpdateLocationSmb_ServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantURI, descResp["LocationUri"]) if domain, isSet := tt.update["Domain"]; isSet { diff --git a/services/datasync/handler_locations_test.go b/services/datasync/handler_locations_test.go index c611b115e1..84011e8154 100644 --- a/services/datasync/handler_locations_test.go +++ b/services/datasync/handler_locations_test.go @@ -1,13 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -36,7 +37,7 @@ func TestDataSync_LocationS3(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["LocationArn"], "arn:aws:datasync:us-east-1:000000000000:location/") }, }, @@ -76,7 +77,7 @@ func TestDataSync_LocationS3(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) locs, ok := resp["Locations"].([]any) require.True(t, ok) assert.Empty(t, locs) @@ -114,14 +115,14 @@ func TestDataSync_LocationS3CRUD(t *testing.T) { rec := doRequest(t, h, "DescribeLocationS3", map[string]any{"LocationArn": locationArn}) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Contains(t, descResp["LocationUri"].(string), "s3://my-test-bucket/") // List rec = doRequest(t, h, "ListLocations", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Locations"], 1) // Delete @@ -195,14 +196,14 @@ func TestDataSync_LocationS3AgentArns(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) locArn := createResp["LocationArn"].(string) rec = doRequest(t, h, "DescribeLocationS3", map[string]any{"LocationArn": locArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) agentArns, ok := descResp["AgentArns"].([]any) require.True(t, ok) require.Len(t, agentArns, 1) diff --git a/services/datasync/handler_tags_test.go b/services/datasync/handler_tags_test.go index 5d074051a9..9b51cc36fe 100644 --- a/services/datasync/handler_tags_test.go +++ b/services/datasync/handler_tags_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDataSync_Tags(t *testing.T) { @@ -30,7 +32,7 @@ func TestDataSync_Tags(t *testing.T) { rec = doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": agentArn}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 2) @@ -44,7 +46,7 @@ func TestDataSync_Tags(t *testing.T) { // Verify untag rec = doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": agentArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags = listResp["Tags"].([]any) assert.Len(t, tags, 1) @@ -79,7 +81,7 @@ func TestDataSync_TagsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) require.True(t, ok) @@ -102,7 +104,7 @@ func TestDataSync_TagsRoundTrip(t *testing.T) { rec = doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": agentArn}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok = listResp["Tags"].([]any) require.True(t, ok) diff --git a/services/datasync/handler_tasks_test.go b/services/datasync/handler_tasks_test.go index d2f28e65e1..141727c413 100644 --- a/services/datasync/handler_tasks_test.go +++ b/services/datasync/handler_tasks_test.go @@ -1,13 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -53,7 +54,7 @@ func TestDataSync_Task(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) tasks, ok := resp["Tasks"].([]any) require.True(t, ok) assert.Empty(t, tasks) @@ -91,7 +92,7 @@ func TestDataSync_TaskCRUD(t *testing.T) { rec := doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "AVAILABLE", descResp["Status"]) assert.Equal(t, "test-task", descResp["Name"]) @@ -101,14 +102,14 @@ func TestDataSync_TaskCRUD(t *testing.T) { // Verify update rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "renamed-task", descResp["Name"]) // List rec = doRequest(t, h, "ListTasks", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Tasks"], 1) // Delete @@ -131,7 +132,7 @@ func TestDataSync_TaskExecution(t *testing.T) { rec := doRequest(t, h, "StartTaskExecution", map[string]any{"TaskArn": taskArn}) assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn, ok := startResp["TaskExecutionArn"].(string) require.True(t, ok) assert.Contains(t, execArn, "/execution/") @@ -144,7 +145,7 @@ func TestDataSync_TaskExecution(t *testing.T) { rec = doRequest(t, h, "ListTaskExecutions", map[string]any{"TaskArn": taskArn}) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["TaskExecutions"], 1) // CancelTaskExecution on an execution already settled into SUCCESS (by @@ -155,7 +156,7 @@ func TestDataSync_TaskExecution(t *testing.T) { // List after the rejected cancel - execution is unchanged, still SUCCESS. rec = doRequest(t, h, "ListTaskExecutions", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) execs, ok := listResp["TaskExecutions"].([]any) require.True(t, ok) require.Len(t, execs, 1) @@ -186,7 +187,7 @@ func TestDataSync_UpdateTaskExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) tests := []struct { @@ -237,7 +238,7 @@ func TestDataSync_UpdateTaskExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var startResp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &startResp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &startResp2, jsonx.V1())) execArn2 := startResp2["TaskExecutionArn"].(string) updRec := doRequest(t, h, "UpdateTaskExecution", map[string]any{ @@ -253,7 +254,7 @@ func TestDataSync_UpdateTaskExecution(t *testing.T) { Options map[string]any `json:"Options"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.InDelta(t, float64(2097152), descResp.Options["BytesPerSecond"], 0) for _, tc := range tests { @@ -280,7 +281,7 @@ func TestDataSync_CancelTaskExecutionStatusChange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) // Cancel. @@ -292,7 +293,7 @@ func TestDataSync_CancelTaskExecutionStatusChange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) execs, ok := listResp["TaskExecutions"].([]any) require.True(t, ok) @@ -351,7 +352,7 @@ func TestDataSync_CancelTaskExecution_RejectsTerminal(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) tt.settle(t, h, execArn) @@ -365,7 +366,7 @@ func TestDataSync_CancelTaskExecution_RejectsTerminal(t *testing.T) { rec = doRequest(t, h, "DescribeTaskExecution", map[string]any{"TaskExecutionArn": execArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantErr, descResp["Status"]) }) } @@ -385,7 +386,7 @@ func TestDataSync_DescribeTaskExecutionLazyAdvance(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) // Before describe: execution exists as LAUNCHING in list. @@ -393,7 +394,7 @@ func TestDataSync_DescribeTaskExecutionLazyAdvance(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) execs := listResp["TaskExecutions"].([]any) require.Len(t, execs, 1) @@ -431,7 +432,7 @@ func TestDataSync_StartTaskExecutionRejectsConcurrent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) // Second start while the first execution is still LAUNCHING must fail. @@ -462,24 +463,24 @@ func TestDataSync_TaskStatusRunningWhileExecuting(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var taskResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp, jsonx.V1())) assert.Equal(t, "AVAILABLE", taskResp["Status"]) rec = doRequest(t, h, "StartTaskExecution", map[string]any{"TaskArn": taskArn}) require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["TaskExecutionArn"].(string) rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp, jsonx.V1())) assert.Equal(t, "RUNNING", taskResp["Status"]) settleExecution(t, h, execArn) rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp, jsonx.V1())) assert.Equal(t, "AVAILABLE", taskResp["Status"]) } @@ -522,14 +523,14 @@ func TestDataSync_TaskSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskArn := createResp["TaskArn"].(string) rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) opts, ok := descResp["Options"].(map[string]any) require.True(t, ok) @@ -571,7 +572,7 @@ func TestDataSync_TaskSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "settings-task-renamed", descResp["Name"]) opts, ok = descResp["Options"].(map[string]any) require.True(t, ok) @@ -587,7 +588,7 @@ func TestDataSync_TaskSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = doRequest(t, h, "DescribeTask", map[string]any{"TaskArn": taskArn}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) excludes, ok = descResp["Excludes"].([]any) require.True(t, ok) require.Len(t, excludes, 1) @@ -609,7 +610,7 @@ func TestDataSync_TaskModeDefaultsToBasic(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "BASIC", descResp["TaskMode"]) } @@ -635,7 +636,7 @@ func TestDataSync_ListTaskExecutionsAllTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) execs, ok := listResp["TaskExecutions"].([]any) require.True(t, ok) @@ -651,7 +652,7 @@ func settleExecution(t *testing.T, h *datasync.Handler, execArn string) map[stri rec := doRequest(t, h, "DescribeTaskExecution", map[string]any{"TaskExecutionArn": execArn}) require.Equal(t, http.StatusOK, rec.Code) out = map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if out["Status"] == "SUCCESS" { break @@ -670,7 +671,7 @@ func TestDataSync_TaskExecutionPhases(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var start map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) execArn := start["TaskExecutionArn"].(string) got := make([]string, 0, 5) @@ -678,7 +679,7 @@ func TestDataSync_TaskExecutionPhases(t *testing.T) { for range 5 { rec = doRequest(t, h, "DescribeTaskExecution", map[string]any{"TaskExecutionArn": execArn}) var d map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &d)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &d, jsonx.V1())) got = append(got, d["Status"].(string)) } diff --git a/services/datasync/handler_test.go b/services/datasync/handler_test.go index 7a682d0371..79919b786a 100644 --- a/services/datasync/handler_test.go +++ b/services/datasync/handler_test.go @@ -2,7 +2,7 @@ package datasync_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -30,7 +31,7 @@ func doRequest(t *testing.T, h *datasync.Handler, action string, body any) *http if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -56,7 +57,7 @@ func createTestAgent(t *testing.T, h *datasync.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["AgentArn"].(string) require.True(t, ok) @@ -77,7 +78,7 @@ func createTestLocationS3(t *testing.T, h *datasync.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["LocationArn"].(string) require.True(t, ok) @@ -96,7 +97,7 @@ func createTestTask(t *testing.T, h *datasync.Handler, srcArn, dstArn string) st require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["TaskArn"].(string) require.True(t, ok) @@ -185,7 +186,7 @@ func TestDataSync_NotFoundReturnsInvalidRequest(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "expected 400 for %s with unknown ARN", tt.action) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["__type"]) }) } diff --git a/services/datasync/persistence.go b/services/datasync/persistence.go index b946dab075..8ded002749 100644 --- a/services/datasync/persistence.go +++ b/services/datasync/persistence.go @@ -2,7 +2,7 @@ package datasync import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -31,7 +31,7 @@ const datasyncSnapshotVersion = 1 // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/datasync/validation.go b/services/datasync/validation.go index 6f3facf08e..d3d40a3fde 100644 --- a/services/datasync/validation.go +++ b/services/datasync/validation.go @@ -1,7 +1,7 @@ package datasync import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" @@ -9,6 +9,7 @@ import ( dstypes "github.com/aws/aws-sdk-go-v2/service/datasync/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -115,7 +116,7 @@ func describeNotFound(err error, body []byte) error { } var fields map[string]any - if json.Unmarshal(body, &fields) != nil { + if json.Unmarshal(body, &fields, jsonx.V1()) != nil { return err } diff --git a/services/datasync/validation_test.go b/services/datasync/validation_test.go index 0783e48a3e..5278fda462 100644 --- a/services/datasync/validation_test.go +++ b/services/datasync/validation_test.go @@ -1,12 +1,14 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDataSync_RequestValidation(t *testing.T) { @@ -86,7 +88,7 @@ func TestDataSync_RequestValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "InvalidRequestException", out["__type"]) assert.Contains(t, out["message"], tt.wantMsg) assert.NotContains(t, out["message"], "InvalidRequestException:") @@ -106,7 +108,7 @@ func TestDataSync_ResourceIDFormats(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var start map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) tests := []struct { name string diff --git a/services/datasync/wire_field_fixes_test.go b/services/datasync/wire_field_fixes_test.go index 9ce0c91ea0..606391660f 100644 --- a/services/datasync/wire_field_fixes_test.go +++ b/services/datasync/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package datasync_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/datasync" ) @@ -169,7 +170,7 @@ func TestListLocations_NoFabricatedCreationTime(t *testing.T) { var resp struct { Locations []map[string]any `json:"Locations"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Locations, 1, "must exercise a non-empty collection") _, hasCreationTime := resp.Locations[0]["CreationTime"] diff --git a/services/dax/handler_clusters.go b/services/dax/handler_clusters.go index 8829275065..2ef509884d 100644 --- a/services/dax/handler_clusters.go +++ b/services/dax/handler_clusters.go @@ -1,10 +1,11 @@ package dax import ( - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createClusterRequest struct { @@ -205,7 +206,7 @@ func toClusterResponse(c *Cluster) clusterResponse { func (h *Handler) handleCreateCluster(body []byte) (any, error) { var req createClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -247,7 +248,7 @@ func (h *Handler) handleCreateCluster(body []byte) (any, error) { func (h *Handler) handleDescribeClusters(body []byte) (any, error) { var req describeClustersRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -279,7 +280,7 @@ func (h *Handler) handleDescribeClusters(body []byte) (any, error) { func (h *Handler) handleUpdateCluster(body []byte) (any, error) { var req updateClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -295,7 +296,7 @@ func (h *Handler) handleUpdateCluster(body []byte) (any, error) { func (h *Handler) handleDeleteCluster(body []byte) (any, error) { var req deleteClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -311,7 +312,7 @@ func (h *Handler) handleDeleteCluster(body []byte) (any, error) { func (h *Handler) handleIncreaseReplicationFactor(body []byte) (any, error) { var req increaseReplicationFactorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -327,7 +328,7 @@ func (h *Handler) handleIncreaseReplicationFactor(body []byte) (any, error) { func (h *Handler) handleDecreaseReplicationFactor(body []byte) (any, error) { var req decreaseReplicationFactorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -343,7 +344,7 @@ func (h *Handler) handleDecreaseReplicationFactor(body []byte) (any, error) { func (h *Handler) handleRebootNode(body []byte) (any, error) { var req rebootNodeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/dax/handler_clusters_test.go b/services/dax/handler_clusters_test.go index 3c42f2672d..aefd34ab8a 100644 --- a/services/dax/handler_clusters_test.go +++ b/services/dax/handler_clusters_test.go @@ -1,13 +1,14 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -81,7 +82,7 @@ func TestHandlerCreateCluster(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -104,7 +105,7 @@ func TestHandlerClusterResponseHasNoTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) _, hasTags := cluster["Tags"] assert.False(t, hasTags, "Cluster response must not include a Tags field") @@ -122,7 +123,7 @@ func TestHandlerCreateCluster_Duplicate(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ClusterAlreadyExistsFault", errResp["__type"]) } @@ -164,7 +165,7 @@ func TestHandlerDescribeClusters(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) assert.Len(t, clusters, tt.wantCount) }) @@ -237,7 +238,7 @@ func TestHandlerUpdateCluster(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -351,7 +352,7 @@ func TestHandlerReplicationFactor(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -407,7 +408,7 @@ func TestHandlerRebootNode(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) diff --git a/services/dax/handler_events.go b/services/dax/handler_events.go index e4dc4b3067..9ee9600be7 100644 --- a/services/dax/handler_events.go +++ b/services/dax/handler_events.go @@ -1,21 +1,22 @@ package dax import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type describeEventsRequest struct { - SourceName string `json:"SourceName"` - SourceType string `json:"SourceType"` - StartTime json.Number `json:"StartTime"` - EndTime json.Number `json:"EndTime"` - NextToken string `json:"NextToken"` - MaxResults int `json:"MaxResults"` - Duration int `json:"Duration"` // minutes to look back; applied when StartTime is absent + SourceName string `json:"SourceName"` + SourceType string `json:"SourceType"` + StartTime jsonx.Number `json:"StartTime"` + EndTime jsonx.Number `json:"EndTime"` + NextToken string `json:"NextToken"` + MaxResults int `json:"MaxResults"` + Duration int `json:"Duration"` // minutes to look back; applied when StartTime is absent } type eventResponse struct { @@ -30,7 +31,7 @@ type eventResponse struct { // awsjson1.1 Timestamp wire format DAX uses -- see smithytime.ParseEpochSeconds // in the real SDK deserializer) into a time.Time, preserving sub-second // precision carried in the fractional part. -func parseEpochSeconds(n json.Number) (time.Time, error) { +func parseEpochSeconds(n jsonx.Number) (time.Time, error) { f, err := n.Float64() if err != nil { return time.Time{}, fmt.Errorf("%w: %w", errInvalidRequest, err) @@ -45,7 +46,7 @@ func parseEpochSeconds(n json.Number) (time.Time, error) { func (h *Handler) handleDescribeEvents(body []byte) (any, error) { var req describeEventsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/dax/handler_events_test.go b/services/dax/handler_events_test.go index 3a5f996aa6..35e2395c2c 100644 --- a/services/dax/handler_events_test.go +++ b/services/dax/handler_events_test.go @@ -1,13 +1,14 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -61,7 +62,7 @@ func TestHandlerDescribeEvents(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) diff --git a/services/dax/handler_parameter_groups.go b/services/dax/handler_parameter_groups.go index e71ffe49c4..5301481fe0 100644 --- a/services/dax/handler_parameter_groups.go +++ b/services/dax/handler_parameter_groups.go @@ -1,8 +1,10 @@ package dax import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createParameterGroupRequest struct { @@ -81,7 +83,7 @@ func toParameterResponse(p *Parameter) parameterResponse { func (h *Handler) handleCreateParameterGroup(body []byte) (any, error) { var req createParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -101,7 +103,7 @@ func (h *Handler) handleCreateParameterGroup(body []byte) (any, error) { func (h *Handler) handleDescribeParameterGroups(body []byte) (any, error) { var req describeParameterGroupsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -136,7 +138,7 @@ func (h *Handler) handleDescribeParameterGroups(body []byte) (any, error) { func (h *Handler) handleUpdateParameterGroup(body []byte) (any, error) { var req updateParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -169,7 +171,7 @@ func (h *Handler) handleUpdateParameterGroup(body []byte) (any, error) { func (h *Handler) handleDeleteParameterGroup(body []byte) (any, error) { var req deleteParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -184,7 +186,7 @@ func (h *Handler) handleDeleteParameterGroup(body []byte) (any, error) { func (h *Handler) handleDescribeParameters(body []byte) (any, error) { var req describeParametersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -217,7 +219,7 @@ func (h *Handler) handleDescribeParameters(body []byte) (any, error) { func (h *Handler) handleDescribeDefaultParameters(body []byte) (any, error) { var req describeDefaultParametersRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -245,7 +247,7 @@ func (h *Handler) handleDescribeDefaultParameters(body []byte) (any, error) { func (h *Handler) handleResetParameterGroup(body []byte) (any, error) { var req resetParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/dax/handler_parameter_groups_test.go b/services/dax/handler_parameter_groups_test.go index fb2cbd0ce4..ec2f21fc7e 100644 --- a/services/dax/handler_parameter_groups_test.go +++ b/services/dax/handler_parameter_groups_test.go @@ -1,13 +1,14 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -120,7 +121,7 @@ func TestHandlerParameterGroups(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -141,6 +142,6 @@ func TestHandlerUpdateParameterGroupRequiresParameterNameValues(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["__type"]) } diff --git a/services/dax/handler_subnet_groups.go b/services/dax/handler_subnet_groups.go index 329db1682e..6df9bf1fa3 100644 --- a/services/dax/handler_subnet_groups.go +++ b/services/dax/handler_subnet_groups.go @@ -1,8 +1,10 @@ package dax import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createSubnetGroupRequest struct { @@ -66,7 +68,7 @@ func toSubnetGroupResponse(sg *SubnetGroup) subnetGroupResponse { func (h *Handler) handleCreateSubnetGroup(body []byte) (any, error) { var req createSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -83,7 +85,7 @@ func (h *Handler) handleCreateSubnetGroup(body []byte) (any, error) { func (h *Handler) handleDescribeSubnetGroups(body []byte) (any, error) { var req describeSubnetGroupsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -115,7 +117,7 @@ func (h *Handler) handleDescribeSubnetGroups(body []byte) (any, error) { func (h *Handler) handleUpdateSubnetGroup(body []byte) (any, error) { var req updateSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -131,7 +133,7 @@ func (h *Handler) handleUpdateSubnetGroup(body []byte) (any, error) { func (h *Handler) handleDeleteSubnetGroup(body []byte) (any, error) { var req deleteSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/dax/handler_subnet_groups_test.go b/services/dax/handler_subnet_groups_test.go index 741846be9e..7d0900e432 100644 --- a/services/dax/handler_subnet_groups_test.go +++ b/services/dax/handler_subnet_groups_test.go @@ -1,13 +1,14 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -104,7 +105,7 @@ func TestHandlerSubnetGroups(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -126,7 +127,7 @@ func TestHandlerSubnetGroupSubnetHasSupportedNetworkTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sg := resp["SubnetGroup"].(map[string]any) subnets := sg["Subnets"].([]any) require.Len(t, subnets, 1) diff --git a/services/dax/handler_tags.go b/services/dax/handler_tags.go index 6bb5336ea3..0e4f4cdb01 100644 --- a/services/dax/handler_tags.go +++ b/services/dax/handler_tags.go @@ -1,10 +1,12 @@ package dax import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceRequest struct { @@ -24,7 +26,7 @@ type listTagsRequest struct { func (h *Handler) handleTagResource(body []byte) (any, error) { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -60,7 +62,7 @@ func (h *Handler) handleTagResource(body []byte) (any, error) { func (h *Handler) handleUntagResource(body []byte) (any, error) { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -87,7 +89,7 @@ func (h *Handler) handleUntagResource(body []byte) (any, error) { func (h *Handler) handleListTags(body []byte) (any, error) { var req listTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/dax/handler_tags_test.go b/services/dax/handler_tags_test.go index 8ff61accca..f96af9c0bc 100644 --- a/services/dax/handler_tags_test.go +++ b/services/dax/handler_tags_test.go @@ -1,7 +1,7 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -29,7 +30,7 @@ func TestHandlerTagResource(t *testing.T) { t.Helper() rec := daxRequest(t, h, "CreateCluster", validClusterBody("tagged-cluster")) var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["Cluster"].(map[string]any)["ClusterArn"].(string) }, @@ -72,14 +73,14 @@ func TestHandlerTagResourceRequiresTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) rec := daxRequest(t, h, "TagResource", map[string]any{"ResourceName": arn}) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["__type"]) } @@ -94,14 +95,14 @@ func TestHandlerUntagResourceRequiresTagKeys(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) rec := daxRequest(t, h, "UntagResource", map[string]any{"ResourceName": arn}) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["__type"]) } @@ -113,7 +114,7 @@ func TestHandlerListTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) tagRec := daxRequest(t, h, "TagResource", map[string]any{ @@ -126,7 +127,7 @@ func TestHandlerListTags(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) assert.NotEmpty(t, tags) } @@ -204,7 +205,7 @@ func TestHandlerCreateClusterTagsAsArray(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.checkTag != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) // The Cluster response carries no Tags field (real AWS has none on @@ -215,7 +216,7 @@ func TestHandlerCreateClusterTagsAsArray(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rawTags := listResp["Tags"].([]any) found := false for _, rt := range rawTags { @@ -287,7 +288,7 @@ func TestHandlerTagResourceValidation(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) rec := daxRequest(t, h, "TagResource", map[string]any{ @@ -300,7 +301,7 @@ func TestHandlerTagResourceValidation(t *testing.T) { } else { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantCode, errResp["__type"]) } }) @@ -317,7 +318,7 @@ func TestHandlerTagResourceQuotaExceeded(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) // Add 50 tags in two batches of 25. @@ -345,7 +346,7 @@ func TestHandlerTagResourceQuotaExceeded(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "TagQuotaPerResourceExceeded", errResp["__type"]) } @@ -359,7 +360,7 @@ func TestHandlerTagResourceReturnsAllTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) // Add first tag. @@ -377,7 +378,7 @@ func TestHandlerTagResourceReturnsAllTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) rawTags := resp["Tags"].([]any) assert.Len(t, rawTags, 2, "TagResource must return all tags on the resource, not just added ones") @@ -402,7 +403,7 @@ func TestHandlerUntagResourceReturnsRemainingTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Cluster"].(map[string]any)["ClusterArn"].(string) tagRec := daxRequest(t, h, "TagResource", map[string]any{ @@ -418,7 +419,7 @@ func TestHandlerUntagResourceReturnsRemainingTags(t *testing.T) { require.Equal(t, http.StatusOK, untagRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(untagRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(untagRec.Body.Bytes(), &resp, jsonx.V1())) rawTags, ok := resp["Tags"].([]any) require.True(t, ok, "UntagResource must return Tags in response") diff --git a/services/dax/handler_test.go b/services/dax/handler_test.go index 4fbde0373c..521e00da74 100644 --- a/services/dax/handler_test.go +++ b/services/dax/handler_test.go @@ -2,7 +2,8 @@ package dax_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -26,7 +28,7 @@ func daxRequest(t *testing.T, h *dax.Handler, target string, body any) *httptest var buf bytes.Buffer if body != nil { - require.NoError(t, json.NewEncoder(&buf).Encode(body)) + require.NoError(t, json.MarshalEncode(jsontext.NewEncoder(&buf, jsonx.V1()), body)) } req := httptest.NewRequest(http.MethodPost, "/", &buf) @@ -163,7 +165,7 @@ func TestHandlerErrorMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantCode, errResp["__type"]) }) } @@ -183,7 +185,7 @@ func TestHandlerCreateSubnetGroupNameNotFormatValidated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sg := resp["SubnetGroup"].(map[string]any) assert.Equal(t, "1sg--bad", sg["SubnetGroupName"]) } @@ -201,7 +203,7 @@ func TestHandlerReset(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) assert.Empty(t, clusters) } diff --git a/services/dax/handler_wire_shape_test.go b/services/dax/handler_wire_shape_test.go index c6014471a4..2a8b6583bd 100644 --- a/services/dax/handler_wire_shape_test.go +++ b/services/dax/handler_wire_shape_test.go @@ -1,7 +1,7 @@ package dax_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -25,7 +26,7 @@ func TestHandlerClusterStatusWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) assert.Equal(t, "creating", cluster["Status"], "wire key must be Status") @@ -48,7 +49,7 @@ func TestHandlerTimestampsAreEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) cluster := createResp["Cluster"].(map[string]any) nodes := cluster["Nodes"].([]any) @@ -63,7 +64,7 @@ func TestHandlerTimestampsAreEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, eventsRec.Code) var eventsResp map[string]any - require.NoError(t, json.Unmarshal(eventsRec.Body.Bytes(), &eventsResp)) + require.NoError(t, json.Unmarshal(eventsRec.Body.Bytes(), &eventsResp, jsonx.V1())) events := eventsResp["Events"].([]any) require.NotEmpty(t, events) @@ -93,7 +94,7 @@ func TestHandlerDescribeEventsAcceptsEpochSecondsRequest(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Events"], "a StartTime in the future should exclude all existing events") past := float64(time.Now().UTC().Add(-time.Hour).Unix()) @@ -104,7 +105,7 @@ func TestHandlerDescribeEventsAcceptsEpochSecondsRequest(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.NotEmpty(t, resp2["Events"], "a StartTime in the past should include existing events") } @@ -155,7 +156,7 @@ func TestHandlerParameterGroupNodeIdsToRebootWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) require.Len(t, clusters, 1) @@ -185,7 +186,7 @@ func TestHandlerClusterNetworkTypeWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) assert.Equal(t, "ipv4", cluster["NetworkType"]) @@ -211,7 +212,7 @@ func TestHandlerDecreaseReplicationFactorNodeIdsToRemoveWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) nodeIDs, ok := cluster["NodeIdsToRemove"].([]any) @@ -233,7 +234,7 @@ func TestHandlerSubnetGroupSupportedNetworkTypesWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sg := resp["SubnetGroup"].(map[string]any) types, ok := sg["SupportedNetworkTypes"].([]any) @@ -263,7 +264,7 @@ func TestHandlerDescribeParametersSourceFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) params := resp["Parameters"].([]any) require.Len(t, params, 1) diff --git a/services/dax/multi_region_test.go b/services/dax/multi_region_test.go index 98cc37b4ca..1bf2150652 100644 --- a/services/dax/multi_region_test.go +++ b/services/dax/multi_region_test.go @@ -2,7 +2,7 @@ package dax_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dax" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/dax/persistence.go b/services/dax/persistence.go index 4dfb239655..133a074a9e 100644 --- a/services/dax/persistence.go +++ b/services/dax/persistence.go @@ -2,11 +2,13 @@ package dax import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -37,7 +39,7 @@ const daxSnapshotVersion = 1 // (older or newer) build of this backend as though it were the current // shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -124,7 +126,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { b.mu.RLock("Snapshot") defer b.mu.RUnlock() - var tables map[string]json.RawMessage + var tables map[string]jsontext.Value tables, tblErr = b.registry.SnapshotAll() @@ -144,7 +146,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { return nil } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).ErrorContext(ctx, "dax: failed to marshal snapshot", "error", err) diff --git a/services/detective/handler_administrator.go b/services/detective/handler_administrator.go index a06fa50f95..0b8faa40b8 100644 --- a/services/detective/handler_administrator.go +++ b/services/detective/handler_administrator.go @@ -1,12 +1,13 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleStartMonitoringMember(c *echo.Context) error { @@ -20,7 +21,7 @@ func (h *Handler) handleStartMonitoringMember(c *echo.Context) error { AccountId string `json:"AccountId"` //nolint:revive,staticcheck // existing issue. } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -57,7 +58,7 @@ func (h *Handler) handleEnableOrganizationAdminAccount(c *echo.Context) error { AccountId string `json:"AccountId"` //nolint:revive,staticcheck // existing issue. } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -84,7 +85,7 @@ func (h *Handler) handleListOrganizationAdminAccounts(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } diff --git a/services/detective/handler_administrator_test.go b/services/detective/handler_administrator_test.go index 82d7560682..20b5725c1c 100644 --- a/services/detective/handler_administrator_test.go +++ b/services/detective/handler_administrator_test.go @@ -1,12 +1,14 @@ package detective_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetective_StartMonitoringMember(t *testing.T) { //nolint:paralleltest // existing issue. @@ -17,7 +19,7 @@ func TestDetective_StartMonitoringMember(t *testing.T) { //nolint:paralleltest / require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) // Add a member (status INVITED, not ACCEPTED_BUT_DISABLED). @@ -98,7 +100,7 @@ func TestOrganizationAdminAccounts(t *testing.T) { //nolint:paralleltest // exis check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) admins, ok := resp["Administrators"].([]any) require.True(t, ok) assert.Empty(t, admins) @@ -134,7 +136,7 @@ func TestOrganizationAdminAccounts(t *testing.T) { //nolint:paralleltest // exis check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) admins, ok := resp["Administrators"].([]any) require.True(t, ok) require.Len(t, admins, 1) @@ -158,7 +160,7 @@ func TestOrganizationAdminAccounts(t *testing.T) { //nolint:paralleltest // exis check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) admins, ok := resp["Administrators"].([]any) require.True(t, ok) assert.Empty(t, admins) diff --git a/services/detective/handler_datasource_packages.go b/services/detective/handler_datasource_packages.go index 5ae981c4c4..89ec997576 100644 --- a/services/detective/handler_datasource_packages.go +++ b/services/detective/handler_datasource_packages.go @@ -1,13 +1,14 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ingestHistoryToJSON encodes a MembershipDatasources.IngestHistory map to @@ -45,7 +46,7 @@ func (h *Handler) handleBatchGetGraphMemberDatasources(c *echo.Context) error { AccountIds []string `json:"AccountIds"` //nolint:revive // existing issue. } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -83,7 +84,7 @@ func (h *Handler) handleBatchGetMembershipDatasources(c *echo.Context) error { GraphArns []string `json:"GraphArns"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -127,7 +128,7 @@ func (h *Handler) handleListDatasourcePackages(c *echo.Context) error { MaxResults int32 `json:"MaxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -169,7 +170,7 @@ func (h *Handler) handleUpdateDatasourcePackages(c *echo.Context) error { DatasourcePackages []string `json:"DatasourcePackages"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/detective/handler_datasource_packages_test.go b/services/detective/handler_datasource_packages_test.go index 25f581746b..7982869ca7 100644 --- a/services/detective/handler_datasource_packages_test.go +++ b/services/detective/handler_datasource_packages_test.go @@ -2,12 +2,14 @@ package detective_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing issue. @@ -18,7 +20,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) // Add a member. @@ -46,7 +48,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) pkgs, ok := resp["DatasourcePackages"].(map[string]any) require.True(t, ok) assert.Contains(t, pkgs, "DETECTIVE_CORE") @@ -69,7 +71,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) pkgs, ok := resp["DatasourcePackages"].(map[string]any) require.True(t, ok) assert.Contains(t, pkgs, "DETECTIVE_CORE") @@ -84,7 +86,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) members, ok := resp["MemberDatasources"].([]any) require.True(t, ok) assert.Len(t, members, 1) @@ -99,7 +101,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedAccounts"].([]any) require.True(t, ok) assert.Len(t, unprocessed, 1) @@ -114,7 +116,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) members, ok := resp["MembershipDatasources"].([]any) require.True(t, ok) assert.Len(t, members, 1) @@ -133,7 +135,7 @@ func TestDetective_Datasources(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedGraphs"].([]any) require.True(t, ok) assert.Len(t, unprocessed, 1) @@ -195,7 +197,7 @@ func TestUpdateDatasourcePackages_InvalidPackageRejected(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/graph", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var gResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp, jsonx.V1())) graphARN := gResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/datasources/update", map[string]any{ @@ -223,7 +225,7 @@ func TestBatchGetGraphMemberDatasources_WireShape(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/graph", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var gResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp, jsonx.V1())) graphARN := gResp["GraphArn"].(string) memberRec := doRequest(t, h, http.MethodPost, "/graph/members", map[string]any{ @@ -247,7 +249,7 @@ func TestBatchGetGraphMemberDatasources_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) members, ok := resp["MemberDatasources"].([]any) require.True(t, ok) require.Len(t, members, 1) @@ -280,7 +282,7 @@ func TestListDatasourcePackagesOpaqueToken(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/graph", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var gResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gResp, jsonx.V1())) graphARN := gResp["GraphArn"].(string) updateRec := doRequest(t, h, http.MethodPost, "/graph/datasources/update", map[string]any{ @@ -295,7 +297,7 @@ func TestListDatasourcePackagesOpaqueToken(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tok, hasTok := resp["NextToken"].(string) require.True(t, hasTok, "NextToken must be present when more results exist") @@ -310,7 +312,7 @@ func TestListDatasourcePackagesOpaqueToken(t *testing.T) { }) require.Equal(t, http.StatusOK, rec3.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp2, jsonx.V1())) _, hasTok2 := resp2["NextToken"] assert.False(t, hasTok2, "NextToken must be absent on the last page") } diff --git a/services/detective/handler_graphs.go b/services/detective/handler_graphs.go index 3aed84dce6..6079dfdfe4 100644 --- a/services/detective/handler_graphs.go +++ b/services/detective/handler_graphs.go @@ -1,12 +1,13 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateGraph(c *echo.Context) error { @@ -20,7 +21,7 @@ func (h *Handler) handleCreateGraph(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } @@ -45,7 +46,7 @@ func (h *Handler) handleDeleteGraph(c *echo.Context) error { GraphArn string `json:"GraphArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -72,7 +73,7 @@ func (h *Handler) handleListGraphs(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } diff --git a/services/detective/handler_graphs_test.go b/services/detective/handler_graphs_test.go index 94749787c4..568002faee 100644 --- a/services/detective/handler_graphs_test.go +++ b/services/detective/handler_graphs_test.go @@ -2,7 +2,7 @@ package detective_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/detective" ) @@ -32,7 +33,7 @@ func TestDetective_Graph(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Contains(t, resp["GraphArn"], "arn:aws:detective:") }, }, @@ -48,7 +49,7 @@ func TestDetective_Graph(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Contains(t, resp["GraphArn"], "arn:aws:detective:") }, }, @@ -86,7 +87,7 @@ func TestDetective_Graph(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) list, ok := resp["GraphList"].([]any) require.True(t, ok) assert.Empty(t, list) @@ -104,7 +105,7 @@ func TestDetective_Graph(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) list, ok := resp["GraphList"].([]any) require.True(t, ok) assert.Len(t, list, 1) @@ -125,7 +126,7 @@ func TestDetective_Graph(t *testing.T) { //nolint:paralleltest // existing issue // get graphArn from listing listRec := doRequest(t, h, http.MethodPost, "/graphs/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) list := listResp["GraphList"].([]any) if len(list) > 0 { graph := list[0].(map[string]any) @@ -158,7 +159,7 @@ func TestGraphARN_Shape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["GraphArn"].(string) require.NotEmpty(t, arn, "CreateGraph must return a GraphArn") @@ -187,7 +188,7 @@ func TestListGraphs_CreatedTime_Present(t *testing.T) { } `json:"GraphList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.GraphList, 1) assert.NotEmpty(t, resp.GraphList[0].CreatedTime, "ListGraphs must include CreatedTime") assert.Contains(t, resp.GraphList[0].CreatedTime, "T", @@ -209,7 +210,7 @@ func TestListGraphs_Pagination_NextToken_Absent_When_All_Fit(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasToken := resp["NextToken"] assert.False(t, hasToken, "NextToken must be absent when all results fit on one page") } diff --git a/services/detective/handler_investigations.go b/services/detective/handler_investigations.go index 0d6eea760f..3146a8654c 100644 --- a/services/detective/handler_investigations.go +++ b/services/detective/handler_investigations.go @@ -1,12 +1,13 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleStartInvestigation(c *echo.Context) error { @@ -22,7 +23,7 @@ func (h *Handler) handleStartInvestigation(c *echo.Context) error { ScopeEndTime string `json:"ScopeEndTime"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -73,7 +74,7 @@ func (h *Handler) handleGetInvestigation(c *echo.Context) error { InvestigationId string `json:"InvestigationId"` //nolint:revive,staticcheck // existing issue. } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -130,7 +131,7 @@ func (h *Handler) handleListInvestigations(c *echo.Context) error { MaxResults int32 `json:"MaxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -203,7 +204,7 @@ func (h *Handler) handleUpdateInvestigationState(c *echo.Context) error { State string `json:"State"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -240,7 +241,7 @@ func (h *Handler) handleListIndicators(c *echo.Context) error { MaxResults int32 `json:"MaxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/detective/handler_investigations_test.go b/services/detective/handler_investigations_test.go index 32ac23e60c..6721542c04 100644 --- a/services/detective/handler_investigations_test.go +++ b/services/detective/handler_investigations_test.go @@ -2,12 +2,14 @@ package detective_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetective_Investigations(t *testing.T) { //nolint:paralleltest // existing issue. @@ -18,7 +20,7 @@ func TestDetective_Investigations(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) unknownArn := missingGraphARN @@ -79,7 +81,7 @@ func TestDetective_Investigations(t *testing.T) { //nolint:paralleltest // exist check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["InvestigationId"]) }, }, @@ -92,7 +94,7 @@ func TestDetective_Investigations(t *testing.T) { //nolint:paralleltest // exist check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) details, ok := resp["InvestigationDetails"].([]any) require.True(t, ok) assert.Len(t, details, 1) @@ -183,7 +185,7 @@ func TestDetective_InvestigationGetAndUpdate(t *testing.T) { //nolint:parallelte require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) // Start investigation. @@ -196,7 +198,7 @@ func TestDetective_InvestigationGetAndUpdate(t *testing.T) { //nolint:parallelte require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) invID := startResp["InvestigationId"].(string) require.NotEmpty(t, invID) @@ -217,7 +219,7 @@ func TestDetective_InvestigationGetAndUpdate(t *testing.T) { //nolint:parallelte check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, invID, resp["InvestigationId"]) assert.Equal(t, graphArn, resp["GraphArn"]) assert.Equal(t, "arn:aws:iam::000000000000:user/alice", resp["EntityArn"]) @@ -241,7 +243,7 @@ func TestDetective_InvestigationGetAndUpdate(t *testing.T) { //nolint:parallelte check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ARCHIVED", resp["State"]) }, }, @@ -254,7 +256,7 @@ func TestDetective_InvestigationGetAndUpdate(t *testing.T) { //nolint:parallelte check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) indicators, ok := resp["Indicators"].([]any) require.True(t, ok) assert.NotEmpty(t, indicators, "ListIndicators should return indicators for an investigation") diff --git a/services/detective/handler_members.go b/services/detective/handler_members.go index 56dc17a52e..3d43acc8ec 100644 --- a/services/detective/handler_members.go +++ b/services/detective/handler_members.go @@ -1,12 +1,13 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateMembers(c *echo.Context) error { @@ -24,7 +25,7 @@ func (h *Handler) handleCreateMembers(c *echo.Context) error { } `json:"Accounts"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -62,7 +63,7 @@ func (h *Handler) handleDeleteMembers(c *echo.Context) error { AccountIDs []string `json:"AccountIds"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -92,7 +93,7 @@ func (h *Handler) handleGetMembers(c *echo.Context) error { AccountIDs []string `json:"AccountIds"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -123,7 +124,7 @@ func (h *Handler) handleListMembers(c *echo.Context) error { MaxResults int32 `json:"MaxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -156,7 +157,7 @@ func (h *Handler) handleAcceptInvitation(c *echo.Context) error { GraphArn string `json:"GraphArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -181,7 +182,7 @@ func (h *Handler) handleRejectInvitation(c *echo.Context) error { GraphArn string `json:"GraphArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -208,7 +209,7 @@ func (h *Handler) handleListInvitations(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } @@ -238,7 +239,7 @@ func (h *Handler) handleDisassociateMembership(c *echo.Context) error { GraphArn string `json:"GraphArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/detective/handler_members_test.go b/services/detective/handler_members_test.go index c9cae3d32a..41085827d9 100644 --- a/services/detective/handler_members_test.go +++ b/services/detective/handler_members_test.go @@ -2,13 +2,15 @@ package detective_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetective_Members(t *testing.T) { //nolint:paralleltest // existing issue. @@ -96,7 +98,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) require.NotEmpty(t, graphArn) @@ -111,7 +113,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi require.Equal(t, http.StatusOK, rec.Code) var createMembersResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createMembersResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createMembersResp, jsonx.V1())) members := createMembersResp["Members"].([]any) assert.Len(t, members, 2) @@ -122,7 +124,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) memberDetails := listResp["MemberDetails"].([]any) assert.Len(t, memberDetails, 2) @@ -134,7 +136,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) gotMembers := getResp["MemberDetails"].([]any) assert.Len(t, gotMembers, 1) unprocessed := getResp["UnprocessedAccounts"].([]any) @@ -148,7 +150,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp, jsonx.V1())) deleted := deleteResp["AccountIds"].([]any) assert.Len(t, deleted, 1) @@ -158,7 +160,7 @@ func TestDetective_MembersLifecycle(t *testing.T) { //nolint:paralleltest // exi }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) memberDetails = listResp["MemberDetails"].([]any) assert.Len(t, memberDetails, 1) } @@ -284,7 +286,7 @@ func TestListMembers_Pagination_NextToken_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) accounts := make([]any, 3) @@ -308,7 +310,7 @@ func TestListMembers_Pagination_NextToken_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page1, jsonx.V1())) memberList1, _ := page1["MemberDetails"].([]any) assert.Len(t, memberList1, 2, "MaxResults=2 must return exactly 2 members") nextToken, hasToken := page1["NextToken"].(string) @@ -321,7 +323,7 @@ func TestListMembers_Pagination_NextToken_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var all map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &all)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &all, jsonx.V1())) _, hasAllToken := all["NextToken"] assert.False(t, hasAllToken, "NextToken must be absent when all members fit") } @@ -339,7 +341,7 @@ func TestMemberDetail_MasterId_Equals_AdministratorId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/members", map[string]any{ @@ -351,7 +353,7 @@ func TestMemberDetail_MasterId_Equals_AdministratorId(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var membersResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &membersResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &membersResp, jsonx.V1())) members, _ := membersResp["Members"].([]any) require.Len(t, members, 1) @@ -376,7 +378,7 @@ func TestMemberDetail_AllRequiredFields_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/members", map[string]any{ @@ -388,7 +390,7 @@ func TestMemberDetail_AllRequiredFields_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var membersResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &membersResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &membersResp, jsonx.V1())) members, _ := membersResp["Members"].([]any) require.Len(t, members, 1) @@ -424,7 +426,7 @@ func TestMemberDetail_DatasourcePackageIngestStates_MirrorsGraph(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/members", map[string]any{ @@ -448,7 +450,7 @@ func TestMemberDetail_DatasourcePackageIngestStates_MirrorsGraph(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) details, _ := getResp["MemberDetails"].([]any) require.Len(t, details, 1) @@ -471,7 +473,7 @@ func TestDeleteMembers_AllUnprocessed_AccountIds_Empty_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/members/removal", map[string]any{ @@ -481,7 +483,7 @@ func TestDeleteMembers_AllUnprocessed_AccountIds_Empty_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) accountIDs, hasKey := resp["AccountIds"] assert.True(t, hasKey, "DeleteMembers response must include AccountIds") @@ -510,7 +512,7 @@ func TestCreateMembers_UnprocessedAccounts_Empty_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/graph/members", map[string]any{ @@ -522,7 +524,7 @@ func TestCreateMembers_UnprocessedAccounts_Empty_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) unprocessed, hasKey := resp["UnprocessedAccounts"] assert.True(t, hasKey, "CreateMembers must always include UnprocessedAccounts") @@ -591,7 +593,7 @@ func TestDetective_Invitations(t *testing.T) { //nolint:paralleltest // existing require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) // Invite account 000000000000 (the backend's own account) will fail; use another. @@ -621,7 +623,7 @@ func TestDetective_Invitations(t *testing.T) { //nolint:paralleltest // existing check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) invitations, ok := resp["Invitations"].([]any) require.True(t, ok) assert.Empty(t, invitations) @@ -700,7 +702,7 @@ func TestDetective_InvitationLifecycle(t *testing.T) { //nolint:paralleltest // require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) // Invite the backend's own account ID so it can accept/reject via the same backend. @@ -724,7 +726,7 @@ func TestDetective_InvitationLifecycle(t *testing.T) { //nolint:paralleltest // require.Equal(t, http.StatusOK, rec3.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &getResp, jsonx.V1())) details := getResp["MemberDetails"].([]any) require.Len(t, details, 1) member := details[0].(map[string]any) diff --git a/services/detective/handler_organization.go b/services/detective/handler_organization.go index 34a8e9249c..f68afd99c8 100644 --- a/services/detective/handler_organization.go +++ b/services/detective/handler_organization.go @@ -1,12 +1,13 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeOrganizationConfiguration(c *echo.Context) error { @@ -19,7 +20,7 @@ func (h *Handler) handleDescribeOrganizationConfiguration(c *echo.Context) error GraphArn string `json:"GraphArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -48,7 +49,7 @@ func (h *Handler) handleUpdateOrganizationConfiguration(c *echo.Context) error { AutoEnable bool `json:"AutoEnable"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/detective/handler_organization_test.go b/services/detective/handler_organization_test.go index 73a2510b62..f8c1b138b4 100644 --- a/services/detective/handler_organization_test.go +++ b/services/detective/handler_organization_test.go @@ -1,12 +1,14 @@ package detective_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestOrganizationConfiguration covers the DescribeOrganizationConfiguration / @@ -22,7 +24,7 @@ func TestOrganizationConfiguration(t *testing.T) { //nolint:paralleltest // exis require.Equal(t, http.StatusOK, rec.Code) var graphResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &graphResp, jsonx.V1())) graphArn := graphResp["GraphArn"].(string) unknownArn := missingGraphARN @@ -58,7 +60,7 @@ func TestOrganizationConfiguration(t *testing.T) { //nolint:paralleltest // exis check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, false, resp["AutoEnable"]) }, }, @@ -92,7 +94,7 @@ func TestOrganizationConfiguration(t *testing.T) { //nolint:paralleltest // exis check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, true, resp["AutoEnable"]) }, }, diff --git a/services/detective/handler_tags.go b/services/detective/handler_tags.go index 519704798f..7eb5b4874f 100644 --- a/services/detective/handler_tags.go +++ b/services/detective/handler_tags.go @@ -1,13 +1,14 @@ package detective import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleTagResource(c *echo.Context) error { @@ -25,7 +26,7 @@ func (h *Handler) handleTagResource(c *echo.Context) error { Tags map[string]string `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/detective/handler_tags_test.go b/services/detective/handler_tags_test.go index 7703213b9e..977e37b52f 100644 --- a/services/detective/handler_tags_test.go +++ b/services/detective/handler_tags_test.go @@ -1,7 +1,7 @@ package detective_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/detective" ) @@ -20,7 +21,7 @@ func TestDetective_Tags(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) tests := []struct { @@ -39,7 +40,7 @@ func TestDetective_Tags(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) tags, ok := resp["Tags"].(map[string]any) require.True(t, ok) assert.Empty(t, tags) @@ -67,7 +68,7 @@ func TestDetective_Tags(t *testing.T) { //nolint:paralleltest // existing issue. tagRec := doRequest(t, h, http.MethodGet, "/tags/"+graphArn, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].(map[string]any) assert.Equal(t, "myval", tags["mykey"]) }, @@ -237,7 +238,7 @@ func TestTagResource_EmptyTags_Succeeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodPost, "/tags/"+graphArn, map[string]any{ @@ -259,7 +260,7 @@ func TestUntagResource_NonExistentKey_Succeeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) req := doRequest(t, h, http.MethodDelete, "/tags/"+graphArn+"?tagKeys=does-not-exist", nil) @@ -279,14 +280,14 @@ func TestListTagsForResource_NoTags_Returns_EmptyObject(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodGet, "/tags/"+graphArn, nil) require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tags, hasKey := resp["Tags"] assert.True(t, hasKey, "ListTagsForResource must include Tags field") @@ -309,14 +310,14 @@ func TestCreateGraph_Tags_VisibleInListTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) graphArn := createResp["GraphArn"].(string) rec2 := doRequest(t, h, http.MethodGet, "/tags/"+graphArn, nil) require.Equal(t, http.StatusOK, rec2.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp, jsonx.V1())) tags, _ := tagsResp["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"], "tags set at graph creation must be visible via ListTagsForResource") diff --git a/services/detective/handler_test.go b/services/detective/handler_test.go index c6cab5fab4..9f7ca6a986 100644 --- a/services/detective/handler_test.go +++ b/services/detective/handler_test.go @@ -2,7 +2,7 @@ package detective_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/detective" ) @@ -33,7 +34,7 @@ func doRequest(t *testing.T, h *detective.Handler, method, path string, body any if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -53,7 +54,7 @@ func doRequest(t *testing.T, h *detective.Handler, method, path string, body any func parseJSON(t *testing.T, data []byte, v any) { t.Helper() - require.NoError(t, json.Unmarshal(data, v)) + require.NoError(t, json.Unmarshal(data, v, jsonx.V1())) } // TestDetectiveHandler_RouteMatcher verifies the RouteMatcher against every @@ -254,7 +255,7 @@ func TestDetective_UnknownOperation_Returns400(t *testing.T) { assert.Equal(t, tc.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidInputException", resp["__type"]) }) } diff --git a/services/detective/persistence.go b/services/detective/persistence.go index 8a95e88bc7..668d507b29 100644 --- a/services/detective/persistence.go +++ b/services/detective/persistence.go @@ -2,7 +2,7 @@ package detective import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -31,7 +31,7 @@ const detectiveSnapshotVersion = 1 // order-sensitive slice; all four are carried directly, matching the // pre-Phase-3.3 snapshot shape for those fields. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` Datasources map[string]map[string]string `json:"datasources"` // DatasourceChangedAt is additive (gopherstack-c902): graphARN -> package diff --git a/services/detective/whitebox_test.go b/services/detective/whitebox_test.go index e357f23e18..ac0641862d 100644 --- a/services/detective/whitebox_test.go +++ b/services/detective/whitebox_test.go @@ -3,7 +3,7 @@ package detective import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body any) *httptest.ResponseRecorder { @@ -22,7 +24,7 @@ func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body any) if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -87,7 +89,7 @@ func TestListInvitationsOpaqueToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tok, hasTok := resp["NextToken"].(string) require.True(t, hasTok, "NextToken must be present when more results exist") @@ -105,7 +107,7 @@ func TestListInvitationsOpaqueToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) invitations, ok := resp2["Invitations"].([]any) require.True(t, ok) assert.Len(t, invitations, 1, "second page must return the remaining invitation") diff --git a/services/directconnect/cloudformation.go b/services/directconnect/cloudformation.go index 92630c6d47..f0a3f4a10a 100644 --- a/services/directconnect/cloudformation.go +++ b/services/directconnect/cloudformation.go @@ -2,7 +2,8 @@ package directconnect import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -11,6 +12,8 @@ import ( "strconv" "strings" "unicode" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // CloudFormation resource types provisioned through CreateCFNResource. @@ -94,15 +97,14 @@ func coerceNumbers(m map[string]any, keys ...string) { } func decodeStrict(m map[string]any, out any) error { - raw, err := json.Marshal(lowerKeys(m)) + raw, err := json.Marshal(lowerKeys(m), jsonx.V1()) if err != nil { return clientError(err.Error()) } - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.DisallowUnknownFields() + dec := jsontext.NewDecoder(bytes.NewReader(raw)) - if err = dec.Decode(out); err != nil { + if err = json.UnmarshalDecode(dec, out, jsonx.V1(), json.RejectUnknownMembers(true)); err != nil { return clientError("invalid properties: " + err.Error()) } diff --git a/services/directconnect/connections.go b/services/directconnect/connections.go index c98a389d53..b06177e054 100644 --- a/services/directconnect/connections.go +++ b/services/directconnect/connections.go @@ -1,10 +1,11 @@ package directconnect import ( - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -511,7 +512,7 @@ func (b *InMemoryBackend) macSecSecretARNLocked(req *associateMacSecKeyRequest) return b.synthesizeMacSecSecretARN(id), nil } - payload, err := json.Marshal(map[string]string{"ckn": req.Ckn, "cak": req.Cak}) + payload, err := json.Marshal(map[string]string{"ckn": req.Ckn, "cak": req.Cak}, jsonx.V1()) if err != nil { return "", serverError(err.Error()) } diff --git a/services/directconnect/handler.go b/services/directconnect/handler.go index 266f6fdcf6..a8aaec871f 100644 --- a/services/directconnect/handler.go +++ b/services/directconnect/handler.go @@ -2,7 +2,8 @@ package directconnect import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -186,8 +188,8 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } - var m map[string]json.RawMessage - if unmarshalErr := json.Unmarshal(body, &m); unmarshalErr != nil { + var m map[string]jsontext.Value + if unmarshalErr := json.Unmarshal(body, &m, jsonx.V1()); unmarshalErr != nil { return "" } @@ -199,11 +201,11 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } // extractFirstResourceByKeys returns the first string resource found for the provided JSON field names. -func extractFirstResourceByKeys(body map[string]json.RawMessage, keys ...string) string { +func extractFirstResourceByKeys(body map[string]jsontext.Value, keys ...string) string { for _, key := range keys { if raw, ok := body[key]; ok { var resourceID string - if err := json.Unmarshal(raw, &resourceID); err == nil && resourceID != "" { + if err := json.Unmarshal(raw, &resourceID, jsonx.V1()); err == nil && resourceID != "" { return resourceID } } @@ -281,7 +283,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin } body := map[string]string{"__type": errType, "message": err.Error()} - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.1") @@ -316,7 +318,7 @@ func decodeBody[T any](body []byte) (*T, error) { var v T if len(body) > 0 { - if err := json.Unmarshal(body, &v); err != nil { + if err := json.Unmarshal(body, &v, jsonx.V1()); err != nil { return nil, clientError("malformed request body: " + err.Error()) } } @@ -328,7 +330,7 @@ func decodeBody[T any](body []byte) (*T, error) { // exception (marshaling our own well-typed wire structs should never // fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, serverError("directconnect: marshal response: " + err.Error()) } diff --git a/services/directconnect/persistence.go b/services/directconnect/persistence.go index 36e672ce18..64811d2667 100644 --- a/services/directconnect/persistence.go +++ b/services/directconnect/persistence.go @@ -2,7 +2,7 @@ package directconnect import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -20,11 +20,11 @@ const directConnectSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Direct Connect // backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` - NextASN int64 `json:"nextAmazonSideAsn"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` + NextASN int64 `json:"nextAmazonSideAsn"` } // Snapshot serialises the backend state to JSON. diff --git a/services/directoryservice/handler.go b/services/directoryservice/handler.go index 8bd4698f88..b527cf1866 100644 --- a/services/directoryservice/handler.go +++ b/services/directoryservice/handler.go @@ -2,7 +2,8 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "slices" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -367,7 +369,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -424,8 +426,8 @@ func (h *Handler) handleTwoFieldOp(c *echo.Context, op twoFieldOp) error { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid body")) } - var raw map[string]json.RawMessage - if jsonErr := json.Unmarshal(body, &raw); jsonErr != nil { + var raw map[string]jsontext.Value + if jsonErr := json.Unmarshal(body, &raw, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -447,14 +449,14 @@ func (h *Handler) handleTwoFieldOp(c *echo.Context, op twoFieldOp) error { // jsonString returns the string value stored under key in raw, or "" if absent // or not a JSON string. -func jsonString(raw map[string]json.RawMessage, key string) string { +func jsonString(raw map[string]jsontext.Value, key string) string { v, ok := raw[key] if !ok { return "" } var s string - if err := json.Unmarshal(v, &s); err != nil { + if err := json.Unmarshal(v, &s, jsonx.V1()); err != nil { return "" } diff --git a/services/directoryservice/handler_ad_assessments.go b/services/directoryservice/handler_ad_assessments.go index 8aa176f156..bb85d0941f 100644 --- a/services/directoryservice/handler_ad_assessments.go +++ b/services/directoryservice/handler_ad_assessments.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // startADAssessmentRequest mirrors the real StartADAssessmentInput, including @@ -43,7 +44,7 @@ func (h *Handler) handleStartADAssessment(c *echo.Context) error { var req startADAssessmentRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -117,7 +118,7 @@ func (h *Handler) handleDeleteADAssessment(c *echo.Context) error { AssessmentID string `json:"AssessmentId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -148,7 +149,7 @@ func (h *Handler) handleDescribeADAssessment(c *echo.Context) error { AssessmentID string `json:"AssessmentId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -246,7 +247,7 @@ func (h *Handler) handleListADAssessments(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_ad_assessments_test.go b/services/directoryservice/handler_ad_assessments_test.go index 214e66cca0..1cc19c8a71 100644 --- a/services/directoryservice/handler_ad_assessments_test.go +++ b/services/directoryservice/handler_ad_assessments_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestADAssessments(t *testing.T) { @@ -28,7 +30,7 @@ func TestADAssessments(t *testing.T) { rec1 := doRequest(t, h, "StartADAssessment", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) assessID, _ := r1["AssessmentId"].(string) assert.NotEmpty(t, assessID) @@ -36,7 +38,7 @@ func TestADAssessments(t *testing.T) { rec2 := doRequest(t, h, "ListADAssessments", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) assessments, _ := r2["Assessments"].([]any) assert.Len(t, assessments, 1) @@ -47,7 +49,7 @@ func TestADAssessments(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) assessment, _ := r3["Assessment"].(map[string]any) assert.Equal(t, assessID, assessment["AssessmentId"]) @@ -69,7 +71,7 @@ func TestADAssessments(t *testing.T) { rec5 := doRequest(t, h, "ListADAssessments", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec5.Code) var r5 map[string]any - require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5)) + require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5, jsonx.V1())) assessments2, _ := r5["Assessments"].([]any) assert.Empty(t, assessments2) @@ -138,7 +140,7 @@ func TestStartADAssessment_ConfigurationValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errBody["__type"]) }) } @@ -179,7 +181,7 @@ func TestStartADAssessment_ConfigurationRoundTrip(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assessID, _ := startResp["AssessmentId"].(string) require.NotEmpty(t, assessID) @@ -188,7 +190,7 @@ func TestStartADAssessment_ConfigurationRoundTrip(t *testing.T) { descRec := doRequest(t, h, "DescribeADAssessment", map[string]any{"AssessmentId": assessID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assessment, _ := descResp["Assessment"].(map[string]any) assert.NotContains(t, assessment, "DirectoryId") @@ -214,7 +216,7 @@ func TestStartADAssessment_ConfigurationRoundTrip(t *testing.T) { listRec := doRequest(t, h, "ListADAssessments", map[string]any{}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, _ := listResp["Assessments"].([]any) require.Len(t, summaries, 1) summary, _ := summaries[0].(map[string]any) diff --git a/services/directoryservice/handler_certificates.go b/services/directoryservice/handler_certificates.go index 9ff8d7c193..3b0af7ebff 100644 --- a/services/directoryservice/handler_certificates.go +++ b/services/directoryservice/handler_certificates.go @@ -2,13 +2,14 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleRegisterCertificate(c *echo.Context) error { @@ -26,7 +27,7 @@ func (h *Handler) handleRegisterCertificate(c *echo.Context) error { Type string `json:"Type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -86,7 +87,7 @@ func (h *Handler) handleListCertificates(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -135,7 +136,7 @@ func (h *Handler) handleDescribeCertificate(c *echo.Context) error { CertificateID string `json:"CertificateId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -179,7 +180,7 @@ func (h *Handler) handleEnableCAEnrollmentPolicy(c *echo.Context) error { PcaConnectorArn string `json:"PcaConnectorArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -210,7 +211,7 @@ func (h *Handler) handleDisableCAEnrollmentPolicy(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -235,7 +236,7 @@ func (h *Handler) handleDescribeCAEnrollmentPolicy(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_certificates_test.go b/services/directoryservice/handler_certificates_test.go index 61689d4c48..d1e5d15cdd 100644 --- a/services/directoryservice/handler_certificates_test.go +++ b/services/directoryservice/handler_certificates_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListCertificates_Pagination(t *testing.T) { @@ -73,7 +75,7 @@ func TestCertificates(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) certID, _ := r1["CertificateId"].(string) assert.NotEmpty(t, certID) @@ -81,7 +83,7 @@ func TestCertificates(t *testing.T) { rec2 := doRequest(t, h, "ListCertificates", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) certs, _ := r2["CertificatesInfo"].([]any) assert.Len(t, certs, 1) @@ -92,7 +94,7 @@ func TestCertificates(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) cert, _ := r3["Certificate"].(map[string]any) assert.Equal(t, certID, cert["CertificateId"]) assert.Equal(t, "test", cert["CommonName"], "CommonName must be parsed from the X.509 subject") @@ -108,7 +110,7 @@ func TestCertificates(t *testing.T) { rec5 := doRequest(t, h, "ListCertificates", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec5.Code) var r5 map[string]any - require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5)) + require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5, jsonx.V1())) certs2, _ := r5["CertificatesInfo"].([]any) assert.Empty(t, certs2) @@ -145,7 +147,7 @@ func TestCAEnrollmentPolicy(t *testing.T) { rec2 := doRequest(t, h, "DescribeCAEnrollmentPolicy", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) assert.Equal(t, "Success", r2["CaEnrollmentPolicyStatus"]) assert.Equal(t, connectorArn, r2["PcaConnectorArn"]) assert.Equal(t, dirID, r2["DirectoryId"]) @@ -158,7 +160,7 @@ func TestCAEnrollmentPolicy(t *testing.T) { rec4 := doRequest(t, h, "DescribeCAEnrollmentPolicy", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) assert.Equal(t, "Disabled", r4["CaEnrollmentPolicyStatus"]) assert.Equal(t, connectorArn, r4["PcaConnectorArn"]) diff --git a/services/directoryservice/handler_client_auth.go b/services/directoryservice/handler_client_auth.go index 84e82e6c20..1f6855cb08 100644 --- a/services/directoryservice/handler_client_auth.go +++ b/services/directoryservice/handler_client_auth.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleEnableClientAuthentication(c *echo.Context) error { //nolint:dupl // enable/disable pair. @@ -21,7 +22,7 @@ func (h *Handler) handleEnableClientAuthentication(c *echo.Context) error { //no Type string `json:"Type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -52,7 +53,7 @@ func (h *Handler) handleDisableClientAuthentication(c *echo.Context) error { //n Type string `json:"Type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -86,7 +87,7 @@ func (h *Handler) handleDescribeClientAuthenticationSettings(c *echo.Context) er } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_client_auth_test.go b/services/directoryservice/handler_client_auth_test.go index a6fb302eb1..ef0c57e76e 100644 --- a/services/directoryservice/handler_client_auth_test.go +++ b/services/directoryservice/handler_client_auth_test.go @@ -1,13 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -37,7 +38,7 @@ func TestClientAuthentication(t *testing.T) { rec2 := doRequest(t, h, "DescribeClientAuthenticationSettings", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) settings, _ := r2["ClientAuthenticationSettingsInfo"].([]any) require.Len(t, settings, 1) setting := settings[0].(map[string]any) @@ -125,7 +126,7 @@ func TestEnableClientAuthentication_Validation(t *testing.T) { }) assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidParameterException", body["__type"]) }) } diff --git a/services/directoryservice/handler_conditional_forwarders.go b/services/directoryservice/handler_conditional_forwarders.go index fcea9f04c6..dd397fe6b9 100644 --- a/services/directoryservice/handler_conditional_forwarders.go +++ b/services/directoryservice/handler_conditional_forwarders.go @@ -2,12 +2,13 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateConditionalForwarder(c *echo.Context) error { //nolint:dupl // existing issue. @@ -23,7 +24,7 @@ func (h *Handler) handleCreateConditionalForwarder(c *echo.Context) error { //no DNSIpv6Addrs []string `json:"DnsIpv6Addrs"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -60,7 +61,7 @@ func (h *Handler) handleUpdateConditionalForwarder(c *echo.Context) error { //no DNSIpv6Addrs []string `json:"DnsIpv6Addrs"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -105,7 +106,7 @@ func (h *Handler) handleDescribeConditionalForwarders(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_conditional_forwarders_test.go b/services/directoryservice/handler_conditional_forwarders_test.go index 287ac36c60..9e0d29bbbb 100644 --- a/services/directoryservice/handler_conditional_forwarders_test.go +++ b/services/directoryservice/handler_conditional_forwarders_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConditionalForwarder_Lifecycle(t *testing.T) { @@ -110,7 +112,7 @@ func TestConditionalForwarders(t *testing.T) { rec2 := doRequest(t, h, "DescribeConditionalForwarders", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) fwds, _ := r2["ConditionalForwarders"].([]any) require.Len(t, fwds, 1) fwd := fwds[0].(map[string]any) @@ -128,7 +130,7 @@ func TestConditionalForwarders(t *testing.T) { rec4 := doRequest(t, h, "DescribeConditionalForwarders", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) fwds2, _ := r4["ConditionalForwarders"].([]any) require.Len(t, fwds2, 1) fwd2 := fwds2[0].(map[string]any) @@ -146,7 +148,7 @@ func TestConditionalForwarders(t *testing.T) { rec6 := doRequest(t, h, "DescribeConditionalForwarders", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec6.Code) var r6 map[string]any - require.NoError(t, json.Unmarshal(rec6.Body.Bytes(), &r6)) + require.NoError(t, json.Unmarshal(rec6.Body.Bytes(), &r6, jsonx.V1())) fwds3, _ := r6["ConditionalForwarders"].([]any) assert.Empty(t, fwds3) diff --git a/services/directoryservice/handler_directories.go b/services/directoryservice/handler_directories.go index 9bbc9c6c65..0afadb6ba3 100644 --- a/services/directoryservice/handler_directories.go +++ b/services/directoryservice/handler_directories.go @@ -1,12 +1,13 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateDirectory(c *echo.Context) error { @@ -29,7 +30,7 @@ func (h *Handler) handleCreateDirectory(c *echo.Context) error { } `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -97,7 +98,7 @@ func (h *Handler) handleCreateMicrosoftAD(c *echo.Context) error { } `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -153,6 +154,7 @@ func (h *Handler) handleCreateMicrosoftAD(c *echo.Context) error { }) } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleDeleteDirectory(c *echo.Context) error { body, err := httputils.ReadBody(c.Request()) if err != nil { @@ -163,7 +165,7 @@ func (h *Handler) handleDeleteDirectory(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -193,7 +195,7 @@ func (h *Handler) handleDescribeDirectories(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -238,7 +240,7 @@ func (h *Handler) handleCreateAlias(c *echo.Context) error { Alias string `json:"Alias"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -292,7 +294,7 @@ func (h *Handler) handleCreateComputer(c *echo.Context) error { } `json:"ComputerAttributes"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -353,7 +355,7 @@ func (h *Handler) handleResetUserPassword(c *echo.Context) error { NewPassword string `json:"NewPassword"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -398,7 +400,7 @@ func (h *Handler) handleConnectDirectory(c *echo.Context) error { } `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_directories_extra_test.go b/services/directoryservice/handler_directories_extra_test.go index faf6b379be..481fcec993 100644 --- a/services/directoryservice/handler_directories_extra_test.go +++ b/services/directoryservice/handler_directories_extra_test.go @@ -1,7 +1,7 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -403,7 +404,7 @@ func TestCreateComputer(t *testing.T) { assert.Equal(t, tc.wantCode, rec.Code) if tc.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tc.check(t, resp) } }) @@ -492,7 +493,7 @@ func TestConnectDirectory(t *testing.T) { assert.Equal(t, tc.wantCode, rec.Code) if tc.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tc.check(t, resp) } }) diff --git a/services/directoryservice/handler_directories_test.go b/services/directoryservice/handler_directories_test.go index f849321392..7dda57343e 100644 --- a/services/directoryservice/handler_directories_test.go +++ b/services/directoryservice/handler_directories_test.go @@ -1,7 +1,7 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -33,7 +34,7 @@ func TestDirectoryService_CreateDirectory(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) id, ok := resp["DirectoryId"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -79,7 +80,7 @@ func TestDirectoryService_CreateMicrosoftAD(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) id, ok := resp["DirectoryId"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -115,7 +116,7 @@ func TestDirectoryService_DeleteDirectory(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "DeleteDirectory", map[string]any{"DirectoryId": dirID}) @@ -155,7 +156,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs, ok := resp["DirectoryDescriptions"].([]any) require.True(t, ok) assert.Len(t, dirs, 2) @@ -169,7 +170,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "DescribeDirectories", map[string]any{ @@ -178,7 +179,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs, ok := resp["DirectoryDescriptions"].([]any) require.True(t, ok) require.Len(t, dirs, 1) @@ -188,7 +189,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { rec = doRequest(t, h, "DescribeDirectories", map[string]any{ "DirectoryIds": []string{dirID}, }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs = resp["DirectoryDescriptions"].([]any) dir := dirs[0].(map[string]any) assert.Equal(t, dirID, dir["DirectoryId"]) @@ -214,7 +215,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { "Name": "stage.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "DescribeDirectories", map[string]any{"DirectoryIds": []string{dirID}}) @@ -259,7 +260,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { }, }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "DescribeDirectories", map[string]any{"DirectoryIds": []string{dirID}}) @@ -283,7 +284,7 @@ func TestDirectoryService_DescribeDirectories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs, ok := resp["DirectoryDescriptions"].([]any) require.True(t, ok) assert.Empty(t, dirs) @@ -426,7 +427,7 @@ func TestDirectoryService_CreateAlias(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "CreateAlias", map[string]any{ @@ -436,7 +437,7 @@ func TestDirectoryService_CreateAlias(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, dirID, resp["DirectoryId"]) assert.Equal(t, "myalias", resp["Alias"]) }) @@ -462,7 +463,7 @@ func TestDirectoryService_GetDirectoryLimits(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) limits, ok := resp["DirectoryLimits"].(map[string]any) require.True(t, ok) assert.EqualValues(t, 0, limits["CloudOnlyDirectoriesCurrentCount"]) @@ -479,7 +480,7 @@ func TestDirectoryService_GetDirectoryLimits(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) limits := resp["DirectoryLimits"].(map[string]any) assert.EqualValues(t, 1, limits["CloudOnlyDirectoriesCurrentCount"]) }) @@ -495,7 +496,7 @@ func TestDirectoryService_MicrosoftAD_DescribeReturnsType(t *testing.T) { "Edition": "Enterprise", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) rec := doRequest(t, h, "DescribeDirectories", map[string]any{ @@ -504,7 +505,7 @@ func TestDirectoryService_MicrosoftAD_DescribeReturnsType(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs := resp["DirectoryDescriptions"].([]any) require.Len(t, dirs, 1) dir := dirs[0].(map[string]any) @@ -520,7 +521,7 @@ func TestDirectoryService_DeleteDirectoryRemovesSnapshots(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) doRequest(t, h, "CreateSnapshot", map[string]any{"DirectoryId": dirID}) @@ -529,7 +530,7 @@ func TestDirectoryService_DeleteDirectoryRemovesSnapshots(t *testing.T) { descRec := doRequest(t, h, "DescribeSnapshots", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) snaps := descResp["Snapshots"].([]any) assert.Empty(t, snaps) } diff --git a/services/directoryservice/handler_domain_controllers.go b/services/directoryservice/handler_domain_controllers.go index 2be4fcd8e0..229d133ffb 100644 --- a/services/directoryservice/handler_domain_controllers.go +++ b/services/directoryservice/handler_domain_controllers.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeDomainControllers(c *echo.Context) error { @@ -24,7 +25,7 @@ func (h *Handler) handleDescribeDomainControllers(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -76,7 +77,7 @@ func (h *Handler) handleUpdateNumberOfDomainControllers(c *echo.Context) error { DesiredNumber int32 `json:"DesiredNumber"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_domain_controllers_test.go b/services/directoryservice/handler_domain_controllers_test.go index e17de7f22a..15615ceb48 100644 --- a/services/directoryservice/handler_domain_controllers_test.go +++ b/services/directoryservice/handler_domain_controllers_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDomainControllers_Lifecycle(t *testing.T) { @@ -128,7 +130,7 @@ func TestDomainControllers(t *testing.T) { rec1 := doRequest(t, h, "DescribeDomainControllers", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) dcs, _ := r1["DomainControllers"].([]any) assert.Empty(t, dcs) @@ -143,7 +145,7 @@ func TestDomainControllers(t *testing.T) { rec3 := doRequest(t, h, "DescribeDomainControllers", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) dcs2, _ := r3["DomainControllers"].([]any) assert.Len(t, dcs2, 2) diff --git a/services/directoryservice/handler_event_topics.go b/services/directoryservice/handler_event_topics.go index c5fcfea734..309efbae89 100644 --- a/services/directoryservice/handler_event_topics.go +++ b/services/directoryservice/handler_event_topics.go @@ -2,13 +2,14 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleRegisterEventTopic(c *echo.Context) error { @@ -41,7 +42,7 @@ func (h *Handler) handleDescribeEventTopics(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_event_topics_test.go b/services/directoryservice/handler_event_topics_test.go index 343c314b2e..4c588b6c76 100644 --- a/services/directoryservice/handler_event_topics_test.go +++ b/services/directoryservice/handler_event_topics_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDescribeEventTopics_Filtering(t *testing.T) { @@ -95,7 +97,7 @@ func TestEventTopics(t *testing.T) { rec2 := doRequest(t, h, "DescribeEventTopics", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) topics, _ := r2["EventTopics"].([]any) require.Len(t, topics, 1) topic := topics[0].(map[string]any) @@ -112,7 +114,7 @@ func TestEventTopics(t *testing.T) { rec4 := doRequest(t, h, "DescribeEventTopics", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) topics2, _ := r4["EventTopics"].([]any) assert.Empty(t, topics2) diff --git a/services/directoryservice/handler_hybrid_ad.go b/services/directoryservice/handler_hybrid_ad.go index b914b15405..bba1be3782 100644 --- a/services/directoryservice/handler_hybrid_ad.go +++ b/services/directoryservice/handler_hybrid_ad.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // hybridUpdateInfoEntryJSON builds the real types.HybridUpdateInfoEntry wire @@ -58,7 +59,7 @@ func (h *Handler) handleCreateHybridAD(c *echo.Context) error { } `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -96,7 +97,7 @@ func (h *Handler) handleUpdateHybridAD(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -157,7 +158,7 @@ func (h *Handler) handleDescribeHybridADUpdate(c *echo.Context) error { UpdateType string `json:"UpdateType"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_hybrid_ad_test.go b/services/directoryservice/handler_hybrid_ad_test.go index d89dd98c24..4063ab50ca 100644 --- a/services/directoryservice/handler_hybrid_ad_test.go +++ b/services/directoryservice/handler_hybrid_ad_test.go @@ -1,13 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -20,7 +21,7 @@ func mustStartAssessment(t *testing.T, h *directoryservice.Handler, dirID string require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assessmentID, _ := out["AssessmentId"].(string) require.NotEmpty(t, assessmentID) @@ -44,7 +45,7 @@ func TestHybridAD_CreateUpdateDescribeCycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) hybridDirID, _ := createOut["DirectoryId"].(string) require.NotEmpty(t, hybridDirID) assert.NotContains(t, createOut, "RequestId", "CreateHybridADOutput has no RequestId member") @@ -60,7 +61,7 @@ func TestHybridAD_CreateUpdateDescribeCycle(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) updateAssessmentID, _ := updateOut["AssessmentId"].(string) assert.NotEmpty(t, updateAssessmentID) assert.Equal(t, hybridDirID, updateOut["DirectoryId"]) @@ -73,7 +74,7 @@ func TestHybridAD_CreateUpdateDescribeCycle(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) activities, ok := descOut["UpdateActivities"].(map[string]any) require.True(t, ok, "UpdateActivities must be an object") @@ -102,7 +103,7 @@ func TestHybridAD_CreateUpdateDescribeCycle(t *testing.T) { require.Equal(t, http.StatusOK, describeDirRec.Code) var dirOut map[string]any - require.NoError(t, json.Unmarshal(describeDirRec.Body.Bytes(), &dirOut)) + require.NoError(t, json.Unmarshal(describeDirRec.Body.Bytes(), &dirOut, jsonx.V1())) dirs, ok := dirOut["DirectoryDescriptions"].([]any) require.True(t, ok) require.Len(t, dirs, 1) @@ -218,7 +219,7 @@ func TestHybridAD_UpdateHybridAD_Errors(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) hybridDirID, _ := createOut["DirectoryId"].(string) return map[string]any{"DirectoryId": hybridDirID} diff --git a/services/directoryservice/handler_ip_routes.go b/services/directoryservice/handler_ip_routes.go index f71c124de3..2899984731 100644 --- a/services/directoryservice/handler_ip_routes.go +++ b/services/directoryservice/handler_ip_routes.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddIpRoutes(c *echo.Context) error { //nolint:revive,staticcheck // existing issue. @@ -25,7 +26,7 @@ func (h *Handler) handleAddIpRoutes(c *echo.Context) error { //nolint:revive,sta } `json:"IpRoutes"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -57,7 +58,7 @@ func (h *Handler) handleRemoveIpRoutes(c *echo.Context) error { //nolint:revive, CidrIPv6s []string `json:"CidrIpv6s"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -86,7 +87,7 @@ func (h *Handler) handleListIpRoutes(c *echo.Context) error { //nolint:revive,st } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_ip_routes_test.go b/services/directoryservice/handler_ip_routes_test.go index 4f3be468b8..12e19b6e30 100644 --- a/services/directoryservice/handler_ip_routes_test.go +++ b/services/directoryservice/handler_ip_routes_test.go @@ -1,13 +1,15 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListIpRoutes_Pagination(t *testing.T) { @@ -70,7 +72,7 @@ func TestIpRoutes(t *testing.T) { rec2 := doRequest(t, h, "ListIpRoutes", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) routes, _ := r2["IpRoutesInfo"].([]any) assert.Len(t, routes, 1) @@ -85,7 +87,7 @@ func TestIpRoutes(t *testing.T) { rec4 := doRequest(t, h, "ListIpRoutes", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) routes2, _ := r4["IpRoutesInfo"].([]any) assert.Empty(t, routes2) diff --git a/services/directoryservice/handler_ldaps.go b/services/directoryservice/handler_ldaps.go index 810b552a92..e9d25e20e3 100644 --- a/services/directoryservice/handler_ldaps.go +++ b/services/directoryservice/handler_ldaps.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleEnableLDAPS(c *echo.Context) error { //nolint:dupl // existing issue. @@ -21,7 +22,7 @@ func (h *Handler) handleEnableLDAPS(c *echo.Context) error { //nolint:dupl // ex Type string `json:"Type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -55,7 +56,7 @@ func (h *Handler) handleDisableLDAPS(c *echo.Context) error { //nolint:dupl // e Type string `json:"Type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -92,7 +93,7 @@ func (h *Handler) handleDescribeLDAPSSettings(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_ldaps_test.go b/services/directoryservice/handler_ldaps_test.go index 58ed6915fc..a5bfc9d021 100644 --- a/services/directoryservice/handler_ldaps_test.go +++ b/services/directoryservice/handler_ldaps_test.go @@ -1,13 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -37,7 +38,7 @@ func TestLDAPS(t *testing.T) { rec2 := doRequest(t, h, "DescribeLDAPSSettings", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) settings, _ := r2["LDAPSSettingsInfo"].([]any) require.Len(t, settings, 1) setting := settings[0].(map[string]any) @@ -126,6 +127,6 @@ func TestEnableLDAPS_InvalidType(t *testing.T) { }) assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidParameterException", body["__type"]) } diff --git a/services/directoryservice/handler_log_subscriptions.go b/services/directoryservice/handler_log_subscriptions.go index fc868058fe..2eee4d9f44 100644 --- a/services/directoryservice/handler_log_subscriptions.go +++ b/services/directoryservice/handler_log_subscriptions.go @@ -2,13 +2,14 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateLogSubscription(c *echo.Context) error { @@ -30,7 +31,7 @@ func (h *Handler) handleDeleteLogSubscription(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -58,7 +59,7 @@ func (h *Handler) handleListLogSubscriptions(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_log_subscriptions_test.go b/services/directoryservice/handler_log_subscriptions_test.go index d1d786001e..28a7c91c2b 100644 --- a/services/directoryservice/handler_log_subscriptions_test.go +++ b/services/directoryservice/handler_log_subscriptions_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListLogSubscriptions_Pagination(t *testing.T) { @@ -58,7 +60,7 @@ func TestLogSubscriptions(t *testing.T) { rec2 := doRequest(t, h, "ListLogSubscriptions", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) subs, _ := r2["LogSubscriptions"].([]any) assert.Len(t, subs, 1) @@ -70,7 +72,7 @@ func TestLogSubscriptions(t *testing.T) { rec4 := doRequest(t, h, "ListLogSubscriptions", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) subs2, _ := r4["LogSubscriptions"].([]any) assert.Empty(t, subs2) diff --git a/services/directoryservice/handler_radius.go b/services/directoryservice/handler_radius.go index d4118e5f12..890627d7b9 100644 --- a/services/directoryservice/handler_radius.go +++ b/services/directoryservice/handler_radius.go @@ -1,12 +1,13 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type radiusRequest struct { @@ -32,7 +33,7 @@ func (h *Handler) handleEnableRadius(c *echo.Context) error { //nolint:dupl // e var req radiusRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -69,7 +70,7 @@ func (h *Handler) handleDisableRadius(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -92,7 +93,7 @@ func (h *Handler) handleUpdateRadius(c *echo.Context) error { //nolint:dupl // e var req radiusRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_regions.go b/services/directoryservice/handler_regions.go index 894bca4dfe..110ef3e152 100644 --- a/services/directoryservice/handler_regions.go +++ b/services/directoryservice/handler_regions.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddRegion(c *echo.Context) error { @@ -25,7 +26,7 @@ func (h *Handler) handleAddRegion(c *echo.Context) error { RegionName string `json:"RegionName"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -59,7 +60,7 @@ func (h *Handler) handleRemoveRegion(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -87,7 +88,7 @@ func (h *Handler) handleDescribeRegions(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_regions_test.go b/services/directoryservice/handler_regions_test.go index 02cef1a5da..f322f8da91 100644 --- a/services/directoryservice/handler_regions_test.go +++ b/services/directoryservice/handler_regions_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRegions(t *testing.T) { @@ -39,7 +41,7 @@ func TestRegions(t *testing.T) { rec2 := doRequest(t, h, "DescribeRegions", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) regions, _ := r2["RegionsDescription"].([]any) require.Len(t, regions, 1) region := regions[0].(map[string]any) @@ -57,7 +59,7 @@ func TestRegions(t *testing.T) { rec4 := doRequest(t, h, "DescribeRegions", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) regions2, _ := r4["RegionsDescription"].([]any) assert.Empty(t, regions2) @@ -78,6 +80,6 @@ func TestAddRegion_Validation(t *testing.T) { }) assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidParameterException", body["__type"]) } diff --git a/services/directoryservice/handler_schema_extensions.go b/services/directoryservice/handler_schema_extensions.go index 956cd803bf..bfc6d514e5 100644 --- a/services/directoryservice/handler_schema_extensions.go +++ b/services/directoryservice/handler_schema_extensions.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleStartSchemaExtension(c *echo.Context) error { @@ -23,7 +24,7 @@ func (h *Handler) handleStartSchemaExtension(c *echo.Context) error { CreateSnapshotBeforeSchemaExtension bool `json:"CreateSnapshotBeforeSchemaExtension"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -58,7 +59,7 @@ func (h *Handler) handleCancelSchemaExtension(c *echo.Context) error { SchemaExtensionID string `json:"SchemaExtensionId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -90,7 +91,7 @@ func (h *Handler) handleListSchemaExtensions(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_schema_extensions_test.go b/services/directoryservice/handler_schema_extensions_test.go index 1d6cb8a978..34bf0f0794 100644 --- a/services/directoryservice/handler_schema_extensions_test.go +++ b/services/directoryservice/handler_schema_extensions_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSchemaExtensions_StateLifecycle(t *testing.T) { @@ -155,7 +157,7 @@ func TestSchemaExtensions(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) extID, _ := r1["SchemaExtensionId"].(string) assert.NotEmpty(t, extID) @@ -163,7 +165,7 @@ func TestSchemaExtensions(t *testing.T) { rec2 := doRequest(t, h, "ListSchemaExtensions", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) exts, _ := r2["SchemaExtensionsInfo"].([]any) assert.Len(t, exts, 1) diff --git a/services/directoryservice/handler_settings.go b/services/directoryservice/handler_settings.go index c5a8462b51..5efa56a59e 100644 --- a/services/directoryservice/handler_settings.go +++ b/services/directoryservice/handler_settings.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Directory Data Access --- @@ -22,7 +23,7 @@ func (h *Handler) handleEnableDirectoryDataAccess(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -47,7 +48,7 @@ func (h *Handler) handleDisableDirectoryDataAccess(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -72,7 +73,7 @@ func (h *Handler) handleDescribeDirectoryDataAccess(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -115,7 +116,7 @@ func (h *Handler) handleUpdateSettings(c *echo.Context) error { } `json:"Settings"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -149,7 +150,7 @@ func (h *Handler) handleDescribeSettings(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -226,7 +227,7 @@ func (h *Handler) handleUpdateDirectorySetup(c *echo.Context) error { CreateSnapshotBeforeUpdate bool `json:"CreateSnapshotBeforeUpdate"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -285,7 +286,7 @@ func (h *Handler) handleDescribeUpdateDirectory(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_settings_test.go b/services/directoryservice/handler_settings_test.go index ca6827e12e..2c56a6ce24 100644 --- a/services/directoryservice/handler_settings_test.go +++ b/services/directoryservice/handler_settings_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSettings(t *testing.T) { @@ -31,14 +33,14 @@ func TestSettings(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) assert.Equal(t, dirID, r1["DirectoryId"]) // DescribeSettings rec2 := doRequest(t, h, "DescribeSettings", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) settings, _ := r2["SettingEntries"].([]any) require.Len(t, settings, 1) setting := settings[0].(map[string]any) @@ -79,7 +81,7 @@ func TestUpdateDirectorySetup(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) entries, _ := r2["UpdateActivities"].([]any) assert.Len(t, entries, 1) @@ -107,7 +109,7 @@ func TestDirectoryDataAccess(t *testing.T) { rec1 := doRequest(t, h, "DescribeDirectoryDataAccess", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) assert.Equal(t, "Disabled", r1["DataAccessStatus"]) // Enable @@ -118,7 +120,7 @@ func TestDirectoryDataAccess(t *testing.T) { rec3 := doRequest(t, h, "DescribeDirectoryDataAccess", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) assert.Equal(t, "Enabled", r3["DataAccessStatus"]) // Disable @@ -153,7 +155,7 @@ func TestUpdateDirectorySetup_Validation(t *testing.T) { }) assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidParameterException", body["__type"]) }) } diff --git a/services/directoryservice/handler_shared_directories.go b/services/directoryservice/handler_shared_directories.go index d32be60215..44247d338e 100644 --- a/services/directoryservice/handler_shared_directories.go +++ b/services/directoryservice/handler_shared_directories.go @@ -1,13 +1,14 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleShareDirectory(c *echo.Context) error { @@ -26,7 +27,7 @@ func (h *Handler) handleShareDirectory(c *echo.Context) error { } `json:"ShareTarget"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -71,7 +72,7 @@ func (h *Handler) handleUnshareDirectory(c *echo.Context) error { } `json:"UnshareTarget"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -101,7 +102,7 @@ func (h *Handler) handleAcceptSharedDirectory(c *echo.Context) error { SharedDirectoryID string `json:"SharedDirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -144,7 +145,7 @@ func (h *Handler) handleRejectSharedDirectory(c *echo.Context) error { SharedDirectoryID string `json:"SharedDirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -174,7 +175,7 @@ func (h *Handler) handleDescribeSharedDirectories(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } diff --git a/services/directoryservice/handler_shared_directories_test.go b/services/directoryservice/handler_shared_directories_test.go index bb860cba8e..cbdf119ab8 100644 --- a/services/directoryservice/handler_shared_directories_test.go +++ b/services/directoryservice/handler_shared_directories_test.go @@ -1,13 +1,15 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSharedDirectories(t *testing.T) { @@ -34,7 +36,7 @@ func TestSharedDirectories(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) sharedDirID, _ := r1["SharedDirectoryId"].(string) assert.NotEmpty(t, sharedDirID) @@ -45,7 +47,7 @@ func TestSharedDirectories(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) sd, _ := r2["SharedDirectory"].(map[string]any) require.NotNil(t, sd, "AcceptSharedDirectoryOutput.SharedDirectory must be present") assert.Equal(t, sharedDirID, sd["SharedDirectoryId"]) @@ -63,7 +65,7 @@ func TestSharedDirectories(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) dirs, _ := r3["SharedDirectories"].([]any) assert.Len(t, dirs, 1) @@ -80,7 +82,7 @@ func TestSharedDirectories(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) assert.Equal(t, sharedDirID, r2["SharedDirectoryId"]) } }) @@ -151,7 +153,7 @@ func TestShareDirectory_StatusLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, shareRec.Code) var shareResp map[string]any - require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp, jsonx.V1())) sharedID, _ := shareResp["SharedDirectoryId"].(string) require.NotEmpty(t, sharedID) diff --git a/services/directoryservice/handler_snapshots.go b/services/directoryservice/handler_snapshots.go index ec54062a96..bd52126636 100644 --- a/services/directoryservice/handler_snapshots.go +++ b/services/directoryservice/handler_snapshots.go @@ -1,12 +1,13 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateSnapshot(c *echo.Context) error { @@ -20,7 +21,7 @@ func (h *Handler) handleCreateSnapshot(c *echo.Context) error { Name string `json:"Name"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -38,6 +39,7 @@ func (h *Handler) handleCreateSnapshot(c *echo.Context) error { }) } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleDeleteSnapshot(c *echo.Context) error { body, err := httputils.ReadBody(c.Request()) if err != nil { @@ -48,7 +50,7 @@ func (h *Handler) handleDeleteSnapshot(c *echo.Context) error { SnapshotID string `json:"SnapshotId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -79,7 +81,7 @@ func (h *Handler) handleDescribeSnapshots(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -120,7 +122,7 @@ func (h *Handler) handleGetSnapshotLimits(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -152,7 +154,7 @@ func (h *Handler) handleRestoreFromSnapshot(c *echo.Context) error { SnapshotID string `json:"SnapshotId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_snapshots_test.go b/services/directoryservice/handler_snapshots_test.go index cf531f530e..1bb905fa7b 100644 --- a/services/directoryservice/handler_snapshots_test.go +++ b/services/directoryservice/handler_snapshots_test.go @@ -1,7 +1,7 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -21,7 +22,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["DirectoryId"].(string) } @@ -37,7 +38,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snapID, ok := resp["SnapshotId"].(string) require.True(t, ok) assert.NotEmpty(t, snapID) @@ -56,7 +57,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { dirID := createDir(h) snapRec := doRequest(t, h, "CreateSnapshot", map[string]any{"DirectoryId": dirID}) var snapResp map[string]any - require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp)) + require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp, jsonx.V1())) snapID := snapResp["SnapshotId"].(string) rec := doRequest(t, h, "DeleteSnapshot", map[string]any{"SnapshotId": snapID}) @@ -66,7 +67,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { descRec := doRequest(t, h, "DescribeSnapshots", map[string]any{"SnapshotIds": []string{snapID}}) assert.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) snaps := descResp["Snapshots"].([]any) assert.Empty(t, snaps) }) @@ -87,7 +88,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { "Name": "other.example.com", "Password": "Admin1234!", "Size": "Small", }) var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["DirectoryId"].(string) }() @@ -99,7 +100,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) assert.Len(t, snaps, 1) }) @@ -114,7 +115,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) limits := resp["SnapshotLimits"].(map[string]any) assert.EqualValues(t, 1, limits["ManualSnapshotsCurrentCount"]) }) @@ -125,7 +126,7 @@ func TestDirectoryService_Snapshots(t *testing.T) { dirID := createDir(h) snapRec := doRequest(t, h, "CreateSnapshot", map[string]any{"DirectoryId": dirID}) var snapResp map[string]any - require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp)) + require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp, jsonx.V1())) snapID := snapResp["SnapshotId"].(string) rec := doRequest(t, h, "RestoreFromSnapshot", map[string]any{"SnapshotId": snapID}) @@ -155,11 +156,11 @@ func TestRestoreFromSnapshot_TableDriven(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) snapRec := doRequest(t, h, "CreateSnapshot", map[string]any{"DirectoryId": dirID}) var snapResp map[string]any - require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp)) + require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp, jsonx.V1())) return snapResp["SnapshotId"].(string) }, @@ -179,11 +180,11 @@ func TestRestoreFromSnapshot_TableDriven(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) snapRec := doRequest(t, h, "CreateSnapshot", map[string]any{"DirectoryId": dirID}) var snapResp map[string]any - require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp)) + require.NoError(t, json.Unmarshal(snapRec.Body.Bytes(), &snapResp, jsonx.V1())) snapID := snapResp["SnapshotId"].(string) doRequest(t, h, "DeleteDirectory", map[string]any{"DirectoryId": dirID}) diff --git a/services/directoryservice/handler_sso.go b/services/directoryservice/handler_sso.go index 6d866e829c..2486e4ef0b 100644 --- a/services/directoryservice/handler_sso.go +++ b/services/directoryservice/handler_sso.go @@ -1,12 +1,13 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleEnableSso(c *echo.Context) error { @@ -19,7 +20,7 @@ func (h *Handler) handleEnableSso(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -44,7 +45,7 @@ func (h *Handler) handleDisableSso(c *echo.Context) error { DirectoryID string `json:"DirectoryId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_sso_test.go b/services/directoryservice/handler_sso_test.go index da13dcb679..d35f0d25c9 100644 --- a/services/directoryservice/handler_sso_test.go +++ b/services/directoryservice/handler_sso_test.go @@ -1,12 +1,13 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -18,7 +19,7 @@ func TestDirectoryService_SSO(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["DirectoryId"].(string) } diff --git a/services/directoryservice/handler_tags.go b/services/directoryservice/handler_tags.go index a156333d8e..13a4192efe 100644 --- a/services/directoryservice/handler_tags.go +++ b/services/directoryservice/handler_tags.go @@ -1,12 +1,13 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddTagsToResource(c *echo.Context) error { @@ -23,7 +24,7 @@ func (h *Handler) handleAddTagsToResource(c *echo.Context) error { } `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -51,7 +52,7 @@ func (h *Handler) handleRemoveTagsFromResource(c *echo.Context) error { TagKeys []string `json:"TagKeys"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -79,7 +80,7 @@ func (h *Handler) handleListTagsForResource(c *echo.Context) error { Limit int32 `json:"Limit"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_tags_test.go b/services/directoryservice/handler_tags_test.go index 40fbef0f9e..ff8ba23d1a 100644 --- a/services/directoryservice/handler_tags_test.go +++ b/services/directoryservice/handler_tags_test.go @@ -1,7 +1,7 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -20,7 +21,7 @@ func TestDirectoryService_Tags(t *testing.T) { "Name": "corp.example.com", "Password": "Admin1234!", "Size": "Small", }) var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) return resp["DirectoryId"].(string) } @@ -40,7 +41,7 @@ func TestDirectoryService_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) require.True(t, ok) require.Len(t, tags, 1) @@ -70,7 +71,7 @@ func TestDirectoryService_Tags(t *testing.T) { listRec := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceId": dirID}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "team", tags[0].(map[string]any)["Key"]) @@ -96,12 +97,12 @@ func TestDirectoryService_Tags(t *testing.T) { "Tags": []map[string]any{{"Key": "created", "Value": "yes"}}, }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) dirID := createResp["DirectoryId"].(string) listRec := doRequest(t, h, "ListTagsForResource", map[string]any{"ResourceId": dirID}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "created", tags[0].(map[string]any)["Key"]) diff --git a/services/directoryservice/handler_test.go b/services/directoryservice/handler_test.go index ca8950b562..35cff313ff 100644 --- a/services/directoryservice/handler_test.go +++ b/services/directoryservice/handler_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -62,7 +63,7 @@ func doRequest(t *testing.T, h *directoryservice.Handler, op string, body any) * if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -89,7 +90,7 @@ func mustCreateSimpleAD(t *testing.T, h *directoryservice.Handler, name string) }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, ok := resp["DirectoryId"].(string) require.True(t, ok) require.NotEmpty(t, id) @@ -107,7 +108,7 @@ func mustCreateMicrosoftAD(t *testing.T, h *directoryservice.Handler, name strin }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, ok := resp["DirectoryId"].(string) require.True(t, ok) require.NotEmpty(t, id) @@ -119,7 +120,7 @@ func mustCreateMicrosoftAD(t *testing.T, h *directoryservice.Handler, name strin func respBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/directoryservice/handler_trusts.go b/services/directoryservice/handler_trusts.go index d60c0a5c5e..44c189d5dd 100644 --- a/services/directoryservice/handler_trusts.go +++ b/services/directoryservice/handler_trusts.go @@ -1,7 +1,7 @@ package directoryservice import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/google/uuid" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateTrust(c *echo.Context) error { @@ -29,7 +30,7 @@ func (h *Handler) handleCreateTrust(c *echo.Context) error { ConditionalForwarderIPv6Addrs []string `json:"ConditionalForwarderIpv6Addrs"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -88,7 +89,7 @@ func (h *Handler) handleDeleteTrust(c *echo.Context) error { DeleteAssociatedConditionalForwarder bool `json:"DeleteAssociatedConditionalForwarder"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -120,7 +121,7 @@ func (h *Handler) handleDescribeTrusts(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } } @@ -179,7 +180,7 @@ func (h *Handler) handleUpdateTrust(c *echo.Context) error { SelectiveAuth string `json:"SelectiveAuth"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } @@ -211,7 +212,7 @@ func (h *Handler) handleVerifyTrust(c *echo.Context) error { TrustID string `json:"TrustId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errResp("ClientException", "invalid JSON")) } diff --git a/services/directoryservice/handler_trusts_test.go b/services/directoryservice/handler_trusts_test.go index 0f035f6946..073dfbea56 100644 --- a/services/directoryservice/handler_trusts_test.go +++ b/services/directoryservice/handler_trusts_test.go @@ -1,7 +1,7 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/directoryservice" ) @@ -240,7 +241,7 @@ func TestTrusts(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) trustID, _ := r1["TrustId"].(string) assert.NotEmpty(t, trustID) @@ -248,7 +249,7 @@ func TestTrusts(t *testing.T) { rec2 := doRequest(t, h, "DescribeTrusts", map[string]any{"DirectoryId": dirID}) assert.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) trusts, _ := r2["Trusts"].([]any) require.Len(t, trusts, 1) trust := trusts[0].(map[string]any) @@ -261,27 +262,27 @@ func TestTrusts(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec3.Code) var r3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &r3, jsonx.V1())) assert.Equal(t, trustID, r3["TrustId"]) // Verify rec4 := doRequest(t, h, "VerifyTrust", map[string]any{"TrustId": trustID}) assert.Equal(t, http.StatusOK, rec4.Code) var r4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &r4, jsonx.V1())) assert.Equal(t, trustID, r4["TrustId"]) // Delete rec5 := doRequest(t, h, "DeleteTrust", map[string]any{"TrustId": trustID}) assert.Equal(t, http.StatusOK, rec5.Code) var r5 map[string]any - require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5)) + require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &r5, jsonx.V1())) assert.Equal(t, trustID, r5["TrustId"]) require.Eventually(t, func() bool { var r6 map[string]any rec6 := doRequest(t, h, "DescribeTrusts", map[string]any{"DirectoryId": dirID}) - require.NoError(t, json.Unmarshal(rec6.Body.Bytes(), &r6)) + require.NoError(t, json.Unmarshal(rec6.Body.Bytes(), &r6, jsonx.V1())) trusts2, _ := r6["Trusts"].([]any) return rec6.Code == http.StatusOK && len(trusts2) == 0 diff --git a/services/directoryservice/id_format_test.go b/services/directoryservice/id_format_test.go index d7846cba1c..579db14509 100644 --- a/services/directoryservice/id_format_test.go +++ b/services/directoryservice/id_format_test.go @@ -1,12 +1,14 @@ package directoryservice_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // realIDPattern matches terraform-provider-aws's client-side ValidateFunc for @@ -35,7 +37,7 @@ func TestIDFormats_MatchRealAWSPattern(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var snapResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &snapResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &snapResp, jsonx.V1())) snapID, _ := snapResp["SnapshotId"].(string) require.Regexp(t, realIDPattern, snapID, "snapshot ID") @@ -48,7 +50,7 @@ func TestIDFormats_MatchRealAWSPattern(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var trustResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &trustResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &trustResp, jsonx.V1())) trustID, _ := trustResp["TrustId"].(string) require.Regexp(t, realIDPattern, trustID, "trust ID") } diff --git a/services/directoryservice/persistence.go b/services/directoryservice/persistence.go index 3de6400285..34398ce177 100644 --- a/services/directoryservice/persistence.go +++ b/services/directoryservice/persistence.go @@ -2,9 +2,11 @@ package directoryservice import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -52,7 +54,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Aliases map[string]map[string]string `json:"aliases"` IPRoutes map[string]map[string][]storedIpRoute `json:"ipRoutes"` DirDataAccess map[string]map[string]bool `json:"dirDataAccess"` @@ -257,7 +259,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dto := buildPersistenceDTOTables() if err := dto.registry.RestoreAll(tables); err != nil { @@ -361,7 +363,7 @@ func (b *InMemoryBackend) BackendSnapshot() []byte { Region: b.region, } - data, _ := json.Marshal(snap) + data, _ := json.Marshal(snap, jsonx.V1()) return data } diff --git a/services/dlm/handler.go b/services/dlm/handler.go index 5d73ed9e93..4f25006a4b 100644 --- a/services/dlm/handler.go +++ b/services/dlm/handler.go @@ -2,7 +2,7 @@ package dlm import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -294,7 +295,7 @@ func (h *Handler) handleCreateLifecyclePolicy(c *echo.Context, body []byte) erro CrossRegionCopyTargets []any `json:"CrossRegionCopyTargets"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errBody(errInvalidRequest, "invalid request body")) } @@ -427,7 +428,7 @@ func (h *Handler) handleUpdateLifecyclePolicy(c *echo.Context, policyID string, CrossRegionCopyTargets []any `json:"CrossRegionCopyTargets"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errBody(errInvalidRequest, "invalid request body")) } @@ -464,7 +465,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body [] Tags map[string]string `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errBody(errInvalidRequest, "invalid request body")) } diff --git a/services/dlm/handler_lifecycle_policies_test.go b/services/dlm/handler_lifecycle_policies_test.go index 93b56557de..b41a6e640f 100644 --- a/services/dlm/handler_lifecycle_policies_test.go +++ b/services/dlm/handler_lifecycle_policies_test.go @@ -2,7 +2,7 @@ package dlm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dlm" ) @@ -31,7 +32,7 @@ func createPolicyWithDetails(t *testing.T, h *dlm.Handler, description string, d require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policyID, ok := resp["PolicyId"].(string) require.True(t, ok, "PolicyId missing from response") @@ -43,7 +44,7 @@ func getPoliciesResp(t *testing.T, rec *httptest.ResponseRecorder) []map[string] t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, ok := resp["Policies"].([]any) require.True(t, ok) @@ -241,7 +242,7 @@ func TestHandler_GetLifecyclePolicies_DeterministicOrder(t *testing.T) { assert.JSONEq(t, firstRec.Body.String(), secondRec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["Policies"].([]any) require.True(t, ok) require.Len(t, policies, tc.count) @@ -304,7 +305,7 @@ func TestHandler_UpdateLifecyclePolicy_PolicyDetails(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["PolicyId"].(string) // Update with new PolicyDetails. @@ -318,7 +319,7 @@ func TestHandler_UpdateLifecyclePolicy_PolicyDetails(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy := getResp["Policy"].(map[string]any) details := policy["PolicyDetails"].(map[string]any) assert.Equal(t, tc.wantType, details["PolicyType"]) @@ -374,7 +375,7 @@ func TestHandler_UpdateLifecyclePolicy_ExplicitEmptyVsOmitted(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["PolicyId"].(string) patchRec := doRequest(t, h, http.MethodPatch, fmt.Sprintf("/policies/%s", policyID), tc.patchBody) @@ -384,7 +385,7 @@ func TestHandler_UpdateLifecyclePolicy_ExplicitEmptyVsOmitted(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy := getResp["Policy"].(map[string]any) assert.Equal(t, tc.wantDesc, policy["Description"]) assert.Equal(t, tc.wantRoleArn, policy["ExecutionRoleArn"]) @@ -483,14 +484,14 @@ func TestHandler_CreateLifecyclePolicy_DefaultPolicyFields(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["PolicyId"].(string) getRec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy := getResp["Policy"].(map[string]any) details := policy["PolicyDetails"].(map[string]any) @@ -526,7 +527,7 @@ func TestHandler_UpdateLifecyclePolicy_DefaultPolicyFields(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["PolicyId"].(string) patchRec := doRequest(t, h, http.MethodPatch, fmt.Sprintf("/policies/%s", policyID), map[string]any{ @@ -539,7 +540,7 @@ func TestHandler_UpdateLifecyclePolicy_DefaultPolicyFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy := getResp["Policy"].(map[string]any) details := policy["PolicyDetails"].(map[string]any) @@ -601,14 +602,14 @@ func TestHandler_GetLifecyclePolicy_DefaultPolicyEcho(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["PolicyId"].(string) getRec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy := getResp["Policy"].(map[string]any) assert.Equal(t, tc.want, policy["DefaultPolicy"]) @@ -682,7 +683,7 @@ func TestHandler_CreateLifecyclePolicy_LimitExceeded(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "LimitExceededException", out["__type"]) } @@ -727,7 +728,7 @@ func TestHandler_CreateLifecyclePolicy_MissingRequiredFields(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["__type"]) }) } @@ -864,7 +865,7 @@ func TestHandler_GetLifecyclePolicies_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies := resp["Policies"].([]any) if tc.wantMaxLen > 0 || tc.wantMinLen > 0 { diff --git a/services/dlm/handler_tags_test.go b/services/dlm/handler_tags_test.go index 3cd238f9a1..edb34e9323 100644 --- a/services/dlm/handler_tags_test.go +++ b/services/dlm/handler_tags_test.go @@ -2,7 +2,7 @@ package dlm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dlm" ) @@ -46,7 +47,7 @@ func TestHandler_TagResource_InvalidBody(t *testing.T) { // Get the ARN. rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["Policy"].(map[string]any)["PolicyArn"].(string) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/tags/%s", arn), bytes.NewReader(tc.body)) @@ -89,7 +90,7 @@ func TestHandler_UntagResource_MultipleQueryKeys(t *testing.T) { rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["Policy"].(map[string]any)["PolicyArn"].(string) // Tag first. @@ -141,12 +142,12 @@ func TestHandler_TagResource_GovCloudPartitionMatcherRouted(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policyARN := getResp["Policy"].(map[string]any)["PolicyArn"].(string) require.Contains(t, policyARN, "arn:aws-us-gov:dlm:", "sanity: PolicyArn must carry the GovCloud partition") tagPath := fmt.Sprintf("/tags/%s", policyARN) - body, err := json.Marshal(map[string]any{"Tags": map[string]string{"env": "prod"}}) + body, err := json.Marshal(map[string]any{"Tags": map[string]string{"env": "prod"}}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, tagPath, bytes.NewReader(body)) @@ -164,7 +165,7 @@ func TestHandler_TagResource_GovCloudPartitionMatcherRouted(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -203,7 +204,7 @@ func TestHandler_UntagResource_URLEncodedTagKey(t *testing.T) { rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["Policy"].(map[string]any)["PolicyArn"].(string) doRequest(t, h, http.MethodPost, fmt.Sprintf("/tags/%s", arn), map[string]any{ @@ -228,7 +229,7 @@ func TestHandler_UntagResource_URLEncodedTagKey(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) assert.NotContains(t, tags, tc.tagKey, "percent-decoded tag key must be removed") diff --git a/services/dlm/handler_test.go b/services/dlm/handler_test.go index 460b11e078..2d539a1813 100644 --- a/services/dlm/handler_test.go +++ b/services/dlm/handler_test.go @@ -2,7 +2,7 @@ package dlm_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dlm" ) @@ -30,7 +31,7 @@ func doRequest(t *testing.T, h *dlm.Handler, method, path string, body any) *htt if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -57,7 +58,7 @@ func createPolicy(t *testing.T, h *dlm.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policyID, ok := resp["PolicyId"].(string) require.True(t, ok, "PolicyId missing from response") @@ -90,7 +91,7 @@ func TestDLM_LifecyclePolicy(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) assert.Contains(t, resp["PolicyId"], "policy-") }, }, @@ -105,7 +106,7 @@ func TestDLM_LifecyclePolicy(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) policies, ok := resp["Policies"].([]any) require.True(t, ok) assert.Len(t, policies, 1) @@ -124,7 +125,7 @@ func TestDLM_LifecyclePolicy(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) policy, ok := resp["Policy"].(map[string]any) require.True(t, ok) assert.Contains(t, policy["PolicyId"], "policy-") @@ -220,7 +221,7 @@ func TestDLM_Tags(t *testing.T) { policyID := createPolicy(t, h) rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) arn := policy["PolicyArn"].(string) @@ -239,7 +240,7 @@ func TestDLM_Tags(t *testing.T) { policyID := createPolicy(t, h) rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) arn := policy["PolicyArn"].(string) doRequest(t, h, http.MethodPost, fmt.Sprintf("/tags/%s", arn), map[string]any{ @@ -255,7 +256,7 @@ func TestDLM_Tags(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) tags, ok := resp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -268,7 +269,7 @@ func TestDLM_Tags(t *testing.T) { policyID := createPolicy(t, h) rec := doRequest(t, h, http.MethodGet, fmt.Sprintf("/policies/%s", policyID), nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) arn := policy["PolicyArn"].(string) doRequest(t, h, http.MethodPost, fmt.Sprintf("/tags/%s", arn), map[string]any{ @@ -359,7 +360,7 @@ func TestHandler_Reset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies := resp["Policies"].([]any) assert.Empty(t, policies) }) @@ -585,7 +586,7 @@ func TestHandler_MapError_InternalError(t *testing.T) { var bodyBytes []byte if tc.method == http.MethodPost { - bodyBytes, _ = json.Marshal(map[string]any{"Tags": map[string]string{"k": "v"}}) + bodyBytes, _ = json.Marshal(map[string]any{"Tags": map[string]string{"k": "v"}}, jsonx.V1()) } req := httptest.NewRequest(tc.method, tc.path, bytes.NewReader(bodyBytes)) @@ -599,7 +600,7 @@ func TestHandler_MapError_InternalError(t *testing.T) { assert.Equal(t, tc.wantCode, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) }) } diff --git a/services/dlm/persistence.go b/services/dlm/persistence.go index 1bb2b1bd8e..da0faec0d2 100644 --- a/services/dlm/persistence.go +++ b/services/dlm/persistence.go @@ -2,7 +2,7 @@ package dlm import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "math" "strconv" @@ -33,9 +33,9 @@ const dlmSnapshotVersion = 1 // (older or newer) build of this backend as though it were the current // shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Counter int `json:"counter"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Counter int `json:"counter"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. diff --git a/services/dlm/persistence_test.go b/services/dlm/persistence_test.go index a158965a93..0e83b4d51e 100644 --- a/services/dlm/persistence_test.go +++ b/services/dlm/persistence_test.go @@ -2,12 +2,14 @@ package dlm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dlm" ) @@ -307,8 +309,8 @@ func TestPersistence_CurrentVersionRoundTripsThroughJSON(t *testing.T) { snap := b.Snapshot(context.Background()) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) _, hasVersion := raw["version"] assert.True(t, hasVersion, "snapshot must carry a top-level version field") @@ -319,8 +321,8 @@ func TestPersistence_CurrentVersionRoundTripsThroughJSON(t *testing.T) { _, hasCounter := raw["counter"] assert.True(t, hasCounter, "snapshot must carry a top-level counter field") - var tables map[string]json.RawMessage - require.NoError(t, json.Unmarshal(raw["tables"], &tables)) + var tables map[string]jsontext.Value + require.NoError(t, json.Unmarshal(raw["tables"], &tables, jsonx.V1())) _, hasPoliciesTable := tables["policies"] assert.True( diff --git a/services/dms/data_providers.go b/services/dms/data_providers.go index df816c9b90..e489098965 100644 --- a/services/dms/data_providers.go +++ b/services/dms/data_providers.go @@ -2,7 +2,8 @@ package dms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" @@ -10,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -226,8 +228,9 @@ func mergeDataProviderSettings(current, incoming string, exact bool) string { return incoming } - var cur, inc map[string]map[string]json.RawMessage - if json.Unmarshal([]byte(current), &cur) != nil || json.Unmarshal([]byte(incoming), &inc) != nil { + var cur, inc map[string]map[string]jsontext.Value + if json.Unmarshal([]byte(current), &cur, jsonx.V1()) != nil || + json.Unmarshal([]byte(incoming), &inc, jsonx.V1()) != nil { return incoming } @@ -240,7 +243,7 @@ func mergeDataProviderSettings(current, incoming string, exact bool) string { maps.Copy(curFields, fields) } - out, err := json.Marshal(cur) + out, err := json.Marshal(cur, jsonx.V1()) if err != nil { return incoming } diff --git a/services/dms/endpoints.go b/services/dms/endpoints.go index 77c4763f2a..e8d68715eb 100644 --- a/services/dms/endpoints.go +++ b/services/dms/endpoints.go @@ -2,7 +2,8 @@ package dms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" @@ -10,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -419,14 +421,15 @@ func mergeEndpointSettings(current, incoming string, exact bool) string { return incoming } - var cur, inc map[string]json.RawMessage - if json.Unmarshal([]byte(current), &cur) != nil || json.Unmarshal([]byte(incoming), &inc) != nil { + var cur, inc map[string]jsontext.Value + if json.Unmarshal([]byte(current), &cur, jsonx.V1()) != nil || + json.Unmarshal([]byte(incoming), &inc, jsonx.V1()) != nil { return incoming } maps.Copy(cur, inc) - out, err := json.Marshal(cur) + out, err := json.Marshal(cur, jsonx.V1()) if err != nil { return incoming } diff --git a/services/dms/engine.go b/services/dms/engine.go index 52b3305115..da381f970f 100644 --- a/services/dms/engine.go +++ b/services/dms/engine.go @@ -4,13 +4,14 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "sync" "time" "github.com/blackbirdworks/gopherstack/pkgs/dmsengine" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -153,7 +154,7 @@ func endpointConfig(ep *Endpoint) dmsengine.EndpointConfig { Port int `json:"Port"` } - if json.Unmarshal([]byte(raw), &s) == nil && s.ServerName != "" { + if json.Unmarshal([]byte(raw), &s, jsonx.V1()) == nil && s.ServerName != "" { cfg.Server, cfg.Port, cfg.Database = s.ServerName, s.Port, s.DatabaseName cfg.User, cfg.Password = s.Username, s.Password diff --git a/services/dms/handler.go b/services/dms/handler.go index 01e9a6fcbd..2cd5e1f11a 100644 --- a/services/dms/handler.go +++ b/services/dms/handler.go @@ -2,7 +2,7 @@ package dms import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" @@ -332,7 +333,7 @@ func extractField(c *echo.Context, keys ...string) string { } var raw map[string]string - if unmarshalErr := json.Unmarshal(bodyBytes, &raw); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(bodyBytes, &raw, jsonx.V1()); unmarshalErr != nil { return "" } @@ -380,7 +381,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { diff --git a/services/dms/handler_assessment_runs_test.go b/services/dms/handler_assessment_runs_test.go index bc5d16b60e..50239ccc3e 100644 --- a/services/dms/handler_assessment_runs_test.go +++ b/services/dms/handler_assessment_runs_test.go @@ -1,7 +1,7 @@ package dms_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -150,7 +151,7 @@ func TestStartReplicationTaskAssessment(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundFault", body["__type"]) }) @@ -250,7 +251,7 @@ func TestStartReplicationTaskAssessment(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidResourceStateFault", body["__type"]) }) @@ -274,7 +275,7 @@ func TestStartReplicationTaskAssessment(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidResourceStateFault", body["__type"]) }) } diff --git a/services/dms/handler_data_providers.go b/services/dms/handler_data_providers.go index 2d82f1ef47..ae82969d8d 100644 --- a/services/dms/handler_data_providers.go +++ b/services/dms/handler_data_providers.go @@ -2,30 +2,32 @@ package dms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/pkgs/service" ) type createDataProviderInput struct { - DataProviderName *string `json:"DataProviderName"` - Engine *string `json:"Engine"` - Description *string `json:"Description"` - Settings json.RawMessage `json:"Settings"` - Virtual *bool `json:"Virtual"` - Tags []tagEntry `json:"Tags"` + DataProviderName *string `json:"DataProviderName"` + Engine *string `json:"Engine"` + Description *string `json:"Description"` + Settings jsontext.Value `json:"Settings"` + Virtual *bool `json:"Virtual"` + Tags []tagEntry `json:"Tags"` } type dataProviderJSON struct { - DataProviderName string `json:"DataProviderName"` - DataProviderArn string `json:"DataProviderArn"` - Engine string `json:"Engine"` - Description string `json:"Description,omitempty"` - Settings json.RawMessage `json:"Settings,omitempty"` - Virtual bool `json:"Virtual,omitempty"` + DataProviderName string `json:"DataProviderName"` + DataProviderArn string `json:"DataProviderArn"` + Engine string `json:"Engine"` + Description string `json:"Description,omitempty"` + Settings jsontext.Value `json:"Settings,omitempty"` + Virtual bool `json:"Virtual,omitempty"` } type createDataProviderOutput struct { @@ -80,13 +82,13 @@ func isDataProviderSettingsArm(arm string) bool { // validateDataProviderSettings checks that a supplied Settings document is a // union with exactly one known member and returns it as a string. -func validateDataProviderSettings(raw json.RawMessage) (string, error) { +func validateDataProviderSettings(raw jsontext.Value) (string, error) { if len(raw) == 0 || string(raw) == "null" { return "", nil } - var doc map[string]json.RawMessage - if err := json.Unmarshal(raw, &doc); err != nil || len(doc) != 1 { + var doc map[string]jsontext.Value + if err := json.Unmarshal(raw, &doc, jsonx.V1()); err != nil || len(doc) != 1 { return "", fmt.Errorf("%w: Settings must be a union with exactly one member", ErrValidation) } @@ -108,7 +110,7 @@ func dpToJSON(dp *DataProvider) dataProviderJSON { Virtual: dp.Virtual, } if dp.Settings != "" { - out.Settings = json.RawMessage(dp.Settings) + out.Settings = jsontext.Value(dp.Settings) } return out @@ -173,13 +175,13 @@ func (h *Handler) handleDescribeDataProviders( } type modifyDataProviderInput struct { - DataProviderIdentifier *string `json:"DataProviderIdentifier"` - DataProviderName *string `json:"DataProviderName"` - Engine *string `json:"Engine"` - Description *string `json:"Description"` - Virtual *bool `json:"Virtual"` - ExactSettings *bool `json:"ExactSettings"` - Settings json.RawMessage `json:"Settings"` + DataProviderIdentifier *string `json:"DataProviderIdentifier"` + DataProviderName *string `json:"DataProviderName"` + Engine *string `json:"Engine"` + Description *string `json:"Description"` + Virtual *bool `json:"Virtual"` + ExactSettings *bool `json:"ExactSettings"` + Settings jsontext.Value `json:"Settings"` } type modifyDataProviderOutput struct { diff --git a/services/dms/handler_data_providers_test.go b/services/dms/handler_data_providers_test.go index fdbe2237cd..eb884557a6 100644 --- a/services/dms/handler_data_providers_test.go +++ b/services/dms/handler_data_providers_test.go @@ -1,7 +1,7 @@ package dms_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -125,7 +126,7 @@ func TestDeleteDataProvider_NotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundFault", body["__type"]) } diff --git a/services/dms/handler_endpoints.go b/services/dms/handler_endpoints.go index 83db2ccf75..65db07f692 100644 --- a/services/dms/handler_endpoints.go +++ b/services/dms/handler_endpoints.go @@ -2,13 +2,15 @@ package dms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "sync" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -17,25 +19,25 @@ import ( // CreateEndpoint/ModifyEndpoint (api_op_CreateEndpoint.go, databasemigrationservice@v1.66.4). // Blocks are stored as raw JSON and echoed back; no engine consumes them. type engineSettingsFields struct { - DmsTransferSettings json.RawMessage `json:"DmsTransferSettings,omitempty"` - DocDBSettings json.RawMessage `json:"DocDbSettings,omitempty"` - DynamoDBSettings json.RawMessage `json:"DynamoDbSettings,omitempty"` - ElasticsearchSettings json.RawMessage `json:"ElasticsearchSettings,omitempty"` - GcpMySQLSettings json.RawMessage `json:"GcpMySQLSettings,omitempty"` - IBMDb2Settings json.RawMessage `json:"IBMDb2Settings,omitempty"` - KafkaSettings json.RawMessage `json:"KafkaSettings,omitempty"` - KinesisSettings json.RawMessage `json:"KinesisSettings,omitempty"` - MicrosoftSQLServerSettings json.RawMessage `json:"MicrosoftSQLServerSettings,omitempty"` - MongoDBSettings json.RawMessage `json:"MongoDbSettings,omitempty"` - MySQLSettings json.RawMessage `json:"MySQLSettings,omitempty"` - NeptuneSettings json.RawMessage `json:"NeptuneSettings,omitempty"` - OracleSettings json.RawMessage `json:"OracleSettings,omitempty"` - PostgreSQLSettings json.RawMessage `json:"PostgreSQLSettings,omitempty"` - RedisSettings json.RawMessage `json:"RedisSettings,omitempty"` - RedshiftSettings json.RawMessage `json:"RedshiftSettings,omitempty"` - S3Settings json.RawMessage `json:"S3Settings,omitempty"` - SybaseSettings json.RawMessage `json:"SybaseSettings,omitempty"` - TimestreamSettings json.RawMessage `json:"TimestreamSettings,omitempty"` + DmsTransferSettings jsontext.Value `json:"DmsTransferSettings,omitempty"` + DocDBSettings jsontext.Value `json:"DocDbSettings,omitempty"` + DynamoDBSettings jsontext.Value `json:"DynamoDbSettings,omitempty"` + ElasticsearchSettings jsontext.Value `json:"ElasticsearchSettings,omitempty"` + GcpMySQLSettings jsontext.Value `json:"GcpMySQLSettings,omitempty"` + IBMDb2Settings jsontext.Value `json:"IBMDb2Settings,omitempty"` + KafkaSettings jsontext.Value `json:"KafkaSettings,omitempty"` + KinesisSettings jsontext.Value `json:"KinesisSettings,omitempty"` + MicrosoftSQLServerSettings jsontext.Value `json:"MicrosoftSQLServerSettings,omitempty"` + MongoDBSettings jsontext.Value `json:"MongoDbSettings,omitempty"` + MySQLSettings jsontext.Value `json:"MySQLSettings,omitempty"` + NeptuneSettings jsontext.Value `json:"NeptuneSettings,omitempty"` + OracleSettings jsontext.Value `json:"OracleSettings,omitempty"` + PostgreSQLSettings jsontext.Value `json:"PostgreSQLSettings,omitempty"` + RedisSettings jsontext.Value `json:"RedisSettings,omitempty"` + RedshiftSettings jsontext.Value `json:"RedshiftSettings,omitempty"` + S3Settings jsontext.Value `json:"S3Settings,omitempty"` + SybaseSettings jsontext.Value `json:"SybaseSettings,omitempty"` + TimestreamSettings jsontext.Value `json:"TimestreamSettings,omitempty"` } // endpointSecretKeys are the credential members inside the engine settings @@ -46,16 +48,16 @@ var endpointSecretKeys = []string{ //nolint:gochecknoglobals // immutable lookup // rawIsSet reports whether a json.RawMessage field was present in the request // body with a non-null value. -func rawIsSet(raw json.RawMessage) bool { +func rawIsSet(raw jsontext.Value) bool { return len(raw) > 0 && string(raw) != "null" } func (f *engineSettingsFields) entries() []struct { - raw *json.RawMessage + raw *jsontext.Value name string } { return []struct { - raw *json.RawMessage + raw *jsontext.Value name string }{ {&f.DmsTransferSettings, "DmsTransferSettings"}, @@ -89,8 +91,8 @@ func (f *engineSettingsFields) otherSettings() (map[string]string, error) { continue } - var obj map[string]json.RawMessage - if err := json.Unmarshal(*e.raw, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(*e.raw, &obj, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %s must be an object", ErrValidation, e.name) } @@ -113,19 +115,19 @@ func (f *engineSettingsFields) apply(stored map[string]string) { } } -func redactEndpointSecrets(raw string) json.RawMessage { - var obj map[string]json.RawMessage - if json.Unmarshal([]byte(raw), &obj) != nil { - return json.RawMessage(raw) +func redactEndpointSecrets(raw string) jsontext.Value { + var obj map[string]jsontext.Value + if json.Unmarshal([]byte(raw), &obj, jsonx.V1()) != nil { + return jsontext.Value(raw) } for _, k := range endpointSecretKeys { delete(obj, k) } - out, err := json.Marshal(obj) + out, err := json.Marshal(obj, jsonx.V1()) if err != nil { - return json.RawMessage(raw) + return jsontext.Value(raw) } return out @@ -376,7 +378,7 @@ func epToJSON(ep *Endpoint) endpointJSON { } if ep.S3Settings != "" { - out.S3Settings = json.RawMessage(ep.S3Settings) + out.S3Settings = jsontext.Value(ep.S3Settings) } out.apply(ep.EngineSettings) diff --git a/services/dms/handler_endpoints_test.go b/services/dms/handler_endpoints_test.go index c62db0e2ff..ad4af382b5 100644 --- a/services/dms/handler_endpoints_test.go +++ b/services/dms/handler_endpoints_test.go @@ -1,7 +1,7 @@ package dms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -378,7 +379,7 @@ func TestDeleteEndpoint_RejectsIfInUse(t *testing.T) { require.Equal(t, http.StatusBadRequest, delRec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidResourceStateFault", body["__type"], "deleting an endpoint used by a task must return InvalidResourceStateFault") }) diff --git a/services/dms/handler_event_subscriptions_test.go b/services/dms/handler_event_subscriptions_test.go index 0e53c48675..99d77b9a1a 100644 --- a/services/dms/handler_event_subscriptions_test.go +++ b/services/dms/handler_event_subscriptions_test.go @@ -1,13 +1,14 @@ package dms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -65,7 +66,7 @@ func TestCreateEventSubscription_Duplicate(t *testing.T) { require.Equal(t, http.StatusConflict, rec2.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ResourceAlreadyExistsFault", errBody["__type"]) } diff --git a/services/dms/handler_replication_configs.go b/services/dms/handler_replication_configs.go index 562a3424e7..1a5a83666e 100644 --- a/services/dms/handler_replication_configs.go +++ b/services/dms/handler_replication_configs.go @@ -2,12 +2,13 @@ package dms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -449,7 +450,7 @@ func validatePremigrationAssessmentSettings(raw string) error { FailOnAssessmentFailure *bool `json:"FailOnAssessmentFailure"` } - if err := json.Unmarshal([]byte(raw), &settings); err != nil { + if err := json.Unmarshal([]byte(raw), &settings, jsonx.V1()); err != nil { return fmt.Errorf("%w: PremigrationAssessmentSettings is not a valid settings object: %w", ErrValidation, err) } diff --git a/services/dms/handler_replication_tasks.go b/services/dms/handler_replication_tasks.go index c752128118..feecd090c0 100644 --- a/services/dms/handler_replication_tasks.go +++ b/services/dms/handler_replication_tasks.go @@ -2,12 +2,13 @@ package dms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/dmsengine" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -402,7 +403,7 @@ func buildTableStatistics(tableMappings string) []tableStatisticJSON { } var doc tableMappingsDoc - if err := json.Unmarshal([]byte(tableMappings), &doc); err != nil { + if err := json.Unmarshal([]byte(tableMappings), &doc, jsonx.V1()); err != nil { return []tableStatisticJSON{} } diff --git a/services/dms/handler_replication_tasks_test.go b/services/dms/handler_replication_tasks_test.go index 1ee271336a..311f4139df 100644 --- a/services/dms/handler_replication_tasks_test.go +++ b/services/dms/handler_replication_tasks_test.go @@ -1,7 +1,7 @@ package dms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -191,7 +192,7 @@ func TestCreateReplicationTask_ARNValidation(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundFault", body["__type"], "non-existent ARN in CreateReplicationTask must return ResourceNotFoundFault") }) @@ -227,7 +228,7 @@ func TestCreateReplicationTask_InvalidMigrationType(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"], "invalid MigrationType must return ValidationException") }) @@ -306,7 +307,7 @@ func TestStopReplicationTask_NotRunning(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidResourceStateFault", body["__type"], "stopping a non-running task must return InvalidResourceStateFault") }) diff --git a/services/dms/handler_test.go b/services/dms/handler_test.go index 3ae1e74a6a..5bd5257e63 100644 --- a/services/dms/handler_test.go +++ b/services/dms/handler_test.go @@ -2,7 +2,7 @@ package dms_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dms" ) @@ -24,7 +25,7 @@ func newTestDMSHandler() *dms.Handler { func doDMS(t *testing.T, h *dms.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -45,7 +46,7 @@ func parseJSON(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -266,7 +267,7 @@ func TestHandler_ExtractResource(t *testing.T) { t.Parallel() h := newTestDMSHandler() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -733,7 +734,7 @@ func TestValidationException_NotInvalidResourceStateFault(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) errType, ok := body["__type"].(string) require.True(t, ok, "response must have __type field") @@ -800,7 +801,7 @@ func TestInvalidResourceStateFault_ForStateErrors(t *testing.T) { require.Equal(t, http.StatusBadRequest, startAgainRec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(startAgainRec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(startAgainRec.Body.Bytes(), &body, jsonx.V1())) errType, ok := body["__type"].(string) require.True(t, ok, "response must have __type field") diff --git a/services/dms/persistence.go b/services/dms/persistence.go index 25e0ac2f72..59fd28f000 100644 --- a/services/dms/persistence.go +++ b/services/dms/persistence.go @@ -2,9 +2,11 @@ package dms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -43,7 +45,7 @@ const dmsSnapshotVersion = 1 // non-serializable live fields. ResourceTags is the side table that // survives what Tables can't carry -- see reinitTagsLocked. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourceTags map[string]map[string]string `json:"resourceTags,omitempty"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -70,7 +72,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "dms: Snapshot marshal failure", "error", err) diff --git a/services/dms/validation.go b/services/dms/validation.go index 0c5f1b3834..622b8e61ac 100644 --- a/services/dms/validation.go +++ b/services/dms/validation.go @@ -1,11 +1,13 @@ package dms import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -45,7 +47,7 @@ func validateReplicationInstanceInput(identifier, class string, allocatedStorage } func validateTableMappings(mappings string) error { - if mappings != "" && !json.Valid([]byte(mappings)) { + if mappings != "" && !jsontext.Value([]byte(mappings)).IsValid(jsonx.V1()) { return fmt.Errorf("%w: TableMappings must be valid JSON", ErrValidation) } @@ -59,7 +61,7 @@ func (h *Handler) checkMarker(body []byte) error { NextToken *string `json:"NextToken"` } - if json.Unmarshal(body, &in) != nil { + if json.Unmarshal(body, &in, jsonx.V1()) != nil { return nil //nolint:nilerr // malformed bodies are rejected by the op decoder } diff --git a/services/docdb/master_secret.go b/services/docdb/master_secret.go index 1ad5f23abe..f7ed329317 100644 --- a/services/docdb/master_secret.go +++ b/services/docdb/master_secret.go @@ -3,13 +3,14 @@ package docdb import ( "crypto/rand" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net/url" "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -40,7 +41,7 @@ func masterSecretBody(user string) (string, string, error) { password := hex.EncodeToString(raw) - body, err := json.Marshal(map[string]string{"username": user, "password": password}) + body, err := json.Marshal(map[string]string{"username": user, "password": password}, jsonx.V1()) if err != nil { return "", "", fmt.Errorf("encode master user secret: %w", err) } diff --git a/services/docdb/persistence.go b/services/docdb/persistence.go index 8b372b82eb..27f03718ce 100644 --- a/services/docdb/persistence.go +++ b/services/docdb/persistence.go @@ -2,7 +2,7 @@ package docdb import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -52,7 +52,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // guards against decoding a snapshot from an incompatible (older or newer) // build of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string][]Tag `json:"tags"` EventsLog map[string][]Event `json:"eventsLog"` PendingMaintenanceActions map[string]map[string]PendingMaintenanceAction `json:"pendingMaintenanceActions"` @@ -252,7 +252,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, clusterDTOs, instanceDTOs, subnetGroupDTOs, clusterParameterGroupDTOs, clusterSnapshotDTOs, eventSubscriptionDTOs, snapshotAttributesDTOs, globalClusterDTOs := buildPersistenceDTORegistry() diff --git a/services/dsql/handler_clusters.go b/services/dsql/handler_clusters.go index f5a1383f95..953b54e073 100644 --- a/services/dsql/handler_clusters.go +++ b/services/dsql/handler_clusters.go @@ -2,11 +2,13 @@ package dsql import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchClusterOps handles cluster CRUD and vpc-endpoint-service-name operations. @@ -37,7 +39,7 @@ func (h *Handler) dispatchClusterOps( func (h *Handler) handleCreateCluster(ctx context.Context, c *echo.Context, body []byte) error { var req createClusterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeInvalidBody(c) } } @@ -98,7 +100,7 @@ func (h *Handler) handleListClusters(c *echo.Context) error { func (h *Handler) handleUpdateCluster(c *echo.Context, identifier string, body []byte) error { var req updateClusterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeInvalidBody(c) } } diff --git a/services/dsql/handler_policy.go b/services/dsql/handler_policy.go index 1b12f264e9..3eb6f8d5da 100644 --- a/services/dsql/handler_policy.go +++ b/services/dsql/handler_policy.go @@ -1,10 +1,12 @@ package dsql import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchPolicyOps handles cluster resource-policy operations. @@ -32,7 +34,7 @@ func (h *Handler) handleGetClusterPolicy(c *echo.Context, identifier string) err func (h *Handler) handlePutClusterPolicy(c *echo.Context, identifier string, body []byte) error { var req putClusterPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeInvalidBody(c) } diff --git a/services/dsql/handler_streams.go b/services/dsql/handler_streams.go index 7622ba5a3f..0f21015838 100644 --- a/services/dsql/handler_streams.go +++ b/services/dsql/handler_streams.go @@ -1,11 +1,13 @@ package dsql import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchStreamOps handles cluster change-data-capture stream operations. @@ -26,7 +28,7 @@ func (h *Handler) dispatchStreamOps(c *echo.Context, op, resource string, body [ func (h *Handler) handleCreateStream(c *echo.Context, clusterIdentifier string, body []byte) error { var req createStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeInvalidBody(c) } diff --git a/services/dsql/handler_tags.go b/services/dsql/handler_tags.go index 16c71a726c..e34eec5a6f 100644 --- a/services/dsql/handler_tags.go +++ b/services/dsql/handler_tags.go @@ -1,10 +1,12 @@ package dsql import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchTagOps handles the generic ARN-scoped tagging trio. @@ -23,7 +25,7 @@ func (h *Handler) dispatchTagOps(c *echo.Context, op, resource string, body []by func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeInvalidBody(c) } diff --git a/services/dsql/persistence.go b/services/dsql/persistence.go index f5c26dac25..0ff65d6a43 100644 --- a/services/dsql/persistence.go +++ b/services/dsql/persistence.go @@ -2,7 +2,7 @@ package dsql import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -29,8 +29,8 @@ const dsqlSnapshotVersion = 1 // holds one JSON-encoded array per registered table name (clusters, streams // -- see store_setup.go), produced by b.registry.SnapshotAll(). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes backend state to JSON. diff --git a/services/dsql/validation.go b/services/dsql/validation.go index 14f414ab05..a6f7dbacbd 100644 --- a/services/dsql/validation.go +++ b/services/dsql/validation.go @@ -1,7 +1,7 @@ package dsql import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -46,7 +47,7 @@ func (b *InMemoryBackend) dwell(def time.Duration) time.Duration { func validatePolicyDocument(policy string) error { var doc map[string]any - if policy == "" || json.Unmarshal([]byte(policy), &doc) != nil || doc == nil { + if policy == "" || json.Unmarshal([]byte(policy), &doc, jsonx.V1()) != nil || doc == nil { return fmt.Errorf("%w: policy must be a valid JSON policy document", ErrValidation) } diff --git a/services/dynamodb/backup_interface_test.go b/services/dynamodb/backup_interface_test.go index e695473ac5..c1b3217230 100644 --- a/services/dynamodb/backup_interface_test.go +++ b/services/dynamodb/backup_interface_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "log/slog" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -645,7 +646,7 @@ func TestHandler_TagOperations_Via_Handler(t *testing.T) { doHandlerReq := func(target string, payload any) *httptest.ResponseRecorder { t.Helper() - body, marshalErr := json.Marshal(payload) + body, marshalErr := json.Marshal(payload, jsonx.V1()) require.NoError(t, marshalErr) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", target) @@ -689,7 +690,7 @@ func TestHandler_TableMutationOps_Via_Handler(t *testing.T) { doHandlerReq := func(target string, payload any) *httptest.ResponseRecorder { t.Helper() - body, marshalErr := json.Marshal(payload) + body, marshalErr := json.Marshal(payload, jsonx.V1()) require.NoError(t, marshalErr) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", target) @@ -732,7 +733,7 @@ func TestHandler_UpdateTable_Via_Handler(t *testing.T) { body, err := json.Marshal(map[string]any{ "TableName": "UpdateMe", "BillingMode": "PAY_PER_REQUEST", - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -783,7 +784,7 @@ func TestHandler_RestoreTableToPointInTime(t *testing.T) { "PointInTimeRecoverySpecification": map[string]any{ "PointInTimeRecoveryEnabled": true, }, - }) + }, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", "DynamoDB_20120810.UpdateContinuousBackups") req.Header.Set("Content-Type", "application/x-amz-json-1.0") @@ -813,7 +814,7 @@ func TestHandler_RestoreTableToPointInTime(t *testing.T) { tt.setup(t, db) } - body, err := json.Marshal(tt.payload) + body, err := json.Marshal(tt.payload, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -892,7 +893,7 @@ func TestInMemoryDB_ListBackups_BackupTypeFilter(t *testing.T) { body, marshalErr := json.Marshal(map[string]any{ "TableName": "T", "BackupType": "USER", - }) + }, jsonx.V1()) require.NoError(t, marshalErr) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -908,7 +909,7 @@ func TestInMemoryDB_ListBackups_BackupTypeFilter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["BackupSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 2) @@ -931,7 +932,7 @@ func TestInMemoryDB_PutResourcePolicy_EmptyPolicy(t *testing.T) { body, marshalErr := json.Marshal(map[string]any{ "ResourceArn": tableARN, "Policy": "", - }) + }, jsonx.V1()) require.NoError(t, marshalErr) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -1324,7 +1325,7 @@ func TestInMemoryDB_DescribeBackup_ViaHandler(t *testing.T) { require.NoError(t, err) backupArn := aws.ToString(out.BackupDetails.BackupArn) - reqBody, _ := json.Marshal(map[string]string{"BackupArn": backupArn}) + reqBody, _ := json.Marshal(map[string]string{"BackupArn": backupArn}, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) req.Header.Set("Content-Type", "application/x-amz-json-1.0") @@ -1336,7 +1337,7 @@ func TestInMemoryDB_DescribeBackup_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var respBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody, jsonx.V1())) assert.Contains(t, respBody, "BackupDescription") } diff --git a/services/dynamodb/backup_ops.go b/services/dynamodb/backup_ops.go index a4978814af..6b80d17ca6 100644 --- a/services/dynamodb/backup_ops.go +++ b/services/dynamodb/backup_ops.go @@ -2,7 +2,7 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -37,7 +38,7 @@ func backupARN(region, accountID, tableName string, ts time.Time) string { func (h *DynamoDBHandler) createBackup(ctx context.Context, body []byte) (any, error) { var req models.CreateBackupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -65,7 +66,7 @@ func (h *DynamoDBHandler) createBackup(ctx context.Context, body []byte) (any, e func (h *DynamoDBHandler) describeBackup(ctx context.Context, body []byte) (any, error) { var req models.DescribeBackupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -89,7 +90,7 @@ func (h *DynamoDBHandler) describeBackup(ctx context.Context, body []byte) (any, func (h *DynamoDBHandler) deleteBackup(ctx context.Context, body []byte) (any, error) { var req models.DeleteBackupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -107,7 +108,7 @@ func (h *DynamoDBHandler) deleteBackup(ctx context.Context, body []byte) (any, e func (h *DynamoDBHandler) listBackups(ctx context.Context, body []byte) (any, error) { var req models.ListBackupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -420,7 +421,7 @@ func toSDKLSIOverride(lsis []models.LocalSecondaryIndex) []sdktypes.LocalSeconda func (h *DynamoDBHandler) restoreTableFromBackup(ctx context.Context, body []byte) (any, error) { var req models.RestoreTableFromBackupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -500,7 +501,7 @@ func toSDKRestoreDateTime(secs *float64) *time.Time { func (h *DynamoDBHandler) restoreTableToPointInTime(ctx context.Context, body []byte) (any, error) { var req models.RestoreTableToPointInTimeInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/backup_replica_test.go b/services/dynamodb/backup_replica_test.go index 92f6f2b025..62f88014d5 100644 --- a/services/dynamodb/backup_replica_test.go +++ b/services/dynamodb/backup_replica_test.go @@ -3,7 +3,7 @@ package dynamodb_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" @@ -29,7 +30,7 @@ func doBackupRequest( ) (int, map[string]any) { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(raw)) @@ -40,7 +41,7 @@ func doBackupRequest( require.NoError(t, serveEchoHandler(h.Handler(), w, req)) var result map[string]any - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &result, jsonx.V1())) return w.Code, result } diff --git a/services/dynamodb/bench_extract_resource_test.go b/services/dynamodb/bench_extract_resource_test.go index 4a3a1b649e..882f7e5ceb 100644 --- a/services/dynamodb/bench_extract_resource_test.go +++ b/services/dynamodb/bench_extract_resource_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -28,7 +29,7 @@ func BenchmarkExtractResource(b *testing.B) { "TableName": "BenchTable", "Item": item, } - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) if err != nil { b.Fatalf("marshal request body: %v", err) } diff --git a/services/dynamodb/bench_hot_ops_test.go b/services/dynamodb/bench_hot_ops_test.go index 01f5ba388b..86bba4b1e1 100644 --- a/services/dynamodb/bench_hot_ops_test.go +++ b/services/dynamodb/bench_hot_ops_test.go @@ -2,13 +2,14 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" "strconv" "testing" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -26,7 +27,7 @@ func benchRawOp(b *testing.B, h *dynamodb.DynamoDBHandler, action string, body [ func benchMarshal(b *testing.B, v any) []byte { b.Helper() - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { b.Fatal(err) } diff --git a/services/dynamodb/cbor_test.go b/services/dynamodb/cbor_test.go index 403fcd2f20..bea346638b 100644 --- a/services/dynamodb/cbor_test.go +++ b/services/dynamodb/cbor_test.go @@ -3,7 +3,7 @@ package dynamodb_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -478,7 +479,7 @@ func TestDynamoDBCBOR_JSONAndCBORCoexist(t *testing.T) { require.Equal(t, http.StatusOK, getRR2.Code) var jsonResp map[string]any - require.NoError(t, json.Unmarshal(getRR2.Body.Bytes(), &jsonResp)) + require.NoError(t, json.Unmarshal(getRR2.Body.Bytes(), &jsonResp, jsonx.V1())) itemJSON := jsonResp["Item"].(map[string]any) valueJSON := itemJSON["value"].(map[string]any) assert.Equal(t, "from-cbor", valueJSON["S"]) diff --git a/services/dynamodb/condition_check_return_test.go b/services/dynamodb/condition_check_return_test.go index ef200f1f75..aff502b79e 100644 --- a/services/dynamodb/condition_check_return_test.go +++ b/services/dynamodb/condition_check_return_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -34,7 +35,7 @@ func doDDBRequest( var decoded map[string]any if w.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &decoded)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &decoded, jsonx.V1())) } return resp.StatusCode, decoded diff --git a/services/dynamodb/expr/evaluator.go b/services/dynamodb/expr/evaluator.go index 9c899e263d..79ee2471c9 100644 --- a/services/dynamodb/expr/evaluator.go +++ b/services/dynamodb/expr/evaluator.go @@ -2,7 +2,7 @@ package expr import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "math" @@ -11,6 +11,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/dynamonum" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Sentinel errors for evaluator. @@ -480,9 +481,9 @@ func containsList(l, targetVal any) bool { if !isList { return false } - targetJSON, _ := json.Marshal(targetVal) + targetJSON, _ := json.Marshal(targetVal, jsonx.V1()) for _, item := range items { - itemJSON, _ := json.Marshal(item) + itemJSON, _ := json.Marshal(item, jsonx.V1()) if string(itemJSON) == string(targetJSON) { return true } @@ -657,7 +658,7 @@ func (e *Evaluator) toString(v any) string { case nil: return "" default: - b, _ := json.Marshal(s) + b, _ := json.Marshal(s, jsonx.V1()) return string(b) } diff --git a/services/dynamodb/global_tables_test.go b/services/dynamodb/global_tables_test.go index 6a74ba3c6f..1bfd3919fb 100644 --- a/services/dynamodb/global_tables_test.go +++ b/services/dynamodb/global_tables_test.go @@ -1,7 +1,7 @@ package dynamodb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -149,7 +150,7 @@ func TestGlobalTables(t *testing.T) { assert.Equal(t, tt.wantStatus, code) if tt.wantBodyContains != "" { - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), tt.wantBodyContains) } }) @@ -883,7 +884,7 @@ func TestUpdateGlobalTableSettings_NotFound_ViaHandler(t *testing.T) { "GlobalTableName": "NoSuchGT", }) require.Equal(t, http.StatusBadRequest, code) - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), "GlobalTableNotFoundException") } diff --git a/services/dynamodb/golden_perf_test.go b/services/dynamodb/golden_perf_test.go index 9fcccf924c..b3d29cf05c 100644 --- a/services/dynamodb/golden_perf_test.go +++ b/services/dynamodb/golden_perf_test.go @@ -11,7 +11,8 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" "path/filepath" @@ -20,6 +21,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -32,9 +34,9 @@ func goldenPath(name string) string { func checkGolden(t *testing.T, name string, got []byte) { t.Helper() - var buf bytes.Buffer - require.NoError(t, json.Indent(&buf, got, "", " ")) - pretty := buf.Bytes() + indented := jsontext.Value(bytes.Clone(got)) + require.NoError(t, indented.Indent(jsonx.V1(), jsontext.WithIndent(" "))) + pretty := []byte(indented) path := goldenPath(name) if os.Getenv("DYNAMODB_GOLDEN_UPDATE") != "" { @@ -114,7 +116,7 @@ func TestGolden_QueryKeyConditionFilterProjection(t *testing.T) { }) require.Equal(t, 200, code, "Query: %v", resp) - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, jsonx.V1()) require.NoError(t, err) checkGolden(t, "golden_query.json", raw) } @@ -141,7 +143,7 @@ func TestGolden_ScanFilter(t *testing.T) { }) require.Equal(t, 200, code, "Scan: %v", resp) - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, jsonx.V1()) require.NoError(t, err) checkGolden(t, "golden_scan.json", raw) } @@ -184,7 +186,7 @@ func TestGolden_TransactWriteItems(t *testing.T) { }) require.Equal(t, 200, code, "TransactWriteItems: %v", resp) - raw, err := json.Marshal(resp) + raw, err := json.Marshal(resp, jsonx.V1()) require.NoError(t, err) checkGolden(t, "golden_transact.json", raw) } diff --git a/services/dynamodb/handler.go b/services/dynamodb/handler.go index e4e250cea3..1ac9fd642d 100644 --- a/services/dynamodb/handler.go +++ b/services/dynamodb/handler.go @@ -2,7 +2,7 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "hash/crc32" @@ -19,6 +19,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/chaos" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" @@ -458,7 +459,7 @@ func (h *DynamoDBHandler) ExtractResource(c *echo.Context) string { TableName string `json:"TableName"` BackupArn string `json:"BackupArn"` } - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -614,14 +615,14 @@ func handleOpErr[WireIn any, SDKIn any, SDKOut any, WireOut any]( var input WireIn if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } debugEnabled := log.Enabled(ctx, slog.LevelDebug) if debugEnabled { - inputJSON, _ := json.Marshal(input) + inputJSON, _ := json.Marshal(input, jsonx.V1()) log.DebugContext(ctx, "handler input", "action", action, "input", string(inputJSON)) } @@ -637,7 +638,7 @@ func handleOpErr[WireIn any, SDKIn any, SDKOut any, WireOut any]( wireOutput := fromSDK(sdkOutput) if debugEnabled { - outputJSON, _ := json.Marshal(wireOutput) + outputJSON, _ := json.Marshal(wireOutput, jsonx.V1()) log.DebugContext(ctx, "handler output", "action", action, "output", string(outputJSON)) } @@ -657,14 +658,14 @@ func handleOp[WireIn any, SDKIn any, SDKOut any, WireOut any]( var input WireIn if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } } debugEnabled := log.Enabled(ctx, slog.LevelDebug) if debugEnabled { - inputJSON, _ := json.Marshal(input) + inputJSON, _ := json.Marshal(input, jsonx.V1()) log.DebugContext(ctx, "handler input", "action", action, "input", string(inputJSON)) } @@ -677,7 +678,7 @@ func handleOp[WireIn any, SDKIn any, SDKOut any, WireOut any]( wireOutput := fromSDK(sdkOutput) if debugEnabled { - outputJSON, _ := json.Marshal(wireOutput) + outputJSON, _ := json.Marshal(wireOutput, jsonx.V1()) log.DebugContext(ctx, "handler output", "action", action, "output", string(outputJSON)) } @@ -877,7 +878,7 @@ func validateTableNameFromBody(body []byte) error { TableName string `json:"TableName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.TableName == "" { return nil // missing table name is handled downstream } @@ -894,7 +895,7 @@ func validateTableNameFromBody(body []byte) error { // such response reached a client as smithy.GenericAPIError{Code:"UnknownError"} // (gopherstack-wlo1). func writeDynamoDBDispatchError(c *echo.Context, status int, errType, message string) error { - body, err := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}) + body, err := json.Marshal(service.JSONErrorResponse{Type: errType, Message: message}, jsonx.V1()) if err != nil { return err } @@ -932,7 +933,7 @@ func (h *DynamoDBHandler) handleError( c.Response().Header().Set("Content-Type", "application/x-amz-json-1.0") - payload, _ := json.Marshal(awsErr) + payload, _ := json.Marshal(awsErr, jsonx.V1()) checksum := crc32.ChecksumIEEE(payload) c.Response().Header().Set("X-Amz-Crc32", strconv.FormatUint(uint64(checksum), 10)) @@ -955,9 +956,7 @@ func (h *DynamoDBHandler) classifyError(reqErr error) (int, *Error) { } // Fallback - var syntaxErr *json.SyntaxError - var unmarshalTypeError *json.UnmarshalTypeError - if errors.As(reqErr, &syntaxErr) || errors.As(reqErr, &unmarshalTypeError) { + if jsonx.IsSyntaxError(reqErr) || jsonx.IsTypeError(reqErr) { return http.StatusBadRequest, NewValidationException( "JSON Error: " + reqErr.Error(), ) diff --git a/services/dynamodb/handler_autoscaling.go b/services/dynamodb/handler_autoscaling.go index 1221bacc14..29517a3bf8 100644 --- a/services/dynamodb/handler_autoscaling.go +++ b/services/dynamodb/handler_autoscaling.go @@ -7,11 +7,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -237,7 +238,7 @@ func (h *DynamoDBHandler) handleUpdateTableReplicaAutoScaling( body []byte, ) (any, error) { var req updateTableReplicaAutoScalingInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_backups.go b/services/dynamodb/handler_backups.go index ea7dc0d90b..56e2c66e22 100644 --- a/services/dynamodb/handler_backups.go +++ b/services/dynamodb/handler_backups.go @@ -9,13 +9,14 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "time" "github.com/aws/aws-sdk-go-v2/aws" sdkdynamodb "github.com/aws/aws-sdk-go-v2/service/dynamodb" sdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -75,7 +76,7 @@ func continuousBackupsOutputFromSDK( func (h *DynamoDBHandler) describeContinuousBackups(ctx context.Context, body []byte) (any, error) { var req describeContinuousBackupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -106,7 +107,7 @@ type updateContinuousBackupsInput struct { func (h *DynamoDBHandler) updateContinuousBackups(ctx context.Context, body []byte) (any, error) { var req updateContinuousBackupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -219,7 +220,7 @@ func exportDescFieldsFromSDK(d *sdktypes.ExportDescription) exportDescriptionFie func (h *DynamoDBHandler) exportTableToPointInTime(ctx context.Context, body []byte) (any, error) { var req exportTableToPointInTimeInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -250,7 +251,7 @@ type describeExportInput struct { func (h *DynamoDBHandler) describeExport(ctx context.Context, body []byte) (any, error) { var req describeExportInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -280,7 +281,7 @@ type listExportsInput struct { func (h *DynamoDBHandler) listExports(ctx context.Context, body []byte) (any, error) { var req listExportsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -327,7 +328,7 @@ func (h *DynamoDBHandler) describeTableReplicaAutoScaling( body []byte, ) (any, error) { var req describeTableReplicaAutoScalingInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_contributor_insights.go b/services/dynamodb/handler_contributor_insights.go index c0fe17207c..981a7ecaf8 100644 --- a/services/dynamodb/handler_contributor_insights.go +++ b/services/dynamodb/handler_contributor_insights.go @@ -7,11 +7,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -34,7 +35,7 @@ func (h *DynamoDBHandler) handleDescribeContributorInsights( body []byte, ) (any, error) { var req describeContributorInsightsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -91,7 +92,7 @@ func (h *DynamoDBHandler) handleListContributorInsights( body []byte, ) (any, error) { var req listContributorInsightsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -145,7 +146,7 @@ func (h *DynamoDBHandler) handleUpdateContributorInsights( body []byte, ) (any, error) { var req updateContributorInsightsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_debug_test.go b/services/dynamodb/handler_debug_test.go index e866f44ba1..7355abb92c 100644 --- a/services/dynamodb/handler_debug_test.go +++ b/services/dynamodb/handler_debug_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" @@ -98,7 +99,7 @@ func TestDebugLogging(t *testing.T) { echoHandler := h.Handler() if tt.setupTarget != "" { - setupBody, err := json.Marshal(tt.setupInput) + setupBody, err := json.Marshal(tt.setupInput, jsonx.V1()) require.NoError(t, err) setupReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(setupBody)) @@ -115,7 +116,7 @@ func TestDebugLogging(t *testing.T) { logBuffer.Reset() } - body, err := json.Marshal(tt.input) + body, err := json.Marshal(tt.input, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) diff --git a/services/dynamodb/handler_execute_transaction.go b/services/dynamodb/handler_execute_transaction.go index 59aec334d4..1a5036c98c 100644 --- a/services/dynamodb/handler_execute_transaction.go +++ b/services/dynamodb/handler_execute_transaction.go @@ -7,12 +7,13 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -38,7 +39,7 @@ type executeTransactionOutput struct { func (h *DynamoDBHandler) handleExecuteTransaction(ctx context.Context, body []byte) (any, error) { var req executeTransactionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_global_tables.go b/services/dynamodb/handler_global_tables.go index de7336ecb0..bdfb7c4966 100644 --- a/services/dynamodb/handler_global_tables.go +++ b/services/dynamodb/handler_global_tables.go @@ -7,11 +7,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -91,7 +92,7 @@ type listGlobalTablesOutput struct { func (h *DynamoDBHandler) handleCreateGlobalTable(ctx context.Context, body []byte) (any, error) { var req createGlobalTableInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -118,7 +119,7 @@ func (h *DynamoDBHandler) handleCreateGlobalTable(ctx context.Context, body []by func (h *DynamoDBHandler) handleDescribeGlobalTable(ctx context.Context, body []byte) (any, error) { var req describeGlobalTableInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -139,7 +140,7 @@ func (h *DynamoDBHandler) handleDescribeGlobalTableSettings( body []byte, ) (any, error) { var req describeGlobalTableSettingsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -164,7 +165,7 @@ func (h *DynamoDBHandler) handleDescribeGlobalTableSettings( func (h *DynamoDBHandler) handleListGlobalTables(ctx context.Context, body []byte) (any, error) { var req listGlobalTablesInput if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } } @@ -213,7 +214,7 @@ func (h *DynamoDBHandler) handleListGlobalTables(ctx context.Context, body []byt func (h *DynamoDBHandler) handleUpdateGlobalTable(ctx context.Context, body []byte) (any, error) { var req updateGlobalTableInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -419,7 +420,7 @@ func (h *DynamoDBHandler) handleUpdateGlobalTableSettings( body []byte, ) (any, error) { var req updateGlobalTableSettingsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_import.go b/services/dynamodb/handler_import.go index 9963ea32ef..21bc1b39ba 100644 --- a/services/dynamodb/handler_import.go +++ b/services/dynamodb/handler_import.go @@ -7,12 +7,13 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/aws/aws-sdk-go-v2/aws" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -146,7 +147,7 @@ type describeImportOutput struct { func (h *DynamoDBHandler) handleDescribeImport(ctx context.Context, body []byte) (any, error) { var req describeImportInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -179,7 +180,7 @@ type importTableOutput struct { func (h *DynamoDBHandler) handleImportTable(ctx context.Context, body []byte) (any, error) { var req importTableInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -243,7 +244,7 @@ type listImportsInput struct { func (h *DynamoDBHandler) handleListImports(ctx context.Context, body []byte) (any, error) { var req listImportsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_kinesis_streaming.go b/services/dynamodb/handler_kinesis_streaming.go index 49b48067fb..1be7060ab2 100644 --- a/services/dynamodb/handler_kinesis_streaming.go +++ b/services/dynamodb/handler_kinesis_streaming.go @@ -7,11 +7,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -64,7 +65,7 @@ func (h *DynamoDBHandler) handleDescribeKinesisStreamingDestination( body []byte, ) (any, error) { var req describeKinesisInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -96,7 +97,7 @@ func (h *DynamoDBHandler) handleDisableKinesisStreamingDestination( body []byte, ) (any, error) { var req disableKinesisInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -154,7 +155,7 @@ func (h *DynamoDBHandler) handleEnableKinesisStreamingDestination( body []byte, ) (any, error) { var req enableKinesisInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -229,7 +230,7 @@ func (h *DynamoDBHandler) handleUpdateKinesisStreamingDestination( body []byte, ) (any, error) { var req updateKinesisStreamingDestinationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_resource_policy.go b/services/dynamodb/handler_resource_policy.go index 3196e23876..1c01cb4b63 100644 --- a/services/dynamodb/handler_resource_policy.go +++ b/services/dynamodb/handler_resource_policy.go @@ -7,10 +7,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/aws/aws-sdk-go-v2/aws" sdkDDB "github.com/aws/aws-sdk-go-v2/service/dynamodb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resourcePolicyInput is the wire format shared by Get and PutResourcePolicy. @@ -45,7 +47,7 @@ func (h *DynamoDBHandler) handleDeleteResourcePolicy( body []byte, ) (any, error) { var req deleteResourcePolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -67,7 +69,7 @@ func (h *DynamoDBHandler) handleDeleteResourcePolicy( func (h *DynamoDBHandler) handleGetResourcePolicy(ctx context.Context, body []byte) (any, error) { var req resourcePolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -89,7 +91,7 @@ func (h *DynamoDBHandler) handleGetResourcePolicy(ctx context.Context, body []by func (h *DynamoDBHandler) handlePutResourcePolicy(ctx context.Context, body []byte) (any, error) { var req resourcePolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/dynamodb/handler_streams_test.go b/services/dynamodb/handler_streams_test.go index 9970130ec7..06ec18ab14 100644 --- a/services/dynamodb/handler_streams_test.go +++ b/services/dynamodb/handler_streams_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -264,7 +265,7 @@ func TestHandler_ExportAndDescribeExport(t *testing.T) { ExportArn string `json:"ExportArn"` } `json:"ExportDescription"` } - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &exp)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &exp, jsonx.V1())) require.NotEmpty(t, exp.ExportDescription.ExportArn) // DescribeExport on the returned ARN succeeds. @@ -286,7 +287,7 @@ func TestHandler_DescribeTable_ReturnsStreamFields(t *testing.T) { handler, streamARN := newStreamEnabledHandler(t) - reqBody, err := json.Marshal(models.DescribeTableInput{TableName: "StreamHandlerTable"}) + reqBody, err := json.Marshal(models.DescribeTableInput{TableName: "StreamHandlerTable"}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) @@ -299,7 +300,7 @@ func TestHandler_DescribeTable_ReturnsStreamFields(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) var out models.DescribeTableOutput - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out, jsonx.V1())) assert.Equal( t, diff --git a/services/dynamodb/handler_test.go b/services/dynamodb/handler_test.go index 250801fae4..e294e3976f 100644 --- a/services/dynamodb/handler_test.go +++ b/services/dynamodb/handler_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "hash/crc32" "log/slog" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" @@ -386,7 +387,7 @@ func TestHandler_CRC32Header(t *testing.T) { func mustMarshal(t *testing.T, v any) string { t.Helper() - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(data) @@ -423,7 +424,7 @@ func TestHandler_TransactOps_AllActions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, _ := json.Marshal(tt.body) + bodyBytes, _ := json.Marshal(tt.body, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) req.Header.Set("X-Amz-Target", "DynamoDB_20120810."+tt.action) w := httptest.NewRecorder() diff --git a/services/dynamodb/import_export_s3.go b/services/dynamodb/import_export_s3.go index 29da95e40b..6ec7cfd837 100644 --- a/services/dynamodb/import_export_s3.go +++ b/services/dynamodb/import_export_s3.go @@ -6,7 +6,8 @@ import ( "compress/gzip" "context" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -22,6 +23,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -342,7 +344,7 @@ func parseDynamoDBJSONLines(data []byte) ([]map[string]any, error) { var rec struct { Item map[string]any `json:"Item"` } - if err := json.Unmarshal(line, &rec); err != nil { + if err := json.Unmarshal(line, &rec, jsonx.V1()); err != nil { return nil, fmt.Errorf("parse DynamoDB JSON line: %w", err) } @@ -422,11 +424,11 @@ func (db *InMemoryDB) exportTableToS3( var buf bytes.Buffer gz := gzip.NewWriter(&buf) - enc := json.NewEncoder(gz) + enc := jsontext.NewEncoder(gz, jsonx.V1()) var count int64 for i := range items { - if err := enc.Encode(struct { + if err := json.MarshalEncode(enc, struct { Item map[string]any `json:"Item"` }{Item: items[i]}); err != nil { _ = gz.Close() @@ -454,7 +456,7 @@ func (db *InMemoryDB) exportTableToS3( "tableArn": tableARN, "itemCount": count, "dataFileS3Key": dataKey, - }) + }, jsonx.V1()) if _, err := s3.PutObject(ctx, &s3sdk.PutObjectInput{ Bucket: &bucket, Key: &manifestKey, diff --git a/services/dynamodb/import_export_s3_test.go b/services/dynamodb/import_export_s3_test.go index f58f10f9e6..354b07e1c2 100644 --- a/services/dynamodb/import_export_s3_test.go +++ b/services/dynamodb/import_export_s3_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "sort" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -408,7 +409,7 @@ func TestDescribeImport(t *testing.T) { assert.Equal(t, tt.wantStatus, code) if tt.wantBodyContains != "" { - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), tt.wantBodyContains) } }) diff --git a/services/dynamodb/item_collection_metrics_test.go b/services/dynamodb/item_collection_metrics_test.go index e20699911e..f3eb2dca36 100644 --- a/services/dynamodb/item_collection_metrics_test.go +++ b/services/dynamodb/item_collection_metrics_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -67,7 +68,7 @@ func TestQuery_ConsistentRead_GSI_Rejected(t *testing.T) { }, }, "BillingMode": "PAY_PER_REQUEST", - }) + }, jsonx.V1()) require.NoError(t, err) putItemBody, err := json.Marshal(map[string]any{ @@ -76,7 +77,7 @@ func TestQuery_ConsistentRead_GSI_Rejected(t *testing.T) { "pk": map[string]any{"S": "user1"}, "email": map[string]any{"S": "test@example.com"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) tests := []struct { @@ -144,7 +145,7 @@ func TestQuery_ConsistentRead_GSI_Rejected(t *testing.T) { require.Equal(t, http.StatusOK, w.Code, "PutItem failed: %s", w.Body.String()) // Execute query. - queryRaw, marshalErr := json.Marshal(tc.query) + queryRaw, marshalErr := json.Marshal(tc.query, jsonx.V1()) require.NoError(t, marshalErr) w = makeHandlerJSONRequest(t, h, "DynamoDB_20120810.Query", queryRaw) @@ -159,7 +160,7 @@ func TestQuery_ConsistentRead_GSI_Rejected(t *testing.T) { // marshalJSONBody marshals v to JSON bytes for use with makeHandlerJSONRequest. func marshalJSONBody(t *testing.T, v any) []byte { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return b @@ -223,7 +224,7 @@ func TestPutItem_ItemCollectionMetrics_PKOnly(t *testing.T) { SizeEstimateRangeGB []float64 `json:"SizeEstimateRangeGB"` } `json:"ItemCollectionMetrics"` } - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.ItemCollectionMetrics) // ItemCollectionKey must contain only the partition key, not sk or non-key attrs. @@ -309,7 +310,7 @@ func TestDeleteUpdate_ItemCollectionMetrics(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var upd icmResult - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &upd)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &upd, jsonx.V1())) require.NotNil(t, upd.ItemCollectionMetrics) assert.Contains(t, upd.ItemCollectionMetrics.ItemCollectionKey, "pk") assert.NotContains(t, upd.ItemCollectionMetrics.ItemCollectionKey, "sk") @@ -325,7 +326,7 @@ func TestDeleteUpdate_ItemCollectionMetrics(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var del icmResult - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &del)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &del, jsonx.V1())) require.NotNil(t, del.ItemCollectionMetrics) assert.Contains(t, del.ItemCollectionMetrics.ItemCollectionKey, "pk") assert.NotContains(t, del.ItemCollectionMetrics.ItemCollectionKey, "sk") @@ -374,7 +375,7 @@ func TestBatchWriteItem_ItemCollectionMetrics(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var withMetrics batchWriteICMResult - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &withMetrics)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &withMetrics, jsonx.V1())) require.Contains(t, withMetrics.ItemCollectionMetrics, "bw-icm-tbl") metrics := withMetrics.ItemCollectionMetrics["bw-icm-tbl"] require.Len(t, metrics, 2, "one metric per PutRequest") @@ -430,7 +431,7 @@ func TestTransactWriteItems_ItemCollectionMetrics(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var withMetrics batchWriteICMResult - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &withMetrics)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &withMetrics, jsonx.V1())) require.Contains(t, withMetrics.ItemCollectionMetrics, "tw-icm-tbl") metrics := withMetrics.ItemCollectionMetrics["tw-icm-tbl"] require.Len(t, metrics, 2, "one metric per Put transact item") diff --git a/services/dynamodb/json_check_test.go b/services/dynamodb/json_check_test.go index 3530cac46e..85f9a17266 100644 --- a/services/dynamodb/json_check_test.go +++ b/services/dynamodb/json_check_test.go @@ -1,9 +1,10 @@ package dynamodb_test import ( - "encoding/json" + "encoding/json/v2" "testing" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" "github.com/aws/aws-sdk-go-v2/aws" @@ -49,7 +50,7 @@ func TestSDKJSONMarshaling(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - b, err := json.Marshal(tt.input) + b, err := json.Marshal(tt.input, jsonx.V1()) require.NoError(t, err) require.NotEmpty(t, b) t.Logf("%s JSON: %s", tt.name, string(b)) diff --git a/services/dynamodb/kinesis_streaming_test.go b/services/dynamodb/kinesis_streaming_test.go index 73c9a27fa2..dddf67c95c 100644 --- a/services/dynamodb/kinesis_streaming_test.go +++ b/services/dynamodb/kinesis_streaming_test.go @@ -1,7 +1,7 @@ package dynamodb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -153,7 +154,7 @@ func TestKinesisDestinations(t *testing.T) { assert.Equal(t, tt.wantStatus, code) if tt.wantBodyContains != "" { - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), tt.wantBodyContains) } }) @@ -262,7 +263,7 @@ func TestEnableKinesisStreamingDestination(t *testing.T) { assert.Equal(t, tt.wantStatus, code) if tt.wantBodyContains != "" { - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), tt.wantBodyContains) } }) @@ -492,7 +493,7 @@ func TestUpdateKinesisDestination_NotFound(t *testing.T) { "StreamArn": "arn:aws:kinesis:us-east-1:123:stream/absent", }) require.Equal(t, http.StatusBadRequest, code) - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), "ResourceNotFoundException") } diff --git a/services/dynamodb/partiql.go b/services/dynamodb/partiql.go index 14461e8cc7..7aa3483949 100644 --- a/services/dynamodb/partiql.go +++ b/services/dynamodb/partiql.go @@ -3,7 +3,7 @@ package dynamodb import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "log/slog" @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -304,7 +305,7 @@ func (r *partiQLRunner) executeStatement( // handleExecuteStatement routes to specific DML/DQL handlers based on the statement type. func (h *DynamoDBHandler) handleExecuteStatement(ctx context.Context, body []byte) (any, error) { var req executeStatementRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -336,7 +337,7 @@ func (h *DynamoDBHandler) handleBatchExecuteStatement( body []byte, ) (any, error) { var req batchExecuteStatementRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -882,7 +883,7 @@ func encodePartiQLNextToken(lastKey map[string]types.AttributeValue) string { wire := models.FromSDKItem(lastKey) - b, err := json.Marshal(wire) + b, err := json.Marshal(wire, jsonx.V1()) if err != nil { return "" } @@ -903,7 +904,7 @@ func decodePartiQLNextToken(token string) map[string]types.AttributeValue { } var wire map[string]any - if unmarshalErr := json.Unmarshal(b, &wire); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(b, &wire, jsonx.V1()); unmarshalErr != nil { return nil } diff --git a/services/dynamodb/partiql_test.go b/services/dynamodb/partiql_test.go index 7776ce05ab..9addcc9c45 100644 --- a/services/dynamodb/partiql_test.go +++ b/services/dynamodb/partiql_test.go @@ -3,7 +3,7 @@ package dynamodb_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "log/slog" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -92,7 +93,7 @@ func partiqlItemsFrom(t *testing.T, rec *httptest.ResponseRecorder) []any { require.Equal(t, http.StatusOK, rec.Code, "response body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Items"].([]any) require.True(t, ok, "Items field missing or wrong type") @@ -335,7 +336,7 @@ func TestPartiQL_Insert(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) item, ok := getResp["Item"].(map[string]any) require.True(t, ok, "item not found for pk=%q", tc.lookupPK) assert.Equal(t, tc.wantVal, item[tc.wantAttr], "attribute %q mismatch", tc.wantAttr) @@ -415,7 +416,7 @@ func TestPartiQL_Update(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) item := getResp["Item"].(map[string]any) assert.Equal(t, tc.wantVal, item[tc.wantAttr]) }) @@ -490,7 +491,7 @@ func TestPartiQL_Delete(t *testing.T) { getRec := doRequest(t, handler, "DynamoDB_20120810.GetItem", getBody) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Nil(t, getResp["Item"], "deleted item should not be present") // Surviving item must still be there. @@ -501,7 +502,7 @@ func TestPartiQL_Delete(t *testing.T) { getRec2 := doRequest(t, handler, "DynamoDB_20120810.GetItem", getBody2) var getResp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2, jsonx.V1())) assert.NotNil(t, getResp2["Item"], "surviving item should still be present") }) } @@ -582,7 +583,7 @@ func TestPartiQL_CompositeKey_CaseInsensitiveAND(t *testing.T) { getRec := doRequest(t, handler, "DynamoDB_20120810.GetItem", getBody) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) if tc.wantGone { assert.Nil(t, getResp["Item"]) @@ -670,7 +671,7 @@ func TestPartiQL_Batch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) responses, ok := resp["Responses"].([]any) require.True(t, ok) require.Len(t, responses, tc.wantLen) @@ -1071,7 +1072,7 @@ func TestBatchExecuteStatement_ParameterConversionFailure_ErrorCode(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) responses, ok := resp["Responses"].([]any) require.True(t, ok) require.Len(t, responses, 1) diff --git a/services/dynamodb/perf_sweep_bench_test.go b/services/dynamodb/perf_sweep_bench_test.go index cf3580f611..9019cc0516 100644 --- a/services/dynamodb/perf_sweep_bench_test.go +++ b/services/dynamodb/perf_sweep_bench_test.go @@ -6,7 +6,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -22,7 +23,7 @@ import ( func invokeOpB(tb testing.TB, handler *dynamodb.DynamoDBHandler, action string, body any) (int, map[string]any) { tb.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(tb, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -32,7 +33,7 @@ func invokeOpB(tb testing.TB, handler *dynamodb.DynamoDBHandler, action string, var resp map[string]any if w.Body.Len() > 0 { - require.NoError(tb, json.Unmarshal(w.Body.Bytes(), &resp)) + require.NoError(tb, json.Unmarshal(w.Body.Bytes(), &resp, jsonx.V1())) } return w.Code, resp diff --git a/services/dynamodb/persistence.go b/services/dynamodb/persistence.go index 6d7f7f1bee..aa0416bba0 100644 --- a/services/dynamodb/persistence.go +++ b/services/dynamodb/persistence.go @@ -2,10 +2,12 @@ package dynamodb import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "log/slog" "strconv" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" @@ -35,7 +37,7 @@ type dbSnapshot struct { type dbSnapshotWire struct { DefaultRegion string `json:"defaultRegion"` AccountID string `json:"accountID"` - Tables json.RawMessage `json:"tables"` + Tables jsontext.Value `json:"tables"` Backups []*Backup `json:"backups,omitempty"` GlobalTables []*StoredGlobalTable `json:"globalTables,omitempty"` Version int `json:"version"` @@ -67,7 +69,7 @@ func (db *InMemoryDB) Snapshot(ctx context.Context) []byte { AccountID: db.accountID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "DynamoDB: failed to serialise snapshot; state will not be persisted", @@ -82,16 +84,16 @@ func (db *InMemoryDB) Snapshot(ctx context.Context) []byte { // marshalTablesRLocked marshals each table under its own table.mu.RLock, then reassembles the JSON array. // Reassembling per-table bytes, rather than marshaling the slice directly, keeps the wire format byte-identical. -func marshalTablesRLocked(tables []*Table) (json.RawMessage, error) { +func marshalTablesRLocked(tables []*Table) (jsontext.Value, error) { if len(tables) == 0 { - return json.RawMessage("null"), nil + return jsontext.Value("null"), nil } - parts := make([]json.RawMessage, len(tables)) + parts := make([]jsontext.Value, len(tables)) for i, t := range tables { t.mu.RLock("Snapshot") - data, err := json.Marshal(t) + data, err := json.Marshal(t, jsonx.V1()) t.mu.RUnlock() if err != nil { @@ -101,7 +103,7 @@ func marshalTablesRLocked(tables []*Table) (json.RawMessage, error) { parts[i] = data } - return json.Marshal(parts) + return json.Marshal(parts, jsonx.V1()) } // Restore loads backend state from a JSON snapshot. diff --git a/services/dynamodb/realism_test.go b/services/dynamodb/realism_test.go index 4393067ce6..96e85df1e9 100644 --- a/services/dynamodb/realism_test.go +++ b/services/dynamodb/realism_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -57,7 +58,7 @@ func TestHandler_Realism(t *testing.T) { Items []map[string]any `json:"Items"` ScannedCount int `json:"ScannedCount"` } - err := json.Unmarshal(w.Body.Bytes(), &output) + err := json.Unmarshal(w.Body.Bytes(), &output, jsonx.V1()) require.NoError(t, err) assert.Less(t, len(output.Items), 20, "Scan should have truncated results") @@ -117,7 +118,7 @@ func TestHandler_Realism(t *testing.T) { LastEvaluatedKey map[string]any `json:"LastEvaluatedKey"` Items []map[string]any `json:"Items"` } - err := json.Unmarshal(w.Body.Bytes(), &output) + err := json.Unmarshal(w.Body.Bytes(), &output, jsonx.V1()) require.NoError(t, err) assert.Less(t, len(output.Items), 20, "Query should have truncated results") @@ -170,7 +171,7 @@ func TestHandler_Realism(t *testing.T) { }, }, } - body, _ := json.Marshal(batchInput) + body, _ := json.Marshal(batchInput, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", "DynamoDB_20120810.BatchGetItem") w := httptest.NewRecorder() @@ -185,7 +186,7 @@ func TestHandler_Realism(t *testing.T) { Responses map[string][]map[string]any `json:"Responses"` UnprocessedKeys map[string]any `json:"UnprocessedKeys"` } - err := json.Unmarshal(w.Body.Bytes(), &output) + err := json.Unmarshal(w.Body.Bytes(), &output, jsonx.V1()) require.NoError(t, err) totalReturned := len(output.Responses[tableName]) diff --git a/services/dynamodb/resource_policy_test.go b/services/dynamodb/resource_policy_test.go index b6edf0914d..413c65588c 100644 --- a/services/dynamodb/resource_policy_test.go +++ b/services/dynamodb/resource_policy_test.go @@ -1,7 +1,7 @@ package dynamodb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" ) @@ -77,7 +78,7 @@ func TestResourcePolicy(t *testing.T) { handler := dynamodb.NewHandler(backend) code, resp := invokeOp(t, handler, "GetResourcePolicy", map[string]any{"ResourceArn": ""}) assert.Equal(t, http.StatusBadRequest, code) - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), "ValidationException") }) @@ -100,7 +101,7 @@ func TestResourcePolicy(t *testing.T) { map[string]any{"ResourceArn": tableARN}, ) assert.Equal(t, http.StatusOK, code2) - bodyBytes, _ := json.Marshal(resp2) + bodyBytes, _ := json.Marshal(resp2, jsonx.V1()) assert.Contains(t, string(bodyBytes), "2012-10-17") }) @@ -116,7 +117,7 @@ func TestResourcePolicy(t *testing.T) { map[string]any{"ResourceArn": tableARN}, ) assert.Equal(t, http.StatusBadRequest, code) - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), "ValidationException") }) @@ -145,7 +146,7 @@ func TestResourcePolicy(t *testing.T) { map[string]any{"ResourceArn": ""}, ) assert.Equal(t, http.StatusBadRequest, code) - bodyBytes, _ := json.Marshal(resp) + bodyBytes, _ := json.Marshal(resp, jsonx.V1()) assert.Contains(t, string(bodyBytes), "ValidationException") }) } diff --git a/services/dynamodb/search_vectors_test.go b/services/dynamodb/search_vectors_test.go index 708ceb3bf3..1ca8e7e6e6 100644 --- a/services/dynamodb/search_vectors_test.go +++ b/services/dynamodb/search_vectors_test.go @@ -2,7 +2,7 @@ package dynamodb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -63,7 +64,7 @@ func TestSearchVectors(t *testing.T) { require.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], "ResourceNotFoundException") assert.Contains(t, errBody["message"], "table not found") }) @@ -82,7 +83,7 @@ func TestSearchVectors(t *testing.T) { require.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], "ResourceNotFoundException") assert.Contains(t, errBody["message"], "Index: vec-index not found") }) diff --git a/services/dynamodb/test_utils_test.go b/services/dynamodb/test_utils_test.go index 762f85ea1e..54f53bc8d0 100644 --- a/services/dynamodb/test_utils_test.go +++ b/services/dynamodb/test_utils_test.go @@ -3,7 +3,7 @@ package dynamodb_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( sdkddb "github.com/aws/aws-sdk-go-v2/service/dynamodb" sdktypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" "github.com/blackbirdworks/gopherstack/services/dynamodb" @@ -23,7 +24,7 @@ import ( func mustUnmarshal[T any](t *testing.T, jsonStr string) T { t.Helper() var val T - err := json.Unmarshal([]byte(jsonStr), &val) + err := json.Unmarshal([]byte(jsonStr), &val, jsonx.V1()) require.NoError(t, err) return val @@ -190,7 +191,7 @@ func invokeOp( ) (int, map[string]any) { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -201,7 +202,7 @@ func invokeOp( var resp map[string]any if w.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp, jsonx.V1())) } return w.Code, resp diff --git a/services/dynamodb/transact_ops.go b/services/dynamodb/transact_ops.go index cc2b4512e6..cfcfed186a 100644 --- a/services/dynamodb/transact_ops.go +++ b/services/dynamodb/transact_ops.go @@ -4,12 +4,13 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "sort" "time" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" @@ -210,7 +211,7 @@ func commitTransactTokenLocked(db *InMemoryDB, token, hash string) { // preserves TransactItems' slice order and each Go struct's fixed field // order, so the same request always hashes the same way. func hashTransactWriteItems(items []types.TransactWriteItem) string { - b, err := json.Marshal(items) + b, err := json.Marshal(items, jsonx.V1()) if err != nil { return "" } diff --git a/services/dynamodb/transact_validation.go b/services/dynamodb/transact_validation.go index 5a5c39ac1c..56732069f7 100644 --- a/services/dynamodb/transact_validation.go +++ b/services/dynamodb/transact_validation.go @@ -5,12 +5,13 @@ package dynamodb import ( - "encoding/json" + "encoding/json/v2" "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -118,7 +119,7 @@ func checkTransactWriteItemSizeAndDupe( // validation error), so it's not surfaced — the item is simply not // tracked in seen and duplicate detection silently skips it, matching // the pre-refactor loop's "continue on marshal error" behavior. - keyBytes, marshalErr := json.Marshal(wireKey) + keyBytes, marshalErr := json.Marshal(wireKey, jsonx.V1()) if marshalErr == nil { twk := transactWriteKey{TableName: tableName, KeyJSON: string(keyBytes)} if seen[twk] { diff --git a/services/dynamodb/wire_codec_internal_test.go b/services/dynamodb/wire_codec_internal_test.go index 8c406032b1..e9b211e97f 100644 --- a/services/dynamodb/wire_codec_internal_test.go +++ b/services/dynamodb/wire_codec_internal_test.go @@ -2,7 +2,8 @@ package dynamodb import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math/rand/v2" "strconv" "strings" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -117,7 +119,7 @@ func TestAppendJSONString_MatchesEncodingJSON(t *testing.T) { } for _, s := range corpus { - want, err := json.Marshal(s) + want, err := json.Marshal(s, jsonx.V1()) require.NoError(t, err) assert.Equal(t, string(want), string(appendJSONString(nil, s)), "input %q", s) } @@ -243,7 +245,7 @@ func TestEncoders_MatchLegacyMarshal(t *testing.T) { items[i] = randItem(rng, 0) } - want, err := json.Marshal(tt.legacy(it, items)) + want, err := json.Marshal(tt.legacy(it, items), jsonx.V1()) require.NoError(t, err) got, release, err := marshalResponse(tt.fast(it, items)) @@ -294,7 +296,7 @@ func TestEncoders_EmptyAndNilOutputs(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - want, err := json.Marshal(tt.legacy()) + want, err := json.Marshal(tt.legacy(), jsonx.V1()) require.NoError(t, err) got, release, err := marshalResponse(tt.fast()) @@ -307,7 +309,7 @@ func TestEncoders_EmptyAndNilOutputs(t *testing.T) { func legacyDecode[In, SDK any](body string, toSDK func(*In) (*SDK, error)) (*SDK, error) { var in In - if err := json.Unmarshal([]byte(body), &in); err != nil { + if err := json.Unmarshal([]byte(body), &in, jsonx.V1()); err != nil { return nil, err } @@ -499,10 +501,15 @@ func TestDecoders_RandomItemsMatchLegacy(t *testing.T) { item := randItem(rng, 0) wireItem := models.FromSDKItem(item) - raw, err := json.Marshal(map[string]any{"TableName": "t", "Item": wireItem}) + raw, err := json.Marshal(map[string]any{"TableName": "t", "Item": wireItem}, jsonx.V1()) require.NoError(t, err) - indented, err := json.MarshalIndent(map[string]any{"TableName": "t", "Item": wireItem}, "", " ") + indented, err := json.Marshal( + map[string]any{"TableName": "t", "Item": wireItem}, + jsonx.V1(), + jsontext.Multiline(true), + jsontext.WithIndent(" "), + ) require.NoError(t, err) for _, body := range [][]byte{raw, indented, pythonEscape(raw)} { @@ -562,7 +569,7 @@ func TestTopLevelStrings_MatchesStructDecode(t *testing.T) { BackupArn string `json:"BackupArn"` } - legacyErr := json.Unmarshal([]byte(body), &data) + legacyErr := json.Unmarshal([]byte(body), &data, jsonx.V1()) table, backup, ok := topLevelStrings([]byte(body)) if !ok { @@ -580,7 +587,7 @@ func legacyWireBytes(t *testing.T, item map[string]any) []byte { t.Helper() sdk, _ := models.ToSDKItem(item) - out, err := json.Marshal(models.FromSDKItem(sdk)) + out, err := json.Marshal(models.FromSDKItem(sdk), jsonx.V1()) require.NoError(t, err) return out @@ -590,11 +597,11 @@ func TestAppendWireItem_MatchesLegacyPipeline(t *testing.T) { t.Parallel() jsonRoundTrip := func(item map[string]any) map[string]any { - raw, err := json.Marshal(item) + raw, err := json.Marshal(item, jsonx.V1()) require.NoError(t, err) var out map[string]any - require.NoError(t, json.Unmarshal(raw, &out)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1())) return out } diff --git a/services/dynamodb/wire_encode.go b/services/dynamodb/wire_encode.go index 886cdf1cda..1fd2c53802 100644 --- a/services/dynamodb/wire_encode.go +++ b/services/dynamodb/wire_encode.go @@ -2,7 +2,7 @@ package dynamodb import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "slices" "strconv" "sync" @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/dynamodb/models" ) @@ -63,7 +64,7 @@ func marshalResponse(v any) ([]byte, func(), error) { return e.bytes(), e.release, nil } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) return b, noopRelease, err } @@ -322,7 +323,7 @@ func appendSDKItems(dst []byte, items []map[string]types.AttributeValue) []byte // appendMarshaled splices json.Marshal(v) for rarely-populated sub-documents. func appendMarshaled(dst []byte, v any) []byte { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return append(dst, "null"...) } diff --git a/services/dynamodbstreams/handler.go b/services/dynamodbstreams/handler.go index 55546fec68..c6284293af 100644 --- a/services/dynamodbstreams/handler.go +++ b/services/dynamodbstreams/handler.go @@ -2,7 +2,7 @@ package dynamodbstreams import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "hash/crc32" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" @@ -99,7 +100,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -154,7 +155,7 @@ func (h *Handler) Handler() echo.HandlerFunc { return h.handleError(ctx, c, operation, reqErr) } - payload, err := json.Marshal(response) + payload, err := json.Marshal(response, jsonx.V1()) if err != nil { log.ErrorContext(ctx, "failed to marshal JSON response", "error", err) @@ -203,7 +204,7 @@ func dispatchGetShardIterator( ) (any, error) { var input dynamodbstreams.GetShardIteratorInput if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errSerialization, err) } } @@ -230,7 +231,7 @@ func dispatchListStreams( ) (any, error) { var input dynamodbstreams.ListStreamsInput if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errSerialization, err) } } @@ -262,7 +263,7 @@ func dispatchGetRecords( ) (any, error) { var input dynamodbstreams.GetRecordsInput if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errSerialization, err) } } @@ -286,7 +287,7 @@ func writeDynamoDBStreamsDispatchError(c *echo.Context, message string) error { body, _ := json.Marshal(map[string]string{ keyErrType: "com.amazonaws.dynamodbstreams.v20120810#InternalServerError", keyMessage: message, - }) + }, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.0") return c.JSONBlob(http.StatusInternalServerError, body) @@ -339,7 +340,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, operation stri body, _ := json.Marshal(map[string]string{ keyErrType: "com.amazonaws.dynamodbstreams.v20120810#" + operation + "Exception", keyMessage: msg, - }) + }, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.0") return c.JSONBlob(http.StatusBadRequest, body) @@ -352,7 +353,7 @@ func dispatchDescribeStream( ) (any, error) { var input dynamodbstreams.DescribeStreamInput if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errSerialization, err) } } @@ -370,7 +371,7 @@ func dispatchDescribeStream( } func writeJSONError(c *echo.Context, status int, errType, message string) error { - body, _ := json.Marshal(map[string]string{keyErrType: errType, keyMessage: message}) + body, _ := json.Marshal(map[string]string{keyErrType: errType, keyMessage: message}, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.0") return c.JSONBlob(status, body) diff --git a/services/dynamodbstreams/handler_errors_test.go b/services/dynamodbstreams/handler_errors_test.go index b8c9416e91..52a433b81e 100644 --- a/services/dynamodbstreams/handler_errors_test.go +++ b/services/dynamodbstreams/handler_errors_test.go @@ -1,13 +1,14 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -49,7 +50,7 @@ func TestHandler_ErrorPropagation_ResourceNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) // Must use a structured error type (not just the operation name). assert.Contains(t, errBody["__type"], "ResourceNotFoundException", @@ -95,7 +96,7 @@ func TestHandler_ErrorPropagation_ValidationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], tt.wantErrType, "error __type must contain %s", tt.wantErrType) }) @@ -140,7 +141,7 @@ func TestHandler_ErrorNamespace_DynamoDBStreams(t *testing.T) { assert.Equal(t, 400, resp.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &errBody, jsonx.V1())) errType := errBody["__type"] assert.Contains(t, errType, tt.wantErr) diff --git a/services/dynamodbstreams/handler_lifecycle_test.go b/services/dynamodbstreams/handler_lifecycle_test.go index 338dc26093..79cde0583c 100644 --- a/services/dynamodbstreams/handler_lifecycle_test.go +++ b/services/dynamodbstreams/handler_lifecycle_test.go @@ -1,7 +1,7 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -77,7 +78,7 @@ func TestHandler_FullLifecycle_StreamsFlow(t *testing.T) { fmt.Sprintf(`{"TableName":"%s"}`, tableName)) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) streams, ok := listOut["Streams"].([]any) require.True(t, ok) require.Len(t, streams, 1) @@ -87,7 +88,7 @@ func TestHandler_FullLifecycle_StreamsFlow(t *testing.T) { fmt.Sprintf(`{"StreamArn":"%s"}`, table.StreamARN)) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) streamDesc := descOut["StreamDescription"].(map[string]any) shards := streamDesc["Shards"].([]any) require.NotEmpty(t, shards) @@ -99,7 +100,7 @@ func TestHandler_FullLifecycle_StreamsFlow(t *testing.T) { table.StreamARN, shardID)) require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter, ok := iterOut["ShardIterator"].(string) require.True(t, ok) @@ -108,7 +109,7 @@ func TestHandler_FullLifecycle_StreamsFlow(t *testing.T) { fmt.Sprintf(`{"ShardIterator":"%s"}`, shardIter)) require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records, ok := recOut["Records"].([]any) require.True(t, ok) require.Len(t, records, 3, "must return all 3 stream events") @@ -124,7 +125,7 @@ func TestHandler_FullLifecycle_StreamsFlow(t *testing.T) { fmt.Sprintf(`{"ShardIterator":"%s"}`, nextIter)) require.Equal(t, http.StatusOK, emptyResp.Code) var emptyOut map[string]any - require.NoError(t, json.Unmarshal(emptyResp.Body.Bytes(), &emptyOut)) + require.NoError(t, json.Unmarshal(emptyResp.Body.Bytes(), &emptyOut, jsonx.V1())) emptyRecords, ok := emptyOut["Records"].([]any) if ok { assert.Empty(t, emptyRecords, "NextShardIterator must return no new records") diff --git a/services/dynamodbstreams/handler_records_test.go b/services/dynamodbstreams/handler_records_test.go index 1885edf9bb..e28b7f66a8 100644 --- a/services/dynamodbstreams/handler_records_test.go +++ b/services/dynamodbstreams/handler_records_test.go @@ -1,7 +1,7 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -30,7 +31,7 @@ func TestHandler_ErrorPropagation_InvalidShardIterator(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.NotEmpty(t, errBody["__type"], "error response must include __type") } @@ -49,7 +50,7 @@ func TestHandler_WireFormat_ApproximateCreationDateTimeIsFloat(t *testing.T) { require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter, ok := iterOut["ShardIterator"].(string) require.True(t, ok) @@ -61,7 +62,7 @@ func TestHandler_WireFormat_ApproximateCreationDateTimeIsFloat(t *testing.T) { // Parse raw JSON to verify ApproximateCreationDateTime is a number (float64), // not a string. The DynamoDB Streams JSON 1.0 protocol encodes it as epoch seconds. var rawOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &rawOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &rawOut, jsonx.V1())) records, ok := rawOut["Records"].([]any) require.True(t, ok) @@ -139,7 +140,7 @@ func TestHandler_WireFormat_AttributeValueEncoding(t *testing.T) { require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter, ok := iterOut["ShardIterator"].(string) require.True(t, ok) @@ -148,7 +149,7 @@ func TestHandler_WireFormat_AttributeValueEncoding(t *testing.T) { require.Equal(t, http.StatusOK, recResp.Code) var rawOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &rawOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &rawOut, jsonx.V1())) records := rawOut["Records"].([]any) require.Len(t, records, 1) @@ -177,7 +178,7 @@ func TestHandler_OpaqueIterator_TokenNotDecodable(t *testing.T) { require.Equal(t, http.StatusOK, w.Code) var out map[string]any - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out, jsonx.V1())) token, ok := out["ShardIterator"].(string) require.True(t, ok) @@ -231,7 +232,7 @@ func TestHandler_GetShardIterator_UnknownShardId(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], "ResourceNotFoundException") } @@ -259,7 +260,7 @@ func TestHandler_GetRecords_EventFields(t *testing.T) { iterResp := doRequest(t, handler, "GetShardIterator", iterBody) require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter := iterOut["ShardIterator"].(string) recResp := doRequest(t, handler, "GetRecords", @@ -267,7 +268,7 @@ func TestHandler_GetRecords_EventFields(t *testing.T) { require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records := recOut["Records"].([]any) require.NotEmpty(t, records) @@ -339,13 +340,13 @@ func TestHandler_GetShardIterator_AtAfterSequenceNumber_ViaHTTP(t *testing.T) { trimIterResp := doRequest(t, handler, "GetShardIterator", trimIterBody) require.Equal(t, http.StatusOK, trimIterResp.Code) var trimIterOut map[string]any - require.NoError(t, json.Unmarshal(trimIterResp.Body.Bytes(), &trimIterOut)) + require.NoError(t, json.Unmarshal(trimIterResp.Body.Bytes(), &trimIterOut, jsonx.V1())) trimIter := trimIterOut["ShardIterator"].(string) allRecResp := doRequest(t, handler, "GetRecords", fmt.Sprintf(`{"ShardIterator":"%s"}`, trimIter)) require.Equal(t, http.StatusOK, allRecResp.Code) var allRecOut map[string]any - require.NoError(t, json.Unmarshal(allRecResp.Body.Bytes(), &allRecOut)) + require.NoError(t, json.Unmarshal(allRecResp.Body.Bytes(), &allRecOut, jsonx.V1())) allRecords := allRecOut["Records"].([]any) require.Len(t, allRecords, 3) midSeq := allRecords[1].(map[string]any)["dynamodb"].(map[string]any)["SequenceNumber"].(string) @@ -379,14 +380,14 @@ func TestHandler_GetShardIterator_AtAfterSequenceNumber_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter := iterOut["ShardIterator"].(string) recResp := doRequest(t, handler, "GetRecords", fmt.Sprintf(`{"ShardIterator":"%s"}`, shardIter)) require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records, _ := recOut["Records"].([]any) assert.Len(t, records, tt.wantCount) }) @@ -426,7 +427,7 @@ func TestHandler_GetShardIterator_AtAfterSequenceNumber_MissingSequenceNumber(t assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], "ValidationException") assert.Contains(t, errBody["__type"], "dynamodbstreams") }) @@ -490,7 +491,7 @@ func TestHandler_ErrorPropagation_TrimmedDataAccess(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], "TrimmedDataAccessException") assert.Contains(t, errBody["__type"], "dynamodbstreams", "error __type must use the dynamodbstreams namespace") @@ -606,13 +607,13 @@ func TestHandler_GetRecords_StreamViewType_RecordShapes(t *testing.T) { iterResp := doRequest(t, handler, "GetShardIterator", iterBody) require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter := iterOut["ShardIterator"].(string) recResp := doRequest(t, handler, "GetRecords", fmt.Sprintf(`{"ShardIterator":"%s"}`, shardIter)) require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records := recOut["Records"].([]any) require.Len(t, records, 2, "must have exactly INSERT + MODIFY records") @@ -693,14 +694,14 @@ func TestHandler_GetRecords_Limit(t *testing.T) { iterResp := doRequest(t, handler, "GetShardIterator", iterBody) require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter := iterOut["ShardIterator"].(string) // First page: Limit=2. recResp := doRequest(t, handler, "GetRecords", fmt.Sprintf(`{"ShardIterator":"%s","Limit":2}`, shardIter)) require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records := recOut["Records"].([]any) require.Len(t, records, 2, "Limit must cap the number of records returned") @@ -711,7 +712,7 @@ func TestHandler_GetRecords_Limit(t *testing.T) { recResp2 := doRequest(t, handler, "GetRecords", fmt.Sprintf(`{"ShardIterator":"%s"}`, nextIter)) require.Equal(t, http.StatusOK, recResp2.Code) var recOut2 map[string]any - require.NoError(t, json.Unmarshal(recResp2.Body.Bytes(), &recOut2)) + require.NoError(t, json.Unmarshal(recResp2.Body.Bytes(), &recOut2, jsonx.V1())) records2 := recOut2["Records"].([]any) assert.Len(t, records2, 3, "remaining 3 records must be returned on the next page") } @@ -792,7 +793,7 @@ func TestHandler_GetShardIterator_AllIteratorTypes_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIter := iterOut["ShardIterator"].(string) recResp := doRequest(t, handler, "GetRecords", @@ -800,7 +801,7 @@ func TestHandler_GetShardIterator_AllIteratorTypes_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, recResp.Code) var recOut map[string]any - require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut)) + require.NoError(t, json.Unmarshal(recResp.Body.Bytes(), &recOut, jsonx.V1())) records, _ := recOut["Records"].([]any) assert.Len(t, records, tt.wantCount) }) diff --git a/services/dynamodbstreams/handler_streams_test.go b/services/dynamodbstreams/handler_streams_test.go index c39602d073..8875d52a2b 100644 --- a/services/dynamodbstreams/handler_streams_test.go +++ b/services/dynamodbstreams/handler_streams_test.go @@ -1,7 +1,7 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -92,7 +93,7 @@ func TestHandler_DescribeStream_ShardGenealogy(t *testing.T) { require.Equal(t, 200, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) streamDesc := descOut["StreamDescription"].(map[string]any) shards := streamDesc["Shards"].([]any) @@ -160,7 +161,7 @@ func TestHandler_DescribeStream_Pagination_ExclusiveStartShardId(t *testing.T) { fmt.Sprintf(`{"StreamArn":"%s","Limit":1}`, table.StreamARN)) require.Equal(t, 200, page1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(page1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(page1.Body.Bytes(), &out1, jsonx.V1())) desc1 := out1["StreamDescription"].(map[string]any) shards1 := desc1["Shards"].([]any) require.Len(t, shards1, 1, "Limit=1 must cap the shard list to 1") @@ -174,7 +175,7 @@ func TestHandler_DescribeStream_Pagination_ExclusiveStartShardId(t *testing.T) { fmt.Sprintf(`{"StreamArn":"%s","ExclusiveStartShardId":"%s"}`, table.StreamARN, lastShardID)) require.Equal(t, 200, page2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(page2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(page2.Body.Bytes(), &out2, jsonx.V1())) desc2 := out2["StreamDescription"].(map[string]any) shards2 := desc2["Shards"].([]any) require.NotEmpty(t, shards2, "second page must return the remaining shards") @@ -217,7 +218,7 @@ func TestHandler_ListStreams_PaginationViaHTTP(t *testing.T) { w1 := doRequest(t, handler, "ListStreams", `{"Limit":1}`) require.Equal(t, 200, w1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(w1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(w1.Body.Bytes(), &out1, jsonx.V1())) streams1 := out1["Streams"].([]any) require.Len(t, streams1, 1) lastArn1, ok := out1["LastEvaluatedStreamArn"].(string) @@ -228,7 +229,7 @@ func TestHandler_ListStreams_PaginationViaHTTP(t *testing.T) { fmt.Sprintf(`{"Limit":1,"ExclusiveStartStreamArn":"%s"}`, lastArn1)) require.Equal(t, 200, w2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &out2, jsonx.V1())) streams2 := out2["Streams"].([]any) require.Len(t, streams2, 1) @@ -270,7 +271,7 @@ func TestHandler_DescribeStream_StreamLabel(t *testing.T) { require.Equal(t, 200, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) desc, ok := out["StreamDescription"].(map[string]any) require.True(t, ok) @@ -301,7 +302,7 @@ func TestHandler_DescribeStream_CreationRequestDateTime(t *testing.T) { require.Equal(t, 200, resp.Code) var rawOut map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &rawOut)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &rawOut, jsonx.V1())) desc, ok := rawOut["StreamDescription"].(map[string]any) require.True(t, ok) @@ -326,7 +327,7 @@ func TestHandler_ListStreams_StreamLabel(t *testing.T) { require.Equal(t, 200, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) streams, ok := out["Streams"].([]any) require.True(t, ok) @@ -402,7 +403,7 @@ func TestHandler_DescribeStream_StreamARNConsistency(t *testing.T) { require.Equal(t, 200, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) desc, ok := out["StreamDescription"].(map[string]any) require.True(t, ok) diff --git a/services/dynamodbstreams/handler_test.go b/services/dynamodbstreams/handler_test.go index 54e21a7318..df56fa9cc4 100644 --- a/services/dynamodbstreams/handler_test.go +++ b/services/dynamodbstreams/handler_test.go @@ -2,7 +2,7 @@ package dynamodbstreams_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -260,7 +261,7 @@ func TestHandler_GetRecords(t *testing.T) { require.Equal(t, http.StatusOK, iterResp.Code) var iterOut map[string]any - require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut)) + require.NoError(t, json.Unmarshal(iterResp.Body.Bytes(), &iterOut, jsonx.V1())) shardIterRaw, ok := iterOut["ShardIterator"] require.True(t, ok, "ShardIterator key expected in response") diff --git a/services/dynamodbstreams/handler_wire_test.go b/services/dynamodbstreams/handler_wire_test.go index 769c5b9e53..9ab510ca51 100644 --- a/services/dynamodbstreams/handler_wire_test.go +++ b/services/dynamodbstreams/handler_wire_test.go @@ -1,7 +1,7 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" @@ -127,7 +128,7 @@ func TestHandler_WireFormat_NoResultMetadataLeak(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(descW.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descW.Body.Bytes(), &desc, jsonx.V1())) require.NotEmpty(t, desc.StreamDescription.Shards) body := `{"StreamArn":"` + streamARN + `","ShardId":"` + diff --git a/services/dynamodbstreams/isolation_test.go b/services/dynamodbstreams/isolation_test.go index 3165fc35f0..01a03171e3 100644 --- a/services/dynamodbstreams/isolation_test.go +++ b/services/dynamodbstreams/isolation_test.go @@ -2,7 +2,7 @@ package dynamodbstreams_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -101,7 +102,7 @@ func TestDynamoDBStreamsRegionIsolation(t *testing.T) { StreamArn string `json:"StreamArn"` } `json:"Streams"` } - require.NoError(t, json.Unmarshal(eastResp.Body.Bytes(), &eastOut)) + require.NoError(t, json.Unmarshal(eastResp.Body.Bytes(), &eastOut, jsonx.V1())) require.Len(t, eastOut.Streams, 1) assert.Contains(t, eastOut.Streams[0].StreamArn, "us-east-1") @@ -114,7 +115,7 @@ func TestDynamoDBStreamsRegionIsolation(t *testing.T) { StreamArn string `json:"StreamArn"` } `json:"Streams"` } - require.NoError(t, json.Unmarshal(westResp.Body.Bytes(), &westOut)) + require.NoError(t, json.Unmarshal(westResp.Body.Bytes(), &westOut, jsonx.V1())) require.Len(t, westOut.Streams, 1) assert.Contains(t, westOut.Streams[0].StreamArn, "us-west-2") @@ -158,7 +159,7 @@ func TestDynamoDBStreamsDefaultRegionFallback(t *testing.T) { StreamArn string `json:"StreamArn"` } `json:"Streams"` } - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Streams, 1) assert.Contains(t, out.Streams[0].StreamArn, "eu-central-1") } diff --git a/services/dynamodbstreams/request_errors_test.go b/services/dynamodbstreams/request_errors_test.go index 62d763f652..1f81750052 100644 --- a/services/dynamodbstreams/request_errors_test.go +++ b/services/dynamodbstreams/request_errors_test.go @@ -1,13 +1,14 @@ package dynamodbstreams_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ddbbackend "github.com/blackbirdworks/gopherstack/services/dynamodb" "github.com/blackbirdworks/gopherstack/services/dynamodbstreams" ) @@ -60,7 +61,7 @@ func TestHandler_RequestErrorShapes(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w.Code) var body map[string]string - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantType, body["__type"]) assert.NotEmpty(t, body["message"]) diff --git a/services/ec2/cleanup_test.go b/services/ec2/cleanup_test.go index 22d43bf30c..2749e02970 100644 --- a/services/ec2/cleanup_test.go +++ b/services/ec2/cleanup_test.go @@ -1,7 +1,8 @@ package ec2_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ec2" ) @@ -1208,8 +1210,8 @@ func TestSnapshot_NoRedundantInstanceIMDSOptions(t *testing.T) { data := b.Snapshot(t.Context()) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(data, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(data, &raw, jsonx.V1())) _, ok := raw["instanceIMDSOptions"] assert.False(t, ok, "instanceIMDSOptions must not be persisted; it is a dead shadow of Instance fields") diff --git a/services/ec2/client_vpn_auth_policy.go b/services/ec2/client_vpn_auth_policy.go index 65e6a9c45f..9ab34853ee 100644 --- a/services/ec2/client_vpn_auth_policy.go +++ b/services/ec2/client_vpn_auth_policy.go @@ -1,8 +1,10 @@ package ec2 import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -49,7 +51,7 @@ func (b *InMemoryBackend) ModifyClientVpnEndpointAuthorizationPolicy( if policyDocument != nil { var doc map[string]any - if err := json.Unmarshal([]byte(*policyDocument), &doc); err != nil || doc == nil { + if err := json.Unmarshal([]byte(*policyDocument), &doc, jsonx.V1()); err != nil || doc == nil { return false, fmt.Errorf("%w: PolicyDocument must be a JSON object", ErrInvalidParameter) } } diff --git a/services/ec2/docker_ports_internal_test.go b/services/ec2/docker_ports_internal_test.go index 77626e61f0..cece872646 100644 --- a/services/ec2/docker_ports_internal_test.go +++ b/services/ec2/docker_ports_internal_test.go @@ -1,7 +1,7 @@ package ec2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,8 @@ import ( mobynetwork "github.com/moby/moby/api/types/network" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestWorldOpenPorts(t *testing.T) { @@ -255,7 +257,7 @@ func TestServePublishedPorts(t *testing.T) { } var body publishedPortsResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.instance, body.InstanceID) require.Len(t, body.Ports, tt.wantPorts) assert.Equal(t, 8080, body.Ports[0].ContainerPort) diff --git a/services/ec2/images_public_catalog_test.go b/services/ec2/images_public_catalog_test.go index ec191ce956..db122f7d4b 100644 --- a/services/ec2/images_public_catalog_test.go +++ b/services/ec2/images_public_catalog_test.go @@ -1,7 +1,7 @@ package ec2_test import ( - "encoding/json" + "encoding/json/v2" "os" "strings" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ec2" ) @@ -28,7 +29,7 @@ func TestSSMPublicParameterAMIsResolve(t *testing.T) { } `json:"parameters"` } - require.NoError(t, json.Unmarshal(raw, &catalog)) + require.NoError(t, json.Unmarshal(raw, &catalog, jsonx.V1())) b := ec2.NewInMemoryBackend("000000000000", "us-east-1") client := newTestEC2Client(t, ec2.NewHandler(b)) diff --git a/services/ec2/imds_test.go b/services/ec2/imds_test.go index 56e755837c..c930de96a4 100644 --- a/services/ec2/imds_test.go +++ b/services/ec2/imds_test.go @@ -3,7 +3,7 @@ package ec2_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ec2" ) @@ -381,7 +382,7 @@ func TestIMDSIdentityDocument(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var doc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1())) assert.Equal(t, f.instanceID, doc["instanceId"]) assert.Equal(t, "us-east-1", doc["region"]) assert.Equal(t, "000000000000", doc["accountId"]) @@ -403,7 +404,7 @@ func TestIMDSIAMCredentials(t *testing.T) { require.Equal(t, http.StatusOK, info.Code) var infoDoc map[string]string - require.NoError(t, json.Unmarshal(info.Body.Bytes(), &infoDoc)) + require.NoError(t, json.Unmarshal(info.Body.Bytes(), &infoDoc, jsonx.V1())) assert.Equal(t, "Success", infoDoc["Code"]) assert.Equal(t, imdsProfile, infoDoc["InstanceProfileArn"]) assert.Equal(t, "AIPAEXAMPLE", infoDoc["InstanceProfileId"]) @@ -415,7 +416,7 @@ func TestIMDSIAMCredentials(t *testing.T) { require.Equal(t, http.StatusOK, cred.Code) var credDoc map[string]string - require.NoError(t, json.Unmarshal(cred.Body.Bytes(), &credDoc)) + require.NoError(t, json.Unmarshal(cred.Body.Bytes(), &credDoc, jsonx.V1())) assert.Equal(t, "Success", credDoc["Code"]) assert.Equal(t, "AWS-HMAC", credDoc["Type"]) assert.Equal(t, "ASIA1", credDoc["AccessKeyId"]) diff --git a/services/ec2/imds_tree.go b/services/ec2/imds_tree.go index 2e76d5aca1..857391dc43 100644 --- a/services/ec2/imds_tree.go +++ b/services/ec2/imds_tree.go @@ -4,11 +4,14 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -295,7 +298,7 @@ func (t *imdsTree) addIdentityDocument(v *imdsView) { } t.leaves["dynamic/instance-identity/document"] = func() (string, error) { - out, err := json.MarshalIndent(doc, "", imdsDocumentIndex) + out, err := json.Marshal(doc, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(imdsDocumentIndex)) return string(out), err } @@ -353,9 +356,9 @@ func (h *Handler) addIAMLeaves(t *imdsTree, v *imdsView) { updated := v.Instance.LaunchTime.UTC().Format(imdsTimeLayout) t.leaves["meta-data/iam/info"] = func() (string, error) { - out, mErr := json.MarshalIndent(imdsInfoDocument{ + out, mErr := json.Marshal(imdsInfoDocument{ Code: imdsCodeSuccess, LastUpdated: updated, InstanceProfileArn: prof.ARN, InstanceProfileID: prof.ID, - }, "", imdsDocumentIndex) + }, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(imdsDocumentIndex)) return string(out), mErr } @@ -366,11 +369,11 @@ func (h *Handler) addIAMLeaves(t *imdsTree, v *imdsView) { return "", cErr } - out, mErr := json.MarshalIndent(imdsCredentialDocument{ + out, mErr := json.Marshal(imdsCredentialDocument{ Code: imdsCodeSuccess, LastUpdated: updated, Type: imdsCredType, AccessKeyID: creds.AccessKeyID, SecretAccessKey: creds.SecretAccessKey, Token: creds.SessionToken, Expiration: creds.Expires.UTC().Format(imdsTimeLayout), - }, "", imdsDocumentIndex) + }, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(imdsDocumentIndex)) return string(out), mErr } diff --git a/services/ec2/ipam_routing_policy.go b/services/ec2/ipam_routing_policy.go index e6f2226666..a2ab92ffdd 100644 --- a/services/ec2/ipam_routing_policy.go +++ b/services/ec2/ipam_routing_policy.go @@ -1,12 +1,13 @@ package ec2 import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "time" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- IPAM Routing Policy Registrations ---- @@ -40,7 +41,7 @@ func (b *InMemoryBackend) newRoutingPolicyDeltaLocked( assocID string, doc routingPolicyDeltaDoc, ) *IpamRoutingPolicyRegistrationDelta { - body, _ := json.Marshal(doc) + body, _ := json.Marshal(doc, jsonx.V1()) delta := &IpamRoutingPolicyRegistrationDelta{ DeltaID: awsid.New("ipam-rpr-delta-"), @@ -294,7 +295,7 @@ func (b *InMemoryBackend) BatchModifyIpamRoutingPolicyRegistrations( } var entries []routingPolicyBatchEntry - if err := json.Unmarshal([]byte(deltaJSON), &entries); err != nil { + if err := json.Unmarshal([]byte(deltaJSON), &entries, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: DeltaJson is not a valid batch document: %w", ErrInvalidParameter, err) } diff --git a/services/ec2/mac_hosts.go b/services/ec2/mac_hosts.go index 2873ad48d6..32aa91d175 100644 --- a/services/ec2/mac_hosts.go +++ b/services/ec2/mac_hosts.go @@ -1,12 +1,14 @@ package ec2 import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // mac_hosts.go: a Mac Dedicated Host has no separate creation API -- it's a @@ -167,7 +169,7 @@ func validateMacCredentials(raw string) error { } var creds macCredentials - if err := json.Unmarshal([]byte(raw), &creds); err != nil { + if err := json.Unmarshal([]byte(raw), &creds, jsonx.V1()); err != nil { return fmt.Errorf("%w: MacCredentials must be a JSON object: %w", ErrInvalidParameter, err) } diff --git a/services/ec2/persistence.go b/services/ec2/persistence.go index e8ecdf3b3b..b8ea0ba82c 100644 --- a/services/ec2/persistence.go +++ b/services/ec2/persistence.go @@ -2,10 +2,12 @@ package ec2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -23,7 +25,7 @@ const ec2SnapshotVersion = 2 type snapTGWRTProp = TransitGatewayRouteTablePropagation type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` SnapshotAttributes map[string]map[string]string `json:"snapshotAttributes"` ImageDeprecated map[string]string `json:"imageDeprecated"` Tags map[string]map[string]string `json:"tags,omitempty"` @@ -167,7 +169,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { ManagedResourceVisibility: b.managedResourceDefaultVisibility, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ec2: Snapshot marshal failure", "error", err) diff --git a/services/ec2/usage_metrics.go b/services/ec2/usage_metrics.go index 42405efaae..cea32f8efd 100644 --- a/services/ec2/usage_metrics.go +++ b/services/ec2/usage_metrics.go @@ -2,7 +2,8 @@ package ec2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" @@ -10,6 +11,7 @@ import ( mobyclient "github.com/moby/moby/client" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -120,7 +122,7 @@ func (d *DockerCompute) Usage(ctx context.Context, providerID string) (InstanceU defer func() { _ = res.Body.Close() }() var stats mobycontainer.StatsResponse - if decodeErr := json.NewDecoder(res.Body).Decode(&stats); decodeErr != nil { + if decodeErr := json.UnmarshalDecode(jsontext.NewDecoder(res.Body), &stats, jsonx.V1()); decodeErr != nil { return InstanceUsage{}, fmt.Errorf("decode container stats: %w", decodeErr) } diff --git a/services/ecr/auth_token_test.go b/services/ecr/auth_token_test.go index a47ec0c9f0..c69e8a913b 100644 --- a/services/ecr/auth_token_test.go +++ b/services/ecr/auth_token_test.go @@ -6,7 +6,7 @@ package ecr_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -261,7 +262,7 @@ func TestECR_GetAuthorizationToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) authData, ok := resp["authorizationData"].([]any) require.True(t, ok) diff --git a/services/ecr/handler.go b/services/ecr/handler.go index 0cad5d68e5..93ac699393 100644 --- a/services/ecr/handler.go +++ b/services/ecr/handler.go @@ -2,7 +2,7 @@ package ecr import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -345,7 +346,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { RepositoryNames []string `json:"repositoryNames"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.RepositoryName != "" { return req.RepositoryName @@ -543,7 +544,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // ecrErr builds the error body for an ECR error response. @@ -611,14 +612,11 @@ func (h *Handler) classifyError(err error) (int, string) { } } - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, errUnknownAction): return http.StatusBadRequest, "UnknownOperationException" case errors.Is(err, ErrInvalidRepositoryName), errors.Is(err, errInvalidRequest), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return http.StatusBadRequest, "InvalidParameterException" default: return http.StatusInternalServerError, "ServerException" diff --git a/services/ecr/handler_images.go b/services/ecr/handler_images.go index e2889d9f3b..c6f80a09af 100644 --- a/services/ecr/handler_images.go +++ b/services/ecr/handler_images.go @@ -5,11 +5,13 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // batchDeleteImageInput is the request body for BatchDeleteImage. @@ -195,7 +197,7 @@ type manifestArtifactFields struct { func parseManifestArtifactFields(manifest string) (string, string) { var parsed manifestArtifactFields - if err := json.Unmarshal([]byte(manifest), &parsed); err != nil { + if err := json.Unmarshal([]byte(manifest), &parsed, jsonx.V1()); err != nil { return "", "" } @@ -212,7 +214,7 @@ func parseManifestAnnotations(manifest string) map[string]string { Annotations map[string]string `json:"annotations"` } - if err := json.Unmarshal([]byte(manifest), &parsed); err != nil || len(parsed.Annotations) == 0 { + if err := json.Unmarshal([]byte(manifest), &parsed, jsonx.V1()); err != nil || len(parsed.Annotations) == 0 { return nil } diff --git a/services/ecr/handler_test.go b/services/ecr/handler_test.go index dcf8399394..28ec8a6f8f 100644 --- a/services/ecr/handler_test.go +++ b/services/ecr/handler_test.go @@ -12,7 +12,7 @@ package ecr_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -82,7 +83,7 @@ func doECRRequest( ) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -124,7 +125,7 @@ func newHandlerWithBackend() (*ecr.Handler, *ecr.InMemoryBackend) { func doAccuracy(t *testing.T, h *ecr.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -141,7 +142,7 @@ func parseAccuracy(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } @@ -469,7 +470,7 @@ func TestECR_LazyEndpointInit(t *testing.T) { h := ecr.NewHandler(backend, nil) // First request — Host header provides the server address. - bodyBytes, err := json.Marshal(map[string]any{"repositoryName": "lazy-repo"}) + bodyBytes, err := json.Marshal(map[string]any{"repositoryName": "lazy-repo"}, jsonx.V1()) require.NoError(t, err) e := echo.New() diff --git a/services/ecr/interfaces_test.go b/services/ecr/interfaces_test.go index 77285d2699..85a072a985 100644 --- a/services/ecr/interfaces_test.go +++ b/services/ecr/interfaces_test.go @@ -7,7 +7,7 @@ package ecr_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -412,7 +413,7 @@ func TestECR_Handler_AcceptsBackendInterface(t *testing.T) { h := ecr.NewHandler(newStubBackend(), nil) - bodyBytes, err := json.Marshal(map[string]any{"repositoryName": "my-repo"}) + bodyBytes, err := json.Marshal(map[string]any{"repositoryName": "my-repo"}, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -424,7 +425,7 @@ func TestECR_Handler_AcceptsBackendInterface(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, ok := resp["repository"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-repo", repo["repositoryName"]) @@ -455,7 +456,7 @@ func TestECR_GetAuthorizationToken_ProxyEndpointFromBackend(t *testing.T) { h := ecr.NewHandler(newStubBackend(), nil) - bodyBytes, err := json.Marshal(map[string]any{}) + bodyBytes, err := json.Marshal(map[string]any{}, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -467,7 +468,7 @@ func TestECR_GetAuthorizationToken_ProxyEndpointFromBackend(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) authData := resp["authorizationData"].([]any) entry := authData[0].(map[string]any) diff --git a/services/ecr/lifecycle.go b/services/ecr/lifecycle.go index d02411d0c9..9c0505fb77 100644 --- a/services/ecr/lifecycle.go +++ b/services/ecr/lifecycle.go @@ -2,10 +2,12 @@ package ecr import ( "context" - "encoding/json" + "encoding/json/v2" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const weeksPerDays = 7 @@ -72,7 +74,7 @@ func evaluateLifecyclePolicy( } var doc lifecyclePolicyDoc - if err := json.Unmarshal([]byte(policyText), &doc); err != nil { + if err := json.Unmarshal([]byte(policyText), &doc, jsonx.V1()); err != nil { return nil } diff --git a/services/ecr/lifecycle_policy_test.go b/services/ecr/lifecycle_policy_test.go index db9a39efa6..2f2e81badb 100644 --- a/services/ecr/lifecycle_policy_test.go +++ b/services/ecr/lifecycle_policy_test.go @@ -6,13 +6,14 @@ package ecr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -174,7 +175,7 @@ func TestECR_DeleteLifecyclePolicy(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.repositoryName, out["repositoryName"]) } }) diff --git a/services/ecr/manifest_meta.go b/services/ecr/manifest_meta.go index c986c754d3..6385a0c65d 100644 --- a/services/ecr/manifest_meta.go +++ b/services/ecr/manifest_meta.go @@ -1,9 +1,12 @@ package ecr import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -33,14 +36,14 @@ type manifestMeta struct { Layers []struct { Size int64 `json:"size"` } `json:"layers"` - Manifests []json.RawMessage `json:"manifests"` + Manifests []jsontext.Value `json:"manifests"` } // manifestMediaTypeAndSize reports the manifest's media type (declared, else // inferred from its shape) and its compressed size: config plus layer sizes. func manifestMediaTypeAndSize(manifest string) (string, int64) { var m manifestMeta - if err := json.Unmarshal([]byte(manifest), &m); err != nil { + if err := json.Unmarshal([]byte(manifest), &m, jsonx.V1()); err != nil { return "", 0 } diff --git a/services/ecr/multi_region_test.go b/services/ecr/multi_region_test.go index 7e427cb3b1..5e502562ba 100644 --- a/services/ecr/multi_region_test.go +++ b/services/ecr/multi_region_test.go @@ -1,7 +1,7 @@ package ecr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -39,7 +40,7 @@ func regionCall(t *testing.T, h *ecr.Handler, region, op, body string) map[strin require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/ecr/persistence.go b/services/ecr/persistence.go index 6f3e364a1a..e36470e2b9 100644 --- a/services/ecr/persistence.go +++ b/services/ecr/persistence.go @@ -2,7 +2,7 @@ package ecr import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -52,7 +52,7 @@ const ecrSnapshotVersion = 2 // lifecycleLastEvaluated were never persisted before this conversion and // still aren't. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` RepoTags map[string]map[string]string `json:"repoTags,omitempty"` UploadedLayers map[string]map[string]int64 `json:"uploadedLayers,omitempty"` RegistryScanningConfig *RegistryScanningSettings `json:"registryScanningConfig,omitempty"` diff --git a/services/ecr/realism_validation_test.go b/services/ecr/realism_validation_test.go index f68b202195..4c0b7e0c4e 100644 --- a/services/ecr/realism_validation_test.go +++ b/services/ecr/realism_validation_test.go @@ -1,7 +1,7 @@ package ecr_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -87,7 +89,7 @@ func TestECRWire_InputValidation(t *testing.T) { } var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) assert.False(t, strings.HasPrefix(out["message"], tt.wantType), out["message"]) }) diff --git a/services/ecr/repositories_test.go b/services/ecr/repositories_test.go index e1e716ba53..40e2adce69 100644 --- a/services/ecr/repositories_test.go +++ b/services/ecr/repositories_test.go @@ -8,13 +8,14 @@ package ecr_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -53,7 +54,7 @@ func TestECR_CreateRepository(t *testing.T) { if tt.wantARN || tt.wantURI { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, ok := resp["repository"].(map[string]any) require.True(t, ok) @@ -141,7 +142,7 @@ func TestECR_DescribeRepositories(t *testing.T) { if tt.wantLen > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repos, ok := resp["repositories"].([]any) require.True(t, ok) @@ -200,7 +201,7 @@ func TestECR_DeleteRepository(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) repo, ok := resp["repository"].(map[string]any) require.True(t, ok) diff --git a/services/ecr/tags_test.go b/services/ecr/tags_test.go index 97ff4939d3..ac9f33c1b8 100644 --- a/services/ecr/tags_test.go +++ b/services/ecr/tags_test.go @@ -6,13 +6,14 @@ package ecr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecr" ) @@ -26,7 +27,7 @@ func TestECR_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) repo := createResp["repository"].(map[string]any) arn := repo["repositoryArn"].(string) @@ -35,7 +36,7 @@ func TestECR_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags, ok := out["tags"] require.True(t, ok, "response should contain 'tags' field") assert.IsType(t, []any{}, tags, "tags should be an array") @@ -253,7 +254,7 @@ func TestListTagsForResource_HandlerSortsKeys(t *testing.T) { Value string `json:"Value"` } `json:"tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) gotKeys := make([]string, 0, len(resp.Tags)) for _, tag := range resp.Tags { @@ -299,7 +300,7 @@ func TestCreateRepository_TagsRoundTrip(t *testing.T) { RepositoryARN string `json:"repositoryArn"` } `json:"repository"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp.Repository.RepositoryARN listRec := doAccuracy(t, h, "ListTagsForResource", map[string]any{ @@ -310,7 +311,7 @@ func TestCreateRepository_TagsRoundTrip(t *testing.T) { var listResp struct { Tags []any `json:"tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Tags, tt.wantLen) }) } diff --git a/services/ecr/validation.go b/services/ecr/validation.go index 5ab3c4c29d..443c356b18 100644 --- a/services/ecr/validation.go +++ b/services/ecr/validation.go @@ -1,10 +1,12 @@ package ecr import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -33,7 +35,7 @@ func validateRepositoryName(name string) error { func validateJSONObject(field, text string) error { var doc map[string]any - if err := json.Unmarshal([]byte(text), &doc); err != nil || doc == nil { + if err := json.Unmarshal([]byte(text), &doc, jsonx.V1()); err != nil || doc == nil { return invalidParam("Invalid parameter at '%s' failed to satisfy constraint: 'Invalid JSON syntax'", field) } @@ -50,7 +52,7 @@ func validateRepositoryPolicyText(text string) error { func validateLifecyclePolicyText(text string) error { var doc lifecyclePolicyDoc - if err := json.Unmarshal([]byte(text), &doc); err != nil { + if err := json.Unmarshal([]byte(text), &doc, jsonx.V1()); err != nil { return invalidParam("Lifecycle policy validation failure: Invalid JSON") } diff --git a/services/ecrpublic/digest.go b/services/ecrpublic/digest.go index e2df656a20..5c337db6b9 100644 --- a/services/ecrpublic/digest.go +++ b/services/ecrpublic/digest.go @@ -3,7 +3,9 @@ package ecrpublic import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func sha256Digest(data []byte) string { @@ -52,7 +54,7 @@ type manifestDoc struct { func parseManifest(manifest string) (manifestDoc, bool) { var m manifestDoc - return m, json.Unmarshal([]byte(manifest), &m) == nil + return m, json.Unmarshal([]byte(manifest), &m, jsonx.V1()) == nil } // referencedDigests returns every layer/config digest an image manifest diff --git a/services/ecrpublic/handler.go b/services/ecrpublic/handler.go index 92a97f83c7..3d4ac88af6 100644 --- a/services/ecrpublic/handler.go +++ b/services/ecrpublic/handler.go @@ -2,7 +2,7 @@ package ecrpublic import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -95,7 +96,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { RepositoryName string `json:"repositoryName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.RepositoryName } @@ -149,7 +150,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -234,14 +235,10 @@ func classifyError(err error) (int, string) { } } - var syntaxErr *json.SyntaxError - - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, errUnknownAction): return http.StatusBadRequest, "UnknownOperationException" - case errors.As(err, &syntaxErr), errors.As(err, &typeErr): + case jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return http.StatusBadRequest, "InvalidParameterException" case errors.Is(err, awserr.ErrNotFound): return http.StatusBadRequest, "RepositoryNotFoundException" diff --git a/services/ecrpublic/images_and_layers_test.go b/services/ecrpublic/images_and_layers_test.go index 1d26dd0d67..36c590e24a 100644 --- a/services/ecrpublic/images_and_layers_test.go +++ b/services/ecrpublic/images_and_layers_test.go @@ -4,7 +4,7 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -13,6 +13,8 @@ import ( "github.com/aws/smithy-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func layerDigest(data []byte) string { @@ -78,7 +80,7 @@ func buildManifest(configDigest string, layerDigests ...string) string { m.Layers = append(m.Layers, layer{Digest: d}) } - b, _ := json.Marshal(m) + b, _ := json.Marshal(m, jsonx.V1()) return string(b) } diff --git a/services/ecrpublic/manifest_index_test.go b/services/ecrpublic/manifest_index_test.go index f2838b621c..324c649e66 100644 --- a/services/ecrpublic/manifest_index_test.go +++ b/services/ecrpublic/manifest_index_test.go @@ -1,7 +1,7 @@ package ecrpublic_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/smithy-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func buildIndex(t *testing.T, childDigests ...string) string { @@ -28,7 +30,7 @@ func buildIndex(t *testing.T, childDigests ...string) string { doc.Manifests = append(doc.Manifests, child{Digest: d}) } - b, err := json.Marshal(doc) + b, err := json.Marshal(doc, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/ecrpublic/persistence.go b/services/ecrpublic/persistence.go index 2d5b142862..022f3e2b1a 100644 --- a/services/ecrpublic/persistence.go +++ b/services/ecrpublic/persistence.go @@ -2,7 +2,7 @@ package ecrpublic import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "maps" @@ -34,7 +34,7 @@ const ecrPublicSnapshotVersion = 1 // LayerUploads (in-flight sessions) is deliberately NOT persisted, matching // AWS: an in-progress upload does not survive a restart. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TagIndex map[string]map[string]tagBinding `json:"tagIndex,omitempty"` UploadedLayers map[string]map[string]int64 `json:"uploadedLayers,omitempty"` RegistryCatalogData RegistryCatalogData `json:"registryCatalogData"` diff --git a/services/ecrpublic/policy.go b/services/ecrpublic/policy.go index 5eecd7f604..420db903df 100644 --- a/services/ecrpublic/policy.go +++ b/services/ecrpublic/policy.go @@ -1,9 +1,11 @@ package ecrpublic import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // GetRepositoryPolicy returns the repository's resource policy text. @@ -29,7 +31,8 @@ func (b *InMemoryBackend) GetRepositoryPolicy(registryID, name string) (string, // SetRepositoryPolicy sets or replaces the repository's resource policy text. func (b *InMemoryBackend) SetRepositoryPolicy(registryID, name, policyText string) (string, error) { - if !json.Valid([]byte(policyText)) || !strings.HasPrefix(strings.TrimSpace(policyText), "{") { + if !jsontext.Value([]byte(policyText)).IsValid(jsonx.V1()) || + !strings.HasPrefix(strings.TrimSpace(policyText), "{") { return "", fmt.Errorf( "%w: Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided'", ErrInvalidParameter) diff --git a/services/ecs/capacity_provider_strategy_validation_test.go b/services/ecs/capacity_provider_strategy_validation_test.go index 355f21b0db..3f19606e49 100644 --- a/services/ecs/capacity_provider_strategy_validation_test.go +++ b/services/ecs/capacity_provider_strategy_validation_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCapacityProviderStrategy_RejectsUnknownProvider proves that every @@ -169,7 +171,7 @@ func TestCapacityProviderStrategy_AcceptsCreatedProvider(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) strategy := cluster["defaultCapacityProviderStrategy"].([]any) require.Len(t, strategy, 1) @@ -202,7 +204,7 @@ func TestRunTask_CapacityProviderStrategy_SetsCapacityProviderName(t *testing.T) require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) require.Len(t, tasks, 1) task := tasks[0].(map[string]any) diff --git a/services/ecs/docker_inputs.go b/services/ecs/docker_inputs.go index de18c2b688..3178305555 100644 --- a/services/ecs/docker_inputs.go +++ b/services/ecs/docker_inputs.go @@ -2,11 +2,12 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/registryauth" ) @@ -85,7 +86,7 @@ func (b *InMemoryBackend) resolveRegistryAuth(ctx context.Context, cd ContainerD Password string `json:"password"` } - if json.Unmarshal([]byte(raw), &creds) != nil || creds.Username == "" || creds.Password == "" { + if json.Unmarshal([]byte(raw), &creds, jsonx.V1()) != nil || creds.Username == "" || creds.Password == "" { return "", secretLaunchError(sourceASM, param, errRegistryCredentials) } diff --git a/services/ecs/docker_inputs_internal_test.go b/services/ecs/docker_inputs_internal_test.go index 21d2726c35..daf68e93f0 100644 --- a/services/ecs/docker_inputs_internal_test.go +++ b/services/ecs/docker_inputs_internal_test.go @@ -3,11 +3,13 @@ package ecs import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type fakeEnvStore struct { @@ -122,7 +124,7 @@ func TestRepositoryCredentials(t *testing.T) { var ac map[string]string - require.NoError(t, json.Unmarshal(raw, &ac)) + require.NoError(t, json.Unmarshal(raw, &ac, jsonx.V1())) assert.Equal(t, tt.wantUser, ac["username"]) assert.Equal(t, "hunter2", ac["password"]) assert.Equal(t, "registry.example.com", ac["serveraddress"]) diff --git a/services/ecs/docker_linklocal_live_internal_test.go b/services/ecs/docker_linklocal_live_internal_test.go index 5c36c5694c..cb2618da67 100644 --- a/services/ecs/docker_linklocal_live_internal_test.go +++ b/services/ecs/docker_linklocal_live_internal_test.go @@ -2,7 +2,8 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net" "net/http" @@ -21,6 +22,7 @@ import ( dockernetwork "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/network" "github.com/blackbirdworks/gopherstack/internal/dockercompat/client" "github.com/blackbirdworks/gopherstack/internal/linklocal" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -85,7 +87,10 @@ func TestDockerRunnerLiveRelativeCredentialsURI(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/_gopherstack/ecs/credentials/"+taskID, func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]string{"AccessKeyId": "ASIALIVE", "Token": "tok"}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{"AccessKeyId": "ASIALIVE", "Token": "tok"}, + ) }) srv := &http.Server{Handler: mux, ReadHeaderTimeout: time.Second} diff --git a/services/ecs/docker_secrets.go b/services/ecs/docker_secrets.go index 0adaa9bf32..2ba02d237b 100644 --- a/services/ecs/docker_secrets.go +++ b/services/ecs/docker_secrets.go @@ -2,10 +2,13 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -169,8 +172,8 @@ func resolveParameter(ctx context.Context, resolver SecretsResolver, ref secretR } func jsonSecretField(secret, key string) (string, error) { - var fields map[string]json.RawMessage - if err := json.Unmarshal([]byte(secret), &fields); err != nil { + var fields map[string]jsontext.Value + if err := json.Unmarshal([]byte(secret), &fields, jsonx.V1()); err != nil { return "", fmt.Errorf("secret value is not a JSON object: %w", err) } @@ -180,7 +183,7 @@ func jsonSecretField(secret, key string) (string, error) { } var s string - if json.Unmarshal(raw, &s) == nil { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil { return s, nil } diff --git a/services/ecs/docker_usage.go b/services/ecs/docker_usage.go index 05b4a3dc18..18ede3b67b 100644 --- a/services/ecs/docker_usage.go +++ b/services/ecs/docker_usage.go @@ -2,7 +2,11 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const cpuUnitsPerVCPU = 1024.0 @@ -66,7 +70,7 @@ func (r *realDockerRunner) TaskUsage(ctx context.Context, taskArn string) (float var sample dockerStatsSample - decodeErr := json.NewDecoder(body).Decode(&sample) + decodeErr := json.UnmarshalDecode(jsontext.NewDecoder(body), &sample, jsonx.V1()) _ = body.Close() if decodeErr != nil { @@ -83,7 +87,7 @@ func (r *realDockerRunner) TaskUsage(ctx context.Context, taskArn string) (float } // ContainerStatsJSON returns one raw Docker stats sample for containerID. -func (r *realDockerRunner) ContainerStatsJSON(ctx context.Context, containerID string) (json.RawMessage, error) { +func (r *realDockerRunner) ContainerStatsJSON(ctx context.Context, containerID string) (jsontext.Value, error) { body, err := r.cli.ContainerStats(ctx, containerID) if err != nil { return nil, err @@ -91,8 +95,8 @@ func (r *realDockerRunner) ContainerStatsJSON(ctx context.Context, containerID s defer body.Close() - var raw json.RawMessage - if decErr := json.NewDecoder(body).Decode(&raw); decErr != nil { + var raw jsontext.Value + if decErr := json.UnmarshalDecode(jsontext.NewDecoder(body), &raw, jsonx.V1()); decErr != nil { return nil, decErr } diff --git a/services/ecs/events.go b/services/ecs/events.go index fa8fb9cc12..3350db2612 100644 --- a/services/ecs/events.go +++ b/services/ecs/events.go @@ -2,10 +2,11 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -47,7 +48,7 @@ func (b *InMemoryBackend) emitLocked(detailType string, resources []string, deta return } - body, err := json.Marshal(detail) + body, err := json.Marshal(detail, jsonx.V1()) if err != nil { return } diff --git a/services/ecs/events_internal_test.go b/services/ecs/events_internal_test.go index d6a29817ca..1f63c20fa4 100644 --- a/services/ecs/events_internal_test.go +++ b/services/ecs/events_internal_test.go @@ -2,7 +2,7 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type publishedEvent struct { @@ -30,7 +31,7 @@ func (p *recordingPublisher) PublishEvent( ctx context.Context, source, detailType string, resources []string, detail string, ) { var d map[string]any - if err := json.Unmarshal([]byte(detail), &d); err != nil { + if err := json.Unmarshal([]byte(detail), &d, jsonx.V1()); err != nil { panic(err) } diff --git a/services/ecs/handler.go b/services/ecs/handler.go index b5392ba8e1..c50b27c852 100644 --- a/services/ecs/handler.go +++ b/services/ecs/handler.go @@ -2,7 +2,7 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -194,7 +195,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { TaskSet string `json:"taskSet"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ClusterName != "": @@ -355,7 +356,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // Reset clears the backend state. @@ -368,9 +369,6 @@ func (h *Handler) Reset() { } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): code := errorCode(err) @@ -395,8 +393,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err }, ) case errors.Is(err, awserr.ErrInvalidParameter), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): code := errorCode(err) return c.JSON( diff --git a/services/ecs/handler_account_settings_test.go b/services/ecs/handler_account_settings_test.go index f76f2d5120..7e44d82754 100644 --- a/services/ecs/handler_account_settings_test.go +++ b/services/ecs/handler_account_settings_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -40,7 +41,7 @@ func TestAccountSettings_AllValidNames(t *testing.T) { require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) settings := listOut["settings"].([]any) assert.GreaterOrEqual(t, len(settings), len(validNames)) } @@ -71,7 +72,7 @@ func TestAccountSettings_PutDefault_vs_Put(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) settings := listOut["settings"].([]any) assert.GreaterOrEqual(t, len(settings), 1) } @@ -99,7 +100,7 @@ func TestAccountSettings_Delete(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) settings := listOut["settings"].([]any) assert.Empty(t, settings) } @@ -123,7 +124,7 @@ func TestAccountSettings_OverwriteExisting(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) settings := listOut["settings"].([]any) require.NotEmpty(t, settings) // Last write wins @@ -150,7 +151,7 @@ func TestAccountSettings_TaskLongArnFormat(t *testing.T) { require.Equal(t, http.StatusOK, putResp.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut, jsonx.V1())) setting := putOut["setting"].(map[string]any) assert.Equal(t, "taskLongArnFormat", setting["name"]) assert.Equal(t, "enabled", setting["value"]) @@ -168,7 +169,7 @@ func TestAccountSettings_ContainerInstanceLongArnFormat(t *testing.T) { require.Equal(t, http.StatusOK, putResp.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut, jsonx.V1())) setting := putOut["setting"].(map[string]any) assert.Equal(t, "containerInstanceLongArnFormat", setting["name"]) assert.Equal(t, "enabled", setting["value"]) @@ -192,7 +193,7 @@ func TestAccountSettings_FargateSettings(t *testing.T) { require.Equal(t, http.StatusOK, putResp.Code, "PutAccountSetting %s", name) var putOut map[string]any - require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putResp.Body.Bytes(), &putOut, jsonx.V1())) setting := putOut["setting"].(map[string]any) assert.Equal(t, name, setting["name"]) } @@ -215,7 +216,7 @@ func TestAccountSettings_ServiceLongArnFormat(t *testing.T) { require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) settings := listOut["settings"].([]any) require.NotEmpty(t, settings) assert.Equal(t, "enabled", settings[0].(map[string]any)["value"]) @@ -273,7 +274,7 @@ func TestECS_DeleteAccountSetting(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) setting, ok := resp["setting"].(map[string]any) require.True(t, ok) @@ -293,7 +294,7 @@ func TestECS_ListAccountSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) settings, ok := resp["settings"].([]any) require.True(t, ok) @@ -321,7 +322,7 @@ func TestECS_ListAccountSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) settings, ok := resp["settings"].([]any) require.True(t, ok) @@ -376,7 +377,7 @@ func TestECS_PutAccountSetting(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) setting, ok := resp["setting"].(map[string]any) require.True(t, ok) @@ -423,7 +424,7 @@ func TestECS_PutAccountSettingDefault(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) setting, ok := resp["setting"].(map[string]any) require.True(t, ok) diff --git a/services/ecs/handler_agent_ops_test.go b/services/ecs/handler_agent_ops_test.go index 99064f0c5b..be2fc4b57a 100644 --- a/services/ecs/handler_agent_ops_test.go +++ b/services/ecs/handler_agent_ops_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -34,7 +35,7 @@ func TestECS_DescribeServiceRevisions(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svc := svcResp["service"].(map[string]any) deployments := svc["deployments"].([]any) @@ -93,7 +94,7 @@ func TestECS_DescribeServiceRevisions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) revs, _ := resp["serviceRevisions"].([]any) failures, _ := resp["failures"].([]any) @@ -158,7 +159,7 @@ func TestECS_DescribeServiceRevisions_TracksUpdate(t *testing.T) { }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) revs, _ := resp["serviceRevisions"].([]any) assert.Len(t, revs, 2, "both the original and rotated revision remain describable") } @@ -177,7 +178,7 @@ func TestECS_DiscoverPollEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["endpoint"], testRegion) assert.Contains(t, resp["serviceConnectEndpoint"], testRegion) @@ -202,7 +203,7 @@ func TestECS_SubmitTaskStateChange(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp, jsonx.V1())) tasks := runResp["tasks"].([]any) require.Len(t, tasks, 1) task := tasks[0].(map[string]any) @@ -220,7 +221,7 @@ func TestECS_SubmitTaskStateChange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACK", resp["acknowledgment"]) descRec := doECSRequest(t, h, "DescribeTasks", map[string]any{ @@ -230,7 +231,7 @@ func TestECS_SubmitTaskStateChange(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) descTasks := descResp["tasks"].([]any) require.Len(t, descTasks, 1) @@ -259,7 +260,7 @@ func TestECS_SubmitTaskStateChange_UnknownTaskIsNoop(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACK", resp["acknowledgment"]) } @@ -278,7 +279,7 @@ func TestECS_SubmitContainerStateChange(t *testing.T) { }) var runResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp, jsonx.V1())) taskArn := runResp["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) rec := doECSRequest(t, h, "SubmitContainerStateChange", map[string]any{ @@ -299,7 +300,7 @@ func TestECS_SubmitContainerStateChange(t *testing.T) { }) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) updated := descResp["tasks"].([]any)[0].(map[string]any) container := updated["containers"].([]any)[0].(map[string]any) @@ -326,7 +327,7 @@ func TestECS_SubmitAttachmentStateChanges(t *testing.T) { }) var runResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp, jsonx.V1())) task := runResp["tasks"].([]any)[0].(map[string]any) taskArn := task["taskArn"].(string) attachments, _ := task["attachments"].([]any) @@ -347,7 +348,7 @@ func TestECS_SubmitAttachmentStateChanges(t *testing.T) { }) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) updated := descResp["tasks"].([]any)[0].(map[string]any) updatedAttachments := updated["attachments"].([]any) require.NotEmpty(t, updatedAttachments) @@ -392,7 +393,7 @@ func TestHandler_DiscoverPollEndpoint(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantEndpointPfx, out["endpoint"]) if tt.wantTelemetry { assert.NotEmpty(t, out["telemetryEndpoint"]) @@ -477,7 +478,7 @@ func TestHandler_SubmitTaskStateChange(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) tasks := runOut["tasks"].([]any) require.NotEmpty(t, tasks) taskArn = tasks[0].(map[string]any)["taskArn"].(string) @@ -488,7 +489,7 @@ func TestHandler_SubmitTaskStateChange(t *testing.T) { if tt.wantACK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ACK", out["acknowledgment"]) } }) @@ -574,7 +575,7 @@ func TestHandler_SubmitContainerStateChange(t *testing.T) { }) require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) taskArn = runOut["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) } @@ -588,7 +589,7 @@ func TestHandler_SubmitContainerStateChange(t *testing.T) { if tt.wantACK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ACK", out["acknowledgment"]) } }) @@ -664,7 +665,7 @@ func TestHandler_SubmitAttachmentStateChanges(t *testing.T) { if tt.wantACK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ACK", out["acknowledgment"]) } }) @@ -695,7 +696,7 @@ func TestHandler_DescribeServiceRevisions(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) return svc["serviceArn"].(string) @@ -749,7 +750,7 @@ func TestHandler_DescribeServiceRevisions(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) revs, _ := out["serviceRevisions"].([]any) assert.Len(t, revs, tt.wantRevCount) @@ -798,7 +799,7 @@ func TestHandler_DescribeServiceRevisions_CreateAndUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) revs := out2["serviceRevisions"].([]any) require.Len(t, revs, 1, "expected one revision after CreateService") rev := revs[0].(map[string]any) @@ -828,7 +829,7 @@ func TestHandler_DescribeServiceRevisions_CreateAndUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var out3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &out3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &out3, jsonx.V1())) revs3 := out3["serviceRevisions"].([]any) assert.Len(t, revs3, 1, "expected one revision after UpdateService") } diff --git a/services/ecs/handler_attributes_test.go b/services/ecs/handler_attributes_test.go index a54f867e9d..34e47b1c60 100644 --- a/services/ecs/handler_attributes_test.go +++ b/services/ecs/handler_attributes_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -72,7 +73,7 @@ func TestAttributes_PutListDelete_Roundtrip(t *testing.T) { }) require.Equal(t, http.StatusOK, ciResp.Code) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) // Put attributes @@ -102,7 +103,7 @@ func TestAttributes_PutListDelete_Roundtrip(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) attrs := listOut["attributes"].([]any) assert.Len(t, attrs, 2) @@ -126,7 +127,7 @@ func TestAttributes_PutListDelete_Roundtrip(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp2.Code) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(listResp2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(listResp2.Body.Bytes(), &listOut2, jsonx.V1())) attrs2 := listOut2["attributes"].([]any) assert.Len(t, attrs2, 1) assert.Equal(t, "com.example.zone", attrs2[0].(map[string]any)["name"]) @@ -144,7 +145,7 @@ func TestAttributes_FilterByName(t *testing.T) { }) require.Equal(t, http.StatusOK, ciResp.Code) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) doECSRequest(t, h, "PutAttributes", map[string]any{ @@ -173,7 +174,7 @@ func TestAttributes_FilterByName(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) attrs := listOut["attributes"].([]any) assert.Len(t, attrs, 1) assert.Equal(t, "ecs.gpu", attrs[0].(map[string]any)["name"]) @@ -192,7 +193,7 @@ func TestListAttributes_TargetID_Filter(t *testing.T) { }) require.Equal(t, http.StatusOK, ci1Resp.Code) var ci1Out map[string]any - require.NoError(t, json.Unmarshal(ci1Resp.Body.Bytes(), &ci1Out)) + require.NoError(t, json.Unmarshal(ci1Resp.Body.Bytes(), &ci1Out, jsonx.V1())) ci1Arn := ci1Out["containerInstance"].(map[string]any)["containerInstanceArn"].(string) ci2Resp := doECSRequest(t, h, "RegisterContainerInstance", map[string]any{ @@ -201,7 +202,7 @@ func TestListAttributes_TargetID_Filter(t *testing.T) { }) require.Equal(t, http.StatusOK, ci2Resp.Code) var ci2Out map[string]any - require.NoError(t, json.Unmarshal(ci2Resp.Body.Bytes(), &ci2Out)) + require.NoError(t, json.Unmarshal(ci2Resp.Body.Bytes(), &ci2Out, jsonx.V1())) ci2Arn := ci2Out["containerInstance"].(map[string]any)["containerInstanceArn"].(string) doECSRequest(t, h, "PutAttributes", map[string]any{ @@ -231,7 +232,7 @@ func TestListAttributes_TargetID_Filter(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) attrs := listOut["attributes"].([]any) require.Len(t, attrs, 1) assert.Equal(t, "us-east-1a", attrs[0].(map[string]any)["value"]) @@ -262,7 +263,7 @@ func TestListAttributes_TargetID_Filter_NotFound(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) attrs := listOut["attributes"].([]any) assert.Empty(t, attrs) } @@ -304,7 +305,7 @@ func TestListAttributes_TargetID_NoFilter_ReturnsAll(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) attrs := listOut["attributes"].([]any) assert.Len(t, attrs, 3) } @@ -372,7 +373,7 @@ func TestECS_DeleteAttributes(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) attrs, ok := resp["attributes"].([]any) require.True(t, ok) @@ -435,7 +436,7 @@ func TestECS_PutAndListAttributes(t *testing.T) { } var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) attrs, ok := putResp["attributes"].([]any) require.True(t, ok) @@ -446,7 +447,7 @@ func TestECS_PutAndListAttributes(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) listedAttrs, ok := listResp["attributes"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_capacity_providers_test.go b/services/ecs/handler_capacity_providers_test.go index 3603289464..3466750940 100644 --- a/services/ecs/handler_capacity_providers_test.go +++ b/services/ecs/handler_capacity_providers_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -34,7 +35,7 @@ func TestCapacityProvider_ASGBacked_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cp := out["capacityProvider"].(map[string]any) require.Equal(t, "asg-cp", cp["name"]) @@ -69,7 +70,7 @@ func TestCapacityProvider_ASGBacked_NoManagedScaling(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cp := out["capacityProvider"].(map[string]any) asg := cp["autoScalingGroupProvider"].(map[string]any) assert.Nil(t, asg["managedScaling"]) @@ -93,7 +94,7 @@ func TestCapacityProvider_FARGATE_Types(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) providers := out["capacityProviders"].([]any) require.Len(t, providers, 2) @@ -141,7 +142,7 @@ func TestCapacityProvider_Update_ManagedScaling(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cp := out["capacityProvider"].(map[string]any) asg := cp["autoScalingGroupProvider"].(map[string]any) ms := asg["managedScaling"].(map[string]any) @@ -160,7 +161,7 @@ func TestCapacityProvider_DeleteByARN(t *testing.T) { }) require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) cpArn := createOut["capacityProvider"].(map[string]any)["capacityProviderArn"].(string) require.NotEmpty(t, cpArn) @@ -192,7 +193,7 @@ func TestCapacityProvider_FARGATE_BuiltIn_DescribeWithoutCreate(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) providers := out["capacityProviders"].([]any) require.Len(t, providers, 1) cp := providers[0].(map[string]any) @@ -211,7 +212,7 @@ func TestCapacityProvider_FARGATE_SPOT_BuiltIn(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) providers := out["capacityProviders"].([]any) require.Len(t, providers, 2) @@ -242,7 +243,7 @@ func TestCapacityProvider_FARGATE_BuiltIn_WithCreated(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) providers := out["capacityProviders"].([]any) require.Len(t, providers, 2) @@ -267,7 +268,7 @@ func TestCapacityProvider_Unknown_ReturnsFailure(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["capacityProviders"]) failures := out["failures"].([]any) @@ -322,7 +323,7 @@ func TestECS_CreateCapacityProvider(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cp, ok := resp["capacityProvider"].(map[string]any) require.True(t, ok) @@ -395,7 +396,7 @@ func TestECS_DeleteCapacityProvider(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cp, ok := resp["capacityProvider"].(map[string]any) require.True(t, ok) @@ -514,7 +515,7 @@ func TestECS_DescribeCapacityProviders(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) providers, ok := resp["capacityProviders"].([]any) require.True(t, ok) @@ -540,7 +541,7 @@ func TestECS_DescribeCapacityProviders_SortedByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) providers, ok := resp["capacityProviders"].([]any) require.True(t, ok) @@ -559,7 +560,7 @@ func TestECS_DescribeCapacityProviders_ByARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["capacityProvider"].(map[string]any)["capacityProviderArn"].(string) @@ -572,7 +573,7 @@ func TestECS_DescribeCapacityProviders_ByARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) providers, ok := resp["capacityProviders"].([]any) require.True(t, ok) @@ -713,7 +714,7 @@ func TestDescribeCapacityProviders_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cps, ok := resp["capacityProviders"].([]any) require.True(t, ok) @@ -750,7 +751,7 @@ func TestDescribeCapacityProviders_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cp := resp["capacityProviders"].([]any)[0].(map[string]any) assert.Nil(t, cp["tags"]) }) @@ -765,7 +766,7 @@ func TestDescribeCapacityProviders_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cp := resp["capacityProviders"].([]any)[0].(map[string]any) tags := cp["tags"].([]any) require.Len(t, tags, 1) @@ -797,7 +798,7 @@ func TestDescribeCapacityProviders_ClusterFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cps := resp["capacityProviders"].([]any) require.Len(t, cps, 2) @@ -825,7 +826,7 @@ func TestDescribeCapacityProviders_ClusterFilter_UnknownCluster(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cps := resp["capacityProviders"].([]any) assert.Empty(t, cps) } diff --git a/services/ecs/handler_cluster_tags_test.go b/services/ecs/handler_cluster_tags_test.go index 5198069136..b058592a4c 100644 --- a/services/ecs/handler_cluster_tags_test.go +++ b/services/ecs/handler_cluster_tags_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestECS_CreateCluster_TagsAndCapacityProviders proves that CreateCluster @@ -49,7 +51,7 @@ func TestECS_CreateCluster_TagsAndCapacityProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) cluster := createResp["cluster"].(map[string]any) @@ -77,7 +79,7 @@ func TestECS_CreateCluster_TagsAndCapacityProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) described := describeResp["clusters"].([]any)[0].(map[string]any) assert.Nil(t, described["tags"], "tags must be omitted when include=TAGS is not requested") @@ -90,7 +92,7 @@ func TestECS_CreateCluster_TagsAndCapacityProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var describeTaggedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeTaggedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeTaggedResp, jsonx.V1())) describedTagged := describeTaggedResp["clusters"].([]any)[0].(map[string]any) taggedTags, ok := describedTagged["tags"].([]any) diff --git a/services/ecs/handler_clusters_test.go b/services/ecs/handler_clusters_test.go index 1eede85592..c47979e86e 100644 --- a/services/ecs/handler_clusters_test.go +++ b/services/ecs/handler_clusters_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -44,7 +45,7 @@ func TestECS_CreateCluster(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster, ok := resp["cluster"].(map[string]any) require.True(t, ok) @@ -133,7 +134,7 @@ func TestECS_DescribeClusters(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantCount > 0 { clusters, ok := resp["clusters"].([]any) @@ -402,7 +403,7 @@ func TestCluster_PutCapacityProviders_WithStrategy(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) providers := cluster["capacityProviders"].([]any) assert.Len(t, providers, 2) @@ -427,7 +428,7 @@ func TestClusterSetting_ContainerInsights_Enable(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) settings := cluster["settings"].([]any) require.Len(t, settings, 1) @@ -456,7 +457,7 @@ func TestClusterSetting_ContainerInsights_Disable(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) settings := cluster["settings"].([]any) assert.Equal(t, "disabled", settings[0].(map[string]any)["value"]) @@ -476,7 +477,7 @@ func TestCreateCluster_WithSettings(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) rawSettings := cluster["settings"] require.NotNil(t, rawSettings, "settings should be present in response") @@ -508,7 +509,7 @@ func TestUpdateCluster_DoesNotAcceptCapacityProviders(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) cluster := out["cluster"].(map[string]any) providers, ok := cluster["capacityProviders"].([]any) require.True(t, ok) @@ -533,7 +534,7 @@ func TestDescribeClusters_MultipleClusters(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out, jsonx.V1())) clusters := out["clusters"].([]any) assert.Len(t, clusters, 3) } @@ -554,7 +555,7 @@ func TestListClusters_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) arns := out["clusterArns"].([]any) assert.Len(t, arns, 3) assert.NotEmpty(t, out["nextToken"]) @@ -654,7 +655,7 @@ func TestECS_PutClusterCapacityProviders(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster, ok := resp["cluster"].(map[string]any) require.True(t, ok) @@ -707,7 +708,7 @@ func TestECS_UpdateClusterSettings(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) c, ok := resp["cluster"].(map[string]any) require.True(t, ok) @@ -729,7 +730,7 @@ func TestDescribeClusters_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["clusters"].([]any) assert.Empty(t, clusters) @@ -754,7 +755,7 @@ func TestDescribeClusters_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["clusters"].([]any) assert.Len(t, clusters, 1) @@ -776,7 +777,7 @@ func TestDescribeClusters_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["clusters"].([]any) assert.Empty(t, clusters) @@ -796,7 +797,7 @@ func TestDescribeClusters_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["clusters"].([]any) require.Len(t, clusters, 1) @@ -821,7 +822,7 @@ func TestCluster_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster, ok := resp["cluster"].(map[string]any) require.True(t, ok, "cluster key must be present") @@ -846,7 +847,7 @@ func TestCluster_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["cluster"].(map[string]any) @@ -872,7 +873,7 @@ func TestCluster_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["clusters"].([]any) require.Len(t, clusters, 1) @@ -935,7 +936,7 @@ func TestECS_UpdateCluster(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster, ok := resp["cluster"].(map[string]any) require.True(t, ok) @@ -989,7 +990,7 @@ func TestBackend_EnrichCluster_CachedCounters(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) for _, task := range runOut["tasks"].([]any) { taskArns = append(taskArns, task.(map[string]any)["taskArn"].(string)) } @@ -1009,7 +1010,7 @@ func TestBackend_EnrichCluster_CachedCounters(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) clusters := descOut["clusters"].([]any) require.Len(t, clusters, 1) c := clusters[0].(map[string]any) @@ -1035,7 +1036,7 @@ func TestBackend_EnrichCluster_RunAndStopUpdatesCounters(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) c := out["clusters"].([]any)[0].(map[string]any) return c["runningTasksCount"].(float64), c["pendingTasksCount"].(float64) @@ -1054,7 +1055,7 @@ func TestBackend_EnrichCluster_RunAndStopUpdatesCounters(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) tasks := runOut["tasks"].([]any) require.Len(t, tasks, 2) diff --git a/services/ecs/handler_container_instances.go b/services/ecs/handler_container_instances.go index d3ae7babe0..efa1bc1ac4 100644 --- a/services/ecs/handler_container_instances.go +++ b/services/ecs/handler_container_instances.go @@ -2,9 +2,10 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -54,7 +55,7 @@ func ec2InstanceIDFromIdentityDocument(doc string) string { InstanceID string `json:"instanceId"` } - if err := json.Unmarshal([]byte(doc), &parsed); err != nil { + if err := json.Unmarshal([]byte(doc), &parsed, jsonx.V1()); err != nil { return "" } diff --git a/services/ecs/handler_container_instances_test.go b/services/ecs/handler_container_instances_test.go index 4715230853..ce1d34aa4e 100644 --- a/services/ecs/handler_container_instances_test.go +++ b/services/ecs/handler_container_instances_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -23,7 +24,7 @@ func TestContainerInstance_DRAINING_State(t *testing.T) { }) require.Equal(t, http.StatusOK, ciResp.Code) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) assert.Equal(t, "ACTIVE", ciOut["containerInstance"].(map[string]any)["status"]) @@ -36,7 +37,7 @@ func TestContainerInstance_DRAINING_State(t *testing.T) { require.Equal(t, http.StatusOK, drainResp.Code) var drainOut map[string]any - require.NoError(t, json.Unmarshal(drainResp.Body.Bytes(), &drainOut)) + require.NoError(t, json.Unmarshal(drainResp.Body.Bytes(), &drainOut, jsonx.V1())) instances := drainOut["containerInstances"].([]any) require.Len(t, instances, 1) assert.Equal(t, "DRAINING", instances[0].(map[string]any)["status"]) @@ -48,7 +49,7 @@ func TestContainerInstance_DRAINING_State(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) described := descOut["containerInstances"].([]any) require.Len(t, described, 1) assert.Equal(t, "DRAINING", described[0].(map[string]any)["status"]) @@ -65,7 +66,7 @@ func TestContainerInstance_DRAINING_to_ACTIVE(t *testing.T) { "instanceIdentityDocument": fakeInstanceIdentityDocument("i-react-5678"), }) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) // Drain then re-activate @@ -82,7 +83,7 @@ func TestContainerInstance_DRAINING_to_ACTIVE(t *testing.T) { require.Equal(t, http.StatusOK, activeResp.Code) var activeOut map[string]any - require.NoError(t, json.Unmarshal(activeResp.Body.Bytes(), &activeOut)) + require.NoError(t, json.Unmarshal(activeResp.Body.Bytes(), &activeOut, jsonx.V1())) instances := activeOut["containerInstances"].([]any) assert.Equal(t, "ACTIVE", instances[0].(map[string]any)["status"]) } @@ -99,7 +100,7 @@ func TestContainerInstance_UpdateAgent(t *testing.T) { }) require.Equal(t, http.StatusOK, ciResp.Code) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) resp := doECSRequest(t, h, "UpdateContainerAgent", map[string]any{ @@ -109,7 +110,7 @@ func TestContainerInstance_UpdateAgent(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) ci := out["containerInstance"].(map[string]any) assert.NotEmpty(t, ci["containerInstanceArn"]) } @@ -129,7 +130,7 @@ func TestContainerInstance_Deregister_Force(t *testing.T) { "instanceIdentityDocument": fakeInstanceIdentityDocument("i-force-1234"), }) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) // Start a task on this container instance @@ -152,7 +153,7 @@ func TestContainerInstance_Deregister_Force(t *testing.T) { "cluster": "deregforce-cluster", }) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["containerInstanceArns"].([]any) assert.Empty(t, arns) } @@ -219,7 +220,7 @@ func TestECS_RegisterContainerInstance(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ci := resp["containerInstance"].(map[string]any) assert.Equal(t, tt.wantID, ci["ec2InstanceId"]) @@ -302,7 +303,7 @@ func TestECS_ListContainerInstances(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["containerInstanceArns"].([]any) assert.Len(t, arns, tt.wantLen) @@ -348,7 +349,7 @@ func TestECS_DescribeContainerInstances(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) ciArn := regResp["containerInstance"].(map[string]any)["containerInstanceArn"].(string) @@ -364,7 +365,7 @@ func TestECS_DescribeContainerInstances(t *testing.T) { require.Equal(t, tt.wantCode, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) cis := resp["containerInstances"].([]any) assert.Len(t, cis, tt.wantLen) @@ -391,7 +392,7 @@ func TestECS_DescribeContainerInstances_NotFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, _ := resp["containerInstances"].([]any) assert.Empty(t, instances) @@ -428,7 +429,7 @@ func TestECS_DeregisterContainerInstance(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) ciArn := regResp["containerInstance"].(map[string]any)["containerInstanceArn"].(string) @@ -442,7 +443,7 @@ func TestECS_DeregisterContainerInstance(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) ci := resp["containerInstance"].(map[string]any) assert.Equal(t, "INACTIVE", ci["status"]) @@ -545,7 +546,7 @@ func TestECS_UpdateContainerInstancesState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) ciArn := regResp["containerInstance"].(map[string]any)["containerInstanceArn"].(string) @@ -559,7 +560,7 @@ func TestECS_UpdateContainerInstancesState(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) cis := resp["containerInstances"].([]any) require.Len(t, cis, 1) @@ -585,7 +586,7 @@ func TestECS_UpdateContainerInstancesState_NotFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["containerInstances"]) @@ -633,7 +634,7 @@ func TestECS_UpdateContainerAgent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) ci := regResp["containerInstance"].(map[string]any) containerInstanceArn = ci["containerInstanceArn"].(string) } else { @@ -651,7 +652,7 @@ func TestECS_UpdateContainerAgent(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ci, ok := resp["containerInstance"].(map[string]any) require.True(t, ok) @@ -678,7 +679,7 @@ func TestDescribeContainerInstances_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, _ := resp["containerInstances"].([]any) assert.Empty(t, instances) @@ -702,7 +703,7 @@ func TestDescribeContainerInstances_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, regResp.Code) var regOut map[string]any - require.NoError(t, json.Unmarshal(regResp.Body.Bytes(), ®Out)) + require.NoError(t, json.Unmarshal(regResp.Body.Bytes(), ®Out, jsonx.V1())) ciArn := regOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) rec := doECSRequest(t, h, "DescribeContainerInstances", map[string]any{ @@ -715,7 +716,7 @@ func TestDescribeContainerInstances_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, _ := resp["containerInstances"].([]any) assert.Len(t, instances, 1) @@ -750,7 +751,7 @@ func TestDescribeContainerInstances_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) instances, _ := resp["containerInstances"].([]any) assert.Len(t, instances, 2) @@ -778,7 +779,7 @@ func TestDescribeContainerInstances_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, ciResp.Code) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArn := ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) tagResp := doECSRequest(t, h, "TagResource", map[string]any{ @@ -797,7 +798,7 @@ func TestDescribeContainerInstances_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ci := resp["containerInstances"].([]any)[0].(map[string]any) assert.Nil(t, ci["tags"]) }) @@ -813,7 +814,7 @@ func TestDescribeContainerInstances_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ci := resp["containerInstances"].([]any)[0].(map[string]any) tags := ci["tags"].([]any) require.Len(t, tags, 1) diff --git a/services/ecs/handler_daemon_test.go b/services/ecs/handler_daemon_test.go index bba2c08db8..7e023e3aee 100644 --- a/services/ecs/handler_daemon_test.go +++ b/services/ecs/handler_daemon_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -29,7 +30,7 @@ func registerDaemonTaskDef(t *testing.T, h *ecs.Handler, family string) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["daemonTaskDefinitionArn"].(string) require.NotEmpty(t, arn) @@ -45,7 +46,7 @@ func createCapacityProviderForDaemon(t *testing.T, h *ecs.Handler, name string) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cp, ok := resp["capacityProvider"].(map[string]any) require.True(t, ok) @@ -103,7 +104,7 @@ func TestECS_RegisterDaemonTaskDefinition(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["daemonTaskDefinitionArn"].(string) assert.Contains(t, arn, "daemon-task-definition/agent-family:1") @@ -155,7 +156,7 @@ func TestECS_DescribeDaemonTaskDefinition(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td, ok := resp["daemonTaskDefinition"].(map[string]any) require.True(t, ok) @@ -176,7 +177,7 @@ func TestECS_DeleteDaemonTaskDefinition(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, arn, resp["daemonTaskDefinitionArn"]) // Deleted revisions are excluded from the default ACTIVE-only describe lookup by family. @@ -188,7 +189,7 @@ func TestECS_DeleteDaemonTaskDefinition(t *testing.T) { rec = doECSRequest(t, h, "DescribeDaemonTaskDefinition", map[string]any{"daemonTaskDefinition": arn}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td, ok := resp["daemonTaskDefinition"].(map[string]any) require.True(t, ok) assert.Equal(t, "DELETED", td["status"]) @@ -208,7 +209,7 @@ func TestECS_ListDaemonTaskDefinitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tds, ok := resp["daemonTaskDefinitions"].([]any) require.True(t, ok) @@ -216,7 +217,7 @@ func TestECS_ListDaemonTaskDefinitions(t *testing.T) { rec = doECSRequest(t, h, "ListDaemonTaskDefinitions", map[string]any{"familyPrefix": "list-family-a"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tds, ok = resp["daemonTaskDefinitions"].([]any) require.True(t, ok) assert.Len(t, tds, 1) @@ -372,7 +373,7 @@ func TestECS_CreateDaemon(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACTIVE", resp["status"]) assert.NotEmpty(t, resp["daemonArn"]) assert.NotEmpty(t, resp["deploymentArn"]) @@ -415,7 +416,7 @@ func TestECS_DescribeDaemon(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) require.NotEmpty(t, daemonArn) @@ -423,7 +424,7 @@ func TestECS_DescribeDaemon(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) d, ok := resp["daemon"].(map[string]any) require.True(t, ok) @@ -616,7 +617,7 @@ func TestECS_UpdateDaemon(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) firstDeploymentArn, _ := created["deploymentArn"].(string) @@ -628,7 +629,7 @@ func TestECS_UpdateDaemon(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACTIVE", resp["status"]) assert.NotEqual(t, firstDeploymentArn, resp["deploymentArn"]) @@ -655,14 +656,14 @@ func TestECS_DeleteDaemon(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) rec = doECSRequest(t, h, "DeleteDaemon", map[string]any{"daemonArn": daemonArn}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DELETE_IN_PROGRESS", resp["status"]) // Deleted daemons are gone from state; a subsequent Describe 404s. @@ -693,7 +694,7 @@ func TestECS_ListDaemons(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) daemons, ok := resp["daemonSummariesList"].([]any) require.True(t, ok) @@ -701,7 +702,7 @@ func TestECS_ListDaemons(t *testing.T) { rec = doECSRequest(t, h, "ListDaemons", map[string]any{"capacityProviderArns": []string{"arn:none"}}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) daemons, ok = resp["daemonSummariesList"].([]any) require.True(t, ok) assert.Empty(t, daemons) @@ -730,14 +731,14 @@ func TestECS_ListDaemons_OmittedClusterScopesToDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) daemons, ok := resp["daemonSummariesList"].([]any) require.True(t, ok) assert.Empty(t, daemons, "omitted clusterArn must scope to default cluster, not every cluster") rec = doECSRequest(t, h, "ListDaemons", map[string]any{"clusterArn": "other-cluster"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) daemons, ok = resp["daemonSummariesList"].([]any) require.True(t, ok) assert.Len(t, daemons, 1) @@ -760,7 +761,7 @@ func TestECS_DescribeDaemonDeployments(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) deploymentArn, _ := created["deploymentArn"].(string) require.NotEmpty(t, deploymentArn) @@ -770,7 +771,7 @@ func TestECS_DescribeDaemonDeployments(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, ok := resp["daemonDeployments"].([]any) require.True(t, ok) @@ -804,14 +805,14 @@ func TestECS_ListDaemonDeployments(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) rec = doECSRequest(t, h, "ListDaemonDeployments", map[string]any{"daemonArn": daemonArn}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, ok := resp["daemonDeployments"].([]any) require.True(t, ok) @@ -833,14 +834,14 @@ func TestECS_DescribeDaemonRevisions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) rec = doECSRequest(t, h, "DescribeDaemon", map[string]any{"daemonArn": daemonArn}) require.Equal(t, http.StatusOK, rec.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described, jsonx.V1())) d, ok := described["daemon"].(map[string]any) require.True(t, ok) revs, ok := d["currentRevisions"].([]any) @@ -857,7 +858,7 @@ func TestECS_DescribeDaemonRevisions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) revisions, ok := resp["daemonRevisions"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_describe_taskdef_tags_test.go b/services/ecs/handler_describe_taskdef_tags_test.go index 8b8c4b1cf0..f0f551d3f4 100644 --- a/services/ecs/handler_describe_taskdef_tags_test.go +++ b/services/ecs/handler_describe_taskdef_tags_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -72,7 +73,7 @@ func TestECS_DescribeTaskDefinitionInclude(t *testing.T) { Tags []ecs.Tag `json:"tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) gotKeys := make([]string, 0, len(resp.Tags)) for _, tag := range resp.Tags { diff --git a/services/ecs/handler_express_gateway_test.go b/services/ecs/handler_express_gateway_test.go index 264e8c2da8..b2c5fcf147 100644 --- a/services/ecs/handler_express_gateway_test.go +++ b/services/ecs/handler_express_gateway_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -74,7 +75,7 @@ func TestECS_CreateExpressGatewayService(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc, ok := resp["service"].(map[string]any) require.True(t, ok) @@ -127,7 +128,7 @@ func TestECS_DeleteExpressGatewayService(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r, jsonx.V1())) return r["service"].(map[string]any)["serviceArn"].(string) }, @@ -170,7 +171,7 @@ func TestECS_DeleteExpressGatewayService(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc, ok := resp["service"].(map[string]any) require.True(t, ok) @@ -198,7 +199,7 @@ func TestECS_DescribeExpressGatewayService(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r, jsonx.V1())) return r["service"].(map[string]any)["serviceArn"].(string) }, @@ -234,7 +235,7 @@ func TestECS_DescribeExpressGatewayService(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc, ok := resp["service"].(map[string]any) require.True(t, ok) @@ -258,7 +259,7 @@ func TestECS_ExpressGatewayService_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["service"].(map[string]any)["serviceArn"].(string) @@ -309,7 +310,7 @@ func TestECS_UpdateExpressGatewayService(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) svc := createResp["service"].(map[string]any) serviceArn = svc["serviceArn"].(string) } else { @@ -328,7 +329,7 @@ func TestECS_UpdateExpressGatewayService(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // UpdateExpressGatewayServiceOutput.Service is the narrower // UpdatedExpressGatewayService shape (types.UpdatedExpressGatewayService): @@ -349,7 +350,7 @@ func TestECS_UpdateExpressGatewayService(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) descSvc := descResp["service"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/new-infra", descSvc["infrastructureRoleArn"]) }) @@ -419,7 +420,7 @@ func TestExpressGatewayService_TagResource_VisibleOnDescribe(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) serviceArn := createOut["service"].(map[string]any)["serviceArn"].(string) tagResp := doECSRequest(t, h, "TagResource", map[string]any{ @@ -435,7 +436,7 @@ func TestExpressGatewayService_TagResource_VisibleOnDescribe(t *testing.T) { require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) listTags := listOut["tags"].([]any) require.Len(t, listTags, 1) assert.Equal(t, "team", listTags[0].(map[string]any)["key"]) @@ -448,7 +449,7 @@ func TestExpressGatewayService_TagResource_VisibleOnDescribe(t *testing.T) { require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) descTags := descOut["service"].(map[string]any)["tags"].([]any) require.Len(t, descTags, 1) assert.Equal(t, "team", descTags[0].(map[string]any)["key"]) @@ -460,7 +461,7 @@ func TestExpressGatewayService_TagResource_VisibleOnDescribe(t *testing.T) { require.Equal(t, http.StatusOK, noTagsResp.Code) var noTagsOut map[string]any - require.NoError(t, json.Unmarshal(noTagsResp.Body.Bytes(), &noTagsOut)) + require.NoError(t, json.Unmarshal(noTagsResp.Body.Bytes(), &noTagsOut, jsonx.V1())) assert.Nil(t, noTagsOut["service"].(map[string]any)["tags"]) } diff --git a/services/ecs/handler_service_config_test.go b/services/ecs/handler_service_config_test.go index 10f9b74814..d09dff96b3 100644 --- a/services/ecs/handler_service_config_test.go +++ b/services/ecs/handler_service_config_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestService_CapacityProviderStrategy_Roundtrip(t *testing.T) { @@ -34,7 +36,7 @@ func TestService_CapacityProviderStrategy_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) strategy := svc["capacityProviderStrategy"].([]any) require.Len(t, strategy, 2) @@ -85,7 +87,7 @@ func TestService_CapacityProviderStrategy_UpdateService(t *testing.T) { require.Equal(t, http.StatusOK, updateResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) strategy := svc["capacityProviderStrategy"].([]any) assert.Len(t, strategy, 2) @@ -119,7 +121,7 @@ func TestService_CircuitBreaker_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) @@ -156,7 +158,7 @@ func TestService_CircuitBreaker_EnabledNoRollback(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) cb := dc["deploymentCircuitBreaker"].(map[string]any) @@ -196,7 +198,7 @@ func TestService_CircuitBreaker_UpdatePreserved(t *testing.T) { require.Equal(t, http.StatusOK, updateResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) cb := dc["deploymentCircuitBreaker"].(map[string]any) @@ -224,7 +226,7 @@ func TestService_DeploymentConfig_Defaults(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) // AWS defaults: minimumHealthyPercent=100, maximumPercent=200 @@ -265,7 +267,7 @@ func TestServiceConnect_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) sc := primaryServiceConnect(t, svc) assert.Equal(t, true, sc["enabled"]) @@ -307,7 +309,7 @@ func TestServiceConnect_Disabled(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) sc := primaryServiceConnect(t, svc) assert.Equal(t, false, sc["enabled"]) @@ -341,7 +343,7 @@ func TestServiceConnect_UpdateService(t *testing.T) { require.Equal(t, http.StatusOK, updateResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) sc := primaryServiceConnect(t, svc) assert.Equal(t, true, sc["enabled"]) @@ -375,7 +377,7 @@ func TestServiceDiscovery_CloudMap_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) registries := svc["serviceRegistries"].([]any) require.Len(t, registries, 1) @@ -420,7 +422,7 @@ func TestServiceDiscovery_MultipleRegistries(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) registries := svc["serviceRegistries"].([]any) assert.Len(t, registries, 2) @@ -454,7 +456,7 @@ func TestServiceDiscovery_DescribeServices_Preserved(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out, jsonx.V1())) services := out["services"].([]any) require.Len(t, services, 1) svc := services[0].(map[string]any) diff --git a/services/ecs/handler_service_deployments_test.go b/services/ecs/handler_service_deployments_test.go index 46a2e96464..af9b866a8d 100644 --- a/services/ecs/handler_service_deployments_test.go +++ b/services/ecs/handler_service_deployments_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -32,7 +33,7 @@ func TestServiceDeployment_DescribeList_Roundtrip(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) briefs := listOut["serviceDeployments"].([]any) require.NotEmpty(t, briefs) firstArn := briefs[0].(map[string]any)["serviceDeploymentArn"].(string) @@ -42,7 +43,7 @@ func TestServiceDeployment_DescribeList_Roundtrip(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) deps := descOut["serviceDeployments"].([]any) assert.Len(t, deps, 1) dep := deps[0].(map[string]any) @@ -85,7 +86,7 @@ func TestECS_ListServiceDeployments_StableOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) briefs := resp["serviceDeployments"].([]any) require.Len(t, briefs, 3) @@ -118,7 +119,7 @@ func TestStopServiceDeployment(t *testing.T) { }) require.Equal(t, http.StatusOK, stopResp.Code) var stopOut map[string]any - require.NoError(t, json.Unmarshal(stopResp.Body.Bytes(), &stopOut)) + require.NoError(t, json.Unmarshal(stopResp.Body.Bytes(), &stopOut, jsonx.V1())) assert.Equal(t, depArn, stopOut["serviceDeploymentArn"]) assert.NotContains(t, stopOut, "serviceDeployment") @@ -204,7 +205,7 @@ func TestECS_DescribeServiceDeployments(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deployments := resp["serviceDeployments"].([]any) failures := resp["failures"].([]any) @@ -240,7 +241,7 @@ func TestECS_ListServiceDeployments(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) briefs, ok := resp["serviceDeployments"].([]any) require.True(t, ok) @@ -276,7 +277,7 @@ func TestECS_ListServiceDeployments_Shape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasOldShape := resp["serviceDeploymentArns"] assert.False(t, hasOldShape, "response must not carry the bare serviceDeploymentArns shape") @@ -355,7 +356,7 @@ func TestECS_StopServiceDeployment(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["serviceDeploymentArn"]) assert.NotContains(t, resp, "serviceDeployment") @@ -427,7 +428,7 @@ func TestECS_ContinueServiceDeployment(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, tt.wantTypeIsIn, resp["__type"]) }) } diff --git a/services/ecs/handler_service_deployments_wiring_test.go b/services/ecs/handler_service_deployments_wiring_test.go index a3ce2fa160..dbc8ebff5c 100644 --- a/services/ecs/handler_service_deployments_wiring_test.go +++ b/services/ecs/handler_service_deployments_wiring_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestECS_ServiceDeployments_RealDeploymentsAreVisible proves @@ -77,7 +79,7 @@ func TestECS_ServiceDeployments_RealDeploymentsAreVisible(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) briefs, ok := listResp["serviceDeployments"].([]any) require.True(t, ok) @@ -93,7 +95,7 @@ func TestECS_ServiceDeployments_RealDeploymentsAreVisible(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) deployments, ok := describeResp["serviceDeployments"].([]any) require.True(t, ok) @@ -145,7 +147,7 @@ func TestECS_ServiceDeployments_DeletedOnServiceDelete(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) briefs, ok := listResp["serviceDeployments"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_service_fields_test.go b/services/ecs/handler_service_fields_test.go index ec1c1a9a61..8b5b995bba 100644 --- a/services/ecs/handler_service_fields_test.go +++ b/services/ecs/handler_service_fields_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestService_EnableExecuteCommand_Create(t *testing.T) { @@ -30,7 +32,7 @@ func TestService_EnableExecuteCommand_Create(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) assert.Equal(t, true, svc["enableExecuteCommand"]) } @@ -55,7 +57,7 @@ func TestService_EnableExecuteCommand_Default_False(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) eec := svc["enableExecuteCommand"] assert.NotEqual(t, true, eec) @@ -87,7 +89,7 @@ func TestService_EnableExecuteCommand_UpdateService(t *testing.T) { require.Equal(t, http.StatusOK, updResp.Code) var updOut map[string]any - require.NoError(t, json.Unmarshal(updResp.Body.Bytes(), &updOut)) + require.NoError(t, json.Unmarshal(updResp.Body.Bytes(), &updOut, jsonx.V1())) svc := updOut["service"].(map[string]any) assert.Equal(t, true, svc["enableExecuteCommand"]) @@ -100,7 +102,7 @@ func TestService_EnableExecuteCommand_UpdateService(t *testing.T) { require.Equal(t, http.StatusOK, disResp.Code) var disOut map[string]any - require.NoError(t, json.Unmarshal(disResp.Body.Bytes(), &disOut)) + require.NoError(t, json.Unmarshal(disResp.Body.Bytes(), &disOut, jsonx.V1())) disSvc := disOut["service"].(map[string]any) eec := disSvc["enableExecuteCommand"] assert.NotEqual(t, true, eec) @@ -131,7 +133,7 @@ func TestService_EnableExecuteCommand_DescribeServices(t *testing.T) { require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) svcs := descOut["services"].([]any) require.Len(t, svcs, 1) assert.Equal(t, true, svcs[0].(map[string]any)["enableExecuteCommand"]) @@ -159,7 +161,7 @@ func TestService_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc := resp["service"].(map[string]any) @@ -192,7 +194,7 @@ func TestService_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc := resp["service"].(map[string]any) @@ -229,7 +231,7 @@ func TestService_EmptyArrays_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["services"].([]any) require.Len(t, services, 1) @@ -268,7 +270,7 @@ func TestHandler_CreateService_DeploymentConfigDefaults(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) assert.InDelta(t, float64(100), dc["minimumHealthyPercent"], 0.001) @@ -297,7 +299,7 @@ func TestHandler_CreateService_DeploymentConfigCustom(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) assert.InDelta(t, float64(50), dc["minimumHealthyPercent"], 0.001) @@ -343,7 +345,7 @@ func TestHandler_CreateService_DeploymentController_Rolling_Accepted(t *testing. assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentController"].(map[string]any) assert.Equal(t, "ROLLING", dc["type"]) @@ -374,7 +376,7 @@ func TestHandler_CreateService_LoadBalancers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) lbs := svc["loadBalancers"].([]any) require.Len(t, lbs, 1) @@ -408,7 +410,7 @@ func TestHandler_CreateService_ServiceRegistries(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) regs := svc["serviceRegistries"].([]any) require.Len(t, regs, 1) @@ -441,7 +443,7 @@ func TestHandler_CreateService_NetworkConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) nc := svc["networkConfiguration"].(map[string]any) avpc := nc["awsvpcConfiguration"].(map[string]any) @@ -476,7 +478,7 @@ func TestHandler_UpdateService_NetworkConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) nc := svc["networkConfiguration"].(map[string]any) avpc := nc["awsvpcConfiguration"].(map[string]any) @@ -512,7 +514,7 @@ func TestHandler_UpdateService_LoadBalancers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) lbs := svc["loadBalancers"].([]any) require.Len(t, lbs, 1) @@ -545,7 +547,7 @@ func TestHandler_UpdateService_DeploymentConfig_MinMaxPercent(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) dc := svc["deploymentConfiguration"].(map[string]any) assert.InDelta(t, float64(25), dc["minimumHealthyPercent"], 0.001) diff --git a/services/ecs/handler_services_deployments_test.go b/services/ecs/handler_services_deployments_test.go index 80cde2865e..a3fde5f6b3 100644 --- a/services/ecs/handler_services_deployments_test.go +++ b/services/ecs/handler_services_deployments_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -104,7 +105,7 @@ func TestECS_ListServices(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["serviceArns"].([]any) assert.Len(t, arns, tt.wantLen) @@ -195,7 +196,7 @@ func TestDescribeServices_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["services"].([]any) assert.Empty(t, services) @@ -225,7 +226,7 @@ func TestDescribeServices_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["services"].([]any) assert.Len(t, services, 1) @@ -273,7 +274,7 @@ func TestDescribeServices_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["services"].([]any) assert.Len(t, services, 2) @@ -302,7 +303,7 @@ func TestHandler_CreateService_PropagateTags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) assert.Equal(t, "SERVICE", svc["propagateTags"]) } @@ -325,7 +326,7 @@ func TestHandler_CreateService_PropagateTags_DefaultNone(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["service"].(map[string]any) assert.Equal(t, "NONE", svc["propagateTags"]) } @@ -342,7 +343,7 @@ func TestCreateService_DeploymentsField_Populated(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) // Create cluster + service. @@ -357,7 +358,7 @@ func TestCreateService_DeploymentsField_Populated(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code, svcRec.Body.String()) var svcOut map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcOut, jsonx.V1())) svc := svcOut["service"].(map[string]any) require.Contains(t, svc, "deployments", "service response must include deployments") @@ -383,7 +384,7 @@ func TestUpdateService_DeploymentRotation_OnTaskDefChange(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(tdRec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(tdRec1.Body.Bytes(), &out1, jsonx.V1())) tdArn1 := out1["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) tdRec2 := doECSRequest(t, h, "RegisterTaskDefinition", map[string]any{ @@ -392,7 +393,7 @@ func TestUpdateService_DeploymentRotation_OnTaskDefChange(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(tdRec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(tdRec2.Body.Bytes(), &out2, jsonx.V1())) tdArn2 := out2["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) doECSRequest(t, h, "CreateCluster", map[string]any{"clusterName": "staging"}) @@ -414,7 +415,7 @@ func TestUpdateService_DeploymentRotation_OnTaskDefChange(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code, updRec.Body.String()) var updOut map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updOut)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updOut, jsonx.V1())) svc := updOut["service"].(map[string]any) deployments := svc["deployments"].([]any) @@ -442,7 +443,7 @@ func TestDescribeServices_DeploymentsField(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) doECSRequest(t, h, "CreateCluster", map[string]any{"clusterName": "ds-cluster"}) @@ -461,7 +462,7 @@ func TestDescribeServices_DeploymentsField(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) services := descOut["services"].([]any) require.Len(t, services, 1) @@ -528,7 +529,7 @@ func TestECS_CreateService_Tags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc, ok := resp["service"].(map[string]any) require.True(t, ok) @@ -623,7 +624,7 @@ func TestDeploymentRolloutState_CompletedWhenDesiredMet(t *testing.T) { require.Equal(t, http.StatusOK, descResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &out, jsonx.V1())) svcs, ok := out["services"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_services_test.go b/services/ecs/handler_services_test.go index d8407f1ece..dbde9abc6c 100644 --- a/services/ecs/handler_services_test.go +++ b/services/ecs/handler_services_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -78,7 +79,7 @@ func TestECS_CreateService(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc := resp["service"].(map[string]any) assert.NotEmpty(t, svc["serviceArn"]) @@ -131,7 +132,7 @@ func TestECS_DescribeServices(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) svcs := resp["services"].([]any) assert.GreaterOrEqual(t, len(svcs), 1) @@ -141,7 +142,7 @@ func TestECS_DescribeServices(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) svcs3 := resp3["services"].([]any) require.Len(t, svcs3, 1) @@ -198,7 +199,7 @@ func TestECS_UpdateService(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) svc := resp["service"].(map[string]any) assert.Equal(t, tt.wantDC, int(svc["desiredCount"].(float64))) @@ -224,7 +225,7 @@ func TestECS_DeleteService(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) svc := resp["service"].(map[string]any) assert.Equal(t, "del-svc", svc["serviceName"]) @@ -237,7 +238,7 @@ func TestECS_DeleteService(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) svcs3, _ := resp3["services"].([]any) require.Len(t, svcs3, 1) @@ -251,7 +252,7 @@ func TestECS_DeleteService(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var resp4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4, jsonx.V1())) assert.Empty(t, resp4["serviceArns"]) } @@ -579,7 +580,7 @@ func TestCreateService_Tags_PropagateToResource(t *testing.T) { }) require.Equal(t, http.StatusOK, svcResp.Code) var svcOut map[string]any - require.NoError(t, json.Unmarshal(svcResp.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(svcResp.Body.Bytes(), &svcOut, jsonx.V1())) tags := svcOut["service"].(map[string]any)["tags"].([]any) assert.Len(t, tags, 2) } @@ -611,7 +612,7 @@ func TestListServices_LaunchTypeFilter(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["serviceArns"].([]any) assert.Len(t, arns, 3) } @@ -642,7 +643,7 @@ func TestDescribeServices_MultipleServices(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) services := descOut["services"].([]any) assert.Len(t, services, 4) } @@ -736,7 +737,7 @@ func TestListServicesByNamespace_Filter(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out, jsonx.V1())) arns := out["serviceArns"].([]any) assert.Len(t, arns, 2, "only the two services actually attached to the payments namespace") } @@ -768,7 +769,7 @@ func TestService_Tags_ResourceTagSync(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) serviceArn := createOut["service"].(map[string]any)["serviceArn"].(string) listRec := doECSRequest( @@ -777,7 +778,7 @@ func TestService_Tags_ResourceTagSync(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "team", tags[0].(map[string]any)["key"]) @@ -905,7 +906,7 @@ func TestService_Tags_ResourceTagSync(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) serviceArn := createOut["service"].(map[string]any)["serviceArn"].(string) deleteRec := doECSRequest(t, h, "DeleteService", map[string]any{ @@ -915,7 +916,7 @@ func TestService_Tags_ResourceTagSync(t *testing.T) { require.Equal(t, http.StatusOK, deleteRec.Code) var deleteOut map[string]any - require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteOut)) + require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteOut, jsonx.V1())) deletedTags := deleteOut["service"].(map[string]any)["tags"].([]any) require.Len(t, deletedTags, 1) assert.Equal(t, "team", deletedTags[0].(map[string]any)["key"]) @@ -926,7 +927,7 @@ func TestService_Tags_ResourceTagSync(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut["tags"]) }, }, diff --git a/services/ecs/handler_tags_test.go b/services/ecs/handler_tags_test.go index df4ba118d5..2b898e3aba 100644 --- a/services/ecs/handler_tags_test.go +++ b/services/ecs/handler_tags_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -92,7 +93,7 @@ func TestTagResource_Service(t *testing.T) { "desiredCount": 1, }) var svcOut map[string]any - require.NoError(t, json.Unmarshal(svcResp.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(svcResp.Body.Bytes(), &svcOut, jsonx.V1())) svcArn := svcOut["service"].(map[string]any)["serviceArn"].(string) // Tag @@ -111,7 +112,7 @@ func TestTagResource_Service(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["tags"].([]any) assert.Len(t, tags, 2) } @@ -136,7 +137,7 @@ func TestTagResource_Overwrite(t *testing.T) { "resourceArn": fakeArn, }) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "prod", tags[0].(map[string]any)["value"]) @@ -167,7 +168,7 @@ func TestUntagResource(t *testing.T) { "resourceArn": fakeArn, }) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "team", tags[0].(map[string]any)["key"]) @@ -191,7 +192,7 @@ func TestCreateCapacityProvider_TagsVisibleViaListTagsForResource(t *testing.T) require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) cpArn := createOut["capacityProvider"].(map[string]any)["capacityProviderArn"].(string) listResp := doECSRequest(t, h, "ListTagsForResource", map[string]any{ @@ -200,7 +201,7 @@ func TestCreateCapacityProvider_TagsVisibleViaListTagsForResource(t *testing.T) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) tags := listOut["tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "env", tags[0].(map[string]any)["key"]) @@ -222,7 +223,7 @@ func TestDescribeCapacityProviders_TagResource_ReflectsInDescribe(t *testing.T) require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) cpArn := createOut["capacityProvider"].(map[string]any)["capacityProviderArn"].(string) tagResp := doECSRequest(t, h, "TagResource", map[string]any{ @@ -238,7 +239,7 @@ func TestDescribeCapacityProviders_TagResource_ReflectsInDescribe(t *testing.T) require.Equal(t, http.StatusOK, describeResp.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &describeOut, jsonx.V1())) providers := describeOut["capacityProviders"].([]any) require.Len(t, providers, 1) tags := providers[0].(map[string]any)["tags"].([]any) @@ -264,7 +265,7 @@ func TestTagResource_Cluster(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out, jsonx.V1())) tags := out["tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "owner", tags[0].(map[string]any)["key"]) @@ -334,7 +335,7 @@ func TestECS_Tagging(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) tagList, ok := resp["tags"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_task_definition_fields_test.go b/services/ecs/handler_task_definition_fields_test.go index 6009c299bf..0bdeec7c3c 100644 --- a/services/ecs/handler_task_definition_fields_test.go +++ b/services/ecs/handler_task_definition_fields_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTaskDefinition_RuntimePlatform_Roundtrip(t *testing.T) { @@ -27,7 +29,7 @@ func TestTaskDefinition_RuntimePlatform_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rp := td["runtimePlatform"].(map[string]any) assert.Equal(t, "ARM64", rp["cpuArchitecture"]) @@ -41,7 +43,7 @@ func TestTaskDefinition_RuntimePlatform_Roundtrip(t *testing.T) { ) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) descTD := descOut["taskDefinition"].(map[string]any) assert.Equal(t, "ARM64", descTD["runtimePlatform"].(map[string]any)["cpuArchitecture"]) } @@ -64,7 +66,7 @@ func TestTaskDefinition_RuntimePlatform_X86(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rp := td["runtimePlatform"].(map[string]any) assert.Equal(t, "X86_64", rp["cpuArchitecture"]) @@ -89,7 +91,7 @@ func TestTaskDefinition_RuntimePlatform_Windows(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rp := td["runtimePlatform"].(map[string]any) assert.Equal(t, "WINDOWS_SERVER_2022_FULL", rp["operatingSystemFamily"]) @@ -116,7 +118,7 @@ func TestTaskDefinition_EphemeralStorage_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) es := td["ephemeralStorage"].(map[string]any) assert.InDelta(t, float64(50), es["sizeInGiB"], 0.001) @@ -129,7 +131,7 @@ func TestTaskDefinition_EphemeralStorage_Roundtrip(t *testing.T) { ) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) descTD := descOut["taskDefinition"].(map[string]any) assert.InDelta(t, float64(50), descTD["ephemeralStorage"].(map[string]any)["sizeInGiB"], 0.001) } @@ -146,7 +148,7 @@ func TestTaskDefinition_EphemeralStorage_Default(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) _, hasEph := td["ephemeralStorage"] assert.False(t, hasEph, "ephemeralStorage should be absent when not set") @@ -176,7 +178,7 @@ func TestTaskDefinition_InferenceAccelerators_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) accs := td["inferenceAccelerators"].([]any) @@ -212,7 +214,7 @@ func TestTaskDefinition_InferenceAccelerators_Multiple(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) accs := td["inferenceAccelerators"].([]any) assert.Len(t, accs, 2) @@ -239,7 +241,7 @@ func TestContainerDef_GPU_ResourceRequirement(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cds := td["containerDefinitions"].([]any) require.Len(t, cds, 1) @@ -275,7 +277,7 @@ func TestContainerDef_GPU_MultipleContainers(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cds := td["containerDefinitions"].([]any) require.Len(t, cds, 2) @@ -312,7 +314,7 @@ func TestContainerDef_EnvironmentFiles_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cds := td["containerDefinitions"].([]any) cd := cds[0].(map[string]any) @@ -359,7 +361,7 @@ func TestContainerDef_EnvironmentFiles_WithEnv(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cd := td["containerDefinitions"].([]any)[0].(map[string]any) @@ -411,7 +413,7 @@ func TestTaskDefinition_AllAdvancedFields_Combined(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rp := td["runtimePlatform"].(map[string]any) @@ -461,7 +463,7 @@ func TestTaskDefinition_BackwardCompat_NoNewFields(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) // New fields should be absent @@ -495,7 +497,7 @@ func TestTaskDefinition_Volumes_EmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) @@ -524,7 +526,7 @@ func TestTaskDefinition_Volumes_EmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) @@ -560,7 +562,7 @@ func TestTaskDefinition_Volumes_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) vols, _ := td["volumes"].([]any) @@ -588,7 +590,7 @@ func TestHandler_RegisterTaskDefinition_RequiresCompatibilities(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rcs := td["requiresCompatibilities"].([]any) require.Len(t, rcs, 1) @@ -658,7 +660,7 @@ func TestHandler_RegisterTaskDefinition_TaskRoleArn(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/task-role", td["taskRoleArn"]) assert.Equal(t, "arn:aws:iam::000000000000:role/exec-role", td["executionRoleArn"]) @@ -698,7 +700,7 @@ func TestHandler_RegisterTaskDefinition_Volumes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) vols := td["volumes"].([]any) require.Len(t, vols, 2) @@ -734,7 +736,7 @@ func TestHandler_RegisterTaskDefinition_LogConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cd := td["containerDefinitions"].([]any)[0].(map[string]any) lc := cd["logConfiguration"].(map[string]any) @@ -764,7 +766,7 @@ func TestHandler_RegisterTaskDefinition_Secrets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cd := td["containerDefinitions"].([]any)[0].(map[string]any) secrets := cd["secrets"].([]any) @@ -798,7 +800,7 @@ func TestHandler_RegisterTaskDefinition_PortMapping_Extended(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cd := td["containerDefinitions"].([]any)[0].(map[string]any) pm := cd["portMappings"].([]any)[0].(map[string]any) @@ -831,7 +833,7 @@ func TestHandler_RegisterTaskDefinition_HealthCheck(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) cd := td["containerDefinitions"].([]any)[0].(map[string]any) hc := cd["healthCheck"].(map[string]any) @@ -858,7 +860,7 @@ func TestHandler_DescribeTaskDefinition_Volumes_RoundTrip(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) vols := td["volumes"].([]any) require.Len(t, vols, 1) @@ -886,7 +888,7 @@ func TestHandler_DescribeTaskDefinition_RequiresCompatibilities_RoundTrip(t *tes assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) td := out["taskDefinition"].(map[string]any) rcs := td["requiresCompatibilities"].([]any) assert.Len(t, rcs, 2) @@ -937,7 +939,7 @@ func TestECS_RegisterTaskDefinition_CPU_Memory(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td, ok := resp["taskDefinition"].(map[string]any) require.True(t, ok) diff --git a/services/ecs/handler_task_definitions_test.go b/services/ecs/handler_task_definitions_test.go index b6cb457a8f..ac5bca6cb9 100644 --- a/services/ecs/handler_task_definitions_test.go +++ b/services/ecs/handler_task_definitions_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -92,7 +93,7 @@ func TestECS_RegisterTaskDefinition(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td, ok := resp["taskDefinition"].(map[string]any) require.True(t, ok) @@ -119,7 +120,7 @@ func TestECS_RegisterTaskDefinition_MultipleRevisions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) assert.Equal(t, i, int(td["revision"].(float64))) @@ -145,7 +146,7 @@ func TestECS_DescribeTaskDefinition(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) assert.Equal(t, "web", td["family"]) @@ -174,14 +175,14 @@ func TestECS_DeregisterTaskDefinition(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) tdArn := createResp["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) rec2 := doECSRequest(t, h, "DeregisterTaskDefinition", map[string]any{"taskDefinition": tdArn}) require.Equal(t, http.StatusOK, rec2.Code) var deregResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &deregResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &deregResp, jsonx.V1())) td := deregResp["taskDefinition"].(map[string]any) assert.Equal(t, "INACTIVE", td["status"]) @@ -206,7 +207,7 @@ func TestECS_ListTaskDefinitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["taskDefinitionArns"].([]any) assert.GreaterOrEqual(t, len(arns), 3) @@ -216,7 +217,7 @@ func TestECS_ListTaskDefinitions(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) arns2 := resp2["taskDefinitionArns"].([]any) assert.Len(t, arns2, 2) @@ -317,7 +318,7 @@ func TestECS_Handler_ListTaskDefinitions_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns, ok := resp["taskDefinitionArns"].([]any) require.True(t, ok) @@ -391,7 +392,7 @@ func TestECS_ListTaskDefinitions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["taskDefinitionArns"].([]any) assert.GreaterOrEqual(t, len(arns), tt.wantMinCount) @@ -421,7 +422,7 @@ func TestECS_ListTaskDefinitions_TokenChaining(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) arns1 := page1["taskDefinitionArns"].([]any) assert.Len(t, arns1, 2) @@ -440,7 +441,7 @@ func TestECS_ListTaskDefinitions_TokenChaining(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) arns2 := page2["taskDefinitionArns"].([]any) assert.GreaterOrEqual(t, len(arns2), 1) @@ -482,7 +483,7 @@ func TestECS_TaskDefinitionRevisionCap(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) td := resp["taskDefinition"].(map[string]any) latestRev := int(td["revision"].(float64)) @@ -498,7 +499,7 @@ func TestECS_TaskDefinitionRevisionCap(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) arns := listResp["taskDefinitionArns"].([]any) assert.LessOrEqual(t, len(arns), 100, "stored revisions should not exceed the cap") @@ -524,7 +525,7 @@ func TestTaskDefinition_MultipleRevisions_SameFamily(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["taskDefinitionArns"].([]any) assert.Len(t, arns, 3) @@ -534,7 +535,7 @@ func TestTaskDefinition_MultipleRevisions_SameFamily(t *testing.T) { }) require.Equal(t, http.StatusOK, famResp.Code) var famOut map[string]any - require.NoError(t, json.Unmarshal(famResp.Body.Bytes(), &famOut)) + require.NoError(t, json.Unmarshal(famResp.Body.Bytes(), &famOut, jsonx.V1())) families := famOut["families"].([]any) assert.Len(t, families, 1) assert.Equal(t, "rev-family", families[0]) @@ -558,7 +559,7 @@ func TestTaskDefinition_Deregister_Specific_Revision(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) td := descOut["taskDefinition"].(map[string]any) rev1Arn := td["taskDefinitionArn"].(string) require.NotEmpty(t, rev1Arn) @@ -569,7 +570,7 @@ func TestTaskDefinition_Deregister_Specific_Revision(t *testing.T) { }) require.Equal(t, http.StatusOK, deregResp.Code) var deregOut map[string]any - require.NoError(t, json.Unmarshal(deregResp.Body.Bytes(), &deregOut)) + require.NoError(t, json.Unmarshal(deregResp.Body.Bytes(), &deregOut, jsonx.V1())) deregTd := deregOut["taskDefinition"].(map[string]any) assert.Equal(t, "INACTIVE", deregTd["status"]) @@ -580,7 +581,7 @@ func TestTaskDefinition_Deregister_Specific_Revision(t *testing.T) { }) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["taskDefinitionArns"].([]any) assert.Len(t, arns, 2) } @@ -597,7 +598,7 @@ func TestTaskDefinition_DeleteDefinitions_Batch(t *testing.T) { "containerDefinitions": []any{map[string]any{"name": "app", "image": "nginx"}}, }) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) // Deregister first (required before delete) td := out["taskDefinition"].(map[string]any) arn := td["taskDefinitionArn"].(string) @@ -611,7 +612,7 @@ func TestTaskDefinition_DeleteDefinitions_Batch(t *testing.T) { require.Equal(t, http.StatusOK, deleteResp.Code) var deleteOut map[string]any - require.NoError(t, json.Unmarshal(deleteResp.Body.Bytes(), &deleteOut)) + require.NoError(t, json.Unmarshal(deleteResp.Body.Bytes(), &deleteOut, jsonx.V1())) deleted := deleteOut["taskDefinitions"].([]any) assert.Len(t, deleted, 3) } @@ -640,7 +641,7 @@ func TestTaskDefinition_ListByStatus_Active_Inactive(t *testing.T) { "status": "ACTIVE", }) var activeOut map[string]any - require.NoError(t, json.Unmarshal(activeResp.Body.Bytes(), &activeOut)) + require.NoError(t, json.Unmarshal(activeResp.Body.Bytes(), &activeOut, jsonx.V1())) assert.Len(t, activeOut["taskDefinitionArns"].([]any), 1) // List INACTIVE @@ -649,7 +650,7 @@ func TestTaskDefinition_ListByStatus_Active_Inactive(t *testing.T) { "status": "INACTIVE", }) var inactiveOut map[string]any - require.NoError(t, json.Unmarshal(inactiveResp.Body.Bytes(), &inactiveOut)) + require.NoError(t, json.Unmarshal(inactiveResp.Body.Bytes(), &inactiveOut, jsonx.V1())) assert.Len(t, inactiveOut["taskDefinitionArns"].([]any), 1) } @@ -676,7 +677,7 @@ func TestTaskDefinitionFamilies_MultipleStatus(t *testing.T) { }) require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) families := out["families"].([]any) famNames := make([]string, 0, len(families)) for _, f := range families { @@ -707,7 +708,7 @@ func TestECS_DeleteTaskDefinitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r, jsonx.V1())) arn := r["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) doECSRequest( @@ -733,7 +734,7 @@ func TestECS_DeleteTaskDefinitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r, jsonx.V1())) return r["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) }, @@ -781,7 +782,7 @@ func TestECS_DeleteTaskDefinitions(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deleted := resp["taskDefinitions"].([]any) failures := resp["failures"].([]any) @@ -828,7 +829,7 @@ func TestHandler_ListTaskDefinitions_StatusFilter(t *testing.T) { }) require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) arn1 := out1["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) rec2 := doECSRequest(t, h, "RegisterTaskDefinition", map[string]any{ @@ -852,7 +853,7 @@ func TestHandler_ListTaskDefinitions_StatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["taskDefinitionArns"].([]any) require.Len(t, arns, 1) assert.Contains(t, arns[0].(string), tt.wantContains) @@ -906,7 +907,7 @@ func TestECS_ListTaskDefinitionFamilies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) families, ok := resp["families"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_task_exec_test.go b/services/ecs/handler_task_exec_test.go index 24ab8b04ec..a3980eda04 100644 --- a/services/ecs/handler_task_exec_test.go +++ b/services/ecs/handler_task_exec_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -25,7 +26,7 @@ func TestECS_StopTask(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var runResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp, jsonx.V1())) taskArn := runResp["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) @@ -36,7 +37,7 @@ func TestECS_StopTask(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) task := resp2["task"].(map[string]any) assert.Equal(t, "STOPPED", task["lastStatus"]) @@ -59,7 +60,7 @@ func TestECS_ListTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) arns := resp["taskArns"].([]any) assert.Len(t, arns, 3) @@ -82,7 +83,7 @@ func TestECS_ListTasks_DesiredStatusDefaultsToRunning(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var runResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp, jsonx.V1())) tasks := runResp["tasks"].([]any) require.Len(t, tasks, 2) stoppedArn := tasks[0].(map[string]any)["taskArn"].(string) @@ -94,14 +95,14 @@ func TestECS_ListTasks_DesiredStatusDefaultsToRunning(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["taskArns"].([]any) assert.Len(t, arns, 1, "omitted desiredStatus must default to RUNNING only") assert.NotContains(t, arns, stoppedArn) rec = doECSRequest(t, h, "ListTasks", map[string]any{"desiredStatus": "STOPPED"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns = resp["taskArns"].([]any) assert.Equal(t, []any{stoppedArn}, arns) } @@ -208,7 +209,7 @@ func TestECS_ListTasks_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["taskArns"].([]any) assert.GreaterOrEqual(t, len(arns), tt.wantMinCount) @@ -238,7 +239,7 @@ func TestECS_ListTasks_TokenChaining(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) arns1 := page1["taskArns"].([]any) assert.Len(t, arns1, 2) @@ -252,7 +253,7 @@ func TestECS_ListTasks_TokenChaining(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) arns2 := page2["taskArns"].([]any) assert.GreaterOrEqual(t, len(arns2), 1) @@ -287,7 +288,7 @@ func TestECSExec_Basic(t *testing.T) { }) require.Equal(t, http.StatusOK, runResp.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) tasks := runOut["tasks"].([]any) require.Len(t, tasks, 1) taskArn := tasks[0].(map[string]any)["taskArn"].(string) @@ -320,7 +321,7 @@ func TestECSExec_NonInteractive(t *testing.T) { "enableExecuteCommand": true, }) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) taskArn := runOut["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) execResp := doECSRequest(t, h, "ExecuteCommand", map[string]any{ @@ -351,7 +352,7 @@ func TestTaskProtection_Enable_Disable(t *testing.T) { "count": 1, }) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) taskArn := runOut["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) // Enable protection @@ -365,7 +366,7 @@ func TestTaskProtection_Enable_Disable(t *testing.T) { require.Equal(t, http.StatusOK, protResp.Code) var protOut map[string]any - require.NoError(t, json.Unmarshal(protResp.Body.Bytes(), &protOut)) + require.NoError(t, json.Unmarshal(protResp.Body.Bytes(), &protOut, jsonx.V1())) prot := protOut["protectedTasks"].([]any)[0].(map[string]any) assert.Equal(t, true, prot["protectionEnabled"]) @@ -376,7 +377,7 @@ func TestTaskProtection_Enable_Disable(t *testing.T) { }) require.Equal(t, http.StatusOK, getResp.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getResp.Body.Bytes(), &getOut, jsonx.V1())) protTasks := getOut["protectedTasks"].([]any) assert.Len(t, protTasks, 1) assert.Equal(t, true, protTasks[0].(map[string]any)["protectionEnabled"]) @@ -411,7 +412,7 @@ func TestStartTask_MultipleInstances(t *testing.T) { ), }) var ciOut map[string]any - require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut)) + require.NoError(t, json.Unmarshal(ciResp.Body.Bytes(), &ciOut, jsonx.V1())) ciArns[i] = ciOut["containerInstance"].(map[string]any)["containerInstanceArn"].(string) } @@ -423,7 +424,7 @@ func TestStartTask_MultipleInstances(t *testing.T) { require.Equal(t, http.StatusOK, startResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) assert.Len(t, tasks, 3) } @@ -453,7 +454,7 @@ func TestECS_ExecuteCommand(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) taskArn := resp["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) @@ -486,7 +487,7 @@ func TestECS_ExecuteCommand(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) taskArn := resp["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) @@ -518,7 +519,7 @@ func TestECS_ExecuteCommand(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["clusterArn"]) assert.NotEmpty(t, resp["taskArn"]) @@ -552,7 +553,7 @@ func TestTask_EnableExecuteCommand_RunTask(t *testing.T) { require.Equal(t, http.StatusOK, runResp.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) tasks := runOut["tasks"].([]any) require.Len(t, tasks, 1) assert.Equal(t, true, tasks[0].(map[string]any)["enableExecuteCommand"]) @@ -586,7 +587,7 @@ func TestTask_EnableExecuteCommand_ServicePropagation(t *testing.T) { listResp := doECSRequest(t, h2, "ListTasks", map[string]any{"cluster": "svc-exec-cluster"}) require.Equal(t, http.StatusOK, listResp.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["taskArns"].([]any) require.NotEmpty(t, arns) taskArn := arns[0].(string) @@ -597,7 +598,7 @@ func TestTask_EnableExecuteCommand_ServicePropagation(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) tasks := descOut["tasks"].([]any) require.Len(t, tasks, 1) assert.Equal(t, true, tasks[0].(map[string]any)["enableExecuteCommand"]) @@ -643,7 +644,7 @@ func TestECS_TaskProtection(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runResp map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runResp, jsonx.V1())) tasks := runResp["tasks"].([]any) require.Len(t, tasks, 1) taskArn := tasks[0].(map[string]any)["taskArn"].(string) @@ -657,7 +658,7 @@ func TestECS_TaskProtection(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) protectedTasks, ok := resp["protectedTasks"].([]any) require.True(t, ok) @@ -674,7 +675,7 @@ func TestECS_TaskProtection(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) protTasks, ok := getResp["protectedTasks"].([]any) require.True(t, ok) @@ -749,7 +750,7 @@ func TestECS_StartTask_WithSetup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) taskList, ok := resp["tasks"].([]any) require.True(t, ok) @@ -782,7 +783,7 @@ func TestECS_ListTasksFiltered(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns, ok := resp["taskArns"].([]any) require.True(t, ok) @@ -796,7 +797,7 @@ func TestECS_ListTasksFiltered(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) arns2, ok := resp2["taskArns"].([]any) require.True(t, ok) @@ -827,7 +828,7 @@ func TestECS_StartTask_StartedBy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, ok := resp["tasks"].([]any) require.True(t, ok) @@ -876,7 +877,7 @@ func TestECS_ListTasksFiltered_ContainerInstance(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns, ok := resp["taskArns"].([]any) require.True(t, ok) @@ -924,7 +925,7 @@ func TestExecuteCommand_RequiresFlag(t *testing.T) { require.Equal(t, http.StatusOK, runResp.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) taskArn := runOut["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) execResp := doECSRequest(t, h, "ExecuteCommand", map[string]any{ diff --git a/services/ecs/handler_task_sets_test.go b/services/ecs/handler_task_sets_test.go index 0223d97e3c..ae2b953562 100644 --- a/services/ecs/handler_task_sets_test.go +++ b/services/ecs/handler_task_sets_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -70,7 +71,7 @@ func TestTaskSet_ExternalController_CreateDescribeDelete(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) taskSet := createOut["taskSet"].(map[string]any) taskSetArn := taskSet["taskSetArn"].(string) require.NotEmpty(t, taskSetArn) @@ -85,7 +86,7 @@ func TestTaskSet_ExternalController_CreateDescribeDelete(t *testing.T) { }) require.Equal(t, http.StatusOK, describeResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &descOut, jsonx.V1())) taskSets := descOut["taskSets"].([]any) require.Len(t, taskSets, 1) @@ -131,7 +132,7 @@ func TestTaskSet_DescribeUpdateDeleteByShortID(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) taskSet := createOut["taskSet"].(map[string]any) shortID := taskSet["id"].(string) require.NotEmpty(t, shortID) @@ -145,7 +146,7 @@ func TestTaskSet_DescribeUpdateDeleteByShortID(t *testing.T) { require.Equal(t, http.StatusOK, describeResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(describeResp.Body.Bytes(), &descOut, jsonx.V1())) require.Len(t, descOut["taskSets"].([]any), 1) require.Empty(t, descOut["failures"]) @@ -191,7 +192,7 @@ func TestTaskSet_UpdateScale(t *testing.T) { }) require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) taskSetArn := createOut["taskSet"].(map[string]any)["taskSetArn"].(string) // Update scale to 100% @@ -203,7 +204,7 @@ func TestTaskSet_UpdateScale(t *testing.T) { }) require.Equal(t, http.StatusOK, updateResp.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateResp.Body.Bytes(), &updateOut, jsonx.V1())) updatedScale := updateOut["taskSet"].(map[string]any)["scale"].(map[string]any) assert.InDelta(t, float64(100), updatedScale["value"], 0.001) } @@ -235,7 +236,7 @@ func TestTaskSet_UpdateServicePrimaryTaskSet_Switch(t *testing.T) { }) require.Equal(t, http.StatusOK, resp1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(resp1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(resp1.Body.Bytes(), &out1, jsonx.V1())) taskSet1Arn := out1["taskSet"].(map[string]any)["taskSetArn"].(string) resp2 := doECSRequest(t, h, "CreateTaskSet", map[string]any{ @@ -246,7 +247,7 @@ func TestTaskSet_UpdateServicePrimaryTaskSet_Switch(t *testing.T) { }) require.Equal(t, http.StatusOK, resp2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(resp2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(resp2.Body.Bytes(), &out2, jsonx.V1())) taskSet2Arn := out2["taskSet"].(map[string]any)["taskSetArn"].(string) // Update primary task set to ts1 @@ -265,7 +266,7 @@ func TestTaskSet_UpdateServicePrimaryTaskSet_Switch(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) ts := descOut["taskSets"].([]any)[0].(map[string]any) assert.Equal(t, "PRIMARY", ts["status"]) @@ -285,7 +286,7 @@ func TestTaskSet_UpdateServicePrimaryTaskSet_Switch(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp2.Code) var descOut2 map[string]any - require.NoError(t, json.Unmarshal(descResp2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(descResp2.Body.Bytes(), &descOut2, jsonx.V1())) ts1After := descOut2["taskSets"].([]any)[0].(map[string]any) assert.NotEqual(t, "PRIMARY", ts1After["status"]) } @@ -325,7 +326,7 @@ func TestTaskSet_DescribeAll_NoFilter(t *testing.T) { }) require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["taskSets"].([]any), 3) } @@ -398,7 +399,7 @@ func TestECS_CreateTaskSet(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ts := resp["taskSet"].(map[string]any) assert.NotEmpty(t, ts["taskSetArn"]) @@ -447,7 +448,7 @@ func TestECS_DescribeTaskSets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) tsArn := createResp["taskSet"].(map[string]any)["taskSetArn"].(string) @@ -486,7 +487,7 @@ func TestECS_DescribeTaskSets(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sets := resp["taskSets"].([]any) assert.Len(t, sets, tt.wantLen) @@ -517,7 +518,7 @@ func TestECS_UpdateTaskSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) tsArn := createResp["taskSet"].(map[string]any)["taskSetArn"].(string) @@ -558,7 +559,7 @@ func TestECS_UpdateTaskSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) tsArn := createResp["taskSet"].(map[string]any)["taskSetArn"].(string) @@ -585,7 +586,7 @@ func TestECS_UpdateTaskSet(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ts := resp["taskSet"].(map[string]any) scale := ts["scale"].(map[string]any) @@ -616,7 +617,7 @@ func TestECS_DeleteTaskSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) tsArn := createResp["taskSet"].(map[string]any)["taskSetArn"].(string) @@ -670,7 +671,7 @@ func TestECS_DeleteTaskSet(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) sets, _ := resp["taskSets"].([]any) assert.Empty(t, sets) @@ -704,7 +705,7 @@ func TestECS_UpdateServicePrimaryTaskSet(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var ts1Resp map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &ts1Resp)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &ts1Resp, jsonx.V1())) ts1Arn := ts1Resp["taskSet"].(map[string]any)["taskSetArn"].(string) @@ -763,7 +764,7 @@ func TestECS_UpdateServicePrimaryTaskSet(t *testing.T) { if tt.wantPrimary { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ts := resp["taskSet"].(map[string]any) assert.Equal(t, input["_ts1Arn"], ts["taskSetArn"]) @@ -816,7 +817,7 @@ func TestTaskSet_LoadBalancers_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) ts := out["taskSet"].(map[string]any) lbs := ts["loadBalancers"].([]any) @@ -867,7 +868,7 @@ func TestTaskSet_ServiceRegistries_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) ts := out["taskSet"].(map[string]any) srs := ts["serviceRegistries"].([]any) require.Len(t, srs, 1) @@ -907,7 +908,7 @@ func TestTaskSet_DescribePreservesLBAndNC(t *testing.T) { }) require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) tsArn := createOut["taskSet"].(map[string]any)["taskSetArn"].(string) descResp := doECSRequest(t, h, "DescribeTaskSets", map[string]any{ @@ -917,7 +918,7 @@ func TestTaskSet_DescribePreservesLBAndNC(t *testing.T) { }) require.Equal(t, http.StatusOK, descResp.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &descOut, jsonx.V1())) sets := descOut["taskSets"].([]any) require.Len(t, sets, 1) ts := sets[0].(map[string]any) @@ -945,7 +946,7 @@ func TestDescribeTaskSets_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createOut, jsonx.V1())) taskSetArn := createOut["taskSet"].(map[string]any)["taskSetArn"].(string) // CreateTaskSet echoes tags unconditionally (no Include gating on Create). @@ -963,7 +964,7 @@ func TestDescribeTaskSets_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ts := resp["taskSets"].([]any)[0].(map[string]any) assert.Nil(t, ts["tags"]) }) @@ -980,7 +981,7 @@ func TestDescribeTaskSets_TagsRequireInclude(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ts := resp["taskSets"].([]any)[0].(map[string]any) tags := ts["tags"].([]any) require.Len(t, tags, 1) @@ -1010,7 +1011,7 @@ func TestCreateTaskSet_CapacityProviderStrategy_Roundtrip(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) strategy := out["taskSet"].(map[string]any)["capacityProviderStrategy"].([]any) require.Len(t, strategy, 2) } @@ -1039,7 +1040,7 @@ func TestDescribeTaskSets_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sets, _ := resp["taskSets"].([]any) assert.Empty(t, sets) @@ -1066,7 +1067,7 @@ func TestDescribeTaskSets_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) realArn := created["taskSet"].(map[string]any)["taskSetArn"].(string) rec := doECSRequest(t, h, "DescribeTaskSets", map[string]any{ @@ -1077,7 +1078,7 @@ func TestDescribeTaskSets_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sets, _ := resp["taskSets"].([]any) require.Len(t, sets, 1) diff --git a/services/ecs/handler_tasks_fields_test.go b/services/ecs/handler_tasks_fields_test.go index a03fb90b93..243eebefe4 100644 --- a/services/ecs/handler_tasks_fields_test.go +++ b/services/ecs/handler_tasks_fields_test.go @@ -1,13 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -32,7 +33,7 @@ func TestRunTask_ContainersField_Populated(t *testing.T) { require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) // Run a task. @@ -43,7 +44,7 @@ func TestRunTask_ContainersField_Populated(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code, runRec.Body.String()) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) tasks := runOut["tasks"].([]any) require.Len(t, tasks, 1) @@ -88,14 +89,14 @@ func TestRunTask_ContainersStatus_Synced_To_Running(t *testing.T) { require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) runRec := doECSRequest(t, h, "RunTask", map[string]any{"taskDefinition": tdArn}) require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) containers := task["containers"].([]any) @@ -129,14 +130,14 @@ func TestRunTask_ContainersHealthStatus_WithHealthCheck(t *testing.T) { require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) runRec := doECSRequest(t, h, "RunTask", map[string]any{"taskDefinition": tdArn}) require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) containers := task["containers"].([]any) @@ -163,7 +164,7 @@ func TestRunTask_PropagateTagsTaskDefinition(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) // Run a task with propagateTags=TASK_DEFINITION. @@ -174,7 +175,7 @@ func TestRunTask_PropagateTagsTaskDefinition(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code, runRec.Body.String()) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) tags, ok := task["tags"].([]any) @@ -205,7 +206,7 @@ func TestRunTask_PropagateTagsTaskDefinition_ExplicitTagWins(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) // Explicit tag overrides the propagated one. @@ -217,7 +218,7 @@ func TestRunTask_PropagateTagsTaskDefinition_ExplicitTagWins(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) tags := task["tags"].([]any) @@ -243,7 +244,7 @@ func TestRunTask_ECSManagedTags_Injected(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) doECSRequest(t, h, "CreateCluster", map[string]any{"clusterName": "mycluster"}) @@ -256,7 +257,7 @@ func TestRunTask_ECSManagedTags_Injected(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code, runRec.Body.String()) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) tags, ok := task["tags"].([]any) @@ -285,7 +286,7 @@ func TestRunTask_Tags_InResponse(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) runRec := doECSRequest(t, h, "RunTask", map[string]any{ @@ -295,7 +296,7 @@ func TestRunTask_Tags_InResponse(t *testing.T) { require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) task := runOut["tasks"].([]any)[0].(map[string]any) tags, ok := task["tags"].([]any) @@ -318,14 +319,14 @@ func TestDescribeTasks_ContainersField_Present(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) runRec := doECSRequest(t, h, "RunTask", map[string]any{"taskDefinition": tdArn}) require.Equal(t, http.StatusOK, runRec.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) taskArn := runOut["tasks"].([]any)[0].(map[string]any)["taskArn"].(string) // DescribeTasks must also return containers. @@ -335,7 +336,7 @@ func TestDescribeTasks_ContainersField_Present(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) tasks := descOut["tasks"].([]any) require.Len(t, tasks, 1) @@ -368,7 +369,7 @@ func TestECS_RunTask_StartedBy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, ok := resp["tasks"].([]any) require.True(t, ok) diff --git a/services/ecs/handler_tasks_test.go b/services/ecs/handler_tasks_test.go index 190ee4b97d..a4801c700e 100644 --- a/services/ecs/handler_tasks_test.go +++ b/services/ecs/handler_tasks_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -78,7 +79,7 @@ func TestECS_RunTask(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks := resp["tasks"].([]any) assert.Len(t, tasks, tt.wantLen) @@ -105,7 +106,7 @@ func TestECS_DescribeTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var runResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runResp, jsonx.V1())) taskArns := make([]string, 0, 2) for _, t := range runResp["tasks"].([]any) { @@ -117,7 +118,7 @@ func TestECS_DescribeTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2["tasks"].([]any), 2) // Describe specific task. @@ -125,7 +126,7 @@ func TestECS_DescribeTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) assert.Len(t, resp3["tasks"].([]any), 1) } @@ -161,7 +162,7 @@ func TestECS_Handler_RunTask_WithGroup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks := resp["tasks"].([]any) task := tasks[0].(map[string]any) @@ -202,7 +203,7 @@ func TestECS_Handler_DescribeTasks_NotFound(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, _ := resp["tasks"].([]any) assert.Empty(t, tasks) @@ -312,7 +313,7 @@ func TestRunTask_WithOverrides(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) require.Len(t, tasks, 1) } @@ -336,7 +337,7 @@ func TestRunTask_Count_Multiple(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) assert.Len(t, tasks, 5) @@ -365,7 +366,7 @@ func TestDescribeTasks_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, _ := resp["tasks"].([]any) assert.Empty(t, tasks) @@ -390,7 +391,7 @@ func TestDescribeTasks_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, runResp.Code) var runOut map[string]any - require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runResp.Body.Bytes(), &runOut, jsonx.V1())) tasksRaw, _ := runOut["tasks"].([]any) require.Len(t, tasksRaw, 1) taskArn := tasksRaw[0].(map[string]any)["taskArn"].(string) @@ -401,7 +402,7 @@ func TestDescribeTasks_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, _ := resp["tasks"].([]any) assert.Len(t, tasks, 1) @@ -438,7 +439,7 @@ func TestDescribeTasks_FailureSemantics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, _ := resp["tasks"].([]any) assert.Empty(t, tasks) @@ -471,7 +472,7 @@ func TestHandler_RunTask_NetworkConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) require.Len(t, tasks, 1) task := tasks[0].(map[string]any) @@ -558,7 +559,7 @@ func TestHandler_RunTask_PropagateTags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tasks := out["tasks"].([]any) require.Len(t, tasks, 1) task := tasks[0].(map[string]any) diff --git a/services/ecs/handler_test.go b/services/ecs/handler_test.go index dbcf888dda..f0fd4f007a 100644 --- a/services/ecs/handler_test.go +++ b/services/ecs/handler_test.go @@ -3,7 +3,7 @@ package ecs_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -46,7 +47,7 @@ func doECSRequest( ) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -74,7 +75,7 @@ func fakeInstanceIdentityDocument(instanceID string) string { "instanceId": instanceID, "region": testRegion, "accountId": testAccountID, - }) + }, jsonx.V1()) if err != nil { panic(err) } @@ -124,7 +125,7 @@ func registerTestTaskDef(t *testing.T, h *ecs.Handler, family string) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) } @@ -500,7 +501,7 @@ func TestConcurrent_CreateServices(t *testing.T) { "serviceName": "concurrent-svc-" + string(rune('a'+idx)), "taskDefinition": "concurrent-task", "desiredCount": 1, - }) + }, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -540,7 +541,7 @@ func TestConcurrent_RegisterContainerInstances(t *testing.T) { "instanceIdentityDocument": fakeInstanceIdentityDocument( "i-concurrent-" + string(rune('a'+idx)), ), - }) + }, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -565,7 +566,7 @@ func TestConcurrent_RegisterContainerInstances(t *testing.T) { "cluster": "concurrent-ci-cluster", }) var listOut map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listOut, jsonx.V1())) arns := listOut["containerInstanceArns"].([]any) assert.Len(t, arns, 20) } @@ -584,7 +585,7 @@ func TestHandler_Reset_ClearsAll(t *testing.T) { listResp := doECSRequest(t, h, "ListClusters", map[string]any{}) require.Equal(t, http.StatusOK, listResp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &out, jsonx.V1())) // No custom clusters after reset arns := out["clusterArns"].([]any) for _, arn := range arns { @@ -691,7 +692,7 @@ func TestHandler_Reset_ClearsCapacityProviders(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cps, ok := resp["capacityProviders"].([]any) require.True(t, ok) @@ -870,7 +871,7 @@ func TestHandler_Snapshot_Restore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cps, ok := resp["capacityProviders"].([]any) require.True(t, ok) diff --git a/services/ecs/lambda_hooks.go b/services/ecs/lambda_hooks.go index 508fa2fc27..f191d5681f 100644 --- a/services/ecs/lambda_hooks.go +++ b/services/ecs/lambda_hooks.go @@ -2,10 +2,12 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/v2" "maps" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -43,7 +45,7 @@ func parseHookResponse(resp []byte, invokeErr error) (string, time.Duration, map } var r hookResponse - if json.Unmarshal(resp, &r) != nil { + if json.Unmarshal(resp, &r, jsonx.V1()) != nil { return hookStatusFailed, 0, nil } @@ -146,7 +148,7 @@ func (inv *hookInvocation) payload() []byte { event["hookDetails"] = inv.hookDetails } - raw, _ := json.Marshal(event) + raw, _ := json.Marshal(event, jsonx.V1()) return raw } diff --git a/services/ecs/models.go b/services/ecs/models.go index 0d0c16d00b..7e6be6f796 100644 --- a/services/ecs/models.go +++ b/services/ecs/models.go @@ -1,8 +1,10 @@ package ecs import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Load balancer, logging, secrets, and volume models ---- @@ -913,7 +915,7 @@ func (c *ContainerDefinition) UnmarshalJSON(data []byte) error { type plain ContainerDefinition p := plain{Essential: true} - if err := json.Unmarshal(data, &p); err != nil { + if err := json.Unmarshal(data, &p, jsonx.V1()); err != nil { return err } diff --git a/services/ecs/multi_region_test.go b/services/ecs/multi_region_test.go index 89a98ecbda..a16d66c70d 100644 --- a/services/ecs/multi_region_test.go +++ b/services/ecs/multi_region_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -39,7 +40,7 @@ func regionCall(t *testing.T, h *ecs.Handler, region, op, body string) map[strin require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/ecs/pagination_test.go b/services/ecs/pagination_test.go index 84fd1a6dc7..1030f7cf2e 100644 --- a/services/ecs/pagination_test.go +++ b/services/ecs/pagination_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -26,7 +27,7 @@ func TestPaginationCoverage(t *testing.T) { rec := doECSRequest(t, h, "DescribeCapacityProviders", map[string]any{"maxResults": 2}) require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) cps := body["capacityProviders"].([]any) assert.Len(t, cps, 2) assert.NotEmpty(t, body["nextToken"]) @@ -40,7 +41,7 @@ func TestPaginationCoverage(t *testing.T) { } first := doECSRequest(t, h, "DescribeCapacityProviders", map[string]any{"maxResults": 2}) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) next := b1["nextToken"].(string) second := doECSRequest( @@ -51,7 +52,7 @@ func TestPaginationCoverage(t *testing.T) { ) require.Equal(t, http.StatusOK, second.Code) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) cps := b2["capacityProviders"].([]any) assert.Len(t, cps, 1) assert.Empty(t, b2["nextToken"]) @@ -75,7 +76,7 @@ func TestPaginationCoverage(t *testing.T) { rec := doECSRequest(t, h, "DescribeCapacityProviders", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["capacityProviders"].([]any), 100) assert.NotEmpty(t, body["nextToken"]) }) @@ -101,7 +102,7 @@ func TestPaginationCoverage_ListTaskDefFamilies(t *testing.T) { rec := doECSRequest(t, h, "ListTaskDefinitionFamilies", map[string]any{"maxResults": 2}) require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["families"].([]any), 2) assert.NotEmpty(t, body["nextToken"]) }) @@ -122,7 +123,7 @@ func TestPaginationCoverage_ListTaskDefFamilies(t *testing.T) { } first := doECSRequest(t, h, "ListTaskDefinitionFamilies", map[string]any{"maxResults": 2}) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) next := b1["nextToken"].(string) second := doECSRequest( t, @@ -131,7 +132,7 @@ func TestPaginationCoverage_ListTaskDefFamilies(t *testing.T) { map[string]any{"maxResults": 2, "nextToken": next}, ) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) assert.Len(t, b2["families"].([]any), 1) assert.Empty(t, b2["nextToken"]) }) @@ -159,7 +160,7 @@ func TestPaginationCoverage_ListTaskDefFamilies(t *testing.T) { map[string]any{"familyPrefix": "web", "maxResults": 1}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["families"].([]any), 1) assert.NotEmpty(t, body["nextToken"]) }, @@ -200,7 +201,7 @@ func TestPaginationCoverage_ListServices(t *testing.T) { map[string]any{"namespace": "frontend", "maxResults": 1}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["serviceArns"].([]any), 1) assert.NotEmpty(t, body["nextToken"]) }) @@ -238,7 +239,7 @@ func TestPaginationCoverage_ListServices(t *testing.T) { map[string]any{"namespace": "frontend", "maxResults": 2}, ) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) second := doECSRequest( t, h, @@ -250,7 +251,7 @@ func TestPaginationCoverage_ListServices(t *testing.T) { }, ) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) assert.Len(t, b2["serviceArns"].([]any), 1) assert.Empty(t, b2["nextToken"]) }) @@ -290,7 +291,7 @@ func TestPaginationCoverage_ListServices(t *testing.T) { map[string]any{"namespace": "frontend"}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["serviceArns"].([]any), 100) assert.NotEmpty(t, body["nextToken"]) }) @@ -326,7 +327,7 @@ func TestPaginationCoverage_ListServiceDeployments(t *testing.T) { map[string]any{"cluster": "default", "service": "svc", "maxResults": 2}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["serviceDeployments"].([]any), 2) assert.NotEmpty(t, body["nextToken"]) }) @@ -358,7 +359,7 @@ func TestPaginationCoverage_ListServiceDeployments(t *testing.T) { map[string]any{"cluster": "default", "service": "svc", "maxResults": 2}, ) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) second := doECSRequest( t, h, @@ -371,7 +372,7 @@ func TestPaginationCoverage_ListServiceDeployments(t *testing.T) { }, ) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) assert.Len(t, b2["serviceDeployments"].([]any), 1) assert.Empty(t, b2["nextToken"]) }) @@ -403,7 +404,7 @@ func TestPaginationCoverage_ListServiceDeployments(t *testing.T) { map[string]any{"cluster": "default", "service": "svc"}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["serviceDeployments"].([]any), 100) assert.NotEmpty(t, body["nextToken"]) }) @@ -425,7 +426,7 @@ func TestPaginationCoverage_ListAccountSettings(t *testing.T) { } rec := doECSRequest(t, h, "ListAccountSettings", map[string]any{"maxResults": 2}) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["settings"].([]any), 2) assert.NotEmpty(t, body["nextToken"]) }) @@ -443,7 +444,7 @@ func TestPaginationCoverage_ListAccountSettings(t *testing.T) { } first := doECSRequest(t, h, "ListAccountSettings", map[string]any{"maxResults": 2}) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) second := doECSRequest( t, h, @@ -451,7 +452,7 @@ func TestPaginationCoverage_ListAccountSettings(t *testing.T) { map[string]any{"maxResults": 2, "nextToken": b1["nextToken"].(string)}, ) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) assert.Len(t, b2["settings"].([]any), 1) assert.Empty(t, b2["nextToken"]) }) @@ -478,7 +479,7 @@ func TestPaginationCoverage_ListAccountSettings(t *testing.T) { map[string]any{"name": "containerInsights", "maxResults": 2}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["settings"].([]any), 2) assert.NotEmpty(t, body["nextToken"]) }) @@ -501,7 +502,7 @@ func TestPaginationCoverage_ListAccountSettings(t *testing.T) { } rec := doECSRequest(t, h, "ListAccountSettings", map[string]any{}) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["settings"].([]any), 100) assert.NotEmpty(t, body["nextToken"]) }) @@ -532,7 +533,7 @@ func TestPaginationCoverage_ListAttributes(t *testing.T) { } rec := doECSRequest(t, h, "ListAttributes", map[string]any{"maxResults": 2}) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["attributes"].([]any), 2) assert.NotEmpty(t, body["nextToken"]) }) @@ -559,7 +560,7 @@ func TestPaginationCoverage_ListAttributes(t *testing.T) { } first := doECSRequest(t, h, "ListAttributes", map[string]any{"maxResults": 2}) var b1 map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &b1, jsonx.V1())) second := doECSRequest( t, h, @@ -567,7 +568,7 @@ func TestPaginationCoverage_ListAttributes(t *testing.T) { map[string]any{"maxResults": 2, "nextToken": b1["nextToken"].(string)}, ) var b2 map[string]any - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b2, jsonx.V1())) assert.Len(t, b2["attributes"].([]any), 1) assert.Empty(t, b2["nextToken"]) }) @@ -599,7 +600,7 @@ func TestPaginationCoverage_ListAttributes(t *testing.T) { map[string]any{"targetType": "container-instance", "maxResults": 1}, ) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["attributes"].([]any), 1) assert.NotEmpty(t, body["nextToken"]) }) @@ -628,7 +629,7 @@ func TestPaginationCoverage_ListAttributes(t *testing.T) { } rec := doECSRequest(t, h, "ListAttributes", map[string]any{}) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Len(t, body["attributes"].([]any), 100) assert.NotEmpty(t, body["nextToken"]) }) diff --git a/services/ecs/persistence.go b/services/ecs/persistence.go index b70e6306aa..324087e7a8 100644 --- a/services/ecs/persistence.go +++ b/services/ecs/persistence.go @@ -2,7 +2,7 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -34,7 +34,7 @@ const ecsSnapshotVersion = 1 // The remaining fields are resources deliberately left as plain maps by // registerAllTables (see its exclusion list) and so are still serialised directly. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TaskDefinitions map[string][]*TaskDefinition `json:"taskDefinitions"` Attributes map[string]map[string]*Attribute `json:"attributes"` DaemonTaskDefinitions map[string][]*DaemonTaskDefinition `json:"daemonTaskDefinitions"` diff --git a/services/ecs/realism_validation_test.go b/services/ecs/realism_validation_test.go index 50b0504802..783784032e 100644 --- a/services/ecs/realism_validation_test.go +++ b/services/ecs/realism_validation_test.go @@ -1,13 +1,15 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestECSWire_InputValidation(t *testing.T) { @@ -91,7 +93,7 @@ func TestECSWire_InputValidation(t *testing.T) { } var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) assert.False(t, strings.HasPrefix(out["message"], tt.wantType), out["message"]) }) diff --git a/services/ecs/reconciler_test.go b/services/ecs/reconciler_test.go index fad1921da6..e318462307 100644 --- a/services/ecs/reconciler_test.go +++ b/services/ecs/reconciler_test.go @@ -1,11 +1,13 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestReconciler_MinimumHealthyPercent_CapsScaleDown(t *testing.T) { @@ -19,7 +21,7 @@ func TestReconciler_MinimumHealthyPercent_CapsScaleDown(t *testing.T) { }) require.Equal(t, http.StatusOK, tdRec.Code) var tdOut map[string]any - require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut)) + require.NoError(t, json.Unmarshal(tdRec.Body.Bytes(), &tdOut, jsonx.V1())) tdArn := tdOut["taskDefinition"].(map[string]any)["taskDefinitionArn"].(string) doECSRequest(t, h, "CreateCluster", map[string]any{"clusterName": "test"}) diff --git a/services/ecs/run_task_count_test.go b/services/ecs/run_task_count_test.go index 271447d7e3..b903517600 100644 --- a/services/ecs/run_task_count_test.go +++ b/services/ecs/run_task_count_test.go @@ -1,12 +1,14 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRunTaskCount(t *testing.T) { @@ -40,7 +42,7 @@ func TestRunTaskCount(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["tasks"], tt.want) }) } diff --git a/services/ecs/service_deployment_filters_test.go b/services/ecs/service_deployment_filters_test.go index 2f4f2b90d1..829840beae 100644 --- a/services/ecs/service_deployment_filters_test.go +++ b/services/ecs/service_deployment_filters_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -128,7 +129,7 @@ func TestListDaemonDeployments_CreatedAtFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) daemonArn, _ := created["daemonArn"].(string) require.NotEmpty(t, daemonArn) diff --git a/services/ecs/task_metadata.go b/services/ecs/task_metadata.go index 49cb6d019c..609ca87976 100644 --- a/services/ecs/task_metadata.go +++ b/services/ecs/task_metadata.go @@ -2,7 +2,7 @@ package ecs import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "net/http" "strconv" @@ -24,7 +24,7 @@ var ErrContainerStatsUnavailable = errors.New("container stats unavailable") // containerStatsRunner is implemented by runners that can report a container's resource stats. type containerStatsRunner interface { - ContainerStatsJSON(ctx context.Context, containerID string) (json.RawMessage, error) + ContainerStatsJSON(ctx context.Context, containerID string) (jsontext.Value, error) } type metadataLimits struct { @@ -298,7 +298,7 @@ func (b *InMemoryBackend) TaskMetadataFor(taskID string, withTags bool) (TaskMet return doc, true } -func (b *InMemoryBackend) containerStats(ctx context.Context, containerID string) (json.RawMessage, error) { +func (b *InMemoryBackend) containerStats(ctx context.Context, containerID string) (jsontext.Value, error) { r, ok := b.runner.(containerStatsRunner) if !ok { return nil, ErrContainerStatsUnavailable @@ -412,7 +412,7 @@ func (h *Handler) serveStats(c *echo.Context, bk *InMemoryBackend, containers [] ctx, cancel := context.WithTimeout(c.Request().Context(), statsTimeout) defer cancel() - all := make(map[string]json.RawMessage, len(containers)) + all := make(map[string]jsontext.Value, len(containers)) for _, cm := range containers { raw, err := bk.containerStats(ctx, cm.DockerID) diff --git a/services/ecs/task_metadata_test.go b/services/ecs/task_metadata_test.go index 26149530d9..dbea811d49 100644 --- a/services/ecs/task_metadata_test.go +++ b/services/ecs/task_metadata_test.go @@ -2,7 +2,8 @@ package ecs_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/ecs" ) @@ -45,8 +47,8 @@ func (m *metaIssuer) IssueTaskCredentials(roleArn, session string) (ecs.RoleCred type statsRunner struct{ ecs.TaskRunner } -func (statsRunner) ContainerStatsJSON(_ context.Context, id string) (json.RawMessage, error) { - return json.RawMessage(`{"id":"` + id + `","cpu_stats":{"online_cpus":2},"memory_stats":{"usage":1024}}`), nil +func (statsRunner) ContainerStatsJSON(_ context.Context, id string) (jsontext.Value, error) { + return jsontext.Value(`{"id":"` + id + `","cpu_stats":{"online_cpus":2},"memory_stats":{"usage":1024}}`), nil } type metaFixture struct { @@ -111,7 +113,7 @@ func (f *metaFixture) get(t *testing.T, path string) (int, map[string]any) { defer resp.Body.Close() var out map[string]any - _ = json.NewDecoder(resp.Body).Decode(&out) + _ = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1()) return resp.StatusCode, out } diff --git a/services/ecs/taskdef_validation.go b/services/ecs/taskdef_validation.go index c4379721e5..8980697657 100644 --- a/services/ecs/taskdef_validation.go +++ b/services/ecs/taskdef_validation.go @@ -1,11 +1,12 @@ package ecs import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -206,7 +207,7 @@ func validateNextToken(body []byte) error { NextToken string `json:"nextToken"` } - if json.Unmarshal(body, &in) == nil && in.NextToken != "" && page.ValidateToken(in.NextToken) != nil { + if json.Unmarshal(body, &in, jsonx.V1()) == nil && in.NextToken != "" && page.ValidateToken(in.NextToken) != nil { return fmt.Errorf("%w: Invalid token", ErrInvalidParameter) } diff --git a/services/ecs/undeclared_members_test.go b/services/ecs/undeclared_members_test.go index 059bde0c3b..773358428a 100644 --- a/services/ecs/undeclared_members_test.go +++ b/services/ecs/undeclared_members_test.go @@ -1,7 +1,7 @@ package ecs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ecs/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_UndeclaredMembersAbsent(t *testing.T) { @@ -63,7 +65,7 @@ func TestHandler_UndeclaredMembersAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) obj := tc.path(out) for _, a := range tc.absent { diff --git a/services/efs/file_system_policy.go b/services/efs/file_system_policy.go index cab1fb5a5f..e3e70d49ba 100644 --- a/services/efs/file_system_policy.go +++ b/services/efs/file_system_policy.go @@ -2,8 +2,10 @@ package efs import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // DescribeFileSystemPolicy returns the resource-based policy for a file system. @@ -48,7 +50,7 @@ func (b *InMemoryBackend) DeleteFileSystemPolicy(ctx context.Context, fileSystem // PutFileSystemPolicy sets the resource-based policy for a file system. // The policy must be valid JSON and no larger than 20 KB. func (b *InMemoryBackend) PutFileSystemPolicy(ctx context.Context, fileSystemID, policy string) error { - if !json.Valid([]byte(policy)) { + if !jsontext.Value([]byte(policy)).IsValid(jsonx.V1()) { return fmt.Errorf("%w: FileSystemPolicy is not valid JSON", ErrInvalidPolicy) } if len(policy) > maxFileSystemPolicyBytes { diff --git a/services/efs/handler.go b/services/efs/handler.go index ddca51aa5f..da0386fa32 100644 --- a/services/efs/handler.go +++ b/services/efs/handler.go @@ -2,7 +2,8 @@ package efs import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -13,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -455,9 +457,9 @@ func (h *Handler) Handler() echo.HandlerFunc { var body []byte if c.Request().Body != nil { - decoder := json.NewDecoder(c.Request().Body) - var raw json.RawMessage - if err := decoder.Decode(&raw); err == nil { + decoder := jsontext.NewDecoder(c.Request().Body) + var raw jsontext.Value + if err := json.UnmarshalDecode(decoder, &raw, jsonx.V1()); err == nil { body = raw } } diff --git a/services/efs/handler_access_points.go b/services/efs/handler_access_points.go index a61d58c32b..cb80abcba2 100644 --- a/services/efs/handler_access_points.go +++ b/services/efs/handler_access_points.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createAccessPointBody struct { @@ -17,7 +19,7 @@ type createAccessPointBody struct { func (h *Handler) handleCreateAccessPoint(c *echo.Context, body []byte) error { var in createAccessPointBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_access_points_test.go b/services/efs/handler_access_points_test.go index a1523b82f4..8219268a22 100644 --- a/services/efs/handler_access_points_test.go +++ b/services/efs/handler_access_points_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -261,7 +262,7 @@ func TestAccessPoint_ClientTokenIdempotency_HTTP(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) firstRec := doREST(t, h, http.MethodPost, "/2015-02-01/access-points", map[string]any{ "FileSystemId": fsOut.FileSystemID, @@ -272,7 +273,7 @@ func TestAccessPoint_ClientTokenIdempotency_HTTP(t *testing.T) { var firstOut struct { AccessPointID string `json:"AccessPointId"` } - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstOut)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstOut, jsonx.V1())) secondRec := doREST(t, h, http.MethodPost, "/2015-02-01/access-points", map[string]any{ "FileSystemId": fsOut.FileSystemID, @@ -283,7 +284,7 @@ func TestAccessPoint_ClientTokenIdempotency_HTTP(t *testing.T) { var secondOut struct { AccessPointID string `json:"AccessPointId"` } - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondOut)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondOut, jsonx.V1())) assert.Equal(t, firstOut.AccessPointID, secondOut.AccessPointID, "repeated CreateAccessPoint with same ClientToken must return the same access point") @@ -304,7 +305,7 @@ func TestDeleteAccessPoint_CleansAPIndex(t *testing.T) { require.Equal(t, http.StatusOK, apRec.Code) var apOut map[string]any - require.NoError(t, json.Unmarshal(apRec.Body.Bytes(), &apOut)) + require.NoError(t, json.Unmarshal(apRec.Body.Bytes(), &apOut, jsonx.V1())) apID := apOut["AccessPointId"].(string) blockRec := doREST(t, h, http.MethodDelete, "/2015-02-01/file-systems/"+fsID, nil) diff --git a/services/efs/handler_account_preferences.go b/services/efs/handler_account_preferences.go index f2df42b005..ccd7136e1e 100644 --- a/services/efs/handler_account_preferences.go +++ b/services/efs/handler_account_preferences.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeAccountPreferences(c *echo.Context) error { @@ -30,7 +32,7 @@ type putAccountPreferencesBody struct { func (h *Handler) handlePutAccountPreferences(c *echo.Context, body []byte) error { var in putAccountPreferencesBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_backup_policy.go b/services/efs/handler_backup_policy.go index 955a44b50f..71fc5ee336 100644 --- a/services/efs/handler_backup_policy.go +++ b/services/efs/handler_backup_policy.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeBackupPolicy(c *echo.Context, fileSystemID string) error { @@ -28,7 +30,7 @@ type putBackupPolicyBody struct { func (h *Handler) handlePutBackupPolicy(c *echo.Context, fileSystemID string, body []byte) error { var in putBackupPolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_backup_policy_test.go b/services/efs/handler_backup_policy_test.go index 6ba4997209..5a70c56594 100644 --- a/services/efs/handler_backup_policy_test.go +++ b/services/efs/handler_backup_policy_test.go @@ -1,13 +1,14 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -114,7 +115,7 @@ func TestBackupPolicy_EnabledRoundTrip(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) putRec := doREST(t, h, http.MethodPut, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/backup-policy", @@ -130,7 +131,7 @@ func TestBackupPolicy_EnabledRoundTrip(t *testing.T) { Status string `json:"Status"` } `json:"BackupPolicy"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "ENABLED", descOut.BackupPolicy.Status) } @@ -150,7 +151,7 @@ func TestBackupPolicy_InvalidStatusRejected(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rec := doREST(t, h, http.MethodPut, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/backup-policy", diff --git a/services/efs/handler_file_system_policy.go b/services/efs/handler_file_system_policy.go index f5a8e1a814..4f147b6c4e 100644 --- a/services/efs/handler_file_system_policy.go +++ b/services/efs/handler_file_system_policy.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeFileSystemPolicy(c *echo.Context, fileSystemID string) error { @@ -38,7 +40,7 @@ func (h *Handler) handlePutFileSystemPolicy( body []byte, ) error { var in putFileSystemPolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_file_system_policy_test.go b/services/efs/handler_file_system_policy_test.go index e6c340d00e..3910cdd3ee 100644 --- a/services/efs/handler_file_system_policy_test.go +++ b/services/efs/handler_file_system_policy_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -262,7 +263,7 @@ func TestFileSystemPolicy_RoundTrip(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) policy := `{"Version":"2012-10-17","Statement":[` + `{"Effect":"Allow","Principal":{"AWS":"*"},"Action":"elasticfilesystem:ClientMount"}]}` @@ -279,7 +280,7 @@ func TestFileSystemPolicy_RoundTrip(t *testing.T) { var descOut struct { Policy string `json:"Policy"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.JSONEq(t, policy, descOut.Policy, "DescribeFileSystemPolicy must return the stored policy verbatim") diff --git a/services/efs/handler_file_systems.go b/services/efs/handler_file_systems.go index de95e45729..bcfe658f75 100644 --- a/services/efs/handler_file_systems.go +++ b/services/efs/handler_file_systems.go @@ -1,12 +1,13 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -24,7 +25,7 @@ type createFileSystemBody struct { func (h *Handler) handleCreateFileSystem(c *echo.Context, body []byte) error { var in createFileSystemBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } @@ -154,7 +155,7 @@ type updateFileSystemBody struct { func (h *Handler) handleUpdateFileSystem(c *echo.Context, fileSystemID string, body []byte) error { var in updateFileSystemBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_file_systems_test.go b/services/efs/handler_file_systems_test.go index d5bb634666..3afba87518 100644 --- a/services/efs/handler_file_systems_test.go +++ b/services/efs/handler_file_systems_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -613,7 +614,7 @@ func TestSizeInBytes_IncludesStorageClassBreakdown(t *testing.T) { var createOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) descRec := doREST(t, h, http.MethodGet, "/2015-02-01/file-systems?FileSystemId="+createOut.FileSystemID, nil) @@ -624,7 +625,7 @@ func TestSizeInBytes_IncludesStorageClassBreakdown(t *testing.T) { SizeInBytes map[string]any `json:"SizeInBytes"` } `json:"FileSystems"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.Len(t, descOut.FileSystems, 1) sib := descOut.FileSystems[0].SizeInBytes @@ -655,7 +656,7 @@ func TestDeleteFileSystem_RejectedWhenMountTargetsExist(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) mtRec := doREST(t, h, http.MethodPost, "/2015-02-01/mount-targets", map[string]any{ "FileSystemId": fsOut.FileSystemID, @@ -685,7 +686,7 @@ func TestFileSystem_CreatingToAvailableLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "creating", out["LifeCycleState"], "newly created FS should be in 'creating' state immediately after CreateFileSystem") @@ -701,7 +702,7 @@ func TestFileSystem_CreatingToAvailableLifecycle(t *testing.T) { LifeCycleState string `json:"LifeCycleState"` } `json:"FileSystems"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) require.NotEmpty(t, descOut.FileSystems) return descOut.FileSystems[0].LifeCycleState @@ -850,7 +851,7 @@ func TestDescribeFileSystems_PaginationMarker(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page fsPage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page, jsonx.V1())) require.NotEmpty(t, page.FileSystems, "page must not be empty while a marker is being followed") pageSizes = append(pageSizes, len(page.FileSystems)) diff --git a/services/efs/handler_lifecycle_config.go b/services/efs/handler_lifecycle_config.go index dd840e8b2b..f218bde313 100644 --- a/services/efs/handler_lifecycle_config.go +++ b/services/efs/handler_lifecycle_config.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putLifecycleConfigBody struct { @@ -28,7 +30,7 @@ func (h *Handler) handlePutLifecycleConfiguration( body []byte, ) error { var in putLifecycleConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_lifecycle_config_test.go b/services/efs/handler_lifecycle_config_test.go index 3e8162231c..50e7201aaa 100644 --- a/services/efs/handler_lifecycle_config_test.go +++ b/services/efs/handler_lifecycle_config_test.go @@ -1,12 +1,14 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestLifecycleConfiguration tests DescribeLifecycleConfiguration and PutLifecycleConfiguration. @@ -142,7 +144,7 @@ func TestLifecyclePolicy_InvalidTransitionRejected(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rec := doREST(t, h, http.MethodPut, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/lifecycle-configuration", diff --git a/services/efs/handler_mount_target_security_groups.go b/services/efs/handler_mount_target_security_groups.go index 3f8ab3fdfd..fd04a6dcf4 100644 --- a/services/efs/handler_mount_target_security_groups.go +++ b/services/efs/handler_mount_target_security_groups.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeMountTargetSecurityGroups( @@ -34,7 +36,7 @@ func (h *Handler) handleModifyMountTargetSecurityGroups( body []byte, ) error { var in modifyMountTargetSGBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_mount_targets.go b/services/efs/handler_mount_targets.go index 4208533d9c..b132daba17 100644 --- a/services/efs/handler_mount_targets.go +++ b/services/efs/handler_mount_targets.go @@ -1,11 +1,13 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createMountTargetBody struct { @@ -19,7 +21,7 @@ type createMountTargetBody struct { func (h *Handler) handleCreateMountTarget(c *echo.Context, body []byte) error { var in createMountTargetBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_mount_targets_test.go b/services/efs/handler_mount_targets_test.go index cf6ba7d98f..d0aa26b006 100644 --- a/services/efs/handler_mount_targets_test.go +++ b/services/efs/handler_mount_targets_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -559,7 +560,7 @@ func TestMountTarget_SecurityGroupLimitEnforced(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rec := doREST(t, h, http.MethodPost, "/2015-02-01/mount-targets", map[string]any{ "FileSystemId": fsOut.FileSystemID, @@ -589,7 +590,7 @@ func TestMountTarget_DuplicateSubnetRejected(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) firstRec := doREST(t, h, http.MethodPost, "/2015-02-01/mount-targets", map[string]any{ "FileSystemId": fsOut.FileSystemID, @@ -633,7 +634,7 @@ func TestCreateMountTarget_PopulatesVpcAndAZ(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "CreateMountTarget: %s", rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) vpcID, _ := out["VpcId"].(string) azName, _ := out["AvailabilityZoneName"].(string) @@ -666,7 +667,7 @@ func TestCreateMountTarget_VpcIDStablePerSubnet(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var mt1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &mt1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &mt1, jsonx.V1())) rec2 := doREST(t, h, http.MethodPost, "/2015-02-01/mount-targets", map[string]any{ "FileSystemId": fsID2, @@ -675,7 +676,7 @@ func TestCreateMountTarget_VpcIDStablePerSubnet(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var mt2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &mt2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &mt2, jsonx.V1())) assert.Equal(t, mt1["VpcId"], mt2["VpcId"], "same subnet should yield same VpcId across different file systems") @@ -697,7 +698,7 @@ func TestCreateMountTarget_OneZone_InheritsAZ(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fsOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fsOut, jsonx.V1())) fsID := fsOut["FileSystemId"].(string) mtRec := doREST(t, h, http.MethodPost, "/2015-02-01/mount-targets", map[string]any{ @@ -707,7 +708,7 @@ func TestCreateMountTarget_OneZone_InheritsAZ(t *testing.T) { require.Equal(t, http.StatusOK, mtRec.Code) var mt map[string]any - require.NoError(t, json.Unmarshal(mtRec.Body.Bytes(), &mt)) + require.NoError(t, json.Unmarshal(mtRec.Body.Bytes(), &mt, jsonx.V1())) assert.Equal(t, az, mt["AvailabilityZoneName"], "mount target should inherit FS AvailabilityZoneName for One Zone storage class") @@ -770,7 +771,7 @@ func TestDescribeMountTargets_Pagination_HTTP(t *testing.T) { } var pg1 mtPage - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &pg1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &pg1, jsonx.V1())) require.Len(t, pg1.MountTargets, 3) require.NotEmpty(t, pg1.NextMarker) @@ -779,7 +780,7 @@ func TestDescribeMountTargets_Pagination_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var pg2 mtPage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &pg2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &pg2, jsonx.V1())) // 5 items, pageSize=3: page1=[0,1,2] marker=items[3], page2 resumes AT items[3] => [3,4]. assert.Len(t, pg2.MountTargets, 2) assert.Empty(t, pg2.NextMarker) @@ -815,7 +816,7 @@ func TestDeleteMountTarget_CleansSubnetIndex(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var mt1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &mt1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &mt1, jsonx.V1())) mtID := mt1["MountTargetId"].(string) delRec := doREST(t, h, http.MethodDelete, "/2015-02-01/mount-targets/"+mtID, nil) diff --git a/services/efs/handler_replication.go b/services/efs/handler_replication.go index f09b965a77..4cab8409f8 100644 --- a/services/efs/handler_replication.go +++ b/services/efs/handler_replication.go @@ -1,10 +1,12 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createReplicationConfigBody struct { @@ -17,7 +19,7 @@ func (h *Handler) handleCreateReplicationConfiguration( body []byte, ) error { var in createReplicationConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } @@ -132,7 +134,7 @@ func (h *Handler) handleUpdateFileSystemProtection( body []byte, ) error { var in updateFileSystemProtectionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/handler_replication_test.go b/services/efs/handler_replication_test.go index e981e135d7..0ee5845815 100644 --- a/services/efs/handler_replication_test.go +++ b/services/efs/handler_replication_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -351,7 +352,7 @@ func TestReplicationConfiguration_SourceOwnerInResponse(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rcRec := doREST(t, h, http.MethodPost, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/replication-configuration", @@ -365,7 +366,7 @@ func TestReplicationConfiguration_SourceOwnerInResponse(t *testing.T) { var rcOut struct { SourceFileSystemOwnerID string `json:"SourceFileSystemOwnerId"` } - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut, jsonx.V1())) assert.NotEmpty(t, rcOut.SourceFileSystemOwnerID, "SourceFileSystemOwnerId must be present in CreateReplicationConfiguration response") @@ -393,7 +394,7 @@ func TestReplicationConfiguration_DestinationHasOwnerNoArn(t *testing.T) { var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rcRec := doREST(t, h, http.MethodPost, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/replication-configuration", @@ -405,7 +406,7 @@ func TestReplicationConfiguration_DestinationHasOwnerNoArn(t *testing.T) { require.Equal(t, http.StatusOK, rcRec.Code, "CreateReplicationConfiguration failed: %s", rcRec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &raw, jsonx.V1())) dests, ok := raw["Destinations"].([]any) require.True(t, ok) require.Len(t, dests, 1) @@ -420,7 +421,7 @@ func TestReplicationConfiguration_DestinationHasOwnerNoArn(t *testing.T) { Status string `json:"Status"` } `json:"Destinations"` } - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut, jsonx.V1())) require.Len(t, rcOut.Destinations, 1) d := rcOut.Destinations[0] @@ -448,7 +449,7 @@ func TestDescribeReplicationConfigurations_ByDestinationFileSystemID(t *testing. var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rcRec := doREST(t, h, http.MethodPost, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/replication-configuration", @@ -464,7 +465,7 @@ func TestDescribeReplicationConfigurations_ByDestinationFileSystemID(t *testing. FileSystemID string `json:"FileSystemId"` } `json:"Destinations"` } - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut, jsonx.V1())) require.Len(t, rcOut.Destinations, 1) destFSID := rcOut.Destinations[0].FileSystemID @@ -483,7 +484,7 @@ func TestDescribeReplicationConfigurations_ByDestinationFileSystemID(t *testing. SourceFileSystemID string `json:"SourceFileSystemId"` } `json:"Replications"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Replications, 1, "DescribeReplicationConfigurations must return result when filtering by destination FS ID") @@ -602,7 +603,7 @@ func TestCreateReplicationConfiguration_AssignsDestinationFileSystemID(t *testin var fsOut struct { FileSystemID string `json:"FileSystemId"` } - require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(fsRec.Body.Bytes(), &fsOut, jsonx.V1())) rcRec := doREST(t, h, http.MethodPost, "/2015-02-01/file-systems/"+fsOut.FileSystemID+"/replication-configuration", @@ -618,7 +619,7 @@ func TestCreateReplicationConfiguration_AssignsDestinationFileSystemID(t *testin FileSystemID string `json:"FileSystemId"` } `json:"Destinations"` } - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcOut, jsonx.V1())) require.Len(t, rcOut.Destinations, 1) assert.NotEmpty(t, rcOut.Destinations[0].FileSystemID, diff --git a/services/efs/handler_tags.go b/services/efs/handler_tags.go index 5702059043..afdbd1c8b9 100644 --- a/services/efs/handler_tags.go +++ b/services/efs/handler_tags.go @@ -1,13 +1,14 @@ package efs import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -17,7 +18,7 @@ type tagResourceBody struct { func (h *Handler) handleTagResource(c *echo.Context, resourceID string, body []byte) error { var in tagResourceBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } @@ -110,7 +111,7 @@ type createTagsBody struct { func (h *Handler) handleCreateTags(c *echo.Context, fileSystemID string, body []byte) error { var in createTagsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } @@ -128,7 +129,7 @@ type deleteTagsBody struct { func (h *Handler) handleDeleteTags(c *echo.Context, fileSystemID string, body []byte) error { var in deleteTagsBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequest", "invalid request body")) } diff --git a/services/efs/helpers_test.go b/services/efs/helpers_test.go index 9d05f6d4f5..2228963364 100644 --- a/services/efs/helpers_test.go +++ b/services/efs/helpers_test.go @@ -2,7 +2,7 @@ package efs_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -37,7 +38,7 @@ func doREST( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -58,7 +59,7 @@ func parseResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/efs/persistence.go b/services/efs/persistence.go index cbfc3f542d..8c58b6fce5 100644 --- a/services/efs/persistence.go +++ b/services/efs/persistence.go @@ -2,7 +2,7 @@ package efs import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -52,7 +52,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` LifecyclePolicies map[string]map[string][]LifecyclePolicy `json:"lifecyclePolicies"` BackupPolicies map[string]map[string]string `json:"backupPolicies"` FileSystemPolicies map[string]map[string]string `json:"fileSystemPolicies"` @@ -199,7 +199,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/efs/wire_sdk_roundtrip_test.go b/services/efs/wire_sdk_roundtrip_test.go index 7c0cd26100..a0d159ee19 100644 --- a/services/efs/wire_sdk_roundtrip_test.go +++ b/services/efs/wire_sdk_roundtrip_test.go @@ -1,7 +1,7 @@ package efs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/efs" ) @@ -91,7 +92,7 @@ func TestReplicationDestination_NoFabricatedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "CreateReplicationConfiguration failed: %s", rec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) dests, ok := raw["Destinations"].([]any) require.True(t, ok) diff --git a/services/eks/addons.go b/services/eks/addons.go index fde0c8cd77..88af4e5733 100644 --- a/services/eks/addons.go +++ b/services/eks/addons.go @@ -1,7 +1,7 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "time" @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -445,7 +446,7 @@ func (b *InMemoryBackend) DescribeAddonConfiguration(addonName, addonVersion str schema, _ := json.Marshal(map[string]any{ keyType: "object", "properties": map[string]any{}, - }) + }, jsonx.V1()) return map[string]any{ keyAddonName: addonName, diff --git a/services/eks/capability_configuration.go b/services/eks/capability_configuration.go index 4678daca59..f013a834db 100644 --- a/services/eks/capability_configuration.go +++ b/services/eks/capability_configuration.go @@ -1,6 +1,12 @@ package eks -import "encoding/json" +import ( + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // Wire-shape request/response bodies and validation/conversion for // Capability.Configuration -- gopherstack-wf8f item 1. Every field/key here @@ -424,12 +430,12 @@ func (c *Capability) UnmarshalJSON(data []byte) error { aux := struct { *capabilityAlias - Configuration json.RawMessage `json:"configuration"` + Configuration jsontext.Value `json:"configuration"` }{ capabilityAlias: (*capabilityAlias)(c), } - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -440,7 +446,7 @@ func (c *Capability) UnmarshalJSON(data []byte) error { } var cfg CapabilityConfiguration - if err := json.Unmarshal(aux.Configuration, &cfg); err == nil { + if err := json.Unmarshal(aux.Configuration, &cfg, jsonx.V1()); err == nil { c.Configuration = &cfg } diff --git a/services/eks/cluster_config_blocks.go b/services/eks/cluster_config_blocks.go index 833d07808a..08260ad311 100644 --- a/services/eks/cluster_config_blocks.go +++ b/services/eks/cluster_config_blocks.go @@ -1,27 +1,30 @@ package eks import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // clusterConfigBlocksBody holds the cluster config members stored and echoed verbatim. type clusterConfigBlocksBody struct { - ControlPlaneScalingConfig json.RawMessage `json:"controlPlaneScalingConfig"` - KubeAPIServerConfig json.RawMessage `json:"kubeApiServerConfig"` - KubeControllerManagerConfig json.RawMessage `json:"kubeControllerManagerConfig"` - KubeSchedulerConfig json.RawMessage `json:"kubeSchedulerConfig"` - OutpostConfig json.RawMessage `json:"outpostConfig"` - RemoteNetworkConfig json.RawMessage `json:"remoteNetworkConfig"` - ZonalShiftConfig json.RawMessage `json:"zonalShiftConfig"` + ControlPlaneScalingConfig jsontext.Value `json:"controlPlaneScalingConfig"` + KubeAPIServerConfig jsontext.Value `json:"kubeApiServerConfig"` + KubeControllerManagerConfig jsontext.Value `json:"kubeControllerManagerConfig"` + KubeSchedulerConfig jsontext.Value `json:"kubeSchedulerConfig"` + OutpostConfig jsontext.Value `json:"outpostConfig"` + RemoteNetworkConfig jsontext.Value `json:"remoteNetworkConfig"` + ZonalShiftConfig jsontext.Value `json:"zonalShiftConfig"` } // blocks returns the supplied members keyed by wire name; outpostConfig is create-only. -func (b clusterConfigBlocksBody) blocks(forCreate bool) map[string]json.RawMessage { - all := map[string]json.RawMessage{ +func (b clusterConfigBlocksBody) blocks(forCreate bool) map[string]jsontext.Value { + all := map[string]jsontext.Value{ "controlPlaneScalingConfig": b.ControlPlaneScalingConfig, "kubeApiServerConfig": b.KubeAPIServerConfig, "kubeControllerManagerConfig": b.KubeControllerManagerConfig, @@ -51,10 +54,10 @@ func validTiers() []string { return []string{"standard", "tier-xl", "tier-2xl", "tier-4xl", "tier-8xl"} } -func validateConfigBlocks(blocks map[string]json.RawMessage) error { +func validateConfigBlocks(blocks map[string]jsontext.Value) error { for key, raw := range blocks { - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil { return fmt.Errorf("%w: %s must be an object", ErrValidation, key) } } @@ -63,7 +66,7 @@ func validateConfigBlocks(blocks map[string]json.RawMessage) error { var cfg struct { Tier string `json:"tier"` } - _ = json.Unmarshal(raw, &cfg) + _ = json.Unmarshal(raw, &cfg, jsonx.V1()) if cfg.Tier != "" && !slices.Contains(validTiers(), cfg.Tier) { return fmt.Errorf("%w: controlPlaneScalingConfig.tier must be one of %s", @@ -89,7 +92,7 @@ func configBlockParamType(key string) string { return strings.ToUpper(key[:1]) + key[1:] } -func cloneConfigBlocks(src map[string]json.RawMessage) map[string]json.RawMessage { +func cloneConfigBlocks(src map[string]jsontext.Value) map[string]jsontext.Value { if src == nil { return nil } @@ -98,14 +101,14 @@ func cloneConfigBlocks(src map[string]json.RawMessage) map[string]json.RawMessag } // applyConfigBlocksLocked replaces the stored blocks and returns the update params. Caller holds b.mu. -func applyConfigBlocksLocked(c *Cluster, blocks map[string]json.RawMessage) []UpdateParam { +func applyConfigBlocksLocked(c *Cluster, blocks map[string]jsontext.Value) []UpdateParam { if len(blocks) == 0 { return nil } merged := cloneConfigBlocks(c.ConfigBlocks) if merged == nil { - merged = make(map[string]json.RawMessage, len(blocks)) + merged = make(map[string]jsontext.Value, len(blocks)) } var params []UpdateParam @@ -128,11 +131,11 @@ func applyConfigBlocksLocked(c *Cluster, blocks map[string]json.RawMessage) []Up return params } -func blockTier(raw json.RawMessage) string { +func blockTier(raw jsontext.Value) string { var cfg struct { Tier string `json:"tier"` } - _ = json.Unmarshal(raw, &cfg) + _ = json.Unmarshal(raw, &cfg, jsonx.V1()) return cfg.Tier } diff --git a/services/eks/cluster_options_test.go b/services/eks/cluster_options_test.go index fa8e520a93..cae5d09560 100644 --- a/services/eks/cluster_options_test.go +++ b/services/eks/cluster_options_test.go @@ -4,7 +4,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "io" "net/http" @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -405,7 +406,7 @@ func TestServePublishedPorts(t *testing.T) { HostPort int `json:"hostPort"` } `json:"ports"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "c1", body.ClusterName) require.Len(t, body.Ports, tt.wantPorts) assert.Equal(t, 8080, body.Ports[0].ContainerPort) diff --git a/services/eks/clusters.go b/services/eks/clusters.go index 9dbb787b08..4f0b334d49 100644 --- a/services/eks/clusters.go +++ b/services/eks/clusters.go @@ -2,7 +2,7 @@ package eks import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "strings" @@ -21,7 +21,7 @@ type ClusterOptionalConfig struct { AccessConfig *AccessConfig ComputeConfig *ComputeConfig StorageConfig *StorageConfig - ConfigBlocks map[string]json.RawMessage + ConfigBlocks map[string]jsontext.Value UpgradePolicySupportType string CreatorARN string LogEntries []ClusterLogEntry diff --git a/services/eks/clusters_test.go b/services/eks/clusters_test.go index 7d5addd1d3..8a7d959f39 100644 --- a/services/eks/clusters_test.go +++ b/services/eks/clusters_test.go @@ -1,7 +1,7 @@ package eks_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -231,7 +232,7 @@ func TestVpcConfig_HandlerJSON_Fields(t *testing.T) { rec := doREST(t, h, http.MethodGet, "/clusters/c1", nil) body := rec.Body.Bytes() var raw map[string]any - require.NoError(t, json.Unmarshal(body, &raw)) + require.NoError(t, json.Unmarshal(body, &raw, jsonx.V1())) cluster := raw["cluster"].(map[string]any) vpc := cluster["resourcesVpcConfig"].(map[string]any) @@ -748,11 +749,11 @@ func TestSortedListClusters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) resp := parseResp(t, rec) - raw, err := json.Marshal(resp["clusters"]) + raw, err := json.Marshal(resp["clusters"], jsonx.V1()) require.NoError(t, err) var names []string - require.NoError(t, json.Unmarshal(raw, &names)) + require.NoError(t, json.Unmarshal(raw, &names, jsonx.V1())) require.Equal(t, []string{"aaa", "bbb", "mmm", "zzz"}, names) } diff --git a/services/eks/handler.go b/services/eks/handler.go index ab446700ec..29d52702a1 100644 --- a/services/eks/handler.go +++ b/services/eks/handler.go @@ -1,7 +1,8 @@ package eks import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -10,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -539,9 +541,9 @@ func (h *Handler) Handler() echo.HandlerFunc { var body []byte if c.Request().Body != nil { - decoder := json.NewDecoder(c.Request().Body) - var raw json.RawMessage - if err := decoder.Decode(&raw); err == nil { + decoder := jsontext.NewDecoder(c.Request().Body) + var raw jsontext.Value + if err := json.UnmarshalDecode(decoder, &raw, jsonx.V1()); err == nil { body = raw } } diff --git a/services/eks/handler_access_entries.go b/services/eks/handler_access_entries.go index 23fc3bb7bc..f4abf78bac 100644 --- a/services/eks/handler_access_entries.go +++ b/services/eks/handler_access_entries.go @@ -1,13 +1,14 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -146,7 +147,7 @@ type createAccessEntryBody struct { func (h *Handler) handleCreateAccessEntry(c *echo.Context, clusterName string, body []byte) error { var in createAccessEntryBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -224,7 +225,7 @@ type updateAccessEntryBody struct { func (h *Handler) handleUpdateAccessEntry(c *echo.Context, clusterName, principalARN string, body []byte) error { var in updateAccessEntryBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } @@ -248,7 +249,7 @@ type associateAccessPolicyBody struct { func (h *Handler) handleAssociateAccessPolicy(c *echo.Context, clusterName, principalARN string, body []byte) error { var in associateAccessPolicyBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } diff --git a/services/eks/handler_addons.go b/services/eks/handler_addons.go index f815cb8cf0..bbc628d2a6 100644 --- a/services/eks/handler_addons.go +++ b/services/eks/handler_addons.go @@ -1,7 +1,7 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "slices" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -147,7 +148,7 @@ type createAddonBody struct { func (h *Handler) handleCreateAddon(c *echo.Context, clusterName string, body []byte) error { var in createAddonBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -234,7 +235,7 @@ type updateAddonBody struct { func (h *Handler) handleUpdateAddon(c *echo.Context, clusterName, addonName string, body []byte) error { var in updateAddonBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_capabilities.go b/services/eks/handler_capabilities.go index 23507d9aeb..ccf684ff88 100644 --- a/services/eks/handler_capabilities.go +++ b/services/eks/handler_capabilities.go @@ -1,11 +1,12 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -147,7 +148,7 @@ type createCapabilityBody struct { func (h *Handler) handleCreateCapability(c *echo.Context, clusterName string, body []byte) error { var in createCapabilityBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -233,7 +234,7 @@ type updateCapabilityBody struct { func (h *Handler) handleUpdateCapability(c *echo.Context, clusterName, capabilityName string, body []byte) error { var in updateCapabilityBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_certificate_authorities.go b/services/eks/handler_certificate_authorities.go index 66e6d3c04e..44081b6c4f 100644 --- a/services/eks/handler_certificate_authorities.go +++ b/services/eks/handler_certificate_authorities.go @@ -1,12 +1,13 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -134,7 +135,7 @@ func certificateAuthorityIdempotencyFingerprintBody(clusterName, id string) []by payload["certificateAuthorityId"] = id } - raw, err := json.Marshal(payload) + raw, err := json.Marshal(payload, jsonx.V1()) if err != nil { return nil } @@ -149,7 +150,7 @@ type createCertificateAuthorityBody struct { func (h *Handler) handleCreateCertificateAuthority(c *echo.Context, clusterName string, body []byte) error { var in createCertificateAuthorityBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } } @@ -176,7 +177,7 @@ type activateCertificateAuthorityBody struct { func (h *Handler) handleActivateCertificateAuthority(c *echo.Context, clusterName, id string, body []byte) error { var in activateCertificateAuthorityBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } } diff --git a/services/eks/handler_clusters.go b/services/eks/handler_clusters.go index a884273d74..c4cebac5ba 100644 --- a/services/eks/handler_clusters.go +++ b/services/eks/handler_clusters.go @@ -1,7 +1,7 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "slices" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -302,7 +303,7 @@ type createClusterBody struct { func (h *Handler) handleCreateCluster(c *echo.Context, body []byte) error { var in createClusterBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -349,7 +350,7 @@ func (h *Handler) handleCreateCluster(c *echo.Context, body []byte) error { netCfg = withNetworkDefaults(netCfg) var blocksIn clusterConfigBlocksBody - _ = json.Unmarshal(body, &blocksIn) + _ = json.Unmarshal(body, &blocksIn, jsonx.V1()) opt := buildClusterOptConfig(in) opt.CreatorARN = awsmeta.CallerArn(c.Request().Context()) @@ -476,7 +477,7 @@ type registerClusterBody struct { func (h *Handler) handleRegisterCluster(c *echo.Context, body []byte) error { var in registerClusterBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } diff --git a/services/eks/handler_fargate_profiles.go b/services/eks/handler_fargate_profiles.go index 0b9c3d4708..da2ad862ad 100644 --- a/services/eks/handler_fargate_profiles.go +++ b/services/eks/handler_fargate_profiles.go @@ -1,11 +1,12 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -102,7 +103,7 @@ type createFargateProfileBody struct { func (h *Handler) handleCreateFargateProfile(c *echo.Context, clusterName string, body []byte) error { var in createFargateProfileBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } diff --git a/services/eks/handler_identity_providers.go b/services/eks/handler_identity_providers.go index 71ee2752c3..377dc17877 100644 --- a/services/eks/handler_identity_providers.go +++ b/services/eks/handler_identity_providers.go @@ -1,12 +1,13 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -84,7 +85,7 @@ type associateIdentityProviderConfigBody struct { func (h *Handler) handleAssociateIdentityProviderConfig(c *echo.Context, clusterName string, body []byte) error { var in associateIdentityProviderConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -163,7 +164,7 @@ type describeIDPRequestBody struct { func (h *Handler) handleDescribeIdentityProviderConfig(c *echo.Context, clusterName string, body []byte) error { var in describeIDPRequestBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } @@ -233,7 +234,7 @@ type disassociateIDPBody struct { func (h *Handler) handleDisassociateIdentityProviderConfig(c *echo.Context, clusterName string, body []byte) error { var in disassociateIDPBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_insights.go b/services/eks/handler_insights.go index c1dfe454d2..470bfa5b9b 100644 --- a/services/eks/handler_insights.go +++ b/services/eks/handler_insights.go @@ -1,12 +1,13 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -102,7 +103,7 @@ func (h *Handler) handleListInsights(c *echo.Context, clusterName string, body [ // A malformed body is tolerated (ListInsights' filter/pagination // fields are all optional) rather than rejected, matching the // permissive parsing used by every other optional-body op here. - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } if in.Filter != nil { diff --git a/services/eks/handler_irsa.go b/services/eks/handler_irsa.go index af9ddb616a..7674d1accd 100644 --- a/services/eks/handler_irsa.go +++ b/services/eks/handler_irsa.go @@ -1,7 +1,8 @@ package eks import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "time" @@ -9,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -111,7 +113,7 @@ func (h *Handler) serveMintToken(c *echo.Context, cluster string) error { var in serviceAccountTokenBody if c.Request().Body != nil { - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return h.handleError(c, errMsgf(ErrValidation, "invalid request body")) } } diff --git a/services/eks/handler_node_groups.go b/services/eks/handler_node_groups.go index 70ce993eab..fe936ddef5 100644 --- a/services/eks/handler_node_groups.go +++ b/services/eks/handler_node_groups.go @@ -1,7 +1,7 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -294,7 +295,7 @@ type createNodegroupBody struct { func (h *Handler) handleCreateNodegroup(c *echo.Context, clusterName string, body []byte) error { var in createNodegroupBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -448,7 +449,7 @@ func (h *Handler) handleUpdateNodegroupConfig( ) error { var in updateNodegroupConfigInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } @@ -524,7 +525,7 @@ type updateNodegroupVersionBody struct { func (h *Handler) handleUpdateNodegroupVersion(c *echo.Context, clusterName, nodegroupName string, body []byte) error { var in updateNodegroupVersionBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_pod_identity.go b/services/eks/handler_pod_identity.go index 604ea36023..6b4e800545 100644 --- a/services/eks/handler_pod_identity.go +++ b/services/eks/handler_pod_identity.go @@ -1,12 +1,13 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -138,7 +139,7 @@ type createPodIdentityAssociationBody struct { func (h *Handler) handleCreatePodIdentityAssociation(c *echo.Context, clusterName string, body []byte) error { var in createPodIdentityAssociationBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -232,7 +233,7 @@ type updatePodIdentityBody struct { func (h *Handler) handleUpdatePodIdentityAssociation(c *echo.Context, clusterName, assocID string, body []byte) error { var in updatePodIdentityBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_subscriptions.go b/services/eks/handler_subscriptions.go index b3144c682b..b8b787eb32 100644 --- a/services/eks/handler_subscriptions.go +++ b/services/eks/handler_subscriptions.go @@ -1,7 +1,7 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "slices" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -144,7 +145,7 @@ func validateSubscriptionTerm(t *subscriptionTermJSON) error { func (h *Handler) handleCreateEksAnywhereSubscription(c *echo.Context, body []byte) error { var in createEksAnywhereSubscriptionBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -227,7 +228,7 @@ type updateSubscriptionBody struct { func (h *Handler) handleUpdateEksAnywhereSubscription(c *echo.Context, id string, body []byte) error { var in updateSubscriptionBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/handler_tags.go b/services/eks/handler_tags.go index f86e83b335..99e01cdb49 100644 --- a/services/eks/handler_tags.go +++ b/services/eks/handler_tags.go @@ -1,11 +1,13 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dispatchTagOps handles the generic ARN-based tag operations. @@ -62,7 +64,7 @@ type tagResourceBody struct { func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body []byte) error { var in tagResourceBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("BadRequestException", "invalid request body")) } diff --git a/services/eks/handler_test.go b/services/eks/handler_test.go index ac3549e183..f8ef960e42 100644 --- a/services/eks/handler_test.go +++ b/services/eks/handler_test.go @@ -2,7 +2,7 @@ package eks_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -50,7 +51,7 @@ func doREST( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -70,7 +71,7 @@ func parseResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/eks/handler_updates.go b/services/eks/handler_updates.go index f3d3e2cb13..54042d33be 100644 --- a/services/eks/handler_updates.go +++ b/services/eks/handler_updates.go @@ -1,13 +1,14 @@ package eks import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -84,7 +85,7 @@ type associateEncryptionConfigBody struct { func (h *Handler) handleAssociateEncryptionConfig(c *echo.Context, clusterName string, body []byte) error { var in associateEncryptionConfigBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", "invalid request body")) } @@ -149,7 +150,7 @@ func (h *Handler) handleUpdateClusterConfig(c *echo.Context, clusterName string, var in updateClusterConfigBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } @@ -157,7 +158,7 @@ func (h *Handler) handleUpdateClusterConfig(c *echo.Context, clusterName string, cfgUpd := buildClusterConfigUpdate(in) var blocksIn clusterConfigBlocksBody - _ = json.Unmarshal(body, &blocksIn) + _ = json.Unmarshal(body, &blocksIn, jsonx.V1()) cfgUpd.ConfigBlocks = blocksIn.blocks(false) return h.withIdempotency(c, opUpdateClusterConfig, in.ClientRequestToken, body, func() (int, any, error) { @@ -270,7 +271,7 @@ type updateClusterVersionBody struct { func (h *Handler) handleUpdateClusterVersion(c *echo.Context, clusterName string, body []byte) error { var in updateClusterVersionBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } @@ -369,7 +370,7 @@ type cancelUpdateBody struct { func (h *Handler) handleCancelUpdate(c *echo.Context, clusterName, updateID string, body []byte) error { var in cancelUpdateBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("InvalidParameterException", err.Error())) } } diff --git a/services/eks/idempotency.go b/services/eks/idempotency.go index e59e5f7569..50645f93a5 100644 --- a/services/eks/idempotency.go +++ b/services/eks/idempotency.go @@ -3,12 +3,14 @@ package eks import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -39,12 +41,12 @@ const idempotencyTokenTTL = 24 * time.Hour const maxIdempotencyRecords = 1024 type idempotencyRecord struct { - CreatedAt time.Time `json:"createdAt"` - Op string `json:"op"` - Token string `json:"token"` - Fingerprint string `json:"fingerprint"` - Body json.RawMessage `json:"body"` - StatusCode int `json:"statusCode"` + CreatedAt time.Time `json:"createdAt"` + Op string `json:"op"` + Token string `json:"token"` + Fingerprint string `json:"fingerprint"` + Body jsontext.Value `json:"body"` + StatusCode int `json:"statusCode"` } func idempotencyKey(op, token string) string { return op + "\x00" + token } @@ -59,7 +61,7 @@ func idempotencyKeyFn(v *idempotencyRecord) string { return idempotencyKey(v.Op, // byte-identically. func idempotencyFingerprint(reqBody []byte) string { var v map[string]any - if err := json.Unmarshal(reqBody, &v); err != nil { + if err := json.Unmarshal(reqBody, &v, jsonx.V1()); err != nil { sum := sha256.Sum256(reqBody) return hex.EncodeToString(sum[:]) @@ -67,7 +69,7 @@ func idempotencyFingerprint(reqBody []byte) string { delete(v, "clientRequestToken") - canon, err := json.Marshal(v) + canon, err := json.Marshal(v, jsonx.V1()) if err != nil { canon = reqBody } @@ -100,7 +102,7 @@ func idempotencyExpired(rec *idempotencyRecord, now time.Time) bool { } // storeIdempotency records a successful (2xx) response for later replay. -func (b *InMemoryBackend) storeIdempotency(op, token, fingerprint string, statusCode int, body json.RawMessage) { +func (b *InMemoryBackend) storeIdempotency(op, token, fingerprint string, statusCode int, body jsontext.Value) { b.mu.Lock("storeIdempotency") defer b.mu.Unlock() @@ -158,7 +160,7 @@ func (h *Handler) withIdempotency( } var body any - if err := json.Unmarshal(rec.Body, &body); err != nil { + if err := json.Unmarshal(rec.Body, &body, jsonx.V1()); err != nil { return c.JSON(http.StatusInternalServerError, errResp("InternalFailure", err.Error())) } @@ -171,7 +173,7 @@ func (h *Handler) withIdempotency( } if status >= http.StatusOK && status < http.StatusMultipleChoices { - if raw, mErr := json.Marshal(body); mErr == nil { + if raw, mErr := json.Marshal(body, jsonx.V1()); mErr == nil { h.Backend.storeIdempotency(op, token, fp, status, raw) } } diff --git a/services/eks/irsa.go b/services/eks/irsa.go index fada781d57..dd74360fac 100644 --- a/services/eks/irsa.go +++ b/services/eks/irsa.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "math/big" "slices" @@ -13,6 +13,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -284,12 +286,12 @@ func serviceAccountClaims(issuer string, req ServiceAccountTokenRequest, now, ex } func signServiceAccountJWT(key *rsa.PrivateKey, claims map[string]any) (string, error) { - hdr, err := json.Marshal(map[string]string{"alg": jwtAlgRS256, "kid": irsaKeyID(&key.PublicKey)}) + hdr, err := json.Marshal(map[string]string{"alg": jwtAlgRS256, "kid": irsaKeyID(&key.PublicKey)}, jsonx.V1()) if err != nil { return "", err } - body, err := json.Marshal(claims) + body, err := json.Marshal(claims, jsonx.V1()) if err != nil { return "", err } diff --git a/services/eks/irsa_test.go b/services/eks/irsa_test.go index ea7040d5c6..84889d4592 100644 --- a/services/eks/irsa_test.go +++ b/services/eks/irsa_test.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "math/big" "net/http" "net/http/httptest" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -34,7 +35,7 @@ func jwtParts(t *testing.T, token string) (map[string]any, map[string]any, []byt for i, dst := range []*map[string]any{&hdr, &claims} { raw, err := base64.RawURLEncoding.DecodeString(parts[i]) require.NoError(t, err) - require.NoError(t, json.Unmarshal(raw, dst)) + require.NoError(t, json.Unmarshal(raw, dst, jsonx.V1())) } sig, err := base64.RawURLEncoding.DecodeString(parts[2]) @@ -301,7 +302,7 @@ func TestOIDCEndpoints(t *testing.T) { if tt.wantKey != "" { var doc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &doc, jsonx.V1())) assert.Contains(t, doc, tt.wantKey) } }) diff --git a/services/eks/k8s_api.go b/services/eks/k8s_api.go index df399162f7..e4d6310468 100644 --- a/services/eks/k8s_api.go +++ b/services/eks/k8s_api.go @@ -5,13 +5,15 @@ import ( "context" "crypto/tls" "crypto/x509" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" "net/http" "net/url" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -111,7 +113,7 @@ func (a *k8sAPI) getJSON(ctx context.Context, path string, out any) error { return err } - if err = json.Unmarshal(raw, out); err != nil { + if err = json.Unmarshal(raw, out, jsonx.V1()); err != nil { return fmt.Errorf("decode %s: %w", path, err) } @@ -120,7 +122,7 @@ func (a *k8sAPI) getJSON(ctx context.Context, path string, out any) error { // apply server-side-applies obj (JSON is valid YAML) at path, forcing field ownership. func (a *k8sAPI) apply(ctx context.Context, path string, obj any) error { - raw, err := json.Marshal(obj) + raw, err := json.Marshal(obj, jsonx.V1()) if err != nil { return fmt.Errorf("encode %s: %w", path, err) } @@ -132,7 +134,7 @@ func (a *k8sAPI) apply(ctx context.Context, path string, obj any) error { } func (a *k8sAPI) mergePatch(ctx context.Context, path string, patch any) error { - raw, err := json.Marshal(patch) + raw, err := json.Marshal(patch, jsonx.V1()) if err != nil { return fmt.Errorf("encode %s: %w", path, err) } diff --git a/services/eks/k8s_proxy.go b/services/eks/k8s_proxy.go index b767fb5b34..2329ee1de9 100644 --- a/services/eks/k8s_proxy.go +++ b/services/eks/k8s_proxy.go @@ -9,7 +9,8 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "errors" "fmt" @@ -24,6 +25,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -188,7 +190,7 @@ func (p *k8sProxy) close() { _ = p.srv.Close() } func writeK8sStatus(w http.ResponseWriter, code int, reason string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - _ = json.NewEncoder(w).Encode(map[string]any{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{ keyKind: "Status", keyAPIVersion: "v1", "status": "Failure", keyMessage: reason, "reason": reason, keyCode: code, }) diff --git a/services/eks/k8s_reconcile_internal_test.go b/services/eks/k8s_reconcile_internal_test.go index 965146dc18..58766a901e 100644 --- a/services/eks/k8s_reconcile_internal_test.go +++ b/services/eks/k8s_reconcile_internal_test.go @@ -2,7 +2,8 @@ package eks import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const rbacPrefix = "/apis/rbac.authorization.k8s.io/v1/" @@ -125,7 +127,7 @@ func (f *fakeK8s) listBindings(set map[string]bool, nsMode string) map[string]an func writeJSON(w http.ResponseWriter, v any) { w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(v) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), v) } // engineRuntime records containers and registers a node in the fake API for every agent started. diff --git a/services/eks/models.go b/services/eks/models.go index 2981183033..50dc3bd263 100644 --- a/services/eks/models.go +++ b/services/eks/models.go @@ -1,10 +1,12 @@ package eks import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -148,7 +150,7 @@ func (a activationExpiry) Time() time.Time { return time.Time(a) } func (a activationExpiry) IsZero() bool { return time.Time(a).IsZero() } func (a activationExpiry) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Time(a)) + return json.Marshal(time.Time(a), jsonx.V1()) } func (a *activationExpiry) UnmarshalJSON(data []byte) error { @@ -157,14 +159,14 @@ func (a *activationExpiry) UnmarshalJSON(data []byte) error { } var t time.Time - if err := json.Unmarshal(data, &t); err == nil { + if err := json.Unmarshal(data, &t, jsonx.V1()); err == nil { *a = activationExpiry(t) return nil } var epochSeconds float64 - if err := json.Unmarshal(data, &epochSeconds); err != nil { + if err := json.Unmarshal(data, &epochSeconds, jsonx.V1()); err != nil { return fmt.Errorf("activationExpiry: unsupported JSON value %s: %w", data, err) } @@ -201,23 +203,23 @@ type Cluster struct { StorageConfig *StorageConfig `json:"storageConfig,omitempty"` ConnectorConfig *ConnectorConfig `json:"connectorConfig,omitempty"` // ConfigBlocks holds the opaque create/update config members keyed by wire name. - ConfigBlocks map[string]json.RawMessage `json:"configBlocks,omitempty"` - ARN string `json:"arn"` - Name string `json:"name"` - Endpoint string `json:"endpoint,omitempty"` - OIDCIssuer string `json:"oidcIssuer,omitempty"` - Version string `json:"version"` - Status string `json:"status"` - RoleARN string `json:"roleArn,omitempty"` - AccountID string `json:"accountId"` - Region string `json:"region"` - PlatformVersion string `json:"platformVersion,omitempty"` - CreatorARN string `json:"creatorArn,omitempty"` - CertificateAuthority string `json:"certificateAuthority,omitempty"` - UpgradePolicySupportType string `json:"upgradePolicySupportType,omitempty"` - ClusterLogging []ClusterLogEntry `json:"clusterLogging,omitempty"` - EncryptionConfig []EncryptionConfig `json:"encryptionConfig,omitempty"` - DeletionProtection bool `json:"deletionProtection,omitempty"` + ConfigBlocks map[string]jsontext.Value `json:"configBlocks,omitempty"` + ARN string `json:"arn"` + Name string `json:"name"` + Endpoint string `json:"endpoint,omitempty"` + OIDCIssuer string `json:"oidcIssuer,omitempty"` + Version string `json:"version"` + Status string `json:"status"` + RoleARN string `json:"roleArn,omitempty"` + AccountID string `json:"accountId"` + Region string `json:"region"` + PlatformVersion string `json:"platformVersion,omitempty"` + CreatorARN string `json:"creatorArn,omitempty"` + CertificateAuthority string `json:"certificateAuthority,omitempty"` + UpgradePolicySupportType string `json:"upgradePolicySupportType,omitempty"` + ClusterLogging []ClusterLogEntry `json:"clusterLogging,omitempty"` + EncryptionConfig []EncryptionConfig `json:"encryptionConfig,omitempty"` + DeletionProtection bool `json:"deletionProtection,omitempty"` } // NodegroupTaint represents a Kubernetes taint applied to managed nodes. diff --git a/services/eks/multi_region_test.go b/services/eks/multi_region_test.go index b8aabb515d..a532b948c6 100644 --- a/services/eks/multi_region_test.go +++ b/services/eks/multi_region_test.go @@ -1,7 +1,8 @@ package eks_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -41,7 +43,7 @@ func regionCall(t *testing.T, h *eks.Handler, region, method, path, body string) out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out @@ -130,9 +132,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/eks/node_groups_test.go b/services/eks/node_groups_test.go index 20e9fa9097..70889e0772 100644 --- a/services/eks/node_groups_test.go +++ b/services/eks/node_groups_test.go @@ -1,7 +1,7 @@ package eks_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -975,11 +976,11 @@ func TestSortedListNodegroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) resp := parseResp(t, rec) - raw, err := json.Marshal(resp["nodegroups"]) + raw, err := json.Marshal(resp["nodegroups"], jsonx.V1()) require.NoError(t, err) var names []string - require.NoError(t, json.Unmarshal(raw, &names)) + require.NoError(t, json.Unmarshal(raw, &names, jsonx.V1())) require.Equal(t, []string{"aaa", "mmm", "zzz"}, names) } diff --git a/services/eks/persistence.go b/services/eks/persistence.go index 8a87bdb81d..1ca5a7c70a 100644 --- a/services/eks/persistence.go +++ b/services/eks/persistence.go @@ -2,9 +2,11 @@ package eks import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -50,7 +52,7 @@ import ( const eksSnapshotVersion = 2 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` AccessPolicies map[string]map[string][]*AccessPolicyAssociation `json:"accessPolicies,omitempty"` EncryptionConfigs map[string][]EncryptionConfig `json:"encryptionConfigs,omitempty"` AccountID string `json:"accountId"` @@ -79,7 +81,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "eks: Snapshot marshal failed", "error", err) diff --git a/services/eks/pod_identity_auth.go b/services/eks/pod_identity_auth.go index 1941656df9..62c83aed9b 100644 --- a/services/eks/pod_identity_auth.go +++ b/services/eks/pod_identity_auth.go @@ -5,7 +5,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "slices" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -124,7 +125,7 @@ type audience []string func (a *audience) UnmarshalJSON(raw []byte) error { var one string - if json.Unmarshal(raw, &one) == nil { + if json.Unmarshal(raw, &one, jsonx.V1()) == nil { *a = audience{one} return nil @@ -132,7 +133,7 @@ func (a *audience) UnmarshalJSON(raw []byte) error { var many []string - if err := json.Unmarshal(raw, &many); err != nil { + if err := json.Unmarshal(raw, &many, jsonx.V1()); err != nil { return err } @@ -163,7 +164,7 @@ func verifyPodIdentityToken(token, issuer string, pub *rsa.PublicKey, now time.T } var c podIdentityClaims - if json.Unmarshal(raw, &c) != nil { + if json.Unmarshal(raw, &c, jsonx.V1()) != nil { return nil, errMsgf(ErrInvalidToken, "The token payload is malformed.") } @@ -309,7 +310,7 @@ func (h *Handler) handleAssumeRoleForPodIdentity(c *echo.Context, clusterName st Token string `json:"token"` } - if err := json.Unmarshal(body, &in); err != nil || in.Token == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Token == "" { return h.handleError(c, errMsgf(ErrValidation, "token is required.")) } diff --git a/services/eks/updates.go b/services/eks/updates.go index cf447837bf..45cee74ef9 100644 --- a/services/eks/updates.go +++ b/services/eks/updates.go @@ -1,7 +1,8 @@ package eks import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" @@ -10,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // updateTransitionDelay is the async delay before an InProgress Update reaches @@ -81,7 +83,7 @@ func (b *InMemoryBackend) AssociateEncryptionConfig( result := make([]EncryptionConfig, len(stored)) copy(result, stored) - encryptionConfigJSON, err := json.Marshal(result) + encryptionConfigJSON, err := json.Marshal(result, jsonx.V1()) if err != nil { return nil, nil, fmt.Errorf("marshal encryption config: %w", err) } @@ -111,7 +113,7 @@ type ClusterConfigUpdate struct { ComputeConfig *ComputeConfig StorageConfig *StorageConfig // ConfigBlocks are the opaque config members, keyed by wire name. - ConfigBlocks map[string]json.RawMessage + ConfigBlocks map[string]jsontext.Value DeletionProtection *bool ElasticLoadBalancing *bool UpgradeSupportType string diff --git a/services/eks/updates_test.go b/services/eks/updates_test.go index e8719f8c76..e482ca5e55 100644 --- a/services/eks/updates_test.go +++ b/services/eks/updates_test.go @@ -1,7 +1,7 @@ package eks_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eks" ) @@ -624,7 +625,7 @@ func TestLogging_HTTP_AllTypes_In_Response(t *testing.T) { rec2 := doREST(t, h, http.MethodGet, "/clusters/logging-http-all", nil) cluster := parseClusterResp(t, rec2) - raw, _ := json.Marshal(cluster) + raw, _ := json.Marshal(cluster, jsonx.V1()) body := string(raw) for _, lt := range []string{"api", "audit", "authenticator", "controllerManager", "scheduler"} { diff --git a/services/elasticache/persistence.go b/services/elasticache/persistence.go index 4da5251368..14ff8c6d85 100644 --- a/services/elasticache/persistence.go +++ b/services/elasticache/persistence.go @@ -2,7 +2,7 @@ package elasticache import ( "context" - "encoding/json" + "encoding/json/jsontext" "time" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -66,7 +66,7 @@ type clusterSnapshot struct { // directly below as region -> that table's own deterministic // [store.Table.Snapshot] slice. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Clusters map[string][]*clusterSnapshot `json:"clusters"` ReplicationGroups map[string][]*ReplicationGroup `json:"replicationGroups"` ParameterGroups map[string][]*CacheParameterGroup `json:"parameterGroups"` diff --git a/services/elasticache/persistence_test.go b/services/elasticache/persistence_test.go index 9547503578..5b3ba830b9 100644 --- a/services/elasticache/persistence_test.go +++ b/services/elasticache/persistence_test.go @@ -2,12 +2,13 @@ package elasticache_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elasticache" ) @@ -205,10 +206,10 @@ func TestInMemoryBackend_Restore_IncompatibleVersion(t *testing.T) { // current elasticacheSnapshotVersion constant. var raw map[string]any - require.NoError(t, json.Unmarshal(original.Snapshot(ctx), &raw)) + require.NoError(t, json.Unmarshal(original.Snapshot(ctx), &raw, jsonx.V1())) raw["version"] = -1 - badSnap, err := json.Marshal(raw) + badSnap, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) fresh := elasticache.NewInMemoryBackend("redis", "000000000000", "us-east-1", nil) diff --git a/services/elasticbeanstalk/persistence.go b/services/elasticbeanstalk/persistence.go index 1cf141771a..bcd449b678 100644 --- a/services/elasticbeanstalk/persistence.go +++ b/services/elasticbeanstalk/persistence.go @@ -2,7 +2,7 @@ package elasticbeanstalk import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -53,7 +53,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ManagedActionHistory map[string]map[string][]*ManagedActionHistory `json:"managedActionHistory,omitempty"` Events map[string][]*EventRecord `json:"events,omitempty"` DeletedEnvironments map[string][]*Environment `json:"deletedEnvironments,omitempty"` @@ -243,7 +243,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/elasticbeanstalk/store_test.go b/services/elasticbeanstalk/store_test.go index 1ac070a850..308a14265a 100644 --- a/services/elasticbeanstalk/store_test.go +++ b/services/elasticbeanstalk/store_test.go @@ -2,12 +2,13 @@ package elasticbeanstalk_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elasticbeanstalk" ) @@ -65,7 +66,7 @@ func TestInMemoryBackend_Snapshot_NotNilOnFreshBackend(t *testing.T) { // Must be valid JSON. var m map[string]any - require.NoError(t, json.Unmarshal(snap, &m)) + require.NoError(t, json.Unmarshal(snap, &m, jsonx.V1())) } // TestInMemoryBackend_SeedHelpers_PopulateAllCollections verifies that seed diff --git a/services/elasticsearch/handler.go b/services/elasticsearch/handler.go index 6ec0d16def..d630ab1d5f 100644 --- a/services/elasticsearch/handler.go +++ b/services/elasticsearch/handler.go @@ -2,7 +2,7 @@ package elasticsearch import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -537,7 +538,7 @@ func (h *Handler) decodeRequest(w http.ResponseWriter, r *http.Request, out any) return true } - if err = json.Unmarshal(body, out); err != nil { + if err = json.Unmarshal(body, out, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return false diff --git a/services/elasticsearch/handler_domain_advanced_options_test.go b/services/elasticsearch/handler_domain_advanced_options_test.go index d98ff37c6a..0e8ec6b040 100644 --- a/services/elasticsearch/handler_domain_advanced_options_test.go +++ b/services/elasticsearch/handler_domain_advanced_options_test.go @@ -1,12 +1,14 @@ package elasticsearch_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestElasticsearchHandler_CreateDomain_VPCOptions verifies VPCOptions @@ -401,7 +403,7 @@ func TestElasticsearchBackend_CreateDomain_InvalidAutoTuneDesiredState(t *testin body := []byte(`{"DomainName":"raw-autotune","AutoTuneOptions":{"DesiredState":"BOGUS"}}`) var decoded map[string]any - require.NoError(t, json.Unmarshal(body, &decoded)) + require.NoError(t, json.Unmarshal(body, &decoded, jsonx.V1())) resp := doRequest(t, h, http.MethodPost, "/2015-01-01/es/domain", decoded) defer resp.Body.Close() diff --git a/services/elasticsearch/handler_domain_config.go b/services/elasticsearch/handler_domain_config.go index 4e2859f34d..bf0b83b4e1 100644 --- a/services/elasticsearch/handler_domain_config.go +++ b/services/elasticsearch/handler_domain_config.go @@ -1,7 +1,7 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // updateDomainConfigRequest is the request body for UpdateElasticsearchDomainConfig. @@ -38,7 +39,7 @@ func (h *Handler) handleUpdateDomainConfig(w http.ResponseWriter, r *http.Reques } var req updateDomainConfigRequest - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_domain_fields_test.go b/services/elasticsearch/handler_domain_fields_test.go index 98d1b233e7..d6240ed841 100644 --- a/services/elasticsearch/handler_domain_fields_test.go +++ b/services/elasticsearch/handler_domain_fields_test.go @@ -1,12 +1,15 @@ package elasticsearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestElasticsearchHandler_DomainDedicatedMaster verifies dedicated master @@ -57,7 +60,7 @@ func TestElasticsearchHandler_DomainDedicatedMaster(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) cfg := status["ElasticsearchClusterConfig"].(map[string]any) @@ -116,7 +119,7 @@ func TestElasticsearchHandler_DomainZoneAwareness(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) cfg := status["ElasticsearchClusterConfig"].(map[string]any) @@ -178,7 +181,7 @@ func TestElasticsearchHandler_DomainWarm(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) cfg := status["ElasticsearchClusterConfig"].(map[string]any) @@ -243,7 +246,7 @@ func TestElasticsearchHandler_DomainEBSIopsAndThroughput(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) ebs := status["EBSOptions"].(map[string]any) @@ -291,7 +294,7 @@ func TestElasticsearchHandler_DomainEncryptionAtRest(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) enc := status["EncryptionAtRestOptions"].(map[string]any) @@ -338,7 +341,7 @@ func TestElasticsearchHandler_DomainNodeToNodeEncryption(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) n2n := status["NodeToNodeEncryptionOptions"].(map[string]any) @@ -393,7 +396,7 @@ func TestElasticsearchHandler_DomainEndpointOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) endpoint := status["DomainEndpointOptions"].(map[string]any) @@ -467,7 +470,7 @@ func TestElasticsearchHandler_DomainSnapshotOptions(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) snap := status["SnapshotOptions"].(map[string]any) @@ -536,7 +539,7 @@ func TestElasticsearchHandler_DomainAdvancedOptions(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) advOpts := status["AdvancedOptions"].(map[string]any) @@ -602,7 +605,7 @@ func TestElasticsearchHandler_DomainAccessPolicies(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) assert.Equal(t, tc.wantPolicy, status["AccessPolicies"]) @@ -683,7 +686,7 @@ func TestElasticsearchHandler_UpdateDomainNewFields(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) status := out["DomainStatus"].(map[string]any) tc.checkFn(t, status) diff --git a/services/elasticsearch/handler_domain_lifecycle.go b/services/elasticsearch/handler_domain_lifecycle.go index 17d8aeb5f6..736a1ac990 100644 --- a/services/elasticsearch/handler_domain_lifecycle.go +++ b/services/elasticsearch/handler_domain_lifecycle.go @@ -1,13 +1,14 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -46,7 +47,7 @@ func (h *Handler) handleCancelElasticsearchServiceSoftwareUpdate(w http.Response } var req cancelSoftwareUpdateRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_domains.go b/services/elasticsearch/handler_domains.go index b012dfc60e..714a96014a 100644 --- a/services/elasticsearch/handler_domains.go +++ b/services/elasticsearch/handler_domains.go @@ -1,13 +1,14 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" "time" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // domainZoneAwarenessConfig holds zone awareness sub-config. @@ -333,7 +334,7 @@ func (h *Handler) handleCreateDomain(w http.ResponseWriter, r *http.Request) { } var req domainJSON - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -553,7 +554,7 @@ func (h *Handler) handleDescribeElasticsearchDomains(w http.ResponseWriter, r *h } var req describeDomainsRequest - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_domains_test.go b/services/elasticsearch/handler_domains_test.go index 6db1b1bc79..e2315c946f 100644 --- a/services/elasticsearch/handler_domains_test.go +++ b/services/elasticsearch/handler_domains_test.go @@ -2,7 +2,8 @@ package elasticsearch_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elasticsearch" ) @@ -237,7 +239,7 @@ func TestElasticsearchHandler_ListDomainNames(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) names, ok := out["DomainNames"].([]any) require.True(t, ok) @@ -363,7 +365,7 @@ func TestElasticsearchHandler_DomainCollectionRouteMatrix(t *testing.T) { var out map[string]any if resp.Header.Get("Content-Type") != "" { - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) } resp.Body.Close() @@ -442,7 +444,7 @@ func TestElasticsearchHandler_DescribeElasticsearchDomains(t *testing.T) { assert.Equal(t, tt.wantCode, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainStatusList"].([]any) require.True(t, ok) @@ -611,7 +613,7 @@ func TestElasticsearchHandler_NonNilEmptyDomainList(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) domainNames, ok := out["DomainNames"] require.True(t, ok) diff --git a/services/elasticsearch/handler_outbound_connections.go b/services/elasticsearch/handler_outbound_connections.go index 8b9e6235d4..e24913f14b 100644 --- a/services/elasticsearch/handler_outbound_connections.go +++ b/services/elasticsearch/handler_outbound_connections.go @@ -1,11 +1,12 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // outboundConnectionJSON is the JSON representation of an outbound cross-cluster connection. @@ -37,7 +38,7 @@ func (h *Handler) handleCreateOutboundCrossClusterSearchConnection(w http.Respon } var req createOutboundConnectionRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_packages.go b/services/elasticsearch/handler_packages.go index eec513afcc..7ae9a6ba13 100644 --- a/services/elasticsearch/handler_packages.go +++ b/services/elasticsearch/handler_packages.go @@ -1,7 +1,7 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "slices" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -68,7 +69,7 @@ func (h *Handler) handleCreatePackage(w http.ResponseWriter, r *http.Request) { } var req createPackageRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_tags.go b/services/elasticsearch/handler_tags.go index 37b723561e..c43a80f549 100644 --- a/services/elasticsearch/handler_tags.go +++ b/services/elasticsearch/handler_tags.go @@ -1,7 +1,7 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -9,6 +9,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -52,7 +53,7 @@ func (h *Handler) handleAddTags(w http.ResponseWriter, r *http.Request) { } var req addTagsInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -130,7 +131,7 @@ func (h *Handler) handleRemoveTags(w http.ResponseWriter, r *http.Request) { } var req removeTagsInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/handler_tags_test.go b/services/elasticsearch/handler_tags_test.go index 8e07d4b789..52f7492e5d 100644 --- a/services/elasticsearch/handler_tags_test.go +++ b/services/elasticsearch/handler_tags_test.go @@ -1,7 +1,8 @@ package elasticsearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +11,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestElasticsearchHandler_Tags(t *testing.T) { @@ -63,7 +66,7 @@ func TestElasticsearchHandler_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, listResp.StatusCode) var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) tagList, ok := listOut["TagList"].([]any) require.True(t, ok) @@ -300,7 +303,7 @@ func TestElasticsearchHandler_SortedListTagsResponse(t *testing.T) { Value string `json:"Value"` } `json:"TagList"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) require.Len(t, out.TagList, 3) assert.Equal(t, "aaa", out.TagList[0].Key) assert.Equal(t, "mmm", out.TagList[1].Key) @@ -365,7 +368,7 @@ func TestElasticsearchHandler_AddTags_DuplicateKeyRejected(t *testing.T) { assert.Equal(t, tt.wantCode, resp.StatusCode) if tt.wantCode == http.StatusBadRequest { var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Contains(t, out["message"], "Duplicate tag key") } }) @@ -421,7 +424,7 @@ func TestElasticsearchHandler_AddRemoveTags_UnknownARN(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Contains(t, out["message"], "domain not found") }) } diff --git a/services/elasticsearch/handler_test.go b/services/elasticsearch/handler_test.go index b0d34f98b6..30f114b4dc 100644 --- a/services/elasticsearch/handler_test.go +++ b/services/elasticsearch/handler_test.go @@ -2,7 +2,8 @@ package elasticsearch_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elasticsearch" ) @@ -27,7 +29,7 @@ func doRequest(t *testing.T, h *elasticsearch.Handler, method, path string, body var reqBody io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) @@ -53,7 +55,7 @@ func createDomainAndGetARN(t *testing.T, h *elasticsearch.Handler, domainName st }) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() status := createOut["DomainStatus"].(map[string]any) @@ -78,7 +80,7 @@ func readJSONBody(t *testing.T, resp *http.Response) map[string]any { require.NoError(t, err) var m map[string]any - require.NoError(t, json.Unmarshal(b, &m)) + require.NoError(t, json.Unmarshal(b, &m, jsonx.V1())) return m } diff --git a/services/elasticsearch/handler_versions_test.go b/services/elasticsearch/handler_versions_test.go index f5f8099c4b..4789518b77 100644 --- a/services/elasticsearch/handler_versions_test.go +++ b/services/elasticsearch/handler_versions_test.go @@ -1,12 +1,15 @@ package elasticsearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestElasticsearchHandler_ListElasticsearchVersions verifies all 22 valid @@ -33,7 +36,7 @@ func TestElasticsearchHandler_ListElasticsearchVersions(t *testing.T) { var out struct { ElasticsearchVersions []string `json:"ElasticsearchVersions"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) require.Len(t, out.ElasticsearchVersions, 22, "expected all 22 versions") versionSet := make(map[string]bool) @@ -100,7 +103,7 @@ func TestElasticsearchHandler_CompatibleVersionsDomain(t *testing.T) { TargetVersions []string `json:"TargetVersions"` } `json:"CompatibleElasticsearchVersions"` } - require.NoError(t, json.NewDecoder(compatResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(compatResp.Body), &out, jsonx.V1())) require.Len(t, out.CompatibleElasticsearchVersions, 1) assert.Equal(t, tc.wantSourceVersion, out.CompatibleElasticsearchVersions[0].SourceVersion) }) diff --git a/services/elasticsearch/handler_vpc_endpoints.go b/services/elasticsearch/handler_vpc_endpoints.go index a73f7289f8..f7cfcd6f16 100644 --- a/services/elasticsearch/handler_vpc_endpoints.go +++ b/services/elasticsearch/handler_vpc_endpoints.go @@ -1,12 +1,13 @@ package elasticsearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // keyNextToken is the required (always-empty, single-page emulator) pagination @@ -50,7 +51,7 @@ func (h *Handler) handleCreateVpcEndpoint(w http.ResponseWriter, r *http.Request } var req createVpcEndpointRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -144,7 +145,7 @@ func (h *Handler) handleAuthorizeVpcEndpointAccess(w http.ResponseWriter, r *htt } var req authorizeVpcEndpointAccessRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/elasticsearch/persistence.go b/services/elasticsearch/persistence.go index 0a21fa91b7..cb5ebf8b28 100644 --- a/services/elasticsearch/persistence.go +++ b/services/elasticsearch/persistence.go @@ -2,7 +2,7 @@ package elasticsearch import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -96,7 +96,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` PackagesByName map[string]map[string]string `json:"packagesByName"` PackageAssociations map[string]map[string][]string `json:"packageAssociations"` PackageAssociationDetails packageAssociationDetails `json:"packageAssociationDetails,omitempty"` @@ -245,7 +245,7 @@ func (b *InMemoryBackend) rebuildArnIndex() { // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/elasticsearch/persistence_test.go b/services/elasticsearch/persistence_test.go index b7d4076c31..a3a150ebff 100644 --- a/services/elasticsearch/persistence_test.go +++ b/services/elasticsearch/persistence_test.go @@ -2,12 +2,13 @@ package elasticsearch_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elasticsearch" ) @@ -519,7 +520,7 @@ func TestElasticsearch_SnapshotWarnOnEmptyState(t *testing.T) { require.NotNil(t, snap) var v map[string]any - require.NoError(t, json.Unmarshal(snap, &v)) + require.NoError(t, json.Unmarshal(snap, &v, jsonx.V1())) } // TestElasticsearch_PersistenceReservedAndVpcAccessCombined verifies that VPC diff --git a/services/elb/persistence.go b/services/elb/persistence.go index 54f08dfcb5..674c6b9053 100644 --- a/services/elb/persistence.go +++ b/services/elb/persistence.go @@ -2,9 +2,10 @@ package elb import ( "context" - "encoding/json" + "encoding/json/v2" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -217,7 +218,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Version: snapshotVersion, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "elb: Snapshot marshal failure", "error", err) diff --git a/services/elb/persistence_test.go b/services/elb/persistence_test.go index d5c01cc2a2..a8845c1524 100644 --- a/services/elb/persistence_test.go +++ b/services/elb/persistence_test.go @@ -2,7 +2,7 @@ package elb_test import ( "context" - "encoding/json" + "encoding/json/jsontext" "encoding/xml" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/blackbirdworks/gopherstack/services/elb" @@ -370,7 +371,7 @@ func TestPersistenceEmptySnapshot(t *testing.T) { b := newBackend() snap := b.Snapshot(t.Context()) require.NotNil(t, snap) - require.True(t, json.Valid(snap)) + require.True(t, jsontext.Value(snap).IsValid(jsonx.V1())) } // TestPersistenceRoundTripBackendServerDescriptions verifies that Snapshot/Restore preserves diff --git a/services/elbv2/dataplane.go b/services/elbv2/dataplane.go index b4a32fd47b..0af8c8af4e 100644 --- a/services/elbv2/dataplane.go +++ b/services/elbv2/dataplane.go @@ -2,7 +2,8 @@ package elbv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/netip" @@ -15,6 +16,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -300,5 +303,5 @@ func (h *Handler) serveListenerEndpoints(w http.ResponseWriter) { sort.Slice(out, func(i, j int) bool { return out[i].ListenerArn < out[j].ListenerArn }) w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(out) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), out) } diff --git a/services/elbv2/dataplane_lambda.go b/services/elbv2/dataplane_lambda.go index c3e6dda871..dc02331ebf 100644 --- a/services/elbv2/dataplane_lambda.go +++ b/services/elbv2/dataplane_lambda.go @@ -2,7 +2,7 @@ package elbv2 import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "mime" "net/http" @@ -11,6 +11,7 @@ import ( "unicode/utf8" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxLambdaTargetBodyBytes is the 1 MB request body limit for ALB Lambda targets. @@ -72,7 +73,7 @@ func (h *Handler) serveLambda(w http.ResponseWriter, req *dpRequest, tg *TargetG setForwardHeaders(hdr, req) hdr.Set("Host", req.host) - payload, err := json.Marshal(buildALBEvent(req, tg, hdr, body)) + payload, err := json.Marshal(buildALBEvent(req, tg, hdr, body), jsonx.V1()) if err != nil { writeDPError(w, http.StatusBadGateway, msgBadGateway) @@ -169,7 +170,7 @@ func isTextContent(ct string) bool { func writeLambdaResponse(w http.ResponseWriter, payload []byte) { var resp albResponse - if err := json.Unmarshal(payload, &resp); err != nil || resp.StatusCode == nil { + if err := json.Unmarshal(payload, &resp, jsonx.V1()); err != nil || resp.StatusCode == nil { writeDPError(w, http.StatusBadGateway, msgBadGateway) return diff --git a/services/elbv2/dataplane_lambda_test.go b/services/elbv2/dataplane_lambda_test.go index 6968c88f16..7f332b5539 100644 --- a/services/elbv2/dataplane_lambda_test.go +++ b/services/elbv2/dataplane_lambda_test.go @@ -3,7 +3,7 @@ package elbv2_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -13,6 +13,8 @@ import ( elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const testFnARN = "arn:aws:lambda:us-east-1:123456789012:function:web" @@ -43,7 +45,7 @@ func (f *fakeInvoker) event(t *testing.T) map[string]any { var ev map[string]any - require.NoError(t, json.Unmarshal(f.payload, &ev)) + require.NoError(t, json.Unmarshal(f.payload, &ev, jsonx.V1())) return ev } diff --git a/services/elbv2/persistence.go b/services/elbv2/persistence.go index 62217465f4..0355a96ce4 100644 --- a/services/elbv2/persistence.go +++ b/services/elbv2/persistence.go @@ -2,7 +2,7 @@ package elbv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "time" @@ -37,7 +37,7 @@ const elbv2SnapshotVersion = 2 // ResourcePolicies / TargetReadyAt / TargetDrainingUntil are persisted // separately because they are not store.Table-shaped -- see store_setup.go. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourcePolicies map[string]string `json:"resourcePolicies"` TargetReadyAt map[string]map[string]time.Time `json:"targetReadyAt"` TargetDrainingUntil map[string]map[string]time.Time `json:"targetDrainingUntil"` diff --git a/services/elbv2/target_groups_test.go b/services/elbv2/target_groups_test.go index fcfe897c49..7135de0ccc 100644 --- a/services/elbv2/target_groups_test.go +++ b/services/elbv2/target_groups_test.go @@ -2,7 +2,7 @@ package elbv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/elbv2" ) @@ -255,7 +256,7 @@ func TestDeleteTargetGroup_LifecycleMapsCleaned(t *testing.T) { TargetReadyAt map[string]map[string]time.Time `json:"targetReadyAt"` TargetDrainingUntil map[string]map[string]time.Time `json:"targetDrainingUntil"` } - require.NoError(t, json.Unmarshal(h.Snapshot(context.Background()), &snap)) + require.NoError(t, json.Unmarshal(h.Snapshot(context.Background()), &snap, jsonx.V1())) byField := map[string]map[string]map[string]time.Time{ "targetReadyAt": snap.TargetReadyAt, diff --git a/services/emr/handler.go b/services/emr/handler.go index f19fb36497..859c94bf65 100644 --- a/services/emr/handler.go +++ b/services/emr/handler.go @@ -2,7 +2,7 @@ package emr import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -193,7 +194,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { TargetResourceArn string `json:"TargetResourceArn"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ClusterID != "": @@ -330,7 +331,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // handleError maps backend errors to EMR's wire error shape. EMR's modeled diff --git a/services/emr/handler_cluster_settings_test.go b/services/emr/handler_cluster_settings_test.go index fcfc28034c..2b20cf6c71 100644 --- a/services/emr/handler_cluster_settings_test.go +++ b/services/emr/handler_cluster_settings_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTerminationProtection(t *testing.T) { @@ -19,7 +21,7 @@ func TestTerminationProtection(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) clusterID := create.JobFlowID protectRec := doEMRRequest(t, h, "SetTerminationProtection", map[string]any{ @@ -63,7 +65,7 @@ func TestSetKeepJobFlowAliveWhenNoSteps_SyncsAutoTerminate(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) clusterID := create.JobFlowID describe := func() bool { @@ -75,7 +77,7 @@ func TestSetKeepJobFlowAliveWhenNoSteps_SyncsAutoTerminate(t *testing.T) { AutoTerminate bool `json:"AutoTerminate"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.Cluster.AutoTerminate } @@ -107,7 +109,7 @@ func TestModifyCluster(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) modRec := doEMRRequest(t, h, "ModifyCluster", map[string]any{ "ClusterId": create.JobFlowID, @@ -118,7 +120,7 @@ func TestModifyCluster(t *testing.T) { var modOut struct { StepConcurrencyLevel int `json:"StepConcurrencyLevel"` } - require.NoError(t, json.Unmarshal(modRec.Body.Bytes(), &modOut)) + require.NoError(t, json.Unmarshal(modRec.Body.Bytes(), &modOut, jsonx.V1())) assert.Equal(t, 5, modOut.StepConcurrencyLevel) } @@ -132,7 +134,7 @@ func TestModifyCluster_InvalidRange(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) modRec := doEMRRequest(t, h, "ModifyCluster", map[string]any{ "ClusterId": create.JobFlowID, diff --git a/services/emr/handler_clusters_test.go b/services/emr/handler_clusters_test.go index 96bdd04d2b..a1f8af0705 100644 --- a/services/emr/handler_clusters_test.go +++ b/services/emr/handler_clusters_test.go @@ -2,7 +2,7 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -54,7 +55,7 @@ func TestEMR_RunJobFlow(t *testing.T) { ClusterArn string `json:"ClusterArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.JobFlowID) assert.Contains(t, out.ClusterArn, "elasticmapreduce") }) @@ -95,7 +96,7 @@ func TestEMR_DescribeCluster(t *testing.T) { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) clusterID := tt.clusterID if clusterID == "" { @@ -114,7 +115,7 @@ func TestEMR_DescribeCluster(t *testing.T) { } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, clusterID, out.Cluster.ID) assert.Equal(t, "test-cluster", out.Cluster.Name) } @@ -160,7 +161,7 @@ func TestEMR_ListClusters(t *testing.T) { Clusters []any `json:"Clusters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.Clusters), tt.wantMinCount) }) } @@ -183,7 +184,7 @@ func TestEMR_TerminateJobFlows(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return []string{out.JobFlowID} }, @@ -282,7 +283,7 @@ func TestRunJobFlow_SecurityConfigurationValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errOut["__type"]) }) @@ -322,7 +323,7 @@ func TestRunJobFlow_DefaultReleaseLabel(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": out.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -332,7 +333,7 @@ func TestRunJobFlow_DefaultReleaseLabel(t *testing.T) { ReleaseLabel string `json:"ReleaseLabel"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, emr.DefaultReleaseLabel, desc.Cluster.ReleaseLabel) } @@ -349,7 +350,7 @@ func TestRunJobFlow_ApplicationsDefaulted(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": out.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -361,7 +362,7 @@ func TestRunJobFlow_ApplicationsDefaulted(t *testing.T) { } `json:"Applications"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.NotEmpty(t, desc.Cluster.Applications) } @@ -389,7 +390,7 @@ func TestListClusters_DateFilter(t *testing.T) { ID string `json:"Id"` } `json:"Clusters"` } - require.NoError(t, json.Unmarshal(listInRange.Body.Bytes(), &inRange)) + require.NoError(t, json.Unmarshal(listInRange.Body.Bytes(), &inRange, jsonx.V1())) assert.Len(t, inRange.Clusters, 1) listOld := doEMRRequest(t, h, "ListClusters", map[string]any{ @@ -402,7 +403,7 @@ func TestListClusters_DateFilter(t *testing.T) { ID string `json:"Id"` } `json:"Clusters"` } - require.NoError(t, json.Unmarshal(listOld.Body.Bytes(), &old)) + require.NoError(t, json.Unmarshal(listOld.Body.Bytes(), &old, jsonx.V1())) assert.Empty(t, old.Clusters) } @@ -427,7 +428,7 @@ func TestConfigurations_RunJobFlow(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": out.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -443,7 +444,7 @@ func TestConfigurations_RunJobFlow(t *testing.T) { } `json:"Configurations"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) require.Len(t, desc.Cluster.Configurations, 1) assert.Equal(t, "spark-defaults", desc.Cluster.Configurations[0].Classification) assert.Equal(t, "4g", desc.Cluster.Configurations[0].Properties["spark.executor.memory"]) @@ -466,7 +467,7 @@ func TestClusterFields_EbsAndOS(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": out.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -478,7 +479,7 @@ func TestClusterFields_EbsAndOS(t *testing.T) { EbsRootVolumeSize int `json:"EbsRootVolumeSize"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, 64, desc.Cluster.EbsRootVolumeSize) assert.Equal(t, "2.0.20230718", desc.Cluster.OSReleaseLabel) assert.Equal(t, "ami-0123456789abcdef0", desc.Cluster.CustomAmiID) @@ -579,13 +580,13 @@ func TestDescribeCluster_TagsEmptyNotAbsent(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) cluster, ok := raw["Cluster"].(map[string]any) require.True(t, ok, "Cluster must be an object") @@ -612,13 +613,13 @@ func TestDescribeCluster_TagsRoundTrip(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) cluster := raw["Cluster"].(map[string]any) tags := cluster["Tags"].([]any) @@ -642,7 +643,7 @@ func TestDescribeCluster_TagsEmptyAfterRemove(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rmRec := doEMRRequest(t, h, "RemoveTags", map[string]any{ "ResourceId": create.JobFlowID, @@ -654,7 +655,7 @@ func TestDescribeCluster_TagsEmptyAfterRemove(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) cluster := raw["Cluster"].(map[string]any) tags, hasKey := cluster["Tags"] @@ -673,7 +674,7 @@ func TestErrorMapping_NotFound_ClusterNotFoundException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errOut["__type"]) } @@ -728,7 +729,7 @@ func newSessionForTest(t *testing.T, h *emr.Handler, clusterName string) (string var cluster struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cluster)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cluster, jsonx.V1())) startRec := doEMRRequest(t, h, "StartSession", map[string]any{"ClusterId": cluster.JobFlowID}) require.Equal(t, http.StatusOK, startRec.Code, startRec.Body.String()) @@ -736,7 +737,7 @@ func newSessionForTest(t *testing.T, h *emr.Handler, clusterName string) (string var session struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &session)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &session, jsonx.V1())) return cluster.JobFlowID, session.ID } @@ -765,7 +766,7 @@ func TestEMR_StartSession(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.JobFlowID }, @@ -789,7 +790,7 @@ func TestEMR_StartSession(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) termRec := doEMRRequest(t, h, "TerminateJobFlows", map[string]any{ "JobFlowIds": []string{out.JobFlowID}, @@ -822,7 +823,7 @@ func TestEMR_StartSession(t *testing.T) { ARN string `json:"Arn"` State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.ID) assert.Equal(t, clusterID, out.ClusterID) assert.Equal(t, emr.SessionStateSubmitted, out.State) @@ -879,7 +880,7 @@ func TestEMR_GetSession(t *testing.T) { State string `json:"State"` } `json:"Session"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, sessionID, out.Session.ID) assert.Equal(t, clusterID, out.Session.ClusterID) assert.Equal(t, emr.SessionStateSubmitted, out.Session.State) @@ -921,7 +922,7 @@ func TestEMR_ListSessions(t *testing.T) { var cluster struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cluster)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cluster, jsonx.V1())) for range tt.numSessions { startRec := doEMRRequest(t, h, "StartSession", map[string]any{"ClusterId": cluster.JobFlowID}) @@ -941,7 +942,7 @@ func TestEMR_ListSessions(t *testing.T) { ID string `json:"Id"` } `json:"Sessions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Sessions, tt.wantCount) }) } @@ -996,7 +997,7 @@ func TestEMR_TerminateSession(t *testing.T) { SessionID string `json:"SessionId"` State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, clusterID, out.ClusterID) assert.Equal(t, sessionID, out.SessionID) assert.Equal(t, emr.SessionStateTerminated, out.State) @@ -1050,7 +1051,7 @@ func TestEMR_GetSessionEndpoint(t *testing.T) { Endpoint string `json:"Endpoint"` AuthToken string `json:"AuthToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.Endpoint) assert.NotEmpty(t, out.AuthToken) assert.Contains(t, out.Credentials, "UsernamePassword") @@ -1104,7 +1105,7 @@ func TestEMR_TerminateJobFlows_CascadesToSessions(t *testing.T) { State string `json:"State"` } `json:"Session"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, emr.SessionStateTerminated, out.Session.State, "session must be terminated when its cluster is terminated") }) diff --git a/services/emr/handler_instance_fleets_test.go b/services/emr/handler_instance_fleets_test.go index ff19295b17..7c29afb470 100644 --- a/services/emr/handler_instance_fleets_test.go +++ b/services/emr/handler_instance_fleets_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -23,7 +24,7 @@ func TestEMR_ListInstanceFleets(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) rec := doEMRRequest(t, h, "ListInstanceFleets", map[string]any{ "ClusterId": createOut.JobFlowID, @@ -35,7 +36,7 @@ func TestEMR_ListInstanceFleets(t *testing.T) { InstanceFleets []emr.InstanceFleet `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.InstanceFleets) } @@ -71,7 +72,7 @@ func TestEMR_AddInstanceFleet(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) clusterID := tt.clusterID if clusterID == "" { @@ -94,7 +95,7 @@ func TestEMR_AddInstanceFleet(t *testing.T) { ClusterID string `json:"ClusterId"` InstanceFleetID string `json:"InstanceFleetId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.InstanceFleetID) assert.Equal(t, clusterID, out.ClusterID) assert.Contains(t, out.ClusterArn, "elasticmapreduce") @@ -114,7 +115,7 @@ func TestEMR_AddInstanceFleet_ListInstanceFleets(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) // Add a fleet. addRec := doEMRRequest(t, h, "AddInstanceFleet", map[string]any{ @@ -138,7 +139,7 @@ func TestEMR_AddInstanceFleet_ListInstanceFleets(t *testing.T) { Name string `json:"Name"` } `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.InstanceFleets, 1) assert.Equal(t, "task-fleet", listOut.InstanceFleets[0].Name) assert.NotEmpty(t, listOut.InstanceFleets[0].ID) @@ -165,7 +166,7 @@ func TestInstanceFleet_CapacityTargets(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) addRec := doEMRRequest(t, h, "AddInstanceFleet", map[string]any{ "ClusterId": create.JobFlowID, @@ -193,7 +194,7 @@ func TestInstanceFleet_CapacityTargets(t *testing.T) { ProvisionedSpotCapacity int `json:"ProvisionedSpotCapacity"` } `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.InstanceFleets, 1) f := out.InstanceFleets[0] assert.Equal(t, 5, f.TargetOnDemandCapacity) @@ -234,7 +235,7 @@ func TestListInstanceFleetsTyped(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) addRec := doEMRRequest(t, h, "AddInstanceFleet", map[string]any{ "ClusterId": createOut.JobFlowID, @@ -250,7 +251,7 @@ func TestListInstanceFleetsTyped(t *testing.T) { var out struct { InstanceFleets []emr.InstanceFleet `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.InstanceFleets, 1) assert.Equal(t, "typed-fleet", out.InstanceFleets[0].Name) assert.NotEmpty(t, out.InstanceFleets[0].ID) diff --git a/services/emr/handler_instance_groups_test.go b/services/emr/handler_instance_groups_test.go index da4fe31db5..fe184951a6 100644 --- a/services/emr/handler_instance_groups_test.go +++ b/services/emr/handler_instance_groups_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -48,7 +49,7 @@ func TestEMR_ListInstanceGroups(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) rec := doEMRRequest(t, h, "ListInstanceGroups", map[string]any{ "ClusterId": createOut.JobFlowID, @@ -63,7 +64,7 @@ func TestEMR_ListInstanceGroups(t *testing.T) { } `json:"Status"` } `json:"InstanceGroups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.InstanceGroups, tt.wantCount) for _, g := range out.InstanceGroups { @@ -116,7 +117,7 @@ func TestEMR_AddInstanceGroups(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) jobFlowID := tt.jobFlowID if jobFlowID == "" { @@ -138,7 +139,7 @@ func TestEMR_AddInstanceGroups(t *testing.T) { JobFlowID string `json:"JobFlowId"` InstanceGroupIDs []string `json:"InstanceGroupIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.InstanceGroupIDs, 1) assert.Equal(t, jobFlowID, out.JobFlowID) assert.Contains(t, out.ClusterArn, "elasticmapreduce") @@ -194,7 +195,7 @@ func TestModifyInstanceGroups(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) listRec := doEMRRequest(t, h, "ListInstanceGroups", map[string]any{"ClusterId": create.JobFlowID}) var listOut struct { @@ -202,7 +203,7 @@ func TestModifyInstanceGroups(t *testing.T) { ID string `json:"Id"` } `json:"InstanceGroups"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.InstanceGroups) modRec := doEMRRequest(t, h, "ModifyInstanceGroups", map[string]any{ @@ -238,7 +239,7 @@ func TestInstanceGroup_BidPrice(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) listRec := doEMRRequest(t, h, "ListInstanceGroups", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, listRec.Code) @@ -249,7 +250,7 @@ func TestInstanceGroup_BidPrice(t *testing.T) { BidPrice string `json:"BidPrice"` } `json:"InstanceGroups"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.InstanceGroups, 1) assert.Equal(t, "SPOT", out.InstanceGroups[0].Market) assert.Equal(t, "0.05", out.InstanceGroups[0].BidPrice) @@ -322,8 +323,8 @@ func TestBuildInstanceGroups_UniqueIDs(t *testing.T) { var c1, c2 struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &c1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &c2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &c1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &c2, jsonx.V1())) // Gather instance group IDs via ListInstanceGroups for each cluster allIDs := make(map[string]bool) @@ -336,7 +337,7 @@ func TestBuildInstanceGroups_UniqueIDs(t *testing.T) { ID string `json:"Id"` } `json:"InstanceGroups"` } - require.NoError(t, json.Unmarshal(igRec.Body.Bytes(), &igOut)) + require.NoError(t, json.Unmarshal(igRec.Body.Bytes(), &igOut, jsonx.V1())) for _, ig := range igOut.InstanceGroups { assert.False(t, allIDs[ig.ID], "duplicate instance group ID %s", ig.ID) diff --git a/services/emr/handler_instances_test.go b/services/emr/handler_instances_test.go index 50e0b0ff40..4d8b9e1034 100644 --- a/services/emr/handler_instances_test.go +++ b/services/emr/handler_instances_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListInstances(t *testing.T) { @@ -27,7 +29,7 @@ func TestListInstances(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) listRec := doEMRRequest(t, h, "ListInstances", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, listRec.Code) @@ -38,7 +40,7 @@ func TestListInstances(t *testing.T) { Ec2InstanceID string `json:"Ec2InstanceId"` } `json:"Instances"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Instances, 3) } @@ -53,7 +55,7 @@ func TestListInstances_InstanceFleets(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) addFleet := func(fleetType string, onDemand, spot int) string { rec := doEMRRequest(t, h, "AddInstanceFleet", map[string]any{ @@ -70,7 +72,7 @@ func TestListInstances_InstanceFleets(t *testing.T) { var out struct { InstanceFleetID string `json:"InstanceFleetId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.InstanceFleetID } @@ -116,7 +118,7 @@ func TestListInstances_InstanceFleets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out instancesResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Instances, tt.wantLen) for _, inst := range out.Instances { diff --git a/services/emr/handler_job_flows_test.go b/services/emr/handler_job_flows_test.go index efd29a67bf..0a8135d0a5 100644 --- a/services/emr/handler_job_flows_test.go +++ b/services/emr/handler_job_flows_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDescribeJobFlows(t *testing.T) { @@ -19,7 +21,7 @@ func TestDescribeJobFlows(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeJobFlows", map[string]any{ "JobFlowIds": []string{create.JobFlowID}, @@ -32,7 +34,7 @@ func TestDescribeJobFlows(t *testing.T) { Name string `json:"Name"` } `json:"JobFlows"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.JobFlows, 1) assert.Equal(t, create.JobFlowID, out.JobFlows[0].JobFlowID) assert.Equal(t, "jf-cluster", out.JobFlows[0].Name) @@ -65,7 +67,7 @@ func TestDescribeJobFlows_EmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) flows, hasKey := raw["JobFlows"] assert.True(t, hasKey, "DescribeJobFlows must include 'JobFlows' key") diff --git a/services/emr/handler_notebook_executions_test.go b/services/emr/handler_notebook_executions_test.go index e033d742db..fcd592d1b1 100644 --- a/services/emr/handler_notebook_executions_test.go +++ b/services/emr/handler_notebook_executions_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -31,7 +32,7 @@ func TestNotebookExecution(t *testing.T) { var startOut struct { NotebookExecutionID string `json:"NotebookExecutionId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut.NotebookExecutionID) // Describe it. @@ -47,7 +48,7 @@ func TestNotebookExecution(t *testing.T) { NotebookExecutionName string `json:"NotebookExecutionName"` } `json:"NotebookExecution"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, startOut.NotebookExecutionID, descOut.NotebookExecution.NotebookExecutionID) assert.Equal(t, "RUNNING", descOut.NotebookExecution.Status) assert.Equal(t, "test-run", descOut.NotebookExecution.NotebookExecutionName) @@ -62,7 +63,7 @@ func TestNotebookExecution(t *testing.T) { Status string `json:"Status"` } `json:"NotebookExecutions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.NotebookExecutions, 1) assert.Equal(t, startOut.NotebookExecutionID, listOut.NotebookExecutions[0].NotebookExecutionID) @@ -83,7 +84,7 @@ func TestNotebookExecution(t *testing.T) { Status string `json:"Status"` } `json:"NotebookExecution"` } - require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descOut2, jsonx.V1())) assert.Equal(t, "STOPPED", descOut2.NotebookExecution.Status) } @@ -127,7 +128,7 @@ func TestNotebookExecution_ListFilter(t *testing.T) { NotebookExecutionID string `json:"NotebookExecutionId"` } `json:"NotebookExecutions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.NotebookExecutions, 3) } @@ -155,7 +156,7 @@ func TestNotebookExecution_ListNoParamsOrTagsLeak(t *testing.T) { var raw struct { NotebookExecutions []map[string]any `json:"NotebookExecutions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.NotebookExecutions, 1) item := raw.NotebookExecutions[0] @@ -232,7 +233,7 @@ func TestNotebookExecution_TagsEmptyNotAbsent(t *testing.T) { var start struct { NotebookExecutionID string `json:"NotebookExecutionId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &start, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeNotebookExecution", map[string]any{ "NotebookExecutionId": start.NotebookExecutionID, @@ -240,7 +241,7 @@ func TestNotebookExecution_TagsEmptyNotAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) ne, ok := raw["NotebookExecution"].(map[string]any) require.True(t, ok, "NotebookExecution must be an object") diff --git a/services/emr/handler_persistent_app_ui_test.go b/services/emr/handler_persistent_app_ui_test.go index 51ee63559b..a4b9093d30 100644 --- a/services/emr/handler_persistent_app_ui_test.go +++ b/services/emr/handler_persistent_app_ui_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -48,7 +49,7 @@ func TestEMR_CreatePersistentAppUI(t *testing.T) { var out struct { PersistentAppUIID string `json:"PersistentAppUIId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.PersistentAppUIID) } }) @@ -65,7 +66,7 @@ func TestGetClusterSessionCredentials(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "GetClusterSessionCredentials", map[string]any{ "ClusterId": create.JobFlowID, @@ -77,7 +78,7 @@ func TestGetClusterSessionCredentials(t *testing.T) { Credentials map[string]any `json:"Credentials"` ExpiresAt float64 `json:"ExpiresAt"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.Credentials) assert.NotZero(t, out.ExpiresAt) } @@ -92,7 +93,7 @@ func TestGetClusterSessionCredentials_MissingRole(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "GetClusterSessionCredentials", map[string]any{ "ClusterId": create.JobFlowID, @@ -131,6 +132,6 @@ func TestGetOnClusterPresignedURL_NonExistentCluster(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errOut["__type"]) } diff --git a/services/emr/handler_policies_test.go b/services/emr/handler_policies_test.go index 20959928a0..9d8c49edb2 100644 --- a/services/emr/handler_policies_test.go +++ b/services/emr/handler_policies_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestManagedScalingPolicy(t *testing.T) { @@ -19,7 +21,7 @@ func TestManagedScalingPolicy(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) clusterID := create.JobFlowID putRec := doEMRRequest(t, h, "PutManagedScalingPolicy", map[string]any{ @@ -45,7 +47,7 @@ func TestManagedScalingPolicy(t *testing.T) { } `json:"ComputeLimits"` } `json:"ManagedScalingPolicy"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "InstanceFleetUnits", getOut.ManagedScalingPolicy.ComputeLimits.UnitType) assert.Equal(t, 10, getOut.ManagedScalingPolicy.ComputeLimits.MaximumCapacityUnits) @@ -63,7 +65,7 @@ func TestAutoTerminationPolicy(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) clusterID := create.JobFlowID putRec := doEMRRequest(t, h, "PutAutoTerminationPolicy", map[string]any{ @@ -80,7 +82,7 @@ func TestAutoTerminationPolicy(t *testing.T) { IdleTimeout int64 `json:"IdleTimeout"` } `json:"AutoTerminationPolicy"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, int64(3600), getOut.AutoTerminationPolicy.IdleTimeout) } @@ -94,7 +96,7 @@ func TestAutoTerminationPolicy_InvalidTimeout(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) putRec := doEMRRequest(t, h, "PutAutoTerminationPolicy", map[string]any{ "ClusterId": create.JobFlowID, @@ -116,7 +118,7 @@ func TestBlockPublicAccessConfiguration(t *testing.T) { BlockPublicSecurityGroupRules bool `json:"BlockPublicSecurityGroupRules"` } `json:"BlockPublicAccessConfiguration"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.True(t, getOut.BlockPublicAccessConfiguration.BlockPublicSecurityGroupRules) putRec := doEMRRequest(t, h, "PutBlockPublicAccessConfiguration", map[string]any{ @@ -128,7 +130,7 @@ func TestBlockPublicAccessConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) getRec2 := doEMRRequest(t, h, "GetBlockPublicAccessConfiguration", map[string]any{}) - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut, jsonx.V1())) assert.False(t, getOut.BlockPublicAccessConfiguration.BlockPublicSecurityGroupRules) } @@ -149,7 +151,7 @@ func TestAutoScalingPolicy(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) listRec := doEMRRequest(t, h, "ListInstanceGroups", map[string]any{"ClusterId": create.JobFlowID}) var listOut struct { @@ -157,7 +159,7 @@ func TestAutoScalingPolicy(t *testing.T) { ID string `json:"Id"` } `json:"InstanceGroups"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.InstanceGroups) groupID := listOut.InstanceGroups[0].ID @@ -177,7 +179,7 @@ func TestAutoScalingPolicy(t *testing.T) { } `json:"Constraints"` } `json:"AutoScalingPolicy"` } - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) require.NotNil(t, putOut.AutoScalingPolicy) assert.Equal(t, 10, putOut.AutoScalingPolicy.Constraints.MaxCapacity) diff --git a/services/emr/handler_release_labels_test.go b/services/emr/handler_release_labels_test.go index 3bedf9f21a..d62f68087e 100644 --- a/services/emr/handler_release_labels_test.go +++ b/services/emr/handler_release_labels_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListReleaseLabels(t *testing.T) { @@ -20,7 +22,7 @@ func TestListReleaseLabels(t *testing.T) { var out struct { ReleaseLabels []string `json:"ReleaseLabels"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.ReleaseLabels) assert.Contains(t, out.ReleaseLabels, "emr-7.3.0") } @@ -39,7 +41,7 @@ func TestDescribeReleaseLabel(t *testing.T) { Name string `json:"Name"` } `json:"Applications"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "emr-7.3.0", out.ReleaseLabel) assert.NotEmpty(t, out.Applications) } @@ -58,6 +60,6 @@ func TestListSupportedInstanceTypes(t *testing.T) { VCPU int `json:"VCPU"` } `json:"SupportedInstanceTypes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.SupportedInstanceTypes) } diff --git a/services/emr/handler_security_configurations_test.go b/services/emr/handler_security_configurations_test.go index 5dc8fb0dd3..1f2598ad4d 100644 --- a/services/emr/handler_security_configurations_test.go +++ b/services/emr/handler_security_configurations_test.go @@ -2,7 +2,7 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -103,7 +104,7 @@ func TestEMR_SecurityConfiguration(t *testing.T) { Name string `json:"Name"` CreationDateTime float64 `json:"CreationDateTime"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.scName, out.Name) assert.NotZero(t, out.CreationDateTime) } @@ -154,7 +155,7 @@ func TestListSecurityConfigurations(t *testing.T) { Name string `json:"Name"` } `json:"SecurityConfigurations"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecurityConfigurations, 3) assert.Equal(t, "alpha", out.SecurityConfigurations[0].Name) } @@ -223,7 +224,7 @@ func TestDescribeSecurityConfiguration(t *testing.T) { SecurityConfiguration string `json:"SecurityConfiguration"` CreationDateTime float64 `json:"CreationDateTime"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.scName, out.Name) assert.NotZero(t, out.CreationDateTime) assert.JSONEq(t, `{"EncryptionConfiguration":{}}`, out.SecurityConfiguration) diff --git a/services/emr/handler_steps_test.go b/services/emr/handler_steps_test.go index 723a035171..29a044f94f 100644 --- a/services/emr/handler_steps_test.go +++ b/services/emr/handler_steps_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -33,7 +34,7 @@ func TestEMR_AddJobFlowSteps(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) rec := doEMRRequest(t, h, "AddJobFlowSteps", map[string]any{ "JobFlowId": createOut.JobFlowID, @@ -52,7 +53,7 @@ func TestEMR_AddJobFlowSteps(t *testing.T) { StepIDs []string `json:"StepIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.StepIDs, 1) assert.Contains(t, out.StepIDs[0], "s-") } @@ -68,7 +69,7 @@ func TestEMR_AddJobFlowSteps_RejectsTerminatedCluster(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) termRec := doEMRRequest(t, h, "TerminateJobFlows", map[string]any{ "JobFlowIds": []string{createOut.JobFlowID}, @@ -91,7 +92,7 @@ func TestEMR_AddJobFlowSteps_RejectsTerminatedCluster(t *testing.T) { var errBody struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errBody.Type) listRec := doEMRRequest(t, h, "ListSteps", map[string]any{"ClusterId": createOut.JobFlowID}) @@ -100,7 +101,7 @@ func TestEMR_AddJobFlowSteps_RejectsTerminatedCluster(t *testing.T) { var listOut struct { Steps []any `json:"Steps"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut.Steps) } @@ -115,7 +116,7 @@ func TestEMR_ListBootstrapActions(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) rec := doEMRRequest(t, h, "ListBootstrapActions", map[string]any{ "ClusterId": createOut.JobFlowID, @@ -127,7 +128,7 @@ func TestEMR_ListBootstrapActions(t *testing.T) { BootstrapActions []any `json:"BootstrapActions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.BootstrapActions) } @@ -163,7 +164,7 @@ func TestEMR_CancelSteps(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) clusterID := tt.clusterID if clusterID == "" { @@ -181,7 +182,7 @@ func TestEMR_CancelSteps(t *testing.T) { var out struct { CancelStepsInfoList []any `json:"CancelStepsInfoList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.CancelStepsInfoList) } }) @@ -199,7 +200,7 @@ func TestSteps(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) clusterID := create.JobFlowID addRec := doEMRRequest(t, h, "AddJobFlowSteps", map[string]any{ @@ -222,7 +223,7 @@ func TestSteps(t *testing.T) { var addOut struct { StepIDs []string `json:"StepIds"` } - require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addOut)) + require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &addOut, jsonx.V1())) require.Len(t, addOut.StepIDs, 2) assert.NotEmpty(t, addOut.StepIDs[0]) @@ -234,7 +235,7 @@ func TestSteps(t *testing.T) { ID string `json:"Id"` } `json:"Steps"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Steps, 2) descRec := doEMRRequest(t, h, "DescribeStep", map[string]any{ @@ -332,7 +333,7 @@ func TestListBootstrapActions_RoundTrip(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) listRec := doEMRRequest(t, h, "ListBootstrapActions", map[string]any{ "ClusterId": createOut.JobFlowID, @@ -346,7 +347,7 @@ func TestListBootstrapActions_RoundTrip(t *testing.T) { Args []string `json:"Args"` } `json:"BootstrapActions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) if tt.wantActions == nil { assert.Empty(t, listOut.BootstrapActions) @@ -423,7 +424,7 @@ func TestCancelSteps_ReturnsPerStepInfo(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) // List steps to get IDs listRec := doEMRRequest(t, h, "ListSteps", map[string]any{"ClusterId": create.JobFlowID}) @@ -434,7 +435,7 @@ func TestCancelSteps_ReturnsPerStepInfo(t *testing.T) { ID string `json:"Id"` } `json:"Steps"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.Steps) stepID := listOut.Steps[0].ID @@ -450,7 +451,7 @@ func TestCancelSteps_ReturnsPerStepInfo(t *testing.T) { Status string `json:"Status"` } `json:"CancelStepsInfoList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.CancelStepsInfoList, 1) assert.Equal(t, stepID, out.CancelStepsInfoList[0].StepID) assert.NotEmpty(t, out.CancelStepsInfoList[0].Status) diff --git a/services/emr/handler_studios_test.go b/services/emr/handler_studios_test.go index 94c40ea677..c4e0983b05 100644 --- a/services/emr/handler_studios_test.go +++ b/services/emr/handler_studios_test.go @@ -2,7 +2,7 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -52,7 +53,7 @@ func TestEMR_Studio(t *testing.T) { var out struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.StudioID }, @@ -112,7 +113,7 @@ func TestEMR_Studio(t *testing.T) { StudioID string `json:"StudioId"` URL string `json:"Url"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.StudioID) assert.NotEmpty(t, out.URL) } @@ -178,7 +179,7 @@ func TestEMR_StudioSessionMapping(t *testing.T) { var studioOut struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(studioRec.Body.Bytes(), &studioOut)) + require.NoError(t, json.Unmarshal(studioRec.Body.Bytes(), &studioOut, jsonx.V1())) studioID = studioOut.StudioID var rec *httptest.ResponseRecorder @@ -253,7 +254,7 @@ func TestEMR_DeleteStudio_CascadesSessionMappings(t *testing.T) { var studioOut struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(studioRec.Body.Bytes(), &studioOut)) + require.NoError(t, json.Unmarshal(studioRec.Body.Bytes(), &studioOut, jsonx.V1())) // Create a session mapping. mappingRec := doEMRRequest(t, h, "CreateStudioSessionMapping", map[string]any{ @@ -296,7 +297,7 @@ func TestStudio_DescribeAndList(t *testing.T) { var create struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) studioID := create.StudioID descRec := doEMRRequest(t, h, "DescribeStudio", map[string]any{"StudioId": studioID}) @@ -307,7 +308,7 @@ func TestStudio_DescribeAndList(t *testing.T) { Name string `json:"Name"` } `json:"Studio"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "my-studio", desc.Studio.Name) listRec := doEMRRequest(t, h, "ListStudios", map[string]any{}) @@ -318,7 +319,7 @@ func TestStudio_DescribeAndList(t *testing.T) { Name string `json:"Name"` } `json:"Studios"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Studios, 1) } @@ -340,7 +341,7 @@ func TestStudioSessionMapping_GetUpdateList(t *testing.T) { var create struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) studioID := create.StudioID doEMRRequest(t, h, "CreateStudioSessionMapping", map[string]any{ @@ -358,7 +359,7 @@ func TestStudioSessionMapping_GetUpdateList(t *testing.T) { SessionPolicyArn string `json:"SessionPolicyArn"` } `json:"SessionMappings"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.SessionMappings, 1) updateRec := doEMRRequest(t, h, "UpdateStudioSessionMapping", map[string]any{ @@ -381,7 +382,7 @@ func TestStudioSessionMapping_GetUpdateList(t *testing.T) { SessionPolicyArn string `json:"SessionPolicyArn"` } `json:"SessionMapping"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "arn:policy:new", getOut.SessionMapping.SessionPolicyArn) } @@ -502,13 +503,13 @@ func TestDescribeStudio_TagsEmptyNotAbsent(t *testing.T) { var create struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeStudio", map[string]any{"StudioId": create.StudioID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) studio, ok := raw["Studio"].(map[string]any) require.True(t, ok, "Studio must be an object") @@ -538,13 +539,13 @@ func TestDescribeStudio_SubnetIdsEmptyNotAbsent(t *testing.T) { var create struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "DescribeStudio", map[string]any{"StudioId": create.StudioID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) studio := raw["Studio"].(map[string]any) @@ -573,7 +574,7 @@ func TestListStudioSessionMappings_EmptyNotNull(t *testing.T) { var create struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "ListStudioSessionMappings", map[string]any{ "StudioId": create.StudioID, @@ -581,7 +582,7 @@ func TestListStudioSessionMappings_EmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) mappings, hasKey := raw["SessionMappings"] assert.True(t, hasKey, "ListStudioSessionMappings must include 'SessionMappings' key") @@ -615,7 +616,7 @@ func TestWireShape_CreateStudio_Description(t *testing.T) { var out struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeStudio", map[string]any{"StudioId": out.StudioID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -625,7 +626,7 @@ func TestWireShape_CreateStudio_Description(t *testing.T) { Description string `json:"Description"` } `json:"Studio"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "a test studio", desc.Studio.Description, "CreateStudio must forward Description to the created Studio") } @@ -654,7 +655,7 @@ func TestWireShape_UpdateStudio_AppliesSubnetIds(t *testing.T) { var created struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) updateRec := doEMRRequest(t, h, "UpdateStudio", map[string]any{ "StudioId": created.StudioID, @@ -670,7 +671,7 @@ func TestWireShape_UpdateStudio_AppliesSubnetIds(t *testing.T) { SubnetIDs []string `json:"SubnetIds"` } `json:"Studio"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.ElementsMatch(t, []string{"subnet-1", "subnet-2"}, desc.Studio.SubnetIDs, "UpdateStudio must apply SubnetIds from the request, not discard them") } diff --git a/services/emr/handler_tags_test.go b/services/emr/handler_tags_test.go index 8129d90cec..da3a3376e9 100644 --- a/services/emr/handler_tags_test.go +++ b/services/emr/handler_tags_test.go @@ -2,13 +2,14 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -37,7 +38,7 @@ func TestEMR_AddAndRemoveTags(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) addRec := doEMRRequest(t, h, "AddTags", map[string]any{ "ResourceId": createOut.JobFlowID, @@ -75,7 +76,7 @@ func TestEMR_ListTagsForResource(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) return createOut.JobFlowID }, @@ -96,7 +97,7 @@ func TestEMR_ListTagsForResource(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) return createOut.JobFlowID }, @@ -131,7 +132,7 @@ func TestEMR_ListTagsForResource(t *testing.T) { var tagOut struct { Tags []emr.Tag `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagOut, jsonx.V1())) tt.checkTags(t, tagOut.Tags) } }) @@ -270,7 +271,7 @@ func TestListTagsForResource_ReturnsList(t *testing.T) { var createOut struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) listRec := doEMRRequest(t, h, "ListTagsForResource", map[string]any{ "ResourceId": createOut.JobFlowID, @@ -280,7 +281,7 @@ func TestListTagsForResource_ReturnsList(t *testing.T) { var out struct { Tags []emr.Tag `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Tags, 2) // Verify sorted order. assert.Equal(t, "a", out.Tags[0].Key) diff --git a/services/emr/handler_test.go b/services/emr/handler_test.go index b0db401029..a92ae1b82d 100644 --- a/services/emr/handler_test.go +++ b/services/emr/handler_test.go @@ -3,7 +3,7 @@ package emr_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -42,7 +43,7 @@ func newTestHandler(t *testing.T) *emr.Handler { func doEMRRequest(t *testing.T, h *emr.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -270,7 +271,7 @@ func TestEMR_ExtractResource(t *testing.T) { h := newTestHandler(t) e := echo.New() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -469,7 +470,7 @@ func TestErrValidationMapping(t *testing.T) { var errBody struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) // EMR's real error model has only InvalidRequestException (client fault) // and InternalServerException (server fault) -- no ValidationException. assert.Equal(t, "InvalidRequestException", errBody.Type) diff --git a/services/emr/handler_wire_shape_test.go b/services/emr/handler_wire_shape_test.go index bc82328d83..0448a2048e 100644 --- a/services/emr/handler_wire_shape_test.go +++ b/services/emr/handler_wire_shape_test.go @@ -1,7 +1,7 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( emrtypes "github.com/aws/aws-sdk-go-v2/service/emr/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestWireShape_ListClusters_IncludesTimeline verifies ListClusters' per-cluster @@ -39,7 +41,7 @@ func TestWireShape_ListClusters_IncludesTimeline(t *testing.T) { } `json:"Status"` } `json:"Clusters"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Clusters, 1) assert.NotZero(t, out.Clusters[0].Status.Timeline.CreationDateTime) assert.NotZero(t, out.Clusters[0].Status.Timeline.ReadyDateTime) @@ -60,7 +62,7 @@ func TestWireShape_ModifyInstanceFleet_AppliesCapacities(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) addRec := doEMRRequest(t, h, "AddInstanceFleet", map[string]any{ "ClusterId": create.JobFlowID, @@ -76,7 +78,7 @@ func TestWireShape_ModifyInstanceFleet_AppliesCapacities(t *testing.T) { var added struct { InstanceFleetID string `json:"InstanceFleetId"` } - require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &added)) + require.NoError(t, json.Unmarshal(addRec.Body.Bytes(), &added, jsonx.V1())) modRec := doEMRRequest(t, h, "ModifyInstanceFleet", map[string]any{ "ClusterId": create.JobFlowID, @@ -100,7 +102,7 @@ func TestWireShape_ModifyInstanceFleet_AppliesCapacities(t *testing.T) { ProvisionedSpotCapacity int `json:"ProvisionedSpotCapacity"` } `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.InstanceFleets, 1) f := out.InstanceFleets[0] assert.Equal(t, 8, f.TargetOnDemandCapacity) @@ -134,7 +136,7 @@ func TestWireShape_CancelSteps_RealEnumValues(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) listRec := doEMRRequest(t, h, "ListSteps", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, listRec.Code) @@ -144,7 +146,7 @@ func TestWireShape_CancelSteps_RealEnumValues(t *testing.T) { ID string `json:"Id"` } `json:"Steps"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.Steps) stepID := listOut.Steps[0].ID @@ -162,7 +164,7 @@ func TestWireShape_CancelSteps_RealEnumValues(t *testing.T) { Status string `json:"Status"` } `json:"CancelStepsInfoList"` } - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstOut)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &firstOut, jsonx.V1())) require.Len(t, firstOut.CancelStepsInfoList, 1) assert.Equal(t, "SUBMITTED", firstOut.CancelStepsInfoList[0].Status) @@ -180,7 +182,7 @@ func TestWireShape_CancelSteps_RealEnumValues(t *testing.T) { Reason string `json:"Reason"` } `json:"CancelStepsInfoList"` } - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondOut)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &secondOut, jsonx.V1())) require.Len(t, secondOut.CancelStepsInfoList, 1) assert.Equal(t, "FAILED", secondOut.CancelStepsInfoList[0].Status) assert.NotEmpty(t, secondOut.CancelStepsInfoList[0].Reason) @@ -207,7 +209,7 @@ func TestWireShape_NotebookExecution_EpochTimestamps(t *testing.T) { var started struct { NotebookExecutionID string `json:"NotebookExecutionId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeNotebookExecution", map[string]any{ "NotebookExecutionId": started.NotebookExecutionID, @@ -219,7 +221,7 @@ func TestWireShape_NotebookExecution_EpochTimestamps(t *testing.T) { StartTime float64 `json:"StartTime"` } `json:"NotebookExecution"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.NotZero(t, out.NotebookExecution.StartTime) } @@ -239,7 +241,7 @@ func TestWireShape_GetOnClusterAppUIPresignedURL_FieldName(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "GetOnClusterAppUIPresignedURL", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, rec.Code) @@ -248,13 +250,13 @@ func TestWireShape_GetOnClusterAppUIPresignedURL_FieldName(t *testing.T) { PresignedURL string `json:"PresignedURL"` PresignedURLReady bool `json:"PresignedURLReady"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.PresignedURL) assert.True(t, out.PresignedURLReady) // The old, wrong field name must not be the only thing populated. var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.NotContains(t, raw, "URL") } @@ -274,7 +276,7 @@ func TestWireShape_GetPersistentAppUIPresignedURL_ReadyFlag(t *testing.T) { PresignedURL string `json:"PresignedURL"` PresignedURLReady bool `json:"PresignedURLReady"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.PresignedURL) assert.True(t, out.PresignedURLReady) } @@ -297,7 +299,7 @@ func TestWireShape_RunJobFlow_JobFlowRole(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -309,7 +311,7 @@ func TestWireShape_RunJobFlow_JobFlowRole(t *testing.T) { } `json:"Ec2InstanceAttributes"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "EMR_EC2_DefaultRole", out.Cluster.Ec2InstanceAttributes.IamInstanceProfile) } @@ -338,7 +340,7 @@ func TestWireShape_RunJobFlow_InlinePolicies(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) msRec := doEMRRequest(t, h, "GetManagedScalingPolicy", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, msRec.Code) @@ -350,7 +352,7 @@ func TestWireShape_RunJobFlow_InlinePolicies(t *testing.T) { } `json:"ComputeLimits"` } `json:"ManagedScalingPolicy"` } - require.NoError(t, json.Unmarshal(msRec.Body.Bytes(), &msOut)) + require.NoError(t, json.Unmarshal(msRec.Body.Bytes(), &msOut, jsonx.V1())) assert.Equal(t, 10, msOut.ManagedScalingPolicy.ComputeLimits.MaximumCapacityUnits) atRec := doEMRRequest(t, h, "GetAutoTerminationPolicy", map[string]any{"ClusterId": create.JobFlowID}) @@ -361,7 +363,7 @@ func TestWireShape_RunJobFlow_InlinePolicies(t *testing.T) { IdleTimeout int64 `json:"IdleTimeout"` } `json:"AutoTerminationPolicy"` } - require.NoError(t, json.Unmarshal(atRec.Body.Bytes(), &atOut)) + require.NoError(t, json.Unmarshal(atRec.Body.Bytes(), &atOut, jsonx.V1())) assert.Equal(t, int64(3600), atOut.AutoTerminationPolicy.IdleTimeout) } @@ -382,13 +384,13 @@ func TestWireShape_GetManagedScalingPolicy_OmitsFieldWhenUnset(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "GetManagedScalingPolicy", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.NotContains(t, raw, "ManagedScalingPolicy") } @@ -406,13 +408,13 @@ func TestWireShape_GetAutoTerminationPolicy_OmitsFieldWhenUnset(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &create, jsonx.V1())) rec := doEMRRequest(t, h, "GetAutoTerminationPolicy", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.NotContains(t, raw, "AutoTerminationPolicy") } @@ -436,7 +438,7 @@ func TestWireShape_AddTags_StudioResource(t *testing.T) { var created struct { StudioID string `json:"StudioId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) addRec := doEMRRequest(t, h, "AddTags", map[string]any{ "ResourceId": created.StudioID, @@ -453,7 +455,7 @@ func TestWireShape_AddTags_StudioResource(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Tags, 1) assert.Equal(t, "env", listOut.Tags[0].Key) assert.Equal(t, "prod", listOut.Tags[0].Value) @@ -470,7 +472,7 @@ func TestWireShape_AddTags_StudioResource(t *testing.T) { var listOut2 struct { Tags []struct{} `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2, jsonx.V1())) assert.Empty(t, listOut2.Tags) } @@ -501,7 +503,7 @@ func TestWireShape_RunJobFlow_InstanceFleets(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -511,7 +513,7 @@ func TestWireShape_RunJobFlow_InstanceFleets(t *testing.T) { InstanceCollectionType string `json:"InstanceCollectionType"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "INSTANCE_FLEET", descOut.Cluster.InstanceCollectionType) listRec := doEMRRequest(t, h, "ListInstanceFleets", map[string]any{"ClusterId": create.JobFlowID}) @@ -523,7 +525,7 @@ func TestWireShape_RunJobFlow_InstanceFleets(t *testing.T) { TargetOnDemandCapacity int `json:"TargetOnDemandCapacity"` } `json:"InstanceFleets"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.InstanceFleets, 1) assert.Equal(t, "primary", listOut.InstanceFleets[0].Name) assert.Equal(t, 1, listOut.InstanceFleets[0].TargetOnDemandCapacity) @@ -553,7 +555,7 @@ func TestWireShape_RunJobFlow_KerberosAttributesAndPlacementGroups(t *testing.T) var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -569,7 +571,7 @@ func TestWireShape_RunJobFlow_KerberosAttributesAndPlacementGroups(t *testing.T) } `json:"PlacementGroups"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "EC2.INTERNAL", out.Cluster.KerberosAttributes.Realm) require.Len(t, out.Cluster.PlacementGroups, 1) assert.Equal(t, "MASTER", out.Cluster.PlacementGroups[0].InstanceRole) @@ -607,7 +609,7 @@ func TestWireShape_RunJobFlow_OptionalClusterFields(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -629,7 +631,7 @@ func TestWireShape_RunJobFlow_OptionalClusterFields(t *testing.T) { } `json:"MonitoringConfiguration"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "arn:aws:kms:us-east-1:123456789012:key/abc", out.Cluster.LogEncryptionKmsKeyID) assert.Equal(t, "NONE", out.Cluster.RepoUpgradeOnBoot) @@ -664,7 +666,7 @@ func TestWireShape_RunJobFlow_AutoTerminate(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, descRec.Code) @@ -675,7 +677,7 @@ func TestWireShape_RunJobFlow_AutoTerminate(t *testing.T) { KeepJobFlowAliveWhenNoSteps bool `json:"KeepJobFlowAliveWhenNoSteps"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.True(t, out.Cluster.KeepJobFlowAliveWhenNoSteps) assert.False(t, out.Cluster.AutoTerminate) } @@ -702,7 +704,7 @@ func TestWireShape_ListClusters_NoFabricatedReleaseLabel(t *testing.T) { var raw struct { Clusters []map[string]any `json:"Clusters"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.Clusters, 1) _, hasReleaseLabel := raw.Clusters[0]["ReleaseLabel"] diff --git a/services/emr/janitor_test.go b/services/emr/janitor_test.go index d82b23e24b..4a9d0ed2ac 100644 --- a/services/emr/janitor_test.go +++ b/services/emr/janitor_test.go @@ -2,7 +2,7 @@ package emr_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -250,7 +251,7 @@ func TestEMR_Handler_Reset(t *testing.T) { Clusters []any `json:"Clusters"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Clusters) } diff --git a/services/emr/kerberos_secrets_test.go b/services/emr/kerberos_secrets_test.go index a5fe937663..572839fd5b 100644 --- a/services/emr/kerberos_secrets_test.go +++ b/services/emr/kerberos_secrets_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDescribeCluster_KerberosAttributesOmitPasswords(t *testing.T) { @@ -41,7 +43,7 @@ func TestDescribeCluster_KerberosAttributesOmitPasswords(t *testing.T) { var create struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &create, jsonx.V1())) desc := doEMRRequest(t, h, "DescribeCluster", map[string]any{"ClusterId": create.JobFlowID}) require.Equal(t, http.StatusOK, desc.Code) @@ -51,7 +53,7 @@ func TestDescribeCluster_KerberosAttributesOmitPasswords(t *testing.T) { KerberosAttributes map[string]any `json:"KerberosAttributes"` } `json:"Cluster"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &out, jsonx.V1())) ka := out.Cluster.KerberosAttributes assert.Equal(t, "EC2.INTERNAL", ka["Realm"]) diff --git a/services/emr/models.go b/services/emr/models.go index 79d00e007c..06bff5463d 100644 --- a/services/emr/models.go +++ b/services/emr/models.go @@ -1,8 +1,11 @@ package emr import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // regionContextKey is the context key under which the per-request AWS region is stored. @@ -585,13 +588,13 @@ type KerberosAttributes struct { func (k KerberosAttributes) MarshalJSON() ([]byte, error) { type plain KerberosAttributes - raw, err := json.Marshal(plain(k)) + raw, err := json.Marshal(plain(k), jsonx.V1()) if err != nil { return nil, err } - var fields map[string]json.RawMessage - if err = json.Unmarshal(raw, &fields); err != nil { + var fields map[string]jsontext.Value + if err = json.Unmarshal(raw, &fields, jsonx.V1()); err != nil { return nil, err } @@ -599,7 +602,7 @@ func (k KerberosAttributes) MarshalJSON() ([]byte, error) { delete(fields, secret) } - return json.Marshal(fields) + return json.Marshal(fields, jsonx.V1()) } // PlacementGroupConfig is the placement group configuration for a single diff --git a/services/emr/persistence.go b/services/emr/persistence.go index 0773a53a33..a7abc31f6a 100644 --- a/services/emr/persistence.go +++ b/services/emr/persistence.go @@ -2,7 +2,7 @@ package emr import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "time" @@ -89,7 +89,7 @@ func regionOnlyDTOKeyFn[V any](d *regionOnlyDTO[V]) string { return d.Region } // guards against decoding a snapshot from an incompatible (older or newer) // build of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ArnIndex map[string]map[string]string `json:"arnIndex"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -321,7 +321,7 @@ func unwrapRegionOnlyDTOs[V any](dtos *store.Table[regionOnlyDTO[V]], setRegion // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/emr/request_validation_test.go b/services/emr/request_validation_test.go index 2364c2b1dc..fbaf8d943b 100644 --- a/services/emr/request_validation_test.go +++ b/services/emr/request_validation_test.go @@ -1,12 +1,14 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEMR_RequestValidation(t *testing.T) { @@ -78,13 +80,13 @@ func TestEMR_RequestValidation(t *testing.T) { var out struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(created.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(created.Body.Bytes(), &out, jsonx.V1())) rec := doEMRRequest(t, h, tt.action, tt.body(out.JobFlowID)) require.Equal(t, http.StatusBadRequest, rec.Code) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errOut["__type"]) assert.Contains(t, errOut["message"], tt.wantMsg) assert.NotContains(t, errOut["message"], "Exception:") diff --git a/services/emr/security_configurations.go b/services/emr/security_configurations.go index 929463d5e7..19d5e22f80 100644 --- a/services/emr/security_configurations.go +++ b/services/emr/security_configurations.go @@ -2,12 +2,13 @@ package emr import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -63,7 +64,7 @@ func (b *InMemoryBackend) CreateSecurityConfiguration( return nil, fmt.Errorf("%w: Name is required", ErrValidation) } - if !json.Valid([]byte(securityConfig)) { + if !jsontext.Value([]byte(securityConfig)).IsValid(jsonx.V1()) { return nil, fmt.Errorf("%w: SecurityConfiguration must be valid JSON", ErrValidation) } diff --git a/services/emr/validation.go b/services/emr/validation.go index ad620798de..e253b80477 100644 --- a/services/emr/validation.go +++ b/services/emr/validation.go @@ -1,11 +1,12 @@ package emr import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -62,7 +63,7 @@ func checkMarker(body []byte) error { NextToken string `json:"NextToken"` } - if json.Unmarshal(body, &in) == nil { + if json.Unmarshal(body, &in, jsonx.V1()) == nil { for _, tok := range []string{in.Marker, in.NextToken} { if page.ValidateToken(tok) != nil { return fmt.Errorf("%w: Invalid marker", ErrValidation) diff --git a/services/emr/wire_field_fixes_test.go b/services/emr/wire_field_fixes_test.go index f8efa367bb..cdfdcb8b84 100644 --- a/services/emr/wire_field_fixes_test.go +++ b/services/emr/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package emr_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http/httptest" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/emr" ) @@ -250,7 +251,7 @@ func TestWireShape_Cluster_TerminatedAt_NotOnWire(t *testing.T) { var created struct { JobFlowID string `json:"JobFlowId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) termRec := doEMRRequest(t, h, "TerminateJobFlows", map[string]any{"JobFlowIds": []string{created.JobFlowID}}) require.Equal(t, 200, termRec.Code) @@ -261,7 +262,7 @@ func TestWireShape_Cluster_TerminatedAt_NotOnWire(t *testing.T) { var raw struct { Cluster map[string]any `json:"Cluster"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) _, hasTerminatedAt := raw.Cluster["TerminatedAt"] assert.False(t, hasTerminatedAt, "Cluster must not carry a TerminatedAt field on the wire -- real types.Cluster has no such member") @@ -331,7 +332,7 @@ func TestWireShape_StudioSummary_NoFabricatedFields(t *testing.T) { var raw struct { Studios []map[string]any `json:"Studios"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.Studios, 1) _, hasArn := raw.Studios[0]["StudioArn"] @@ -431,7 +432,7 @@ func TestWireShape_DescribePersistentAppUI_RealShape(t *testing.T) { JobFlowID string `json:"JobFlowId"` ClusterArn string `json:"ClusterArn"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cluster)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cluster, jsonx.V1())) createUIRec := doEMRRequest(t, h, "CreatePersistentAppUI", map[string]any{ "TargetResourceArn": cluster.ClusterArn, @@ -441,7 +442,7 @@ func TestWireShape_DescribePersistentAppUI_RealShape(t *testing.T) { var createdUI struct { PersistentAppUIID string `json:"PersistentAppUIId"` } - require.NoError(t, json.Unmarshal(createUIRec.Body.Bytes(), &createdUI)) + require.NoError(t, json.Unmarshal(createUIRec.Body.Bytes(), &createdUI, jsonx.V1())) descRec := doEMRRequest(t, h, "DescribePersistentAppUI", map[string]any{ "PersistentAppUIId": createdUI.PersistentAppUIID, @@ -451,7 +452,7 @@ func TestWireShape_DescribePersistentAppUI_RealShape(t *testing.T) { var raw struct { PersistentAppUI map[string]any `json:"PersistentAppUI"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) _, hasArn := raw.PersistentAppUI["TargetResourceArn"] _, hasRole := raw.PersistentAppUI["RuntimeRoleEnabledCluster"] diff --git a/services/emrserverless/handler.go b/services/emrserverless/handler.go index 5adc5a7192..9c9a691adc 100644 --- a/services/emrserverless/handler.go +++ b/services/emrserverless/handler.go @@ -1,7 +1,7 @@ package emrserverless import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -771,7 +772,7 @@ type createApplicationResponse struct { func (h *Handler) handleCreateApplication(c *echo.Context, body []byte) error { var in createApplicationBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -879,7 +880,7 @@ func applyAutoConfigDefaults(extra map[string]any, whenAbsent bool) { func (h *Handler) handleUpdateApplication(c *echo.Context, applicationID string, body []byte) error { var in updateApplicationBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -974,7 +975,7 @@ type startJobRunResponse struct { func (h *Handler) handleStartJobRun(c *echo.Context, applicationID string, body []byte) error { var in startJobRunBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } @@ -1189,7 +1190,7 @@ type tagResourceBody struct { func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body []byte) error { var in tagResourceBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } diff --git a/services/emrserverless/handler_applications_test.go b/services/emrserverless/handler_applications_test.go index 65d9043c01..fa63b2c466 100644 --- a/services/emrserverless/handler_applications_test.go +++ b/services/emrserverless/handler_applications_test.go @@ -1,7 +1,7 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -34,7 +35,7 @@ func createAppWithArch(t *testing.T, h *emrserverless.Handler, name, releaseLabe require.Equal(t, http.StatusOK, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) id := out["applicationId"] require.NotEmpty(t, id) @@ -146,7 +147,7 @@ func TestHandler_CreateApplication_Architecture(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) app, _ := out["application"].(map[string]any) if tt.wantInOutput { diff --git a/services/emrserverless/handler_job_runs_test.go b/services/emrserverless/handler_job_runs_test.go index ac555a3f0c..f39789287c 100644 --- a/services/emrserverless/handler_job_runs_test.go +++ b/services/emrserverless/handler_job_runs_test.go @@ -1,7 +1,7 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -31,7 +32,7 @@ func startJobRunWithMode(t *testing.T, h *emrserverless.Handler, appID, mode str require.Equal(t, http.StatusOK, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) id := out["jobRunId"] require.NotEmpty(t, id) @@ -151,7 +152,7 @@ func TestHandler_StartJobRun_DefaultsModeToBATCH(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jr, _ := out["jobRun"].(map[string]any) assert.Equal(t, tt.expectedMode, jr["mode"]) }) @@ -423,7 +424,7 @@ func TestHandler_GetJobRun_HasAttemptAndReleaseLabel(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) jr, _ := out["jobRun"].(map[string]any) attempt, hasAttempt := jr["attempt"] diff --git a/services/emrserverless/handler_list_summary_test.go b/services/emrserverless/handler_list_summary_test.go index 8f5e77ae1f..b9fcf48111 100644 --- a/services/emrserverless/handler_list_summary_test.go +++ b/services/emrserverless/handler_list_summary_test.go @@ -1,7 +1,7 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -144,7 +145,7 @@ func listSingleEMR( require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp[listKey].([]any) require.True(t, ok, "%s response missing %q list", path, listKey) diff --git a/services/emrserverless/handler_sessions_test.go b/services/emrserverless/handler_sessions_test.go index da14757795..28fcb47b54 100644 --- a/services/emrserverless/handler_sessions_test.go +++ b/services/emrserverless/handler_sessions_test.go @@ -1,7 +1,7 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -280,7 +281,7 @@ func TestHandler_SessionFilteringAndPersistence(t *testing.T) { var filtered struct { Sessions []map[string]any `json:"sessions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtered)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtered, jsonx.V1())) require.Len(t, filtered.Sessions, 1) assert.Equal(t, emrserverless.SessionStateTerminated, filtered.Sessions[0]["state"]) diff --git a/services/emrserverless/handler_test.go b/services/emrserverless/handler_test.go index 4b8a071ab9..0a1195cbbf 100644 --- a/services/emrserverless/handler_test.go +++ b/services/emrserverless/handler_test.go @@ -2,7 +2,7 @@ package emrserverless_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *emrserverless.Handler, method, path string, body var reqBody *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) } else { @@ -50,7 +51,7 @@ func doRequest(t *testing.T, h *emrserverless.Handler, method, path string, body func mustUnmarshal(t *testing.T, rec *httptest.ResponseRecorder, v any) { t.Helper() - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1())) } // createApp is a test helper that creates an application and returns its ID. @@ -623,7 +624,7 @@ func TestHandlerReset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) apps := out["applications"].([]any) assert.Empty(t, apps) } diff --git a/services/emrserverless/multi_region_test.go b/services/emrserverless/multi_region_test.go index 77b70dd9a8..e3e3ef2f2a 100644 --- a/services/emrserverless/multi_region_test.go +++ b/services/emrserverless/multi_region_test.go @@ -2,7 +2,7 @@ package emrserverless_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/emrserverless/persistence.go b/services/emrserverless/persistence.go index fd464127c0..32b048a6fc 100644 --- a/services/emrserverless/persistence.go +++ b/services/emrserverless/persistence.go @@ -2,10 +2,12 @@ package emrserverless import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -33,7 +35,7 @@ const emrserverlessSnapshotVersion = 1 // against decoding a snapshot from an incompatible (older or newer) build of // this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` SessionTokens map[string]map[string]string `json:"sessionTokens"` ApplicationTokens map[string]string `json:"applicationTokens"` JobRunTokens map[string]map[string]string `json:"jobRunTokens"` @@ -70,7 +72,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "emrserverless: Snapshot marshal failure", "error", err) diff --git a/services/emrserverless/persistence_test.go b/services/emrserverless/persistence_test.go index 582f2d9acf..e32e66121d 100644 --- a/services/emrserverless/persistence_test.go +++ b/services/emrserverless/persistence_test.go @@ -1,7 +1,7 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/emrserverless" ) @@ -168,10 +169,10 @@ func Test_Persistence_VersionMismatch(t *testing.T) { require.NotNil(t, snap) var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) tt.mutateSnap(t, raw) - mutated, err := json.Marshal(raw) + mutated, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) // Restore into a backend that already holds unrelated state, to diff --git a/services/emrserverless/request_validation_test.go b/services/emrserverless/request_validation_test.go index a77b459cd0..3eca0f0c42 100644 --- a/services/emrserverless/request_validation_test.go +++ b/services/emrserverless/request_validation_test.go @@ -1,12 +1,14 @@ package emrserverless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRequestValidation(t *testing.T) { @@ -47,7 +49,7 @@ func TestRequestValidation(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["code"]) assert.Contains(t, out["message"], tt.wantMsg) assert.NotContains(t, out["message"], "Exception:") @@ -73,7 +75,7 @@ func TestJobRunLifecycle(t *testing.T) { State string `json:"state"` } `json:"jobRun"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) got = append(got, out.JobRun.State) } diff --git a/services/emrserverless/session_handler.go b/services/emrserverless/session_handler.go index f81f3daa86..3e2f9a0be5 100644 --- a/services/emrserverless/session_handler.go +++ b/services/emrserverless/session_handler.go @@ -1,7 +1,7 @@ package emrserverless import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -9,6 +9,8 @@ import ( "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type startSessionBody struct { @@ -23,7 +25,7 @@ type startSessionBody struct { func (h *Handler) handleStartSession(c *echo.Context, applicationID string, body []byte) error { var in startSessionBody if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errResp("ValidationException", "invalid request body")) } } diff --git a/services/eventbridge/delivery.go b/services/eventbridge/delivery.go index de2e349e1b..e5fd72cb07 100644 --- a/services/eventbridge/delivery.go +++ b/services/eventbridge/delivery.go @@ -2,7 +2,7 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "strconv" @@ -11,6 +11,7 @@ import ( "time" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/google/uuid" @@ -675,7 +676,7 @@ func sendToDLQ( return } - payload, _ := json.Marshal(envelope) + payload, _ := json.Marshal(envelope, jsonx.V1()) var err error if withAttrs, ok := dt.SQS.(SQSAttributeSender); ok { @@ -732,7 +733,7 @@ func indexedRulesForEvent( // buildEventEnvelope creates a JSON string representing the normalized event for pattern matching. func buildEventEnvelope(entry EventEntry) string { - b, _ := json.Marshal(buildEventEnvelopeMap(entry)) + b, _ := json.Marshal(buildEventEnvelopeMap(entry), jsonx.V1()) return string(b) } @@ -760,7 +761,7 @@ func buildEventEnvelopeMap(entry EventEntry) map[string]any { if entry.Detail != "" { var detail map[string]any - switch err := json.Unmarshal([]byte(entry.Detail), &detail); { + switch err := json.Unmarshal([]byte(entry.Detail), &detail, jsonx.V1()); { case err != nil: envelope["detail"] = entry.Detail case detail == nil: @@ -969,7 +970,7 @@ func buildPayload(target *Target, envelope map[string]any) string { return applyInputTransformer(target.InputTransformer, envelope) } - b, _ := json.Marshal(envelope) + b, _ := json.Marshal(envelope, jsonx.V1()) return string(b) } @@ -987,7 +988,7 @@ func parseDeliveryDetail(entry EventEntry) any { } var d any - if err := json.Unmarshal([]byte(entry.Detail), &d); err != nil { + if err := json.Unmarshal([]byte(entry.Detail), &d, jsonx.V1()); err != nil { return entry.Detail } @@ -1157,7 +1158,7 @@ func entryFromEnvelope(envelope map[string]any, busName string) EventEntry { } if detail, ok := envelope["detail"]; ok { - if b, err := json.Marshal(detail); err == nil { + if b, err := json.Marshal(detail, jsonx.V1()); err == nil { entry.Detail = string(b) } } diff --git a/services/eventbridge/delivery_api_destination.go b/services/eventbridge/delivery_api_destination.go index 944e359411..952dedfd52 100644 --- a/services/eventbridge/delivery_api_destination.go +++ b/services/eventbridge/delivery_api_destination.go @@ -2,7 +2,7 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httpclient" "github.com/blackbirdworks/gopherstack/pkgs/httptarget" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -125,7 +126,7 @@ func mergeBodyParameters(payload string, params []ConnectionBodyParameter) strin } var obj map[string]any - if err := json.Unmarshal([]byte(payload), &obj); err != nil || obj == nil { + if err := json.Unmarshal([]byte(payload), &obj, jsonx.V1()); err != nil || obj == nil { return payload } @@ -133,7 +134,7 @@ func mergeBodyParameters(payload string, params []ConnectionBodyParameter) strin obj[p.Key] = p.Value } - merged, err := json.Marshal(obj) + merged, err := json.Marshal(obj, jsonx.V1()) if err != nil { return payload } @@ -253,7 +254,7 @@ func fetchOAuthToken(ctx context.Context, oauth *ResolvedOAuth) (string, error) var tokenResp struct { AccessToken string `json:"access_token"` } - if err = json.Unmarshal(data, &tokenResp); err != nil { + if err = json.Unmarshal(data, &tokenResp, jsonx.V1()); err != nil { return "", err } if tokenResp.AccessToken == "" { diff --git a/services/eventbridge/delivery_api_destination_test.go b/services/eventbridge/delivery_api_destination_test.go index a1502b59c7..f49253a917 100644 --- a/services/eventbridge/delivery_api_destination_test.go +++ b/services/eventbridge/delivery_api_destination_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -333,7 +334,7 @@ func TestAPIDestinationDelivery_BodyMerge(t *testing.T) { require.Len(t, reqs, 1) var got map[string]any - require.NoError(t, json.Unmarshal([]byte(reqs[0].body), &got)) + require.NoError(t, json.Unmarshal([]byte(reqs[0].body), &got, jsonx.V1())) assert.Equal(t, "world", got["hello"]) assert.Equal(t, "yes", got["injected"]) } diff --git a/services/eventbridge/delivery_ssm_appsync.go b/services/eventbridge/delivery_ssm_appsync.go index 2cd4275c7f..b5ccf0c0f6 100644 --- a/services/eventbridge/delivery_ssm_appsync.go +++ b/services/eventbridge/delivery_ssm_appsync.go @@ -2,12 +2,13 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // arnParts is the field count of arn:partition:service:region:account:resource. @@ -111,7 +112,7 @@ func BuildSSMCommandRequest(target *Target, payload string) (SSMCommandRequest, var in ssmInput - _ = json.Unmarshal([]byte(payload), &in) + _ = json.Unmarshal([]byte(payload), &in, jsonx.V1()) name := in.DocumentName if name == "" { @@ -136,7 +137,7 @@ func BuildSSMAutomationRequest(target *Target, payload string) SSMAutomationRequ var raw map[string]any - _ = json.Unmarshal([]byte(payload), &raw) + _ = json.Unmarshal([]byte(payload), &raw, jsonx.V1()) params := raw if p, ok := raw["Parameters"].(map[string]any); ok { @@ -197,7 +198,7 @@ func deliverToAppSync(ctx context.Context, target *Target, dt DeliveryTargets, p var vars map[string]any - if err := json.Unmarshal([]byte(payload), &vars); err != nil { + if err := json.Unmarshal([]byte(payload), &vars, jsonx.V1()); err != nil { return logTargetErr(ctx, "AppSync", fmt.Errorf("payload is not a JSON object: %w", err)) } diff --git a/services/eventbridge/delivery_test.go b/services/eventbridge/delivery_test.go index 88a12d4266..1047c315c3 100644 --- a/services/eventbridge/delivery_test.go +++ b/services/eventbridge/delivery_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -442,8 +443,8 @@ func TestDelivery_SharedEventIDAcrossTargets(t *testing.T) { msg1 := sqsMock1.MessagesFor("arn:aws:sqs:us-east-1:000000000000:queue-a")[0] msg2 := sqsMock2.MessagesFor("arn:aws:sqs:us-east-1:000000000000:queue-b")[0] - require.NoError(t, json.Unmarshal([]byte(msg1), &id1)) - require.NoError(t, json.Unmarshal([]byte(msg2), &id2)) + require.NoError(t, json.Unmarshal([]byte(msg1), &id1, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(msg2), &id2, jsonx.V1())) assert.NotEmpty(t, id1.ID) assert.Equal(t, id1.ID, id2.ID, "all targets for the same rule+event must share the same event id") }) diff --git a/services/eventbridge/delivery_transform.go b/services/eventbridge/delivery_transform.go index 16b80cf33d..f056983f63 100644 --- a/services/eventbridge/delivery_transform.go +++ b/services/eventbridge/delivery_transform.go @@ -2,10 +2,13 @@ package eventbridge import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // inputPathsMapKeyRe validates InputPathsMap variable names per AWS spec. @@ -19,7 +22,7 @@ func applyInputPath(path string, envelope map[string]any) string { return "" } - b, _ := json.Marshal(val) + b, _ := json.Marshal(val, jsonx.V1()) return string(b) } @@ -175,9 +178,8 @@ func jsonEscapeInner(s string) string { // mirrors AWS, which does not HTML-escape input-transformer substitutions. func marshalNoHTMLEscape(v any) []byte { var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) - if err := enc.Encode(v); err != nil { + enc := jsontext.NewEncoder(&buf, jsonx.V1(), jsontext.EscapeForHTML(false)) + if err := json.MarshalEncode(enc, v); err != nil { return nil } diff --git a/services/eventbridge/envelope_map_test.go b/services/eventbridge/envelope_map_test.go index 473c3d1495..bdad4c8c5f 100644 --- a/services/eventbridge/envelope_map_test.go +++ b/services/eventbridge/envelope_map_test.go @@ -1,11 +1,13 @@ package eventbridge //nolint:testpackage // needs buildEventEnvelopeMap. import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestBuildEventEnvelopeMap_MatchesJSONRoundTrip(t *testing.T) { @@ -36,7 +38,7 @@ func TestBuildEventEnvelopeMap_MatchesJSONRoundTrip(t *testing.T) { t.Parallel() var viaJSON map[string]any - require.NoError(t, json.Unmarshal([]byte(buildEventEnvelope(tt.entry)), &viaJSON)) + require.NoError(t, json.Unmarshal([]byte(buildEventEnvelope(tt.entry)), &viaJSON, jsonx.V1())) require.Equal(t, viaJSON, buildEventEnvelopeMap(tt.entry)) }) } diff --git a/services/eventbridge/event_buses.go b/services/eventbridge/event_buses.go index 74bfc84bdc..e406502a61 100644 --- a/services/eventbridge/event_buses.go +++ b/services/eventbridge/event_buses.go @@ -2,12 +2,14 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // CreateEventBusParams holds the input for CreateEventBus. @@ -250,7 +252,7 @@ func (b *InMemoryBackend) PutPermission(ctx context.Context, input PutPermission // If a raw Policy JSON is provided it replaces the whole policy. if input.Policy != "" { var doc eventBusPolicyDocument - if err := json.Unmarshal([]byte(input.Policy), &doc); err == nil { + if err := json.Unmarshal([]byte(input.Policy), &doc, jsonx.V1()); err == nil { policy.Statements = make(map[string]*EventBusPolicyStatement, len(doc.Statement)) for _, s := range doc.Statement { policy.Statements[s.Sid] = s @@ -342,7 +344,7 @@ func (b *InMemoryBackend) GetEventBusPolicy(ctx context.Context, eventBusName st sort.Slice(stmts, func(i, j int) bool { return stmts[i].Sid < stmts[j].Sid }) - data, err := json.Marshal(eventBusPolicyDocument{Version: policyDocumentVersion, Statement: stmts}) + data, err := json.Marshal(eventBusPolicyDocument{Version: policyDocumentVersion, Statement: stmts}, jsonx.V1()) if err != nil { return "", err } @@ -386,7 +388,7 @@ func (b *InMemoryBackend) PutEventBusPolicy(ctx context.Context, input PutEventB } var doc eventBusPolicyDocument - if err := json.Unmarshal([]byte(input.Policy), &doc); err != nil { + if err := json.Unmarshal([]byte(input.Policy), &doc, jsonx.V1()); err != nil { return fmt.Errorf("%w: policy must be valid JSON: %w", ErrInvalidParameter, err) } diff --git a/services/eventbridge/event_buses_test.go b/services/eventbridge/event_buses_test.go index 387225135f..022361a923 100644 --- a/services/eventbridge/event_buses_test.go +++ b/services/eventbridge/event_buses_test.go @@ -3,7 +3,7 @@ package eventbridge_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -166,7 +167,7 @@ func TestGetEventBusPolicy_IsPolicyDocumentNotBareArray(t *testing.T) { Sid string `json:"Sid"` } `json:"Statement"` } - require.NoError(t, json.Unmarshal([]byte(policy), &doc), + require.NoError(t, json.Unmarshal([]byte(policy), &doc, jsonx.V1()), "Policy must be an IAM-style document (an object with a Statement array), not a bare array") assert.Equal(t, "2012-10-17", doc.Version) require.Len(t, doc.Statement, 1) diff --git a/services/eventbridge/export_test.go b/services/eventbridge/export_test.go index 6ccc6ac186..533bb5e5ba 100644 --- a/services/eventbridge/export_test.go +++ b/services/eventbridge/export_test.go @@ -2,9 +2,11 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // RegionContextKeyForTest exports the regionContextKey type for external tests. @@ -30,7 +32,7 @@ func CompilePatternErrorForTest(pattern string) error { // despite carrying a shape validation would normally reject. func MatchCompiledPatternBypassingValidationForTest(patternJSON, event string) bool { var pattern map[string]any - if err := json.Unmarshal([]byte(patternJSON), &pattern); err != nil { + if err := json.Unmarshal([]byte(patternJSON), &pattern, jsonx.V1()); err != nil { panic(err) } diff --git a/services/eventbridge/handler_api_destinations.go b/services/eventbridge/handler_api_destinations.go index 22036fd6a9..1211f56aad 100644 --- a/services/eventbridge/handler_api_destinations.go +++ b/services/eventbridge/handler_api_destinations.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createAPIDestinationOutput struct { @@ -19,7 +21,7 @@ func (h *Handler) apiDestinationActions() map[string]actionFn { return map[string]actionFn{ "CreateApiDestination": func(ctx context.Context, b []byte) (any, error) { var input CreateAPIDestinationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } dst, err := h.Backend.CreateAPIDestination(ctx, input) @@ -38,7 +40,7 @@ func (h *Handler) apiDestinationActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteAPIDestination(ctx, input.Name); err != nil { @@ -120,7 +122,7 @@ func (h *Handler) extendedAPIDestinationActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -138,7 +140,7 @@ func (h *Handler) extendedAPIDestinationActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } dsts, next, err := h.Backend.ListAPIDestinations( @@ -160,7 +162,7 @@ func (h *Handler) extendedAPIDestinationActions() map[string]actionFn { }, "UpdateApiDestination": func(ctx context.Context, b []byte) (any, error) { var input UpdateAPIDestinationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } dst, err := h.Backend.UpdateAPIDestination(ctx, input) diff --git a/services/eventbridge/handler_archives.go b/services/eventbridge/handler_archives.go index fbade3a164..09f4d1303c 100644 --- a/services/eventbridge/handler_archives.go +++ b/services/eventbridge/handler_archives.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createArchiveOutput struct { @@ -86,7 +88,7 @@ func (h *Handler) archiveActions() map[string]actionFn { return map[string]actionFn{ "CreateArchive": func(ctx context.Context, b []byte) (any, error) { var input CreateArchiveInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } archive, err := h.Backend.CreateArchive(ctx, input) @@ -111,7 +113,7 @@ func (h *Handler) extendedArchiveActions() map[string]actionFn { var input struct { ArchiveName string `json:"ArchiveName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -121,7 +123,7 @@ func (h *Handler) extendedArchiveActions() map[string]actionFn { var input struct { ArchiveName string `json:"ArchiveName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -140,7 +142,7 @@ func (h *Handler) extendedArchiveActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } archives, next, err := h.Backend.ListArchives( @@ -162,7 +164,7 @@ func (h *Handler) extendedArchiveActions() map[string]actionFn { }, "UpdateArchive": func(ctx context.Context, b []byte) (any, error) { var input UpdateArchiveInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } archive, err := h.Backend.UpdateArchive(ctx, input) diff --git a/services/eventbridge/handler_connections.go b/services/eventbridge/handler_connections.go index caa063a089..98ba355153 100644 --- a/services/eventbridge/handler_connections.go +++ b/services/eventbridge/handler_connections.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createConnectionOutput struct { @@ -104,7 +106,7 @@ func (h *Handler) connectionActions() map[string]actionFn { return map[string]actionFn{ "CreateConnection": func(ctx context.Context, b []byte) (any, error) { var input CreateConnectionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } conn, err := h.Backend.CreateConnection(ctx, input) @@ -123,7 +125,7 @@ func (h *Handler) connectionActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } conn, err := h.Backend.DeauthorizeConnection(ctx, input.Name) @@ -153,7 +155,7 @@ func (h *Handler) extendedConnectionActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -163,7 +165,7 @@ func (h *Handler) extendedConnectionActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -181,7 +183,7 @@ func (h *Handler) extendedConnectionActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } conns, next, err := h.Backend.ListConnections( @@ -203,7 +205,7 @@ func (h *Handler) extendedConnectionActions() map[string]actionFn { }, "UpdateConnection": func(ctx context.Context, b []byte) (any, error) { var input UpdateConnectionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } conn, err := h.Backend.UpdateConnection(ctx, input) diff --git a/services/eventbridge/handler_dispatch.go b/services/eventbridge/handler_dispatch.go index 0bca911b97..3893f7c6c4 100644 --- a/services/eventbridge/handler_dispatch.go +++ b/services/eventbridge/handler_dispatch.go @@ -2,7 +2,7 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -346,7 +347,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -440,7 +441,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a standardized JSON error response. @@ -509,7 +510,7 @@ func (h *Handler) handleError( Message: stripExceptionPrefix(reqErr.Error()), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/eventbridge/handler_endpoints.go b/services/eventbridge/handler_endpoints.go index 3c60fa51a6..b09676c70c 100644 --- a/services/eventbridge/handler_endpoints.go +++ b/services/eventbridge/handler_endpoints.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createEndpointOutput matches real CreateEndpointOutput @@ -76,7 +78,7 @@ func (h *Handler) endpointActions() map[string]actionFn { return map[string]actionFn{ "CreateEndpoint": func(ctx context.Context, b []byte) (any, error) { var input CreateEndpointInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } ep, err := h.Backend.CreateEndpoint(ctx, input) @@ -104,7 +106,7 @@ func (h *Handler) extendedEndpointActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -114,7 +116,7 @@ func (h *Handler) extendedEndpointActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } ep, err := h.Backend.DescribeEndpoint(ctx, input.Name) @@ -130,7 +132,7 @@ func (h *Handler) extendedEndpointActions() map[string]actionFn { NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } eps, next, err := h.Backend.ListEndpoints(ctx, input.NamePrefix, input.NextToken, input.MaxResults) @@ -150,7 +152,7 @@ func (h *Handler) extendedEndpointActions() map[string]actionFn { }, "UpdateEndpoint": func(ctx context.Context, b []byte) (any, error) { var input UpdateEndpointInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } ep, err := h.Backend.UpdateEndpoint(ctx, input) diff --git a/services/eventbridge/handler_endpoints_test.go b/services/eventbridge/handler_endpoints_test.go index 7a98ccc224..13d9735a85 100644 --- a/services/eventbridge/handler_endpoints_test.go +++ b/services/eventbridge/handler_endpoints_test.go @@ -1,7 +1,8 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -65,14 +67,14 @@ func TestHandler_Endpoint_TimestampsAreEpochSeconds(t *testing.T) { rec = auditMakeRequest(t, h, e, "DescribeEndpoint", map[string]any{"Name": "epoch-endpoint"}) assert.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) creationRaw, ok := raw["CreationTime"] require.True(t, ok, "CreationTime must be present") var f float64 - require.NoError(t, json.Unmarshal(creationRaw, &f), + require.NoError(t, json.Unmarshal(creationRaw, &f, jsonx.V1()), "CreationTime must be a JSON number (epoch seconds), got: %s", string(creationRaw)) assert.Greater(t, f, float64(0)) } diff --git a/services/eventbridge/handler_event_buses.go b/services/eventbridge/handler_event_buses.go index b99b7274ed..97c52d4d1b 100644 --- a/services/eventbridge/handler_event_buses.go +++ b/services/eventbridge/handler_event_buses.go @@ -2,8 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -116,7 +117,7 @@ func (h *Handler) eventBusActions() map[string]actionFn { func (h *Handler) handleCreateEventBus(ctx context.Context, b []byte) (any, error) { var input createEventBusInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } bus, err := h.Backend.CreateEventBus(ctx, CreateEventBusParams{ @@ -149,7 +150,7 @@ func (h *Handler) handleCreateEventBus(ctx context.Context, b []byte) (any, erro func (h *Handler) handleDeleteEventBus(ctx context.Context, b []byte) (any, error) { var input deleteEventBusInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } // Capture ARN before deletion so we can clean up tags. @@ -166,7 +167,7 @@ func (h *Handler) handleDeleteEventBus(ctx context.Context, b []byte) (any, erro func (h *Handler) handleListEventBuses(ctx context.Context, b []byte) (any, error) { var input listEventBusesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } buses, next, err := h.Backend.ListEventBuses(ctx, input.NamePrefix, input.NextToken, input.Limit) @@ -184,7 +185,7 @@ func (h *Handler) handleListEventBuses(ctx context.Context, b []byte) (any, erro func (h *Handler) handleDescribeEventBus(ctx context.Context, b []byte) (any, error) { var input describeEventBusInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } bus, err := h.Backend.DescribeEventBus(ctx, input.Name) @@ -202,7 +203,7 @@ func (h *Handler) eventBusManagementActions() map[string]actionFn { return map[string]actionFn{ "UpdateEventBus": func(ctx context.Context, b []byte) (any, error) { var input UpdateEventBusInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } bus, err := h.Backend.UpdateEventBus(ctx, input) @@ -228,7 +229,7 @@ func (h *Handler) eventBusManagementActions() map[string]actionFn { }, "PutPermission": func(ctx context.Context, b []byte) (any, error) { var input PutPermissionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -236,7 +237,7 @@ func (h *Handler) eventBusManagementActions() map[string]actionFn { }, "RemovePermission": func(ctx context.Context, b []byte) (any, error) { var input RemovePermissionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -249,7 +250,7 @@ func (h *Handler) policyActions() map[string]actionFn { return map[string]actionFn{ "GetEventBusPolicy": func(ctx context.Context, b []byte) (any, error) { var input GetEventBusPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } policy, err := h.Backend.GetEventBusPolicy(ctx, input.EventBusName) @@ -263,7 +264,7 @@ func (h *Handler) policyActions() map[string]actionFn { }, "PutEventBusPolicy": func(ctx context.Context, b []byte) (any, error) { var input PutEventBusPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/eventbridge/handler_event_buses_test.go b/services/eventbridge/handler_event_buses_test.go index 1c1de7b328..026915c7fc 100644 --- a/services/eventbridge/handler_event_buses_test.go +++ b/services/eventbridge/handler_event_buses_test.go @@ -2,7 +2,8 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -59,7 +61,7 @@ func TestHandler_DescribeEventBus_EchoesPolicy(t *testing.T) { var describeResp struct { Policy string `json:"Policy"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Contains(t, describeResp.Policy, "allow-policy-echo") rec = auditMakeRequest(t, h, e, "ListEventBuses", map[string]any{}) @@ -71,7 +73,7 @@ func TestHandler_DescribeEventBus_EchoesPolicy(t *testing.T) { Policy string `json:"Policy"` } `json:"EventBuses"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) found := false for _, bus := range listResp.EventBuses { if bus.Name == "default" { @@ -97,14 +99,14 @@ func TestHandler_EventBus_TimestampsAreEpochSeconds(t *testing.T) { rec := auditMakeRequest(t, h, e, "DescribeEventBus", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) creationRaw, ok := raw["CreationTime"] require.True(t, ok, "CreationTime must be present") var f float64 - require.NoError(t, json.Unmarshal(creationRaw, &f), + require.NoError(t, json.Unmarshal(creationRaw, &f, jsonx.V1()), "CreationTime must be a JSON number (epoch seconds), got: %s", string(creationRaw)) assert.Greater(t, f, float64(0)) } @@ -170,7 +172,7 @@ func TestCreateEventBus_TagsPersisted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) busArn := createResp["EventBusArn"] require.NotEmpty(t, busArn) @@ -183,7 +185,7 @@ func TestCreateEventBus_TagsPersisted(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) got := make(map[string]string, len(tagsResp.Tags)) for _, kv := range tagsResp.Tags { @@ -363,7 +365,7 @@ func TestHandler_CreateEventBus(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["EventBusArn"], tt.wantArnPart) }) } @@ -411,7 +413,7 @@ func TestHandler_CreateAndListEventBuses(t *testing.T) { CreationTime float64 `json:"CreationTime"` } `json:"EventBuses"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.GreaterOrEqual(t, len(resp.EventBuses), tt.wantMinCount) }) } diff --git a/services/eventbridge/handler_event_sources.go b/services/eventbridge/handler_event_sources.go index 548a2f3546..16a6a9e789 100644 --- a/services/eventbridge/handler_event_sources.go +++ b/services/eventbridge/handler_event_sources.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type ( @@ -16,7 +18,7 @@ func (h *Handler) eventSourceActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.ActivateEventSource(ctx, input.Name); err != nil { @@ -29,7 +31,7 @@ func (h *Handler) eventSourceActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeactivateEventSource(ctx, input.Name); err != nil { @@ -84,7 +86,7 @@ func (h *Handler) extendedEventSourceActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -101,7 +103,7 @@ func (h *Handler) extendedEventSourceActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } srcs, next, err := h.Backend.ListEventSources(ctx, input.NamePrefix, input.NextToken, int(input.Limit)) diff --git a/services/eventbridge/handler_event_sources_test.go b/services/eventbridge/handler_event_sources_test.go index 1677ed7ab3..1f2298843a 100644 --- a/services/eventbridge/handler_event_sources_test.go +++ b/services/eventbridge/handler_event_sources_test.go @@ -1,7 +1,8 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -55,16 +57,16 @@ func TestDescribeEventSourceAndListEventSources_TimestampsAreEpochFloat(t *testi rec := auditMakeRequest(t, h, e, tt.action, input) require.Equal(t, http.StatusOK, rec.Code) - var creationRaw json.RawMessage + var creationRaw jsontext.Value if tt.action == "DescribeEventSource" { - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) creationRaw = raw["CreationTime"] } else { var raw struct { - EventSources []map[string]json.RawMessage `json:"EventSources"` + EventSources []map[string]jsontext.Value `json:"EventSources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.EventSources, 1) creationRaw = raw.EventSources[0]["CreationTime"] } @@ -72,7 +74,7 @@ func TestDescribeEventSourceAndListEventSources_TimestampsAreEpochFloat(t *testi require.NotNil(t, creationRaw, "CreationTime must be present") var f float64 - err := json.Unmarshal(creationRaw, &f) + err := json.Unmarshal(creationRaw, &f, jsonx.V1()) require.NoError(t, err, "CreationTime must be a JSON number (epoch seconds), got: %s", string(creationRaw)) assert.Greater(t, f, float64(0)) }) diff --git a/services/eventbridge/handler_events.go b/services/eventbridge/handler_events.go index c65385f31f..c65b5a08f4 100644 --- a/services/eventbridge/handler_events.go +++ b/services/eventbridge/handler_events.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putEventsInput struct { @@ -33,7 +35,7 @@ func (h *Handler) eventsActions() map[string]actionFn { return map[string]actionFn{ "PutEvents": func(ctx context.Context, b []byte) (any, error) { var input putEventsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } entries, err := h.Backend.PutEvents(ctx, input.Entries) diff --git a/services/eventbridge/handler_events_test.go b/services/eventbridge/handler_events_test.go index e1b4eee9ed..cb06d99614 100644 --- a/services/eventbridge/handler_events_test.go +++ b/services/eventbridge/handler_events_test.go @@ -1,7 +1,7 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -77,7 +78,7 @@ func TestHandler_PutEvents(t *testing.T) { Entries []eventbridge.EventResultEntry `json:"Entries"` FailedEntryCount int `json:"FailedEntryCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantFailedCount, resp.FailedEntryCount) assert.Len(t, resp.Entries, tt.wantEntryCount) for _, entry := range resp.Entries { diff --git a/services/eventbridge/handler_partner_sources.go b/services/eventbridge/handler_partner_sources.go index 011e423e3c..3000bcb2b3 100644 --- a/services/eventbridge/handler_partner_sources.go +++ b/services/eventbridge/handler_partner_sources.go @@ -2,8 +2,10 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createPartnerEventSourceOutput struct { @@ -28,7 +30,7 @@ func (h *Handler) partnerSourceActions() map[string]actionFn { Name string `json:"Name"` Account string `json:"Account"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } src, err := h.Backend.CreatePartnerEventSource(ctx, input.Name, input.Account) @@ -48,7 +50,7 @@ func (h *Handler) extendedPartnerSourceActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -58,7 +60,7 @@ func (h *Handler) extendedPartnerSourceActions() map[string]actionFn { var input struct { Name string `json:"Name"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -70,7 +72,7 @@ func (h *Handler) extendedPartnerSourceActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } srcs, next, err := h.Backend.ListPartnerEventSources( @@ -93,7 +95,7 @@ func (h *Handler) extendedPartnerSourceActions() map[string]actionFn { EventSourceName string `json:"EventSourceName"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if input.EventSourceName == "" { @@ -121,7 +123,7 @@ func (h *Handler) extendedPartnerSourceActions() map[string]actionFn { }, "PutPartnerEvents": func(ctx context.Context, b []byte) (any, error) { var input putEventsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } entries, err := h.Backend.PutPartnerEvents(ctx, input.Entries) diff --git a/services/eventbridge/handler_registries.go b/services/eventbridge/handler_registries.go index 34afc0567c..b2310f9d63 100644 --- a/services/eventbridge/handler_registries.go +++ b/services/eventbridge/handler_registries.go @@ -2,14 +2,16 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) registryActions() map[string]actionFn { return map[string]actionFn{ opCreateRegistry: func(ctx context.Context, b []byte) (any, error) { var input CreateRegistryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -19,7 +21,7 @@ func (h *Handler) registryActions() map[string]actionFn { var input struct { RegistryName string `json:"RegistryName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -29,7 +31,7 @@ func (h *Handler) registryActions() map[string]actionFn { var input struct { RegistryName string `json:"RegistryName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -41,7 +43,7 @@ func (h *Handler) registryActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } regs, next, err := h.Backend.ListRegistries(ctx, input.NamePrefix, input.NextToken, int(input.Limit)) @@ -56,7 +58,7 @@ func (h *Handler) registryActions() map[string]actionFn { }, opUpdateRegistry: func(ctx context.Context, b []byte) (any, error) { var input UpdateRegistryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/eventbridge/handler_replays.go b/services/eventbridge/handler_replays.go index daf9164522..f73a113d89 100644 --- a/services/eventbridge/handler_replays.go +++ b/services/eventbridge/handler_replays.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type cancelReplayOutput struct { @@ -85,7 +87,7 @@ func (h *Handler) replayActions() map[string]actionFn { var input struct { ReplayName string `json:"ReplayName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } replay, err := h.Backend.CancelReplay(ctx, input.ReplayName) @@ -109,7 +111,7 @@ func (h *Handler) extendedReplayActions() map[string]actionFn { var input struct { ReplayName string `json:"ReplayName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } replay, err := h.Backend.DescribeReplay(ctx, input.ReplayName) @@ -127,7 +129,7 @@ func (h *Handler) extendedReplayActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } replays, next, err := h.Backend.ListReplays( @@ -149,7 +151,7 @@ func (h *Handler) extendedReplayActions() map[string]actionFn { }, "StartReplay": func(ctx context.Context, b []byte) (any, error) { var input StartReplayInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } replay, err := h.Backend.StartReplay(ctx, input) diff --git a/services/eventbridge/handler_replays_test.go b/services/eventbridge/handler_replays_test.go index 066f135ab5..0529a522a6 100644 --- a/services/eventbridge/handler_replays_test.go +++ b/services/eventbridge/handler_replays_test.go @@ -1,7 +1,8 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -82,14 +84,14 @@ func TestStartReplay_ReplayStartTimeIsEpochFloat(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) replayStartRaw, ok := raw["ReplayStartTime"] require.True(t, ok, "ReplayStartTime must be present in StartReplay response") var f float64 - err := json.Unmarshal(replayStartRaw, &f) + err := json.Unmarshal(replayStartRaw, &f, jsonx.V1()) require.NoError( t, err, @@ -131,14 +133,14 @@ func TestHandler_DescribeReplay_TimestampsAreEpochSeconds(t *testing.T) { rec = auditMakeRequest(t, h, e, "DescribeReplay", map[string]any{"ReplayName": "epoch-replay"}) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) startRaw, ok := raw["EventStartTime"] require.True(t, ok, "EventStartTime must be present") var f float64 - require.NoError(t, json.Unmarshal(startRaw, &f), + require.NoError(t, json.Unmarshal(startRaw, &f, jsonx.V1()), "EventStartTime must be a JSON number (epoch seconds), got: %s", string(startRaw)) assert.Greater(t, f, float64(0)) } diff --git a/services/eventbridge/handler_rules.go b/services/eventbridge/handler_rules.go index 9ac09598e0..f5322d17ae 100644 --- a/services/eventbridge/handler_rules.go +++ b/services/eventbridge/handler_rules.go @@ -2,8 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -85,7 +86,7 @@ func (h *Handler) ruleActions() map[string]actionFn { return map[string]actionFn{ "PutRule": func(ctx context.Context, b []byte) (any, error) { var input PutRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } rule, err := h.Backend.PutRule(ctx, input) @@ -100,7 +101,7 @@ func (h *Handler) ruleActions() map[string]actionFn { }, "DeleteRule": func(ctx context.Context, b []byte) (any, error) { var input deleteRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } // Capture ARN before deletion so we can clean up tags. @@ -116,7 +117,7 @@ func (h *Handler) ruleActions() map[string]actionFn { }, "ListRules": func(ctx context.Context, b []byte) (any, error) { var input listRulesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } rules, next, err := h.Backend.ListRules( @@ -139,7 +140,7 @@ func (h *Handler) ruleActions() map[string]actionFn { }, "DescribeRule": func(ctx context.Context, b []byte) (any, error) { var input describeRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -158,7 +159,7 @@ func (h *Handler) ruleQueryActions() map[string]actionFn { TargetArn string `json:"TargetArn"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } names, next, err := h.Backend.ListRuleNamesByTarget( @@ -182,7 +183,7 @@ func (h *Handler) ruleQueryActions() map[string]actionFn { Event string `json:"Event"` EventPattern string `json:"EventPattern"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } result, err := h.Backend.TestEventPattern(ctx, input.EventPattern, input.Event) @@ -201,7 +202,7 @@ func (h *Handler) ruleStateActions() map[string]actionFn { return map[string]actionFn{ "EnableRule": func(ctx context.Context, b []byte) (any, error) { var input enableRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.EnableRule(ctx, input.Name, input.EventBusName); err != nil { @@ -212,7 +213,7 @@ func (h *Handler) ruleStateActions() map[string]actionFn { }, "DisableRule": func(ctx context.Context, b []byte) (any, error) { var input disableRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DisableRule(ctx, input.Name, input.EventBusName); err != nil { diff --git a/services/eventbridge/handler_rules_test.go b/services/eventbridge/handler_rules_test.go index 515156884c..0b19c1e77f 100644 --- a/services/eventbridge/handler_rules_test.go +++ b/services/eventbridge/handler_rules_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -52,7 +53,7 @@ func TestHandler_TestEventPattern(t *testing.T) { var resp struct { Result bool `json:"Result"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.True(t, resp.Result) } @@ -124,7 +125,7 @@ func TestPutRule_TagsPersisted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) ruleArn := putResp["RuleArn"] require.NotEmpty(t, ruleArn) @@ -137,7 +138,7 @@ func TestPutRule_TagsPersisted(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) got := make(map[string]string, len(tagsResp.Tags)) for _, kv := range tagsResp.Tags { @@ -187,7 +188,7 @@ func TestDeleteRule_CleansUpTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut, jsonx.V1())) ruleARN := putOut["RuleArn"] require.NotEmpty(t, ruleARN) @@ -248,7 +249,7 @@ func TestHandler_PutRuleAndListRules(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var putResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assert.Contains(t, putResp["RuleArn"], tt.wantArnPart) rec = makeRequestWithHandler(t, handler, e, "ListRules", `{}`) @@ -257,7 +258,7 @@ func TestHandler_PutRuleAndListRules(t *testing.T) { var listResp struct { Rules []eventbridge.Rule `json:"Rules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Rules, tt.wantRuleLen) assert.Equal(t, tt.wantRuleName, listResp.Rules[0].Name) }) @@ -300,7 +301,7 @@ func TestHandler_DescribeRule(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var rule eventbridge.Rule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule, jsonx.V1())) assert.Equal(t, tt.wantRuleName, rule.Name) assert.Equal(t, tt.wantDescription, rule.Description) assert.Equal(t, tt.wantState, rule.State) @@ -346,13 +347,13 @@ func TestHandler_EnableDisableRule(t *testing.T) { rec = makeRequestWithHandler(t, handler, e, "DescribeRule", `{"Name":"`+tt.ruleName+`"}`) var rule eventbridge.Rule - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule, jsonx.V1())) assert.Equal(t, tt.wantDisabledState, rule.State) makeRequestWithHandler(t, handler, e, "EnableRule", `{"Name":"`+tt.ruleName+`"}`) rec = makeRequestWithHandler(t, handler, e, "DescribeRule", `{"Name":"`+tt.ruleName+`"}`) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &rule, jsonx.V1())) assert.Equal(t, tt.wantEnabledState, rule.State) }) } diff --git a/services/eventbridge/handler_schemas.go b/services/eventbridge/handler_schemas.go index ede2802171..5de4d85f62 100644 --- a/services/eventbridge/handler_schemas.go +++ b/services/eventbridge/handler_schemas.go @@ -2,14 +2,16 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) schemaActions() map[string]actionFn { return map[string]actionFn{ opCreateSchema: func(ctx context.Context, b []byte) (any, error) { var input CreateSchemaInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -20,7 +22,7 @@ func (h *Handler) schemaActions() map[string]actionFn { RegistryName string `json:"RegistryName"` SchemaName string `json:"SchemaName"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -32,7 +34,7 @@ func (h *Handler) schemaActions() map[string]actionFn { SchemaName string `json:"SchemaName"` SchemaVersion string `json:"SchemaVersion"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -50,7 +52,7 @@ func (h *Handler) schemaActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } schemas, next, err := h.Backend.ListSchemas( @@ -76,7 +78,7 @@ func (h *Handler) schemaActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } schemas, next, err := h.Backend.SearchSchemas( @@ -97,7 +99,7 @@ func (h *Handler) schemaActions() map[string]actionFn { }, opUpdateSchema: func(ctx context.Context, b []byte) (any, error) { var input UpdateSchemaInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -115,7 +117,7 @@ func (h *Handler) schemaVersionActions() map[string]actionFn { NextToken string `json:"NextToken"` Limit int32 `json:"Limit"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } versions, next, err := h.Backend.ListSchemaVersions( @@ -140,7 +142,7 @@ func (h *Handler) schemaVersionActions() map[string]actionFn { SchemaName string `json:"SchemaName"` SchemaVersion string `json:"SchemaVersion"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -157,7 +159,7 @@ func (h *Handler) schemaVersionActions() map[string]actionFn { SchemaName string `json:"SchemaName"` SchemaVersion string `json:"SchemaVersion"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -170,7 +172,7 @@ func (h *Handler) schemaVersionActions() map[string]actionFn { }, opGetDiscoveredSchema: func(ctx context.Context, b []byte) (any, error) { var input GetDiscoveredSchemaInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } content, err := h.Backend.GetDiscoveredSchema(ctx, input) @@ -189,7 +191,7 @@ func (h *Handler) codeBindingActions() map[string]actionFn { return map[string]actionFn{ opPutCodeBinding: func(ctx context.Context, b []byte) (any, error) { var input PutCodeBindingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -197,7 +199,7 @@ func (h *Handler) codeBindingActions() map[string]actionFn { }, opDescribeCodeBinding: func(ctx context.Context, b []byte) (any, error) { var input DescribeCodeBindingInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -205,7 +207,7 @@ func (h *Handler) codeBindingActions() map[string]actionFn { }, "ListCodeBindings": func(ctx context.Context, b []byte) (any, error) { var input ListCodeBindingsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } bindings, next, err := h.Backend.ListCodeBindings(ctx, input) @@ -225,7 +227,7 @@ func (h *Handler) codeBindingActions() map[string]actionFn { Language string `json:"Language"` SchemaVersion string `json:"SchemaVersion"` } - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } src, err := h.Backend.GetCodeBindingSource( diff --git a/services/eventbridge/handler_schemas_rest.go b/services/eventbridge/handler_schemas_rest.go index 88e722d682..1c630c26f5 100644 --- a/services/eventbridge/handler_schemas_rest.go +++ b/services/eventbridge/handler_schemas_rest.go @@ -2,7 +2,7 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // schemasRESTLimit parses the "limit" query param (schemas@v1.37.4 @@ -828,7 +829,7 @@ func (h *Handler) schemasRESTOps() map[string]schemasRESTOpFunc { } func (h *Handler) writeSchemasREST(c *echo.Context, body any) error { - payload, err := json.Marshal(body) + payload, err := json.Marshal(body, jsonx.V1()) if err != nil { return h.writeSchemasRESTError(c, err) } @@ -865,7 +866,7 @@ func (h *Handler) writeSchemasRESTError(c *echo.Context, err error) error { c.Response().Header().Set("Content-Type", schemasRESTContentType) c.Response().Header().Set("X-Amzn-Errortype", code) - payload, marshalErr := json.Marshal(map[string]string{"message": err.Error()}) + payload, marshalErr := json.Marshal(map[string]string{"message": err.Error()}, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } @@ -883,7 +884,7 @@ func schemasRESTDecodeBody(r *http.Request, out any) error { return nil } - if uerr := json.Unmarshal(body, out); uerr != nil { + if uerr := json.Unmarshal(body, out, jsonx.V1()); uerr != nil { return fmt.Errorf("%w: invalid JSON", ErrInvalidParameter) } diff --git a/services/eventbridge/handler_schemas_test.go b/services/eventbridge/handler_schemas_test.go index ae12ef9e96..3c04bcc881 100644 --- a/services/eventbridge/handler_schemas_test.go +++ b/services/eventbridge/handler_schemas_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -233,7 +234,7 @@ func TestHandler_DeleteSchemaVersion_LastVersionReturns400(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) // Schema still exists and is intact. diff --git a/services/eventbridge/handler_tags.go b/services/eventbridge/handler_tags.go index 49086079d7..021d988159 100644 --- a/services/eventbridge/handler_tags.go +++ b/services/eventbridge/handler_tags.go @@ -2,9 +2,10 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -34,7 +35,7 @@ func (h *Handler) tagActions() map[string]actionFn { return map[string]actionFn{ "ListTagsForResource": func(ctx context.Context, b []byte) (any, error) { var input listTagsForResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.requireTaggable(ctx, input.ResourceARN); err != nil { @@ -50,7 +51,7 @@ func (h *Handler) tagActions() map[string]actionFn { }, "TagResource": func(ctx context.Context, b []byte) (any, error) { var input tagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.requireTaggable(ctx, input.ResourceARN); err != nil { @@ -66,7 +67,7 @@ func (h *Handler) tagActions() map[string]actionFn { }, "UntagResource": func(ctx context.Context, b []byte) (any, error) { var input untagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.requireTaggable(ctx, input.ResourceARN); err != nil { diff --git a/services/eventbridge/handler_tags_test.go b/services/eventbridge/handler_tags_test.go index a4b0f674ec..8f1a1d14bb 100644 --- a/services/eventbridge/handler_tags_test.go +++ b/services/eventbridge/handler_tags_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -107,7 +108,7 @@ func TestHandler_TagOperations(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Tags, tt.wantTagCount) if len(tt.wantTags) > 0 { diff --git a/services/eventbridge/handler_targets.go b/services/eventbridge/handler_targets.go index c552bca5df..53c51b27eb 100644 --- a/services/eventbridge/handler_targets.go +++ b/services/eventbridge/handler_targets.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putTargetsInput struct { @@ -44,7 +46,7 @@ func (h *Handler) targetActions() map[string]actionFn { return map[string]actionFn{ "PutTargets": func(ctx context.Context, b []byte) (any, error) { var input putTargetsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } failed, err := h.Backend.PutTargets(ctx, input.Rule, input.EventBusName, input.Targets) @@ -62,7 +64,7 @@ func (h *Handler) targetActions() map[string]actionFn { }, "RemoveTargets": func(ctx context.Context, b []byte) (any, error) { var input removeTargetsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } failed, err := h.Backend.RemoveTargetsForce(ctx, input.Rule, input.EventBusName, input.IDs, input.Force) @@ -80,7 +82,7 @@ func (h *Handler) targetActions() map[string]actionFn { }, "ListTargetsByRule": func(ctx context.Context, b []byte) (any, error) { var input listTargetsByRuleInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } targets, next, err := h.Backend.ListTargetsByRule( diff --git a/services/eventbridge/handler_targets_test.go b/services/eventbridge/handler_targets_test.go index a3b746063a..350781e2e1 100644 --- a/services/eventbridge/handler_targets_test.go +++ b/services/eventbridge/handler_targets_test.go @@ -1,7 +1,7 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -87,7 +88,7 @@ func TestHandler_PutTargetsListAndRemove(t *testing.T) { FailedEntries []eventbridge.FailedEntry `json:"FailedEntries"` FailedEntryCount int `json:"FailedEntryCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assert.Equal(t, 0, putResp.FailedEntryCount) rec = makeRequestWithHandler(t, handler, e, "ListTargetsByRule", `{"Rule":"`+tt.ruleName+`"}`) @@ -96,7 +97,7 @@ func TestHandler_PutTargetsListAndRemove(t *testing.T) { var listResp struct { Targets []eventbridge.Target `json:"Targets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Targets, tt.wantInitialCount) rec = makeRequestWithHandler(t, handler, e, "RemoveTargets", @@ -104,7 +105,7 @@ func TestHandler_PutTargetsListAndRemove(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) rec = makeRequestWithHandler(t, handler, e, "ListTargetsByRule", `{"Rule":"`+tt.ruleName+`"}`) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Targets, tt.wantRemainingCount) assert.Equal(t, tt.wantRemainingID, listResp.Targets[0].ID) }) diff --git a/services/eventbridge/handler_test.go b/services/eventbridge/handler_test.go index 7fbcbc31a2..c0d6b65a61 100644 --- a/services/eventbridge/handler_test.go +++ b/services/eventbridge/handler_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -104,7 +105,7 @@ func TestHandler_GetSupportedOperations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) for _, op := range tt.wantOps { assert.Contains(t, ops, op) } @@ -183,7 +184,7 @@ func TestHandler_DispatchErrors(t *testing.T) { } if tt.wantErrorType != "" { var errResp service.JSONErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrorType, errResp.Type) } }) @@ -312,7 +313,7 @@ func auditMakeRequest( body map[string]any, ) *httptest.ResponseRecorder { t.Helper() - b, _ := json.Marshal(body) + b, _ := json.Marshal(body, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(b))) req.Header.Set("X-Amz-Target", "AmazonEventBridge."+action) rec := httptest.NewRecorder() @@ -759,6 +760,6 @@ func TestCancelReplay_TerminalStateMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp service.JSONErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "IllegalStatusException", errResp.Type) } diff --git a/services/eventbridge/metrics.go b/services/eventbridge/metrics.go index 96713e4fbb..bba9d09e7f 100644 --- a/services/eventbridge/metrics.go +++ b/services/eventbridge/metrics.go @@ -2,10 +2,11 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AWS/Events rule metrics per docs.aws.amazon.com/eventbridge/latest/userguide/eb-monitoring.html. @@ -48,7 +49,7 @@ func (b *InMemoryBackend) observePutEvents( put("PutEventsEntriesCount", ebUnitCount, float64(len(entries))) put("PutEventsLatency", ebUnitMillis, float64(elapsed.Milliseconds())) - if body, mErr := json.Marshal(entries); mErr == nil { + if body, mErr := json.Marshal(entries, jsonx.V1()); mErr == nil { put("PutEventsRequestSize", ebUnitBytes, float64(len(body))) } } diff --git a/services/eventbridge/pattern.go b/services/eventbridge/pattern.go index 79861834ff..338230779b 100644 --- a/services/eventbridge/pattern.go +++ b/services/eventbridge/pattern.go @@ -1,14 +1,14 @@ package eventbridge import ( - "encoding/json" - "errors" + "encoding/json/v2" "fmt" "reflect" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/eventpattern" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -32,7 +32,7 @@ func compilePattern(patternJSON string) (*compiledPattern, error) { } var pattern map[string]any - if err := json.Unmarshal([]byte(patternJSON), &pattern); err != nil { + if err := json.Unmarshal([]byte(patternJSON), &pattern, jsonx.V1()); err != nil { return nil, err } @@ -268,7 +268,7 @@ func matchCompiledPattern(compiled *compiledPattern, event string) bool { } var eventData map[string]any - if err := json.Unmarshal([]byte(event), &eventData); err != nil { + if err := json.Unmarshal([]byte(event), &eventData, jsonx.V1()); err != nil { return false } @@ -682,12 +682,9 @@ func matchWildcard(pattern, s string) bool { func invalidPatternErr(err error) error { reason := "Invalid JSON" - var syn *json.SyntaxError - var typ *json.UnmarshalTypeError - switch { - case errors.As(err, &syn): - case errors.As(err, &typ): + case jsonx.IsSyntaxError(err): + case jsonx.IsTypeError(err): reason = "Filter is not an object" default: reason = strings.TrimPrefix(err.Error(), ErrInvalidParameter.Error()+": ") diff --git a/services/eventbridge/persistence.go b/services/eventbridge/persistence.go index 5ae382cbee..3a0b97d122 100644 --- a/services/eventbridge/persistence.go +++ b/services/eventbridge/persistence.go @@ -2,11 +2,13 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" "sync" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -59,7 +61,7 @@ const eventbridgeSnapshotVersion = 1 // no reason to leave bus policies unpersisted the way the genuinely // unkeyable maps above are. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` BusPolicies map[string]map[string]*EventBusPolicy `json:"busPolicies,omitempty"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -93,7 +95,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx). WarnContext(ctx, "persistence: snapshot marshal failed", "service", "eventbridge", "error", err) @@ -116,7 +118,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { // first "/" recovers the prefix, and tableAccessorsByPrefix's rules/targets // entries split the remainder again themselves (region never contains "/", // but bus/rule names may). -func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]json.RawMessage) { +func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]jsontext.Value) { for key := range tables { name, rest, found := strings.Cut(key, "/") if !found { @@ -168,7 +170,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { } if snap.Tables == nil { - snap.Tables = make(map[string]json.RawMessage) + snap.Tables = make(map[string]jsontext.Value) } b.preRegisterSnapshotTables(snap.Tables) diff --git a/services/eventbridge/persistence_test.go b/services/eventbridge/persistence_test.go index 43380bd71c..8bea5601fd 100644 --- a/services/eventbridge/persistence_test.go +++ b/services/eventbridge/persistence_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -309,7 +310,7 @@ func TestHandler_Snapshot_IncludesTags(t *testing.T) { tagBody, err := json.Marshal(map[string]any{ "ResourceARN": tt.resourceARN, "Tags": []map[string]string{{"Key": k, "Value": v}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := makeRequestWithHandler(t, h, e, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -322,7 +323,7 @@ func TestHandler_Snapshot_IncludesTags(t *testing.T) { fresh := eventbridge.NewHandler(eventbridge.NewInMemoryBackendWithConfig("123456789012", "us-east-1")) require.NoError(t, fresh.Restore(t.Context(), snap)) - listBody, err := json.Marshal(map[string]string{"ResourceARN": tt.resourceARN}) + listBody, err := json.Marshal(map[string]string{"ResourceARN": tt.resourceARN}, jsonx.V1()) require.NoError(t, err) rec := makeRequestWithFreshHandler(t, fresh, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -333,7 +334,7 @@ func TestHandler_Snapshot_IncludesTags(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Tags, tt.wantTagCount) if tt.wantTagCount > 0 { @@ -387,7 +388,7 @@ func TestHandler_Reset_ClearsTags(t *testing.T) { tagBody, err := json.Marshal(map[string]any{ "ResourceARN": tt.resourceARN, "Tags": []map[string]string{{"Key": k, "Value": v}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := makeRequestWithHandler(t, h, e, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -400,7 +401,7 @@ func TestHandler_Reset_ClearsTags(t *testing.T) { `{"Name":"bleed-rule","EventPattern":"{\"source\":[\"test\"]}"}`) require.Equal(t, http.StatusOK, put.Code) - listBody, err := json.Marshal(map[string]string{"ResourceARN": tt.resourceARN}) + listBody, err := json.Marshal(map[string]string{"ResourceARN": tt.resourceARN}, jsonx.V1()) require.NoError(t, err) rec := makeRequestWithHandler(t, h, e, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -411,7 +412,7 @@ func TestHandler_Reset_ClearsTags(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.Tags, "tags should be cleared after Reset()") }) } @@ -558,7 +559,7 @@ func TestHandlerReset(t *testing.T) { var resp struct { Tags []map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.Tags) } diff --git a/services/eventbridge/put_events.go b/services/eventbridge/put_events.go index 22bba2abe2..dc78b8173c 100644 --- a/services/eventbridge/put_events.go +++ b/services/eventbridge/put_events.go @@ -2,7 +2,7 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/apptrace" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxPutEventsEntries is AWS's per-request cap on PutEvents/PutPartnerEvents @@ -298,7 +299,7 @@ func putEventsEntryBytes(e EventEntry) int { // (MalformedDetail) or whose bus does not exist (ResourceNotFoundException). func (b *InMemoryBackend) validatePutEventsTarget(region string, e EventEntry) (string, string, bool) { var detail map[string]any - if err := json.Unmarshal([]byte(e.Detail), &detail); err != nil { + if err := json.Unmarshal([]byte(e.Detail), &detail, jsonx.V1()); err != nil { return "MalformedDetail", "Detail is malformed.", false } diff --git a/services/eventbridge/put_events_test.go b/services/eventbridge/put_events_test.go index 5542c491e7..298cf66d82 100644 --- a/services/eventbridge/put_events_test.go +++ b/services/eventbridge/put_events_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -255,14 +256,14 @@ func TestPutEvents_FailedEntryCount(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - body, err := json.Marshal(map[string]any{"Entries": tt.entries}) + body, err := json.Marshal(map[string]any{"Entries": tt.entries}, jsonx.V1()) require.NoError(t, err) rec := makeRequest(t, tt.action, string(body)) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var got putEventsResult - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantFailedCount, got.FailedEntryCount) diff --git a/services/eventbridge/region_isolation_test.go b/services/eventbridge/region_isolation_test.go index 90442cb985..522428e9c0 100644 --- a/services/eventbridge/region_isolation_test.go +++ b/services/eventbridge/region_isolation_test.go @@ -2,12 +2,14 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -230,17 +232,17 @@ func TestDefaultBus_RulesAndRestoreInSiblingRegion(t *testing.T) { _, err = b.CreateEventBus(ctx, eventbridge.CreateEventBusParams{Name: "custom"}) require.NoError(t, err) - var snap map[string]json.RawMessage + var snap map[string]jsontext.Value - require.NoError(t, json.Unmarshal(b.Snapshot(ctx), &snap)) + require.NoError(t, json.Unmarshal(b.Snapshot(ctx), &snap, jsonx.V1())) - var tables map[string]json.RawMessage + var tables map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap["tables"], &tables)) + require.NoError(t, json.Unmarshal(snap["tables"], &tables, jsonx.V1())) var buses []map[string]any - require.NoError(t, json.Unmarshal(tables["buses/"+euRegion], &buses)) + require.NoError(t, json.Unmarshal(tables["buses/"+euRegion], &buses, jsonx.V1())) legacy := make([]map[string]any, 0, len(buses)) @@ -252,13 +254,13 @@ func TestDefaultBus_RulesAndRestoreInSiblingRegion(t *testing.T) { require.Len(t, legacy, 1, "legacy snapshot keeps only the custom bus") - tables["buses/"+euRegion], err = json.Marshal(legacy) + tables["buses/"+euRegion], err = json.Marshal(legacy, jsonx.V1()) require.NoError(t, err) - snap["tables"], err = json.Marshal(tables) + snap["tables"], err = json.Marshal(tables, jsonx.V1()) require.NoError(t, err) - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) restored := eventbridge.NewInMemoryBackend() diff --git a/services/eventbridge/rule_arn_format_test.go b/services/eventbridge/rule_arn_format_test.go index c183a9394f..70df82c680 100644 --- a/services/eventbridge/rule_arn_format_test.go +++ b/services/eventbridge/rule_arn_format_test.go @@ -1,7 +1,7 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -166,7 +167,7 @@ func legacySnapshot(t *testing.T, data []byte) []byte { Tags map[string]map[string]string `json:"tags,omitempty"` Backend []byte `json:"backend"` } - require.NoError(t, json.Unmarshal(data, &snap)) + require.NoError(t, json.Unmarshal(data, &snap, jsonx.V1())) modern := ruleARNPrefix + "r1" legacy := ruleARNPrefix + "default/r1" @@ -179,7 +180,7 @@ func legacySnapshot(t *testing.T, data []byte) []byte { } snap.Tags = tags - out, err := json.Marshal(snap) + out, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) return out diff --git a/services/eventbridge/rules.go b/services/eventbridge/rules.go index fb40d36d9b..88cd327026 100644 --- a/services/eventbridge/rules.go +++ b/services/eventbridge/rules.go @@ -2,11 +2,13 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // checkManagedRule returns ManagedRuleException if rule is owned by an AWS @@ -379,7 +381,7 @@ func (b *InMemoryBackend) TestEventPattern( func isValidJSON(s string) bool { var v any - return json.Unmarshal([]byte(s), &v) == nil + return json.Unmarshal([]byte(s), &v, jsonx.V1()) == nil } var ruleNamePattern = regexp.MustCompile(`^[.\-_A-Za-z0-9]+$`) diff --git a/services/eventbridge/ssm_integration.go b/services/eventbridge/ssm_integration.go index 5619c70666..ed65a5017b 100644 --- a/services/eventbridge/ssm_integration.go +++ b/services/eventbridge/ssm_integration.go @@ -2,7 +2,9 @@ package eventbridge import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ssmParameterPolicyEventSource/DetailType are the real AWS wire values SSM @@ -35,7 +37,7 @@ func (b *InMemoryBackend) NotifyParameterPolicyAction( detail, err := json.Marshal(ssmParameterPolicyEventDetail{ ParameterName: parameterName, PolicyType: policyType, - }) + }, jsonx.V1()) if err != nil { return err } diff --git a/services/eventbridge/targets_test.go b/services/eventbridge/targets_test.go index 550fa85f29..f67fe22b0e 100644 --- a/services/eventbridge/targets_test.go +++ b/services/eventbridge/targets_test.go @@ -2,7 +2,7 @@ package eventbridge_test import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -698,7 +699,7 @@ func TestInputTransformer_ProducesValidJSON(t *testing.T) { assert.JSONEq(t, tt.wantJSONEq, got) } if tt.wantValid { - assert.True(t, json.Valid([]byte(got)), "expected valid JSON, got %q", got) + assert.True(t, jsontext.Value([]byte(got)).IsValid(jsonx.V1()), "expected valid JSON, got %q", got) } }) } @@ -744,7 +745,7 @@ func TestInputTransformer_ValueContextEndToEnd(t *testing.T) { }, 2*time.Second, 10*time.Millisecond) msg := sqsMock.MessagesFor(queueARN)[0] - assert.True(t, json.Valid([]byte(msg)), "delivered payload must be valid JSON: %q", msg) + assert.True(t, jsontext.Value([]byte(msg)).IsValid(jsonx.V1()), "delivered payload must be valid JSON: %q", msg) assert.JSONEq(t, `{"wrapped":"it.svc"}`, msg) } diff --git a/services/eventbridge/wire_field_fixes_test.go b/services/eventbridge/wire_field_fixes_test.go index e82774300c..d710a030bf 100644 --- a/services/eventbridge/wire_field_fixes_test.go +++ b/services/eventbridge/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -387,7 +388,7 @@ func TestPutPermission_Condition_RoundTripsThroughPolicy(t *testing.T) { Sid string `json:"Sid"` } `json:"Statement"` } - require.NoError(t, json.Unmarshal([]byte(aws.ToString(described.Policy)), &doc)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(described.Policy)), &doc, jsonx.V1())) require.Len(t, doc.Statement, 1) assert.Equal(t, "OrgGrant", doc.Statement[0].Sid) require.NotEmpty( diff --git a/services/eventbridge/wire_time.go b/services/eventbridge/wire_time.go index c6f5bb243e..d92c19143d 100644 --- a/services/eventbridge/wire_time.go +++ b/services/eventbridge/wire_time.go @@ -1,8 +1,10 @@ package eventbridge import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseEpochSecondsPtr converts an AWS json-1.1 wire-format epoch-seconds @@ -45,7 +47,7 @@ type wireEventEntry struct { // quoted RFC3339 string. func (e *EventEntry) UnmarshalJSON(data []byte) error { var w wireEventEntry - if err := json.Unmarshal(data, &w); err != nil { + if err := json.Unmarshal(data, &w, jsonx.V1()); err != nil { return err } @@ -78,7 +80,7 @@ type wireStartReplayInput struct { // struct-tag-driven json.Unmarshal. func (in *StartReplayInput) UnmarshalJSON(data []byte) error { var w wireStartReplayInput - if err := json.Unmarshal(data, &w); err != nil { + if err := json.Unmarshal(data, &w, jsonx.V1()); err != nil { return err } diff --git a/services/eventbridge/wire_time_test.go b/services/eventbridge/wire_time_test.go index 905e95798f..09e2570d54 100644 --- a/services/eventbridge/wire_time_test.go +++ b/services/eventbridge/wire_time_test.go @@ -1,13 +1,14 @@ package eventbridge_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/eventbridge" ) @@ -55,7 +56,7 @@ func TestEventEntry_UnmarshalJSON_TimeIsEpochSeconds(t *testing.T) { t.Parallel() var entry eventbridge.EventEntry - err := json.Unmarshal([]byte(tt.body), &entry) + err := json.Unmarshal([]byte(tt.body), &entry, jsonx.V1()) if tt.wantErr { require.Error(t, err) @@ -97,7 +98,7 @@ func TestStartReplayInput_UnmarshalJSON_TimesAreEpochSeconds(t *testing.T) { }` var input eventbridge.StartReplayInput - require.NoError(t, json.Unmarshal([]byte(body), &input)) + require.NoError(t, json.Unmarshal([]byte(body), &input, jsonx.V1())) assert.True(t, time.Unix(1_700_000_000, 0).UTC().Equal(input.EventStartTime)) assert.True(t, time.Unix(1_700_003_600, 0).UTC().Equal(input.EventEndTime)) diff --git a/services/firehose/delivery_http.go b/services/firehose/delivery_http.go index 35e5b564f6..b8fb795af0 100644 --- a/services/firehose/delivery_http.go +++ b/services/firehose/delivery_http.go @@ -4,7 +4,8 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "mime" @@ -17,6 +18,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -60,7 +62,10 @@ func buildHTTPEndpointBody(requestID string, records [][]byte) ([]byte, error) { httpRecords = append(httpRecords, httpRecord{Data: base64.StdEncoding.EncodeToString(rec)}) } - return json.Marshal(httpPayload{RequestID: requestID, Timestamp: time.Now().UnixMilli(), Records: httpRecords}) + return json.Marshal( + httpPayload{RequestID: requestID, Timestamp: time.Now().UnixMilli(), Records: httpRecords}, + jsonx.V1(), + ) } // buildHTTPEndpointRequest constructs one POST with the documented Firehose request headers. @@ -97,7 +102,7 @@ func buildHTTPEndpointRequest( attrs[a.AttributeName] = a.AttributeValue } - if raw, mErr := json.Marshal(map[string]any{"commonAttributes": attrs}); mErr == nil { + if raw, mErr := json.Marshal(map[string]any{"commonAttributes": attrs}, jsonx.V1()); mErr == nil { req.Header.Set("X-Amz-Firehose-Common-Attributes", string(raw)) } } @@ -219,10 +224,10 @@ func validateHTTPResponse(resp *http.Response, raw []byte, requestID string) err } var out struct { - Timestamp *json.RawMessage `json:"timestamp"` - RequestID string `json:"requestId"` + Timestamp *jsontext.Value `json:"timestamp"` + RequestID string `json:"requestId"` } - if err := json.Unmarshal(raw, &out); err != nil || out.RequestID != requestID || out.Timestamp == nil { + if err := json.Unmarshal(raw, &out, jsonx.V1()); err != nil || out.RequestID != requestID || out.Timestamp == nil { return errHTTPResponse } @@ -233,7 +238,7 @@ func endpointErrorMessage(raw []byte, status int) string { var out struct { ErrorMessage string `json:"errorMessage"` } - if json.Unmarshal(raw, &out) == nil && out.ErrorMessage != "" { + if json.Unmarshal(raw, &out, jsonx.V1()) == nil && out.ErrorMessage != "" { return out.ErrorMessage } diff --git a/services/firehose/delivery_opensearch.go b/services/firehose/delivery_opensearch.go index 8d02b9fef4..1bb1bcd8ed 100644 --- a/services/firehose/delivery_opensearch.go +++ b/services/firehose/delivery_opensearch.go @@ -3,12 +3,13 @@ package firehose import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -151,7 +152,7 @@ func indexInProcess(idx OpenSearchIndexer, domainARN, index string, records [][] for _, rec := range records { var doc map[string]any - if err := json.Unmarshal(rec, &doc); err != nil { + if err := json.Unmarshal(rec, &doc, jsonx.V1()); err != nil { failed = append( failed, failureRecord(rec, "", "ES.JsonProcessingException", "record is not a JSON object", 1), diff --git a/services/firehose/delivery_realism_helpers_test.go b/services/firehose/delivery_realism_helpers_test.go index 17743a4df8..e89fd490aa 100644 --- a/services/firehose/delivery_realism_helpers_test.go +++ b/services/firehose/delivery_realism_helpers_test.go @@ -7,7 +7,7 @@ import ( "context" "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -21,6 +21,7 @@ import ( "github.com/klauspost/compress/snappy" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -115,7 +116,7 @@ type transformEvent struct { // metadata.partitionKeys{"tenant": }. func contentTransform(payload []byte, _ int) ([]byte, error) { var ev transformEvent - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, err } @@ -140,7 +141,7 @@ func contentTransform(payload []byte, _ int) ([]byte, error) { } } - return json.Marshal(map[string]any{"records": recs}) + return json.Marshal(map[string]any{"records": recs}, jsonx.V1()) } // denyAuthorizer denies the listed actions; role "untrusted" cannot be assumed. diff --git a/services/firehose/delivery_realism_test.go b/services/firehose/delivery_realism_test.go index e1a22fc15c..cf555cf6f7 100644 --- a/services/firehose/delivery_realism_test.go +++ b/services/firehose/delivery_realism_test.go @@ -2,7 +2,8 @@ package firehose_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -219,7 +221,7 @@ func TestS3Delivery_LambdaProcessorResults(t *testing.T) { var ev transformEvent require.Equal(t, 1, fn.callCount()) - require.NoError(t, json.Unmarshal(fn.payloads[0], &ev)) + require.NoError(t, json.Unmarshal(fn.payloads[0], &ev, jsonx.V1())) assert.NotEmpty(t, ev.InvocationID) assert.Equal(t, rtTestRegion, ev.Region) assert.Regexp(t, `^arn:aws:firehose:us-east-1:000000000000:deliverystream/proc$`, ev.DeliveryStreamARN) @@ -234,7 +236,7 @@ func TestS3Delivery_LambdaProcessorResults(t *testing.T) { require.Len(t, failed, 1) var envlp map[string]string - require.NoError(t, json.Unmarshal(failed[0].body, &envlp)) + require.NoError(t, json.Unmarshal(failed[0].body, &envlp, jsonx.V1())) assert.Equal(t, testLambdaARN, envlp["lambdaARN"]) assert.Equal(t, "ProcessingFailed", envlp["errorCode"]) assert.Equal(t, base64.StdEncoding.EncodeToString([]byte("fail-three")), envlp["rawData"]) @@ -422,7 +424,10 @@ func (e *endpoint) serve(t *testing.T) *httptest.Server { func replyOK(w http.ResponseWriter, requestID string) { w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(map[string]any{"requestId": requestID, "timestamp": time.Now().UnixMilli()}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]any{"requestId": requestID, "timestamp": time.Now().UnixMilli()}, + ) } func TestHTTPEndpoint_RequestContract(t *testing.T) { @@ -468,7 +473,7 @@ func TestHTTPEndpoint_RequestContract(t *testing.T) { } `json:"records"` Timestamp int64 `json:"timestamp"` } - require.NoError(t, json.Unmarshal(call.body, &body)) + require.NoError(t, json.Unmarshal(call.body, &body, jsonx.V1())) assert.Equal(t, call.headers.Get("X-Amz-Firehose-Request-Id"), body.RequestID) assert.Positive(t, body.Timestamp) require.Len(t, body.Records, 2) diff --git a/services/firehose/delivery_splunk.go b/services/firehose/delivery_splunk.go index e3b0a906c3..40116d2f8e 100644 --- a/services/firehose/delivery_splunk.go +++ b/services/firehose/delivery_splunk.go @@ -3,11 +3,12 @@ package firehose import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -25,7 +26,7 @@ func buildSplunkBody(records [][]byte, hecType string) ([]byte, string) { var buf bytes.Buffer for _, rec := range records { - line, marshalErr := json.Marshal(hecEvent{Event: string(rec)}) + line, marshalErr := json.Marshal(hecEvent{Event: string(rec)}, jsonx.V1()) if marshalErr != nil { continue } diff --git a/services/firehose/flush_test.go b/services/firehose/flush_test.go index eeb823ce62..d3809037cf 100644 --- a/services/firehose/flush_test.go +++ b/services/firehose/flush_test.go @@ -3,7 +3,7 @@ package firehose_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -498,7 +499,7 @@ func TestHTTPEndpoint_DeliveryFormat_ViaHandler(t *testing.T) { } `json:"records"` Timestamp int64 `json:"timestamp"` } - require.NoError(t, json.Unmarshal(requests[0].body, &payload)) + require.NoError(t, json.Unmarshal(requests[0].body, &payload, jsonx.V1())) assert.NotEmpty(t, payload.RequestID) assert.Positive(t, payload.Timestamp) @@ -541,7 +542,7 @@ func TestOpenSearch_BulkNDJSON_ViaHandler(t *testing.T) { require.GreaterOrEqual(t, len(lines), 2, "NDJSON must have at least action+document lines") var action map[string]any - require.NoError(t, json.Unmarshal([]byte(lines[0]), &action)) + require.NoError(t, json.Unmarshal([]byte(lines[0]), &action, jsonx.V1())) _, hasIndex := action["index"] assert.True(t, hasIndex, "first NDJSON line must be an index action") } @@ -603,7 +604,7 @@ func TestSplunk_Modes_ViaHandler(t *testing.T) { assert.Contains(t, string(requests[0].body), "splunk-raw-data") } else { var evtBody map[string]any - require.NoError(t, json.Unmarshal(requests[0].body, &evtBody)) + require.NoError(t, json.Unmarshal(requests[0].body, &evtBody, jsonx.V1())) _, hasEvent := evtBody["event"] assert.True(t, hasEvent, "Event mode must wrap data in {event:...}") } @@ -629,7 +630,7 @@ func okLambdaResponse(records ...struct{ id, data string }) []byte { Data: base64.StdEncoding.EncodeToString([]byte(r.data)), }) } - b, _ := json.Marshal(out) + b, _ := json.Marshal(out, jsonx.V1()) return b } @@ -689,7 +690,7 @@ func TestLambdaTransform_AllDestinations(t *testing.T) { Data string `json:"data"` } `json:"records"` } - require.NoError(t, json.Unmarshal(srv.captured()[0].body, &payload)) + require.NoError(t, json.Unmarshal(srv.captured()[0].body, &payload, jsonx.V1())) require.Len(t, payload.Records, 1) decoded, decErr := base64.StdEncoding.DecodeString(payload.Records[0].Data) require.NoError(t, decErr) diff --git a/services/firehose/formats.go b/services/firehose/formats.go index e7fd0a2f54..4e8e0f3cdf 100644 --- a/services/firehose/formats.go +++ b/services/firehose/formats.go @@ -2,10 +2,12 @@ package firehose import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // DataFormatConversionConfig controls record-format conversion (JSON → Parquet/ORC). @@ -168,7 +170,7 @@ func convertRecords( // record is not a valid JSON object, which the caller treats as a conversion failure. func deserializeRecord(deser *Deserializer, rec []byte) ([]byte, bool) { var obj map[string]any - if err := json.Unmarshal(bytes.TrimSpace(rec), &obj); err != nil { + if err := json.Unmarshal(bytes.TrimSpace(rec), &obj, jsonx.V1()); err != nil { return nil, false } @@ -225,13 +227,13 @@ func marshalCanonical(obj map[string]any) ([]byte, error) { if i > 0 { buf.WriteByte(',') } - keyJSON, err := json.Marshal(k) + keyJSON, err := json.Marshal(k, jsonx.V1()) if err != nil { return nil, err } buf.Write(keyJSON) buf.WriteByte(':') - valJSON, err := json.Marshal(obj[k]) + valJSON, err := json.Marshal(obj[k], jsonx.V1()) if err != nil { return nil, err } diff --git a/services/firehose/formats_infer_test.go b/services/firehose/formats_infer_test.go index 6a411b08dc..06aa7cf168 100644 --- a/services/firehose/formats_infer_test.go +++ b/services/firehose/formats_infer_test.go @@ -1,11 +1,11 @@ package firehose_test import ( - "encoding/json" "testing" "github.com/stretchr/testify/assert" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -19,23 +19,23 @@ func TestInferColumnsWidens(t *testing.T) { }{ { name: "int then float", want: "double", - objs: []map[string]any{{"c": json.Number("3")}, {"c": json.Number("3.5")}}, + objs: []map[string]any{{"c": jsonx.Number("3")}, {"c": jsonx.Number("3.5")}}, }, { name: "float then int", want: "double", - objs: []map[string]any{{"c": json.Number("3.5")}, {"c": json.Number("3")}}, + objs: []map[string]any{{"c": jsonx.Number("3.5")}, {"c": jsonx.Number("3")}}, }, { name: "string then int", want: "string", - objs: []map[string]any{{"c": "abc"}, {"c": json.Number("5")}}, + objs: []map[string]any{{"c": "abc"}, {"c": jsonx.Number("5")}}, }, { name: "int then string", want: "string", - objs: []map[string]any{{"c": json.Number("5")}, {"c": "abc"}}, + objs: []map[string]any{{"c": jsonx.Number("5")}, {"c": "abc"}}, }, { name: "leading null", want: "bigint", - objs: []map[string]any{{"c": nil}, {"c": json.Number("5")}}, + objs: []map[string]any{{"c": nil}, {"c": jsonx.Number("5")}}, }, { name: "only null", want: "string", @@ -43,7 +43,7 @@ func TestInferColumnsWidens(t *testing.T) { }, { name: "bool and int", want: "string", - objs: []map[string]any{{"c": true}, {"c": json.Number("1")}}, + objs: []map[string]any{{"c": true}, {"c": jsonx.Number("1")}}, }, } diff --git a/services/firehose/formats_parquet.go b/services/firehose/formats_parquet.go index cd2929e0c3..3d030bdd7f 100644 --- a/services/firehose/formats_parquet.go +++ b/services/firehose/formats_parquet.go @@ -3,12 +3,14 @@ package firehose import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "sort" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -120,7 +122,7 @@ func inferType(v any) string { switch t := v.(type) { case bool: return colBoolean - case json.Number: + case jsonx.Number: if _, err := t.Int64(); err == nil { return colBigint } @@ -132,11 +134,10 @@ func inferType(v any) string { } func decodeObject(rec []byte) (map[string]any, bool) { - dec := json.NewDecoder(bytes.NewReader(bytes.TrimSpace(rec))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(bytes.TrimSpace(rec))) var obj map[string]any - if err := dec.Decode(&obj); err != nil || obj == nil { + if err := json.UnmarshalDecode(dec, &obj, jsonx.V1(), jsonx.UseNumber()); err != nil || obj == nil { return nil, false } @@ -162,10 +163,10 @@ func cellFor(obj map[string]any, col string) any { return t case bool: return t - case json.Number: + case jsonx.Number: return t.String() default: - b, err := json.Marshal(t) + b, err := json.Marshal(t, jsonx.V1()) if err != nil { return nil } diff --git a/services/firehose/handler.go b/services/firehose/handler.go index 448dfb73bc..0172096361 100644 --- a/services/firehose/handler.go +++ b/services/firehose/handler.go @@ -2,7 +2,7 @@ package firehose import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -145,7 +146,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req deliveryStreamNameInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.DeliveryStreamName } @@ -195,13 +196,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - respond := func(status int, errType string) error { msg := strings.TrimPrefix(err.Error(), errType+": ") @@ -218,7 +216,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, errUnknownAction): return respond(http.StatusBadRequest, "UnknownOperationException") case errors.Is(err, errInvalidRequest), errors.Is(err, awserr.ErrInvalidParameter), - errors.Is(err, ErrValidation), errors.As(err, &syntaxErr), errors.As(err, &typeErr): + errors.Is(err, ErrValidation), jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return respond(http.StatusBadRequest, "InvalidArgumentException") default: return c.JSON(http.StatusInternalServerError, map[string]string{errFieldMessage: err.Error()}) diff --git a/services/firehose/handler_delivery_streams.go b/services/firehose/handler_delivery_streams.go index e646932a98..4e0ad367a7 100644 --- a/services/firehose/handler_delivery_streams.go +++ b/services/firehose/handler_delivery_streams.go @@ -2,7 +2,7 @@ package firehose import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" @@ -328,7 +328,7 @@ type createDeliveryStreamInput struct { // not a typed struct) so handleCreateDeliveryStream can reject it explicitly instead // of silently dropping it and creating a stream with zero destinations -- see // validateSingleDestination. - AmazonOpenSearchServerlessDestinationConfiguration json.RawMessage `json:"AmazonOpenSearchServerlessDestinationConfiguration,omitempty"` //nolint:lll // AWS field name + AmazonOpenSearchServerlessDestinationConfiguration jsontext.Value `json:"AmazonOpenSearchServerlessDestinationConfiguration,omitempty"` //nolint:lll // AWS field name DatabaseSourceConfiguration *databaseSourceConfigurationInput `json:"DatabaseSourceConfiguration"` //nolint:lll // AWS field name DirectPutSourceConfiguration *directPutSourceConfigurationInput `json:"DirectPutSourceConfiguration"` //nolint:lll // AWS field name DeliveryStreamEncryptionConfigurationInput *EncryptionConfigInput `json:"DeliveryStreamEncryptionConfigurationInput"` //nolint:lll // AWS field name diff --git a/services/firehose/handler_delivery_streams_test.go b/services/firehose/handler_delivery_streams_test.go index cdd8203d49..db49e56ab5 100644 --- a/services/firehose/handler_delivery_streams_test.go +++ b/services/firehose/handler_delivery_streams_test.go @@ -1,7 +1,7 @@ package firehose_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -530,7 +531,7 @@ func TestDeleteThenDescribeReturns404(t *testing.T) { assert.Equal(t, http.StatusNotFound, descRec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -681,7 +682,7 @@ func TestDescribeDeliveryStream_SourceField(t *testing.T) { Source *firehose.SourceDescription `json:"Source"` } `json:"DeliveryStreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.DeliveryStreamDescription.Source) require.NotNil(t, out.DeliveryStreamDescription.Source.KinesisStreamSourceDescription) assert.Equal(t, "arn:aws:kinesis:us-east-1:000000000000:stream/my-ks", @@ -761,7 +762,7 @@ func TestDescribeDeliveryStream_Timestamps(t *testing.T) { LastUpdateTimestamp *int64 `json:"LastUpdateTimestamp"` } `json:"DeliveryStreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out.DeliveryStreamDescription.CreateTimestamp) assert.NotNil(t, out.DeliveryStreamDescription.LastUpdateTimestamp) assert.Positive(t, *out.DeliveryStreamDescription.CreateTimestamp) @@ -1139,7 +1140,7 @@ func TestListDeliveryStreams_Empty(t *testing.T) { DeliveryStreamNames []string `json:"DeliveryStreamNames"` HasMoreDeliveryStreams bool `json:"HasMoreDeliveryStreams"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.DeliveryStreamNames) assert.False(t, out.HasMoreDeliveryStreams) } @@ -1161,7 +1162,7 @@ func TestListDeliveryStreams_Limit(t *testing.T) { DeliveryStreamNames []string `json:"DeliveryStreamNames"` HasMoreDeliveryStreams bool `json:"HasMoreDeliveryStreams"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.DeliveryStreamNames, 2) assert.True(t, out.HasMoreDeliveryStreams) } @@ -1183,7 +1184,7 @@ func TestListDeliveryStreams_ExclusiveStart(t *testing.T) { var out struct { DeliveryStreamNames []string `json:"DeliveryStreamNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, []string{"b-stream", "c-stream"}, out.DeliveryStreamNames) } @@ -1205,7 +1206,7 @@ func TestListDeliveryStreams_Pagination(t *testing.T) { DeliveryStreamNames []string `json:"DeliveryStreamNames"` HasMoreDeliveryStreams bool `json:"HasMoreDeliveryStreams"` } - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.DeliveryStreamNames, 2) assert.True(t, page1.HasMoreDeliveryStreams) @@ -1219,7 +1220,7 @@ func TestListDeliveryStreams_Pagination(t *testing.T) { DeliveryStreamNames []string `json:"DeliveryStreamNames"` HasMoreDeliveryStreams bool `json:"HasMoreDeliveryStreams"` } - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.DeliveryStreamNames, 2) // No overlap between pages. for _, n := range page2.DeliveryStreamNames { @@ -1249,7 +1250,7 @@ func TestListDeliveryStreams_TypeFilter(t *testing.T) { var out struct { DeliveryStreamNames []string `json:"DeliveryStreamNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out.DeliveryStreamNames, "direct-put-stream") assert.NotContains(t, out.DeliveryStreamNames, "kinesis-src-stream-lf") @@ -1259,7 +1260,7 @@ func TestListDeliveryStreams_TypeFilter(t *testing.T) { var out2 struct { DeliveryStreamNames []string `json:"DeliveryStreamNames"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Contains(t, out2.DeliveryStreamNames, "kinesis-src-stream-lf") assert.NotContains(t, out2.DeliveryStreamNames, "direct-put-stream") @@ -1273,7 +1274,7 @@ func TestListDeliveryStreams_TypeFilter(t *testing.T) { var outInvalid struct { DeliveryStreamNames []string `json:"DeliveryStreamNames"` } - require.NoError(t, json.Unmarshal(recInvalid.Body.Bytes(), &outInvalid)) + require.NoError(t, json.Unmarshal(recInvalid.Body.Bytes(), &outInvalid, jsonx.V1())) assert.NotContains(t, outInvalid.DeliveryStreamNames, "direct-put-stream") assert.NotContains(t, outInvalid.DeliveryStreamNames, "kinesis-src-stream-lf") } diff --git a/services/firehose/handler_destination_updates.go b/services/firehose/handler_destination_updates.go index 9cc06868fa..d19c1d6326 100644 --- a/services/firehose/handler_destination_updates.go +++ b/services/firehose/handler_destination_updates.go @@ -2,7 +2,7 @@ package firehose import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" ) @@ -29,7 +29,7 @@ type updateDestinationInput struct { // AmazonOpenSearchServerlessDestinationUpdate: see createDeliveryStreamInput's // AmazonOpenSearchServerlessDestinationConfiguration doc comment -- same // unimplemented-11th-destination-type reasoning, captured only to reject explicitly. - AmazonOpenSearchServerlessDestinationUpdate json.RawMessage `json:"AmazonOpenSearchServerlessDestinationUpdate,omitempty"` //nolint:lll // AWS field name + AmazonOpenSearchServerlessDestinationUpdate jsontext.Value `json:"AmazonOpenSearchServerlessDestinationUpdate,omitempty"` //nolint:lll // AWS field name } type updateDestinationOutput struct{} diff --git a/services/firehose/handler_destination_updates_test.go b/services/firehose/handler_destination_updates_test.go index 888e8786e2..b127f5d165 100644 --- a/services/firehose/handler_destination_updates_test.go +++ b/services/firehose/handler_destination_updates_test.go @@ -1,13 +1,14 @@ package firehose_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -85,7 +86,7 @@ func TestUpdateDestination_Success_VersionIncrements(t *testing.T) { } `json:"Destinations"` } `json:"DeliveryStreamDescription"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &descOut, jsonx.V1())) versionBefore := descOut.DeliveryStreamDescription.VersionID rec := doFirehoseRequest(t, h, "UpdateDestination", map[string]any{ @@ -112,7 +113,7 @@ func TestUpdateDestination_Success_VersionIncrements(t *testing.T) { } `json:"Destinations"` } `json:"DeliveryStreamDescription"` } - require.NoError(t, json.Unmarshal(desc2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(desc2.Body.Bytes(), &descOut2, jsonx.V1())) assert.NotEqual(t, versionBefore, descOut2.DeliveryStreamDescription.VersionID) require.Len(t, descOut2.DeliveryStreamDescription.Destinations, 1) assert.Equal(t, "arn:aws:s3:::updated-bucket", @@ -247,7 +248,7 @@ func TestUpdateDestination_VersionMismatchRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ConcurrentModificationException", body["__type"]) } diff --git a/services/firehose/handler_records_test.go b/services/firehose/handler_records_test.go index bb16b06e4c..d518b27c15 100644 --- a/services/firehose/handler_records_test.go +++ b/services/firehose/handler_records_test.go @@ -2,13 +2,14 @@ package firehose_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -82,7 +83,7 @@ func TestPutRecord_Success_ReturnsRecordId(t *testing.T) { var out struct { RecordID string `json:"RecordId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.RecordID) } @@ -170,7 +171,7 @@ func TestKinesisStreamAsSource_PutRecordRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", body["__type"]) } @@ -249,7 +250,7 @@ func TestPutRecordBatch_Success_ResponseShape(t *testing.T) { RequestResponses []any `json:"RequestResponses"` FailedPutCount int `json:"FailedPutCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, 0, out.FailedPutCount) assert.NotNil(t, out.RequestResponses) } @@ -303,7 +304,7 @@ func TestPutRecordBatch_ResponseShape(t *testing.T) { } `json:"RequestResponses"` FailedPutCount int `json:"FailedPutCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, 0, out.FailedPutCount, "FailedPutCount must always be 0") assert.Len(t, out.RequestResponses, len(tc.recordData), "one response per record") @@ -355,7 +356,7 @@ func TestPutRecordBatch_PerRecordRecordId(t *testing.T) { } `json:"RequestResponses"` FailedPutCount int `json:"FailedPutCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, 0, out.FailedPutCount) require.Len(t, out.RequestResponses, tt.recordCount, @@ -457,7 +458,7 @@ func TestPutRecord_EncryptedField(t *testing.T) { var putOut struct { Encrypted bool `json:"Encrypted"` } - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putOut, jsonx.V1())) assert.Equal(t, tt.wantEncrypted, putOut.Encrypted, "PutRecord Encrypted mismatch") batchRec := doFirehoseRequest(t, h, "PutRecordBatch", map[string]any{ @@ -469,7 +470,7 @@ func TestPutRecord_EncryptedField(t *testing.T) { var batchOut struct { Encrypted bool `json:"Encrypted"` } - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchOut)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchOut, jsonx.V1())) assert.Equal(t, tt.wantEncrypted, batchOut.Encrypted, "PutRecordBatch Encrypted mismatch") }) } diff --git a/services/firehose/handler_tags_test.go b/services/firehose/handler_tags_test.go index c1ceaa052b..92cc0e625a 100644 --- a/services/firehose/handler_tags_test.go +++ b/services/firehose/handler_tags_test.go @@ -1,13 +1,14 @@ package firehose_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -148,7 +149,7 @@ func TestUntagDeliveryStream_RemovesSpecificKeys(t *testing.T) { var out struct { Tags []map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Tags, 1) assert.Equal(t, "team", out.Tags[0]["Key"]) } @@ -280,7 +281,7 @@ func TestListTagsForDeliveryStream_Limit(t *testing.T) { Tags []map[string]string `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Tags, 3) assert.True(t, out.HasMoreTags) } @@ -313,7 +314,7 @@ func TestListTagsForDeliveryStream_ExclusiveStart(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Tags, 2) assert.Equal(t, "b-key", out.Tags[0].Key) assert.Equal(t, "c-key", out.Tags[1].Key) @@ -409,7 +410,7 @@ func TestTagsLifecycle(t *testing.T) { Tags []map[string]string `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Tags, 4) assert.False(t, listOut.HasMoreTags) // Keys must be sorted: env, region, team, version. @@ -425,7 +426,7 @@ func TestTagsLifecycle(t *testing.T) { Tags []map[string]string `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Tags, 2) assert.True(t, page1.HasMoreTags) assert.Equal(t, "env", page1.Tags[0]["Key"]) @@ -441,7 +442,7 @@ func TestTagsLifecycle(t *testing.T) { Tags []map[string]string `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2, jsonx.V1())) require.Len(t, page2.Tags, 2) assert.False(t, page2.HasMoreTags) assert.Equal(t, "team", page2.Tags[0]["Key"]) @@ -459,7 +460,7 @@ func TestTagsLifecycle(t *testing.T) { var finalOut struct { Tags []map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(finalRec.Body.Bytes(), &finalOut)) + require.NoError(t, json.Unmarshal(finalRec.Body.Bytes(), &finalOut, jsonx.V1())) require.Len(t, finalOut.Tags, 2) keys := map[string]bool{} diff --git a/services/firehose/handler_test.go b/services/firehose/handler_test.go index 59faed73d5..97634dc783 100644 --- a/services/firehose/handler_test.go +++ b/services/firehose/handler_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "log/slog" "maps" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -33,7 +34,7 @@ func doFirehoseRequest(t *testing.T, h *firehose.Handler, action string, body an var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -90,7 +91,7 @@ func auditCreateStream(t *testing.T, h *firehose.Handler, name string, extra map var out struct { DeliveryStreamARN string `json:"DeliveryStreamARN"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.DeliveryStreamARN } @@ -104,7 +105,7 @@ func auditDescribe(t *testing.T, h *firehose.Handler, name string) map[string]an require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["DeliveryStreamDescription"].(map[string]any) } @@ -423,7 +424,7 @@ func TestErrorResponse_ResourceInUseException_HasType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceInUseException", body["__type"], "duplicate-stream error must carry __type=ResourceInUseException") } @@ -475,7 +476,7 @@ func TestErrorResponse_InvalidArgumentException_HasType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", body["__type"], "validation error must carry __type=InvalidArgumentException; got: %v", body) }) @@ -491,7 +492,7 @@ func TestErrorResponse_ResourceNotFoundException_HasType(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"], "not-found error must carry __type=ResourceNotFoundException") } @@ -504,7 +505,7 @@ func TestErrorResponse_UnknownOperation_HasType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "UnknownOperationException", body["__type"], "unknown operation must carry __type=UnknownOperationException") } diff --git a/services/firehose/http_endpoint_secret_test.go b/services/firehose/http_endpoint_secret_test.go index 3441dd79d2..bb78cfc88d 100644 --- a/services/firehose/http_endpoint_secret_test.go +++ b/services/firehose/http_endpoint_secret_test.go @@ -1,12 +1,14 @@ package firehose_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDescribeDeliveryStream_HTTPEndpointOmitsAccessKey(t *testing.T) { @@ -51,7 +53,7 @@ func TestDescribeDeliveryStream_HTTPEndpointOmitsAccessKey(t *testing.T) { } `json:"Destinations"` } `json:"DeliveryStreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.DeliveryStreamDescription.Destinations, 1) ec := out.DeliveryStreamDescription.Destinations[0].HTTP.EndpointConfiguration diff --git a/services/firehose/isolation_test.go b/services/firehose/isolation_test.go index d7a55dbe3f..f904eff5f6 100644 --- a/services/firehose/isolation_test.go +++ b/services/firehose/isolation_test.go @@ -2,7 +2,7 @@ package firehose //nolint:testpackage // existing issue. import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestFirehoseRegionIsolation(t *testing.T) { //nolint:paralleltest // existing issue. @@ -25,7 +26,7 @@ func TestFirehoseRegionIsolation(t *testing.T) { //nolint:paralleltest // existi DeliveryStreamName: name, DeliveryStreamType: "DirectPut", } - body, _ := json.Marshal(input) + body, _ := json.Marshal(input, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", firehoseTargetPrefix+"CreateDeliveryStream") req.Header.Set( @@ -43,7 +44,7 @@ func TestFirehoseRegionIsolation(t *testing.T) { //nolint:paralleltest // existi listStreams := func(region string) []string { input := listDeliveryStreamsInput{} - body, _ := json.Marshal(input) + body, _ := json.Marshal(input, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("X-Amz-Target", firehoseTargetPrefix+"ListDeliveryStreams") req.Header.Set( @@ -59,7 +60,7 @@ func TestFirehoseRegionIsolation(t *testing.T) { //nolint:paralleltest // existi assert.Equal(t, http.StatusOK, rec.Code) var output listDeliveryStreamsOutput - err = json.Unmarshal(rec.Body.Bytes(), &output) + err = json.Unmarshal(rec.Body.Bytes(), &output, jsonx.V1()) require.NoError(t, err) return output.DeliveryStreamNames diff --git a/services/firehose/non_s3_delivery_test.go b/services/firehose/non_s3_delivery_test.go index ba1c4e6461..9f08e931d9 100644 --- a/services/firehose/non_s3_delivery_test.go +++ b/services/firehose/non_s3_delivery_test.go @@ -3,7 +3,8 @@ package firehose_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -50,7 +52,7 @@ func newCaptureServer(t *testing.T, statusCode int) *captureServer { cs.mu.Unlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) - _ = json.NewEncoder(w).Encode(map[string]any{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{ "requestId": r.Header.Get("X-Amz-Firehose-Request-Id"), "timestamp": time.Now().UnixMilli(), }) @@ -151,7 +153,7 @@ func TestFirehose_HTTPEndpoint_Delivery(t *testing.T) { } `json:"records"` Timestamp int64 `json:"timestamp"` } - require.NoError(t, json.Unmarshal(reqs[0].body, &payload)) + require.NoError(t, json.Unmarshal(reqs[0].body, &payload, jsonx.V1())) assert.NotEmpty(t, payload.RequestID) assert.Positive(t, payload.Timestamp) diff --git a/services/firehose/partitioning.go b/services/firehose/partitioning.go index 1eaf96cb91..2eed5d7491 100644 --- a/services/firehose/partitioning.go +++ b/services/firehose/partitioning.go @@ -1,9 +1,11 @@ package firehose import ( - "encoding/json" + "encoding/json/v2" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // partitionQueryNamespace is the Firehose namespace for jq-derived partition keys. @@ -81,7 +83,7 @@ func partitionValue( if !*parsed { *parsed = true - if err := json.Unmarshal(rec, obj); err != nil { + if err := json.Unmarshal(rec, obj, jsonx.V1()); err != nil { return "", false } } @@ -292,7 +294,7 @@ func scalarToString(v any) (string, bool) { } return "false", true - case json.Number: + case jsonx.Number: return val.String(), true case float64: // Render integers without a trailing ".0" to match typical partition values. diff --git a/services/firehose/persistence.go b/services/firehose/persistence.go index 321c066787..3269eba568 100644 --- a/services/firehose/persistence.go +++ b/services/firehose/persistence.go @@ -2,10 +2,12 @@ package firehose import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -39,10 +41,10 @@ const firehoseSnapshotVersion = 2 // key is derived from), so no DTO layer is needed the way services/ses or // services/neptune required for region-nested types with a hidden field. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -66,7 +68,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "firehose: failed to marshal snapshot", "error", err) diff --git a/services/firehose/persistence_test.go b/services/firehose/persistence_test.go index ad82d67cea..c1a5bebe0a 100644 --- a/services/firehose/persistence_test.go +++ b/services/firehose/persistence_test.go @@ -2,12 +2,13 @@ package firehose_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/firehose" ) @@ -252,9 +253,9 @@ func TestInMemoryBackend_RestoreVersionMismatch(t *testing.T) { require.NotNil(t, raw) var generic map[string]any - require.NoError(t, json.Unmarshal(raw, &generic)) + require.NoError(t, json.Unmarshal(raw, &generic, jsonx.V1())) tt.mutate(t, generic) - mutated, err := json.Marshal(generic) + mutated, err := json.Marshal(generic, jsonx.V1()) require.NoError(t, err) // Target already holds unrelated state, which the version-mismatch path must diff --git a/services/firehose/transform.go b/services/firehose/transform.go index a0b8020b03..0bb730a404 100644 --- a/services/firehose/transform.go +++ b/services/firehose/transform.go @@ -3,7 +3,7 @@ package firehose import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -94,7 +95,7 @@ func failureRecord(raw []byte, lambdaARN, code, msg string, attempts int) []byte AttemptEndingTimestamp: now, RawData: base64.StdEncoding.EncodeToString(raw), LambdaARN: lambdaARN, - }) + }, jsonx.V1()) if err != nil { return raw } @@ -126,7 +127,7 @@ func buildLambdaTransformPayload(records [][]byte, streamARN, region string) ([] } } - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { return nil, nil } @@ -145,7 +146,7 @@ func parseLambdaTransformResponse( ErrorMessage string `json:"errorMessage"` Records []lambdaTransformResponseRecord `json:"records"` } - if err := json.Unmarshal(result, &resp); err != nil { + if err := json.Unmarshal(result, &resp, jsonx.V1()); err != nil { return transformOutcome{}, &transformError{code: codeLambdaJSON, err: err} } diff --git a/services/fis/actions_test.go b/services/fis/actions_test.go index 9c230c8c77..4a74c23e3c 100644 --- a/services/fis/actions_test.go +++ b/services/fis/actions_test.go @@ -1,7 +1,7 @@ package fis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -612,7 +613,7 @@ func TestFISHandler_StopExperiment_AlreadyStopped(t *testing.T) { } `json:"experiment"` } - if err := json.Unmarshal(rec3.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1()); err != nil { return false } @@ -710,7 +711,7 @@ func TestFISHandler_ExperimentFails_WhenActionProviderFails(t *testing.T) { rec3 := doRequest(t, h, http.MethodGet, "/experiments/"+expID, nil) require.Equal(t, http.StatusOK, rec3.Code) - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &finalResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &finalResp, jsonx.V1())) require.Equal(t, "failed", finalResp.Experiment.Status.Status) // Regression test: cleanupActions used to unconditionally overwrite @@ -799,7 +800,7 @@ func TestFISHandler_ExperimentSucceeds_WithMockActionProvider(t *testing.T) { } `json:"experiment"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) require.Equal(t, "completed", resp.Experiment.Status.Status) }) } diff --git a/services/fis/experiment_actions_mode_test.go b/services/fis/experiment_actions_mode_test.go index af73407e38..ebc5822b5d 100644 --- a/services/fis/experiment_actions_mode_test.go +++ b/services/fis/experiment_actions_mode_test.go @@ -1,7 +1,8 @@ package fis_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -39,10 +41,10 @@ func actionsModeTemplateBody() map[string]any { // pollExperimentUntilTerminal polls GET /experiments/{id} until the experiment // reaches a terminal status and returns the decoded response body. -func pollExperimentUntilTerminal(t *testing.T, h *fis.Handler, expID string) map[string]json.RawMessage { +func pollExperimentUntilTerminal(t *testing.T, h *fis.Handler, expID string) map[string]jsontext.Value { t.Helper() - var result map[string]json.RawMessage + var result map[string]jsontext.Value require.Eventually(t, func() bool { r := doRequest(t, h, http.MethodGet, "/experiments/"+expID, nil) @@ -51,10 +53,10 @@ func pollExperimentUntilTerminal(t *testing.T, h *fis.Handler, expID string) map } var full struct { - Experiment map[string]json.RawMessage `json:"experiment"` + Experiment map[string]jsontext.Value `json:"experiment"` } - if err := json.Unmarshal(r.Body.Bytes(), &full); err != nil { + if err := json.Unmarshal(r.Body.Bytes(), &full, jsonx.V1()); err != nil { return false } @@ -62,7 +64,7 @@ func pollExperimentUntilTerminal(t *testing.T, h *fis.Handler, expID string) map Status string `json:"status"` } - if err := json.Unmarshal(full.Experiment["status"], &status); err != nil { + if err := json.Unmarshal(full.Experiment["status"], &status, jsonx.V1()); err != nil { return false } @@ -127,7 +129,7 @@ func TestStartExperiment_ActionsMode_SkipAll_SkipsActionsAndProvider(t *testing. Status string `json:"status"` } - require.NoError(t, json.Unmarshal(exp["status"], &status)) + require.NoError(t, json.Unmarshal(exp["status"], &status, jsonx.V1())) assert.Equal(t, "completed", status.Status) var actions map[string]struct { @@ -136,7 +138,7 @@ func TestStartExperiment_ActionsMode_SkipAll_SkipsActionsAndProvider(t *testing. } `json:"status"` } - require.NoError(t, json.Unmarshal(exp["actions"], &actions)) + require.NoError(t, json.Unmarshal(exp["actions"], &actions, jsonx.V1())) require.Contains(t, actions, "modeAction") assert.Equal(t, "skipped", actions["modeAction"].Status.Status) @@ -188,7 +190,7 @@ func TestStartExperiment_ActionsMode_RunAll_InvokesProvider(t *testing.T) { Status string `json:"status"` } - require.NoError(t, json.Unmarshal(exp["status"], &status)) + require.NoError(t, json.Unmarshal(exp["status"], &status, jsonx.V1())) assert.Equal(t, "completed", status.Status) assert.Equal(t, 1, mock.Calls, "run-all must invoke the external action provider exactly once") }) diff --git a/services/fis/experiment_execution_test.go b/services/fis/experiment_execution_test.go index f868fdaf2d..298545dc4e 100644 --- a/services/fis/experiment_execution_test.go +++ b/services/fis/experiment_execution_test.go @@ -1,7 +1,7 @@ package fis_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -75,7 +76,7 @@ func TestFISHandler_ExperimentCompletesAfterDuration(t *testing.T) { } `json:"experiment"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) require.Equal(t, "completed", resp.Experiment.Status.Status) }) } @@ -156,7 +157,7 @@ func TestFISHandler_ExperimentCompletes_NoTimedActions(t *testing.T) { } `json:"experiment"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp, jsonx.V1())) require.Equal(t, "completed", resp.Experiment.Status.Status) }) } diff --git a/services/fis/experiment_reports_test.go b/services/fis/experiment_reports_test.go index 666af94159..2b35b29de3 100644 --- a/services/fis/experiment_reports_test.go +++ b/services/fis/experiment_reports_test.go @@ -1,7 +1,8 @@ package fis_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -228,7 +230,7 @@ func startExperimentAndPollTerminal(t *testing.T, h *fis.Handler, templateID str Experiment experimentReportPollResult `json:"experiment"` } - if err := json.Unmarshal(r.Body.Bytes(), &full); err != nil { + if err := json.Unmarshal(r.Body.Bytes(), &full, jsonx.V1()); err != nil { return false } @@ -321,13 +323,13 @@ func TestGetExperiment_NoReportConfig_OmitsReportFields(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value mustJSON(t, rec, &raw) - var exp map[string]json.RawMessage + var exp map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw["experiment"], &exp)) + require.NoError(t, json.Unmarshal(raw["experiment"], &exp, jsonx.V1())) _, hasReportConfig := exp["experimentReportConfiguration"] _, hasReport := exp["experimentReport"] diff --git a/services/fis/experiment_status_test.go b/services/fis/experiment_status_test.go index b74c153c6c..0889d871fa 100644 --- a/services/fis/experiment_status_test.go +++ b/services/fis/experiment_status_test.go @@ -1,7 +1,8 @@ package fis_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -51,13 +53,13 @@ func TestExperiment_EndTime_AbsentBeforeComplete(t *testing.T) { require.Equal(t, http.StatusCreated, rec2.Code) // Immediately after start, endTime should be absent (omitempty). - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value mustJSON(t, rec2, &raw) - var expRaw map[string]json.RawMessage + var expRaw map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw)) + require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw, jsonx.V1())) _, hasEndTime := expRaw["endTime"] assert.False(t, hasEndTime, "endTime must be absent on a running experiment") } @@ -113,13 +115,13 @@ func TestExperiment_EndTime_PresentAfterComplete(t *testing.T) { rec3 := doRequest(t, h, http.MethodGet, "/experiments/"+expID, nil) require.Equal(t, http.StatusOK, rec3.Code) - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value mustJSON(t, rec3, &raw) - var expRaw map[string]json.RawMessage + var expRaw map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw)) + require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw, jsonx.V1())) statusRaw, hasStatus := expRaw["status"] require.True(t, hasStatus) @@ -128,7 +130,7 @@ func TestExperiment_EndTime_PresentAfterComplete(t *testing.T) { Status string `json:"status"` } - require.NoError(t, json.Unmarshal(statusRaw, &status)) + require.NoError(t, json.Unmarshal(statusRaw, &status, jsonx.V1())) require.Equal(t, "completed", status.Status) endTimeRaw, hasEndTime := expRaw["endTime"] @@ -136,7 +138,7 @@ func TestExperiment_EndTime_PresentAfterComplete(t *testing.T) { var endTime float64 - require.NoError(t, json.Unmarshal(endTimeRaw, &endTime)) + require.NoError(t, json.Unmarshal(endTimeRaw, &endTime, jsonx.V1())) assert.Greater(t, endTime, 0.0, "endTime must be a positive Unix timestamp") }) } @@ -359,13 +361,13 @@ func TestExperiment_StatusAndState_BothPresent(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value mustJSON(t, rec, &raw) - var expRaw map[string]json.RawMessage + var expRaw map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw)) + require.NoError(t, json.Unmarshal(raw["experiment"], &expRaw, jsonx.V1())) _, hasStatus := expRaw["status"] _, hasState := expRaw["state"] assert.True(t, hasStatus, "experiment must have 'status' field") @@ -522,22 +524,22 @@ func TestExperimentStatus_ErrorField_Present(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) - var resp map[string]json.RawMessage + var resp map[string]jsontext.Value mustJSON(t, rec, &resp) expRaw := resp["experiment"] - var exp map[string]json.RawMessage + var exp map[string]jsontext.Value - require.NoError(t, json.Unmarshal(expRaw, &exp)) + require.NoError(t, json.Unmarshal(expRaw, &exp, jsonx.V1())) statusRaw, ok := exp["status"] require.True(t, ok, "experiment should have status field") - var status map[string]json.RawMessage + var status map[string]jsontext.Value - require.NoError(t, json.Unmarshal(statusRaw, &status)) + require.NoError(t, json.Unmarshal(statusRaw, &status, jsonx.V1())) // state.error should be absent for non-failed experiments (omitempty). _, hasError := status["error"] diff --git a/services/fis/experiments_test.go b/services/fis/experiments_test.go index 4c46837045..a8ee5a72c5 100644 --- a/services/fis/experiments_test.go +++ b/services/fis/experiments_test.go @@ -1,7 +1,7 @@ package fis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -532,7 +533,7 @@ func TestListExperiments_FilterByStatus(t *testing.T) { } `json:"experiments"` } - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &gr, jsonx.V1())) assert.NotEmpty(t, gr.Experiments) }) } diff --git a/services/fis/handler_experiment_templates.go b/services/fis/handler_experiment_templates.go index e8adb7019d..4da3662003 100644 --- a/services/fis/handler_experiment_templates.go +++ b/services/fis/handler_experiment_templates.go @@ -2,10 +2,12 @@ package fis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -18,7 +20,7 @@ func (h *Handler) handleCreateExperimentTemplate( body []byte, ) error { var input createExperimentTemplateRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "invalid request body: "+err.Error(), "") } @@ -45,7 +47,7 @@ func (h *Handler) handleGetExperimentTemplate(c *echo.Context, id string) error func (h *Handler) handleUpdateExperimentTemplate(c *echo.Context, id string, body []byte) error { var input updateExperimentTemplateRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "invalid request body: "+err.Error(), id) } diff --git a/services/fis/handler_experiments.go b/services/fis/handler_experiments.go index ceed6b3a4c..11fb193a1d 100644 --- a/services/fis/handler_experiments.go +++ b/services/fis/handler_experiments.go @@ -2,11 +2,13 @@ package fis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "slices" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -15,7 +17,7 @@ import ( func (h *Handler) handleStartExperiment(ctx context.Context, c *echo.Context, body []byte) error { var input startExperimentRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "invalid request body: "+err.Error(), "") } diff --git a/services/fis/handler_safety_levers.go b/services/fis/handler_safety_levers.go index bdc6a747a0..d79ada85a1 100644 --- a/services/fis/handler_safety_levers.go +++ b/services/fis/handler_safety_levers.go @@ -1,10 +1,12 @@ package fis import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -22,7 +24,7 @@ func (h *Handler) handleGetSafetyLever(c *echo.Context, id string) error { func (h *Handler) handleUpdateSafetyLeverState(c *echo.Context, id string, body []byte) error { var input updateSafetyLeverStateRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "invalid request body: "+err.Error(), id) } diff --git a/services/fis/handler_tags.go b/services/fis/handler_tags.go index ad4140cb96..26b54195a2 100644 --- a/services/fis/handler_tags.go +++ b/services/fis/handler_tags.go @@ -1,11 +1,13 @@ package fis import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -17,7 +19,7 @@ func (h *Handler) handleTagResource(c *echo.Context, arnStr string, body []byte) Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "invalid request body: "+err.Error(), arnStr) } diff --git a/services/fis/handler_target_account_configurations.go b/services/fis/handler_target_account_configurations.go index 9e27a7dfd4..4961009de4 100644 --- a/services/fis/handler_target_account_configurations.go +++ b/services/fis/handler_target_account_configurations.go @@ -1,7 +1,7 @@ package fis import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -31,7 +32,7 @@ func (h *Handler) handleCreateTargetAccountConfiguration( templateID, accountID := splitCompositeID(compositeID) var input createTargetAccountConfigurationRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -101,7 +102,7 @@ func (h *Handler) handleUpdateTargetAccountConfiguration( templateID, accountID := splitCompositeID(compositeID) var input updateTargetAccountConfigurationRequest - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/fis/handler_test.go b/services/fis/handler_test.go index a8250958f1..5b9280c50d 100644 --- a/services/fis/handler_test.go +++ b/services/fis/handler_test.go @@ -3,7 +3,7 @@ package fis_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -36,7 +37,7 @@ func doRequest(t *testing.T, h *fis.Handler, method, path string, body any) *htt if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -55,7 +56,7 @@ func doRequest(t *testing.T, h *fis.Handler, method, path string, body any) *htt func mustJSON(t *testing.T, rec *httptest.ResponseRecorder, v any) { t.Helper() - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), v, jsonx.V1())) } // ---------------------------------------- @@ -99,7 +100,7 @@ func seedTemplate(t *testing.T, h *fis.Handler) string { // jsonUnmarshalFIS decodes JSON from a recorder into v. func jsonUnmarshalFIS(t *testing.T, data []byte, v any) { t.Helper() - require.NoError(t, json.Unmarshal(data, v)) + require.NoError(t, json.Unmarshal(data, v, jsonx.V1())) } func TestFISHandler_Name(t *testing.T) { diff --git a/services/fis/persistence.go b/services/fis/persistence.go index e1c9ebb6d9..18d79353b0 100644 --- a/services/fis/persistence.go +++ b/services/fis/persistence.go @@ -2,11 +2,13 @@ package fis import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -32,11 +34,11 @@ const fisSnapshotVersion = 1 // snapshot either, so idempotency tokens do not survive a snapshot/restore // cycle -- this preserves that pre-existing behaviour unchanged. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - SafetyLever *SafetyLever `json:"safetyLever"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + SafetyLever *SafetyLever `json:"safetyLever"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -59,7 +61,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "fis: Snapshot marshal failure", "error", err) diff --git a/services/fis/safety_levers_test.go b/services/fis/safety_levers_test.go index 52827f70c3..ae2e274286 100644 --- a/services/fis/safety_levers_test.go +++ b/services/fis/safety_levers_test.go @@ -2,7 +2,8 @@ package fis_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -74,13 +76,13 @@ func TestFISHandler_GetSafetyLever_NoTagsFieldOnWire(t *testing.T) { rec2 := doRequest(t, h, http.MethodGet, "/safetyLevers/000000000000", nil) require.Equal(t, http.StatusOK, rec2.Code) - var raw map[string]json.RawMessage + var raw map[string]jsontext.Value mustJSON(t, rec2, &raw) - var lever map[string]json.RawMessage + var lever map[string]jsontext.Value - require.NoError(t, json.Unmarshal(raw["safetyLever"], &lever)) + require.NoError(t, json.Unmarshal(raw["safetyLever"], &lever, jsonx.V1())) _, hasTags := lever["tags"] assert.False(t, hasTags, "GetSafetyLever response must not include a tags field") diff --git a/services/fis/target_resolution_test.go b/services/fis/target_resolution_test.go index 68bed50db0..4994964329 100644 --- a/services/fis/target_resolution_test.go +++ b/services/fis/target_resolution_test.go @@ -2,13 +2,14 @@ package fis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/fis" ) @@ -142,7 +143,7 @@ func TestStartExperiment_TargetResolution(t *testing.T) { Status string `json:"status"` Reason string `json:"reason"` } - require.NoError(t, json.Unmarshal(exp["status"], &status)) + require.NoError(t, json.Unmarshal(exp["status"], &status, jsonx.V1())) assert.Equal(t, tt.wantStatus, status.Status) assert.Equal(t, tt.wantReason, status.Reason) @@ -152,7 +153,7 @@ func TestStartExperiment_TargetResolution(t *testing.T) { Status string `json:"status"` } `json:"state"` } - require.NoError(t, json.Unmarshal(exp["actions"], &actions)) + require.NoError(t, json.Unmarshal(exp["actions"], &actions, jsonx.V1())) assert.Equal(t, tt.wantActions, actions["act"].State.Status) } diff --git a/services/fis/wire_field_test.go b/services/fis/wire_field_test.go index 85315a0c61..864e3933b1 100644 --- a/services/fis/wire_field_test.go +++ b/services/fis/wire_field_test.go @@ -1,7 +1,8 @@ package fis_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http/httptest" "testing" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/fis" @@ -229,11 +231,11 @@ func assertNoRawKey(t *testing.T, url, listKey, itemKey string) { t.Cleanup(func() { _ = resp.Body.Close() }) - var body map[string]json.RawMessage - require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + var body map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1())) - var items []map[string]json.RawMessage - require.NoError(t, json.Unmarshal(body[listKey], &items)) + var items []map[string]jsontext.Value + require.NoError(t, json.Unmarshal(body[listKey], &items, jsonx.V1())) require.NotEmpty(t, items) for _, item := range items { diff --git a/services/forecast/handler.go b/services/forecast/handler.go index fdc672d6bc..9c6bf169fc 100644 --- a/services/forecast/handler.go +++ b/services/forecast/handler.go @@ -2,7 +2,7 @@ package forecast import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -126,7 +127,7 @@ func (h *Handler) Handler() echo.HandlerFunc { func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byte, error) { var input map[string]any - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } @@ -165,7 +166,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt return nil, err } - return json.Marshal(output) + return json.Marshal(output, jsonx.V1()) } func (h *Handler) execute(action string, spec operationSpec, input map[string]any) (map[string]any, error) { @@ -250,7 +251,7 @@ func (h *Handler) dispatchListMonitorEvaluations(input map[string]any) ([]byte, out["NextToken"] = pg.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } // filterMonitorEvaluations applies ListMonitorEvaluations's Filters @@ -321,7 +322,7 @@ func (h *Handler) dispatchDeleteResourceTree(input map[string]any) ([]byte, erro return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) dispatchResumeResource(input map[string]any) ([]byte, error) { @@ -330,7 +331,7 @@ func (h *Handler) dispatchResumeResource(input map[string]any) ([]byte, error) { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) dispatchStopResource(input map[string]any) ([]byte, error) { @@ -339,7 +340,7 @@ func (h *Handler) dispatchStopResource(input map[string]any) ([]byte, error) { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) dispatchGetAccuracyMetrics(input map[string]any) ([]byte, error) { @@ -348,7 +349,7 @@ func (h *Handler) dispatchGetAccuracyMetrics(input map[string]any) ([]byte, erro return nil, err } - return json.Marshal(metrics) + return json.Marshal(metrics, jsonx.V1()) } func (h *Handler) dispatchListTagsForResource(input map[string]any) ([]byte, error) { @@ -361,7 +362,7 @@ func (h *Handler) dispatchListTagsForResource(input map[string]any) ([]byte, err tagList = append(tagList, map[string]string{"Key": k, "Value": v}) } - return json.Marshal(map[string]any{"Tags": tagList}) + return json.Marshal(map[string]any{"Tags": tagList}, jsonx.V1()) } func (h *Handler) dispatchTagResource(input map[string]any) ([]byte, error) { @@ -370,7 +371,7 @@ func (h *Handler) dispatchTagResource(input map[string]any) ([]byte, error) { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) dispatchUntagResource(input map[string]any) ([]byte, error) { @@ -385,7 +386,7 @@ func (h *Handler) dispatchUntagResource(input map[string]any) ([]byte, error) { return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func resourceIdentifier(spec operationSpec, input map[string]any) string { @@ -600,7 +601,10 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err code, errType = http.StatusInternalServerError, "InternalServerException" } - payload, marshalErr := json.Marshal(map[string]string{"__type": errType, "message": publicMessage(err, errType)}) + payload, marshalErr := json.Marshal( + map[string]string{"__type": errType, "message": publicMessage(err, errType)}, + jsonx.V1(), + ) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } diff --git a/services/forecast/handler_sdk_route_table_test.go b/services/forecast/handler_sdk_route_table_test.go index c91589b8da..94ec160973 100644 --- a/services/forecast/handler_sdk_route_table_test.go +++ b/services/forecast/handler_sdk_route_table_test.go @@ -1,7 +1,7 @@ package forecast_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/forecast" ) @@ -167,7 +168,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var resp struct { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEqual(t, "InvalidInputException: "+tc.op, resp.Message, "target=%s op=%s: dispatched to the unmatched-route handler, body=%s", tc.target, tc.op, rec.Body.String()) diff --git a/services/forecast/handler_test.go b/services/forecast/handler_test.go index ecef421812..350a6cd5f7 100644 --- a/services/forecast/handler_test.go +++ b/services/forecast/handler_test.go @@ -2,7 +2,7 @@ package forecast_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/forecast" ) @@ -40,7 +41,7 @@ func request(t *testing.T, h *forecast.Handler, operation string, body map[strin func doRequest(t *testing.T, h *forecast.Handler, operation string, body map[string]any) *httptest.ResponseRecorder { t.Helper() - payload, err := json.Marshal(body) + payload, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -69,7 +70,7 @@ func unmarshalResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string] t.Helper() var response map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response, jsonx.V1())) return response } diff --git a/services/forecast/persistence.go b/services/forecast/persistence.go index ec45b7e8a9..7bac31da80 100644 --- a/services/forecast/persistence.go +++ b/services/forecast/persistence.go @@ -2,7 +2,7 @@ package forecast import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -41,7 +41,7 @@ const forecastSnapshotVersion = 1 // restored tables instead of trusting a persisted copy that could otherwise // drift out of sync with the tables. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Evaluations map[string][]MonitorEvaluation `json:"evaluations"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` diff --git a/services/forecast/store.go b/services/forecast/store.go index c59a6278c6..e59cea3732 100644 --- a/services/forecast/store.go +++ b/services/forecast/store.go @@ -1,7 +1,7 @@ package forecast import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "sort" @@ -9,6 +9,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -344,13 +345,13 @@ func cloneMap(data map[string]any) map[string]any { return make(map[string]any) } - encoded, err := json.Marshal(data) + encoded, err := json.Marshal(data, jsonx.V1()) if err != nil { return make(map[string]any) } var result map[string]any - if unmarshalErr := json.Unmarshal(encoded, &result); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(encoded, &result, jsonx.V1()); unmarshalErr != nil { return make(map[string]any) } diff --git a/services/fsx/client_request_token_test.go b/services/fsx/client_request_token_test.go index 7f8d4aa5ac..3f742a41da 100644 --- a/services/fsx/client_request_token_test.go +++ b/services/fsx/client_request_token_test.go @@ -1,7 +1,7 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -17,7 +18,7 @@ func responseID(t *testing.T, body []byte, outKey, idKey string) string { var out map[string]map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) id, ok := out[outKey][idKey].(string) require.True(t, ok, string(body)) diff --git a/services/fsx/data_repository_and_cache_config_test.go b/services/fsx/data_repository_and_cache_config_test.go index dc097dfdc4..8cd831c7a9 100644 --- a/services/fsx/data_repository_and_cache_config_test.go +++ b/services/fsx/data_repository_and_cache_config_test.go @@ -1,13 +1,15 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDataRepositoryAssociation_S3Configuration(t *testing.T) { @@ -90,7 +92,7 @@ func TestDataRepositoryAssociation_S3Configuration(t *testing.T) { } `json:"Associations"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Associations, 1) assert.Equal(t, tt.wantExport, out.Associations[0].S3.AutoExportPolicy.Events) assert.Equal(t, tt.wantImport, out.Associations[0].S3.AutoImportPolicy.Events) @@ -130,7 +132,7 @@ func TestDeleteDataRepositoryAssociation_DeleteDataInFileSystem(t *testing.T) { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "DELETING", out["Lifecycle"]) assert.Equal(t, tt.flag, out["DeleteDataInFileSystem"]) }) @@ -176,7 +178,7 @@ func TestCreateFileCache_AssociationsAndCopyTags(t *testing.T) { } `json:"FileCache"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) require.Len(t, created.FileCache.DRAIDs, 1) require.NotNil(t, created.FileCache.CopyTags) assert.Equal(t, tt.copyTags, *created.FileCache.CopyTags) @@ -200,7 +202,7 @@ func TestCreateFileCache_AssociationsAndCopyTags(t *testing.T) { } `json:"Associations"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &assoc)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &assoc, jsonx.V1())) require.Len(t, assoc.Associations, 1) a := assoc.Associations[0] diff --git a/services/fsx/file_cache_wire_test.go b/services/fsx/file_cache_wire_test.go index 9d2c1220e9..26c731e1d0 100644 --- a/services/fsx/file_cache_wire_test.go +++ b/services/fsx/file_cache_wire_test.go @@ -1,7 +1,7 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/fsx/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // testFileCacheLustreConfig builds a minimal valid CreateFileCache @@ -74,7 +76,7 @@ func TestFileCache_TagsWireShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) fcID := createOut["FileCache"].(map[string]any)["FileCacheId"].(string) describeRec := doFSxRequest(t, h, "DescribeFileCaches", map[string]any{ @@ -83,7 +85,7 @@ func TestFileCache_TagsWireShape(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut, jsonx.V1())) caches, ok := describeOut["FileCaches"].([]any) require.True(t, ok) @@ -113,7 +115,7 @@ func TestFileCache_TagsWireShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) fcID := createOut["FileCache"].(map[string]any)["FileCacheId"].(string) updateRec := doFSxRequest(t, h, "UpdateFileCache", map[string]any{ @@ -123,7 +125,7 @@ func TestFileCache_TagsWireShape(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) fc, ok := updateOut["FileCache"].(map[string]any) require.True(t, ok) diff --git a/services/fsx/file_system_type_configuration_test.go b/services/fsx/file_system_type_configuration_test.go index 394ee02d02..3377fb111b 100644 --- a/services/fsx/file_system_type_configuration_test.go +++ b/services/fsx/file_system_type_configuration_test.go @@ -1,12 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestFSx_FileSystem_WindowsConfiguration locks the wire shape of the @@ -31,7 +33,7 @@ func TestFSx_FileSystem_WindowsConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fs := out["FileSystem"].(map[string]any) require.Contains(t, fs, "WindowsConfiguration") @@ -62,7 +64,7 @@ func TestFSx_FileSystem_OntapConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fs := out["FileSystem"].(map[string]any) require.Contains(t, fs, "OntapConfiguration") @@ -96,7 +98,7 @@ func TestFSx_FileSystem_OpenZFSConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fs := out["FileSystem"].(map[string]any) require.Contains(t, fs, "OpenZFSConfiguration") @@ -114,7 +116,7 @@ func TestFSx_FileSystem_OpenZFSConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeOut, jsonx.V1())) volumes := describeOut["Volumes"].([]any) require.Len(t, volumes, 1) assert.Equal(t, "fsx", volumes[0].(map[string]any)["Name"]) @@ -150,7 +152,7 @@ func TestFSx_CreateFileSystem_MissingConfiguration(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "MissingFileSystemConfiguration", out["__type"]) }) } @@ -210,7 +212,7 @@ func TestFSx_CreateFileSystem_RequiredConfigMembers(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "BadRequest", out["__type"]) }) } @@ -235,7 +237,7 @@ func TestFSx_UpdateFileSystem_TypeSpecificConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fs := out["FileSystem"].(map[string]any) winCfg := fs["WindowsConfiguration"].(map[string]any) assert.InEpsilon(t, float64(32), winCfg["ThroughputCapacity"], 0) @@ -257,7 +259,7 @@ func TestFSx_CreateFileSystemFromBackup_CarriesTypeConfig(t *testing.T) { require.Equal(t, http.StatusOK, backupRec.Code) var backupOut map[string]any - require.NoError(t, json.Unmarshal(backupRec.Body.Bytes(), &backupOut)) + require.NoError(t, json.Unmarshal(backupRec.Body.Bytes(), &backupOut, jsonx.V1())) backupID := backupOut["Backup"].(map[string]any)["BackupId"].(string) restoreRec := doFSxRequest( @@ -269,7 +271,7 @@ func TestFSx_CreateFileSystemFromBackup_CarriesTypeConfig(t *testing.T) { require.Equal(t, http.StatusOK, restoreRec.Code) var restoreOut map[string]any - require.NoError(t, json.Unmarshal(restoreRec.Body.Bytes(), &restoreOut)) + require.NoError(t, json.Unmarshal(restoreRec.Body.Bytes(), &restoreOut, jsonx.V1())) restoredFS := restoreOut["FileSystem"].(map[string]any) require.Contains(t, restoredFS, "WindowsConfiguration") diff --git a/services/fsx/file_systems.go b/services/fsx/file_systems.go index b26db974dd..180699575a 100644 --- a/services/fsx/file_systems.go +++ b/services/fsx/file_systems.go @@ -1,7 +1,7 @@ package fsx import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "regexp" @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // storedFileSystem is the persisted form of a FileSystem. @@ -486,7 +487,7 @@ func fingerprintCreateFileSystemInput(input *createFileSystemInput) (string, err cp := *input cp.ClientRequestToken = "" - b, err := json.Marshal(cp) + b, err := json.Marshal(cp, jsonx.V1()) if err != nil { return "", fmt.Errorf("canonicalize CreateFileSystem request: %w", err) } diff --git a/services/fsx/handler.go b/services/fsx/handler.go index ba6804a5ec..c1ef5bbf11 100644 --- a/services/fsx/handler.go +++ b/services/fsx/handler.go @@ -2,7 +2,7 @@ package fsx import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -198,7 +199,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ResourceARN string `json:"ResourceARN"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ResourceARN != "": @@ -326,7 +327,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - out, err := json.Marshal(result) + out, err := json.Marshal(result, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/fsx/handler_backups_test.go b/services/fsx/handler_backups_test.go index 88fe101835..d32a710184 100644 --- a/services/fsx/handler_backups_test.go +++ b/services/fsx/handler_backups_test.go @@ -1,7 +1,7 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -22,7 +23,7 @@ func TestFSx_Backup(t *testing.T) { rec := doFSxRequest(t, h, "CreateBackup", map[string]any{"FileSystemId": fsID}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bk := resp["Backup"].(map[string]any) assert.Contains(t, bk["BackupId"].(string), "backup-") assert.Contains(t, bk["ResourceARN"].(string), "arn:aws:fsx:") @@ -46,7 +47,7 @@ func TestFSx_Backup(t *testing.T) { rec := doFSxRequest(t, h, "DescribeBackups", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bks := resp["Backups"].([]any) assert.Len(t, bks, 2) }) @@ -58,7 +59,7 @@ func TestFSx_Backup(t *testing.T) { rec := doFSxRequest(t, h, "DescribeBackups", map[string]any{"BackupIds": []string{bkID}}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bks := resp["Backups"].([]any) require.Len(t, bks, 1) assert.Equal(t, bkID, bks[0].(map[string]any)["BackupId"]) @@ -92,7 +93,7 @@ func TestFSx_Backup(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["FileSystem"].(map[string]any) assert.Contains(t, fs["FileSystemId"].(string), "fs-") assert.Equal(t, "AVAILABLE", fs["Lifecycle"]) @@ -141,7 +142,7 @@ func TestFSx_CreateFileSystemFromBackup_FileSystemTypeVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["FileSystem"].(map[string]any) got, _ := fs["FileSystemTypeVersion"].(string) @@ -184,7 +185,7 @@ func TestFSx_CopyBackup(t *testing.T) { if tc.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) bk := out["Backup"].(map[string]any) assert.Contains(t, bk["BackupId"].(string), "backup-") newID := bk["BackupId"].(string) @@ -219,7 +220,7 @@ func TestFSx_DeleteBackup_Response(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, bkID, resp["BackupId"]) assert.Equal(t, tc.wantLC, resp["Lifecycle"]) }) @@ -261,7 +262,7 @@ func TestFSx_BackupNotFound_ErrorCode(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) }) } diff --git a/services/fsx/handler_data_repository_associations_test.go b/services/fsx/handler_data_repository_associations_test.go index 4a5e343788..73cebdef89 100644 --- a/services/fsx/handler_data_repository_associations_test.go +++ b/services/fsx/handler_data_repository_associations_test.go @@ -1,13 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -42,7 +43,7 @@ func TestFSx_DataRepositoryAssociation(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) a := out["Association"].(map[string]any) assert.Contains(t, a["AssociationId"].(string), "dra-") assert.Equal(t, tc.fsPath, a["FileSystemPath"]) @@ -67,7 +68,7 @@ func TestFSx_DataRepositoryAssociationLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) id := cr["Association"].(map[string]any)["AssociationId"].(string) assert.Equal(t, 1, fsx.DRACount(b)) @@ -79,7 +80,7 @@ func TestFSx_DataRepositoryAssociationLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur, jsonx.V1())) assert.Equal(t, "s3://bucket/new", ur["Association"].(map[string]any)["DataRepositoryPath"]) // describe @@ -88,7 +89,7 @@ func TestFSx_DataRepositoryAssociationLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec3.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["Associations"].([]any), 1) // delete diff --git a/services/fsx/handler_data_repository_tasks_test.go b/services/fsx/handler_data_repository_tasks_test.go index af58826f38..65004c612c 100644 --- a/services/fsx/handler_data_repository_tasks_test.go +++ b/services/fsx/handler_data_repository_tasks_test.go @@ -1,13 +1,15 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestFSx_DataRepositoryTask(t *testing.T) { @@ -45,7 +47,7 @@ func TestFSx_DataRepositoryTask(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) task := out["DataRepositoryTask"].(map[string]any) assert.Contains(t, task["TaskId"].(string), "task-") assert.Equal(t, "EXECUTING", task["Lifecycle"]) @@ -105,7 +107,7 @@ func TestFSx_DataRepositoryTaskLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) taskID := cr["DataRepositoryTask"].(map[string]any)["TaskId"].(string) // describe @@ -114,14 +116,14 @@ func TestFSx_DataRepositoryTaskLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["DataRepositoryTasks"].([]any), 1) // cancel rec3 := doFSxRequest(t, h, "CancelDataRepositoryTask", map[string]any{"TaskId": taskID}) require.Equal(t, http.StatusOK, rec3.Code) var car map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &car)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &car, jsonx.V1())) assert.Equal(t, "CANCELING", car["Lifecycle"]) }) } @@ -158,7 +160,7 @@ func TestFSx_CreateDataRepositoryTask_RejectsConcurrentExecuting(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "DataRepositoryTaskExecuting", out["__type"]) }) @@ -183,7 +185,7 @@ func TestFSx_CreateDataRepositoryTask_RejectsConcurrentExecuting(t *testing.T) { rec1 := doFSxRequest(t, h, "CreateDataRepositoryTask", newTaskBody(fsID)) require.Equal(t, http.StatusOK, rec1.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &cr, jsonx.V1())) taskID := cr["DataRepositoryTask"].(map[string]any)["TaskId"].(string) cancelRec := doFSxRequest(t, h, "CancelDataRepositoryTask", map[string]any{"TaskId": taskID}) @@ -242,7 +244,7 @@ func TestDataRepositoryTask_TagsStoredAtCreation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) task := out["DataRepositoryTask"].(map[string]any) taskARN := task["ResourceARN"].(string) @@ -253,7 +255,7 @@ func TestDataRepositoryTask_TagsStoredAtCreation(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, "ListTagsForResource on DRT must succeed") var tagOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagOut, jsonx.V1())) tags, ok := tagOut["Tags"].([]any) require.True(t, ok, "Tags must be a JSON array") @@ -279,7 +281,7 @@ func TestDataRepositoryTask_TagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) taskARN := out["DataRepositoryTask"].(map[string]any)["ResourceARN"].(string) // TagResource on a DRT ARN must succeed. @@ -294,7 +296,7 @@ func TestDataRepositoryTask_TagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var tagOut map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagOut)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagOut, jsonx.V1())) tags := tagOut["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "added", tags[0].(map[string]any)["Key"]) diff --git a/services/fsx/handler_file_caches_test.go b/services/fsx/handler_file_caches_test.go index 2bfb7686ba..efe22302fd 100644 --- a/services/fsx/handler_file_caches_test.go +++ b/services/fsx/handler_file_caches_test.go @@ -1,13 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -83,7 +84,7 @@ func TestFSx_CreateFileCache_RequiresLustreConfiguration(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tc.wantType, out["__type"]) }) } @@ -104,7 +105,7 @@ func TestFSx_CreateFileCache_RequiresLustreConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fc := out["FileCache"].(map[string]any) lc, ok := fc["LustreConfiguration"].(map[string]any) require.True(t, ok, "response must have a LustreConfiguration block") @@ -188,7 +189,7 @@ func TestFSx_FileCache(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) c := out["FileCache"].(map[string]any) assert.Contains(t, c["FileCacheId"].(string), "fc-") assert.Equal(t, "AVAILABLE", c["Lifecycle"]) @@ -218,7 +219,7 @@ func TestFSx_FileCacheLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["FileCaches"].([]any), 1) // update @@ -234,7 +235,7 @@ func TestFSx_FileCacheLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur, jsonx.V1())) lc := ur["FileCache"].(map[string]any)["LustreConfiguration"].(map[string]any) assert.Equal(t, "3:04:05", lc["WeeklyMaintenanceStartTime"]) @@ -242,7 +243,7 @@ func TestFSx_FileCacheLifecycle(t *testing.T) { rec3 := doFSxRequest(t, h, "DeleteFileCache", map[string]any{"FileCacheId": fcID}) require.Equal(t, http.StatusOK, rec3.Code) var del map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del, jsonx.V1())) assert.Equal(t, fcID, del["FileCacheId"]) assert.Equal(t, "DELETING", del["Lifecycle"]) assert.Equal(t, 0, fsx.FileCacheCount(b)) diff --git a/services/fsx/handler_file_systems_test.go b/services/fsx/handler_file_systems_test.go index d2d3cbc2dd..2581fd80bb 100644 --- a/services/fsx/handler_file_systems_test.go +++ b/services/fsx/handler_file_systems_test.go @@ -1,13 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -29,7 +30,7 @@ func TestFSx_FileSystem(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["FileSystem"].(map[string]any) assert.Contains(t, fs["FileSystemId"].(string), "fs-") assert.Contains(t, fs["ResourceARN"].(string), "arn:aws:fsx:") @@ -52,7 +53,7 @@ func TestFSx_FileSystem(t *testing.T) { rec := doFSxRequest(t, h, "DescribeFileSystems", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fss := resp["FileSystems"].([]any) assert.Len(t, fss, 2) }) @@ -65,7 +66,7 @@ func TestFSx_FileSystem(t *testing.T) { rec := doFSxRequest(t, h, "DescribeFileSystems", map[string]any{"FileSystemIds": []string{id}}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fss := resp["FileSystems"].([]any) require.Len(t, fss, 1) assert.Equal(t, id, fss[0].(map[string]any)["FileSystemId"]) @@ -101,7 +102,7 @@ func TestFSx_FileSystem(t *testing.T) { rec := doFSxRequest(t, h, "UpdateFileSystem", map[string]any{"FileSystemId": id, "StorageCapacity": 64}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["FileSystem"].(map[string]any) assert.Equal(t, id, fs["FileSystemId"]) }) @@ -131,13 +132,13 @@ func TestFSx_FileSystem(t *testing.T) { rec1 := doFSxRequest(t, h, "CreateFileSystem", body) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) fs1 := resp1["FileSystem"].(map[string]any) rec2 := doFSxRequest(t, h, "CreateFileSystem", body) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) fs2 := resp2["FileSystem"].(map[string]any) // Prove it's the SAME resource, not just a second success: identical @@ -169,7 +170,7 @@ func TestFSx_FileSystem(t *testing.T) { }) require.Equal(t, http.StatusBadRequest, rec2.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "IncompatibleParameterError", errResp["__type"]) assert.Equal(t, 1, fsx.FileSystemCount(fsx.GetBackend(h)), "mismatched retry must not create a resource") }) @@ -189,8 +190,8 @@ func TestFSx_FileSystem(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp1, resp2 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.NotEqual( t, resp1["FileSystem"].(map[string]any)["FileSystemId"], @@ -213,7 +214,7 @@ func TestFSx_FileSystem(t *testing.T) { }) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidNetworkSettings", errResp["__type"]) }) @@ -228,7 +229,7 @@ func TestFSx_FileSystem(t *testing.T) { }) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidNetworkSettings", errResp["__type"]) }) @@ -404,7 +405,7 @@ func TestFSx_DeleteFileSystem_Response(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantID { assert.Equal(t, id, resp["FileSystemId"]) @@ -458,7 +459,7 @@ func TestFSx_Aliases(t *testing.T) { require.Equal(t, tc.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) aliases := out["Aliases"].([]any) assert.Len(t, aliases, tc.wantCount) assert.Equal(t, tc.lifecycle, aliases[0].(map[string]any)["Lifecycle"]) @@ -482,7 +483,7 @@ func TestFSx_AliasesLifecycle(t *testing.T) { rec := doFSxRequest(t, h, "DescribeFileSystemAliases", map[string]any{"FileSystemId": fsID}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Aliases"].([]any), 1) }) @@ -502,14 +503,14 @@ func TestFSx_AliasesLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) aliases := out["Aliases"].([]any) assert.Len(t, aliases, 1) assert.Equal(t, "DELETING", aliases[0].(map[string]any)["Lifecycle"]) rec2 := doFSxRequest(t, h, "DescribeFileSystemAliases", map[string]any{"FileSystemId": fsID}) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Empty(t, out2["Aliases"].([]any)) }) } @@ -558,7 +559,7 @@ func TestFSx_ReleaseFileSystemNfsV3Locks(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["FileSystem"]) } }) @@ -605,7 +606,7 @@ func TestFSx_StartMisconfiguredStateRecovery(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["FileSystem"]) } }) diff --git a/services/fsx/handler_s3_access_points_test.go b/services/fsx/handler_s3_access_points_test.go index 201c703aa6..1f9f7f3965 100644 --- a/services/fsx/handler_s3_access_points_test.go +++ b/services/fsx/handler_s3_access_points_test.go @@ -1,12 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestFSx_S3AccessPoint(t *testing.T) { @@ -59,7 +61,7 @@ func TestFSx_S3AccessPoint(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ap := out["S3AccessPointAttachment"].(map[string]any) assert.Equal(t, tc.apName, ap["Name"]) assert.Equal(t, "AVAILABLE", ap["Lifecycle"]) @@ -93,7 +95,7 @@ func TestFSx_S3AccessPointLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["S3AccessPointAttachments"].([]any), 1) // detach: real DetachAndDeleteS3AccessPointInput has no FileSystemId member. @@ -106,7 +108,7 @@ func TestFSx_S3AccessPointLifecycle(t *testing.T) { rec3 := doFSxRequest(t, h, "DescribeS3AccessPointAttachments", map[string]any{}) require.Equal(t, http.StatusOK, rec3.Code) var dr2 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &dr2)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &dr2, jsonx.V1())) assert.Empty(t, dr2["S3AccessPointAttachments"].([]any)) }) } diff --git a/services/fsx/handler_shared_vpc_configuration_test.go b/services/fsx/handler_shared_vpc_configuration_test.go index eb98d13165..466437fae0 100644 --- a/services/fsx/handler_shared_vpc_configuration_test.go +++ b/services/fsx/handler_shared_vpc_configuration_test.go @@ -1,12 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestFSx_SharedVpcConfiguration(t *testing.T) { @@ -47,7 +49,7 @@ func TestFSx_SharedVpcConfiguration(t *testing.T) { rec := doFSxRequest(t, h, "DescribeSharedVpcConfiguration", map[string]any{}) require.Equal(t, tc.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tc.wantEnabled, out["EnableFsxRouteTableUpdatesFromParticipantAccounts"]) }) } diff --git a/services/fsx/handler_snapshots_test.go b/services/fsx/handler_snapshots_test.go index edabddc164..bad63fb805 100644 --- a/services/fsx/handler_snapshots_test.go +++ b/services/fsx/handler_snapshots_test.go @@ -1,13 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -49,7 +50,7 @@ func TestFSx_Snapshot(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) s := out["Snapshot"].(map[string]any) assert.Contains(t, s["SnapshotId"].(string), "fsvolsnap-") assert.Equal(t, "AVAILABLE", s["Lifecycle"]) @@ -75,7 +76,7 @@ func TestFSx_SnapshotLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) snapID := cr["Snapshot"].(map[string]any)["SnapshotId"].(string) assert.Equal(t, 1, fsx.SnapshotCount(b)) @@ -86,7 +87,7 @@ func TestFSx_SnapshotLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["Snapshots"].([]any), 1) // update @@ -96,14 +97,14 @@ func TestFSx_SnapshotLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec3.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &ur, jsonx.V1())) assert.Equal(t, "snap-renamed", ur["Snapshot"].(map[string]any)["Name"]) // delete rec4 := doFSxRequest(t, h, "DeleteSnapshot", map[string]any{"SnapshotId": snapID}) require.Equal(t, http.StatusOK, rec4.Code) var del map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &del)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &del, jsonx.V1())) assert.Equal(t, snapID, del["SnapshotId"]) assert.Equal(t, "DELETING", del["Lifecycle"]) assert.Equal(t, 0, fsx.SnapshotCount(b)) @@ -125,7 +126,7 @@ func TestFSx_CopySnapshotAndUpdateVolume(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) snapARN := cr["Snapshot"].(map[string]any)["ResourceARN"].(string) rec2 := doFSxRequest(t, h, "CopySnapshotAndUpdateVolume", map[string]any{ @@ -134,7 +135,7 @@ func TestFSx_CopySnapshotAndUpdateVolume(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, volID, out["VolumeId"]) assert.NotEmpty(t, out["Lifecycle"]) actions := out["AdministrativeActions"].([]any) diff --git a/services/fsx/handler_storage_virtual_machines_test.go b/services/fsx/handler_storage_virtual_machines_test.go index 148b0cb082..9b451f122c 100644 --- a/services/fsx/handler_storage_virtual_machines_test.go +++ b/services/fsx/handler_storage_virtual_machines_test.go @@ -1,13 +1,14 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -50,7 +51,7 @@ func TestFSx_StorageVirtualMachine(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svm := out["StorageVirtualMachine"].(map[string]any) assert.Contains(t, svm["StorageVirtualMachineId"].(string), "svm-") assert.Equal(t, "CREATED", svm["Lifecycle"]) @@ -78,7 +79,7 @@ func TestFSx_SVMLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["StorageVirtualMachines"].([]any), 1) // update: SvmAdminPassword is the real op's only scalar updatable @@ -90,7 +91,7 @@ func TestFSx_SVMLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur, jsonx.V1())) assert.Equal(t, svmID, ur["StorageVirtualMachine"].(map[string]any)["StorageVirtualMachineId"]) // delete @@ -99,7 +100,7 @@ func TestFSx_SVMLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec3.Code) var del map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del, jsonx.V1())) assert.Equal(t, svmID, del["StorageVirtualMachineId"]) assert.Equal(t, "DELETING", del["Lifecycle"]) assert.Equal(t, 0, fsx.SVMCount(b)) diff --git a/services/fsx/handler_tags_test.go b/services/fsx/handler_tags_test.go index 4ed4d665ec..bec07c5fb2 100644 --- a/services/fsx/handler_tags_test.go +++ b/services/fsx/handler_tags_test.go @@ -1,7 +1,8 @@ package fsx_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -25,7 +27,7 @@ func TestFSx_Tags(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["FileSystem"].(map[string]any)["ResourceARN"].(string) } @@ -52,7 +54,7 @@ func TestFSx_Tags(t *testing.T) { rec := doFSxRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].([]any) assert.Len(t, tags, 1) tag := tags[0].(map[string]any) @@ -72,7 +74,7 @@ func TestFSx_Tags(t *testing.T) { rec := doFSxRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].([]any) assert.Len(t, tags, 1) assert.Equal(t, "Name", tags[0].(map[string]any)["Key"]) @@ -148,7 +150,7 @@ func TestFSx_TagValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arn := out["FileSystem"].(map[string]any)["ResourceARN"].(string) @@ -160,7 +162,7 @@ func TestFSx_TagValidation(t *testing.T) { if tc.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) } }) @@ -204,7 +206,7 @@ func TestFSx_TagValidation_OnCreate(t *testing.T) { if tc.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) } }) @@ -227,7 +229,7 @@ func TestFSx_TagLimit(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arn := out["FileSystem"].(map[string]any)["ResourceARN"].(string) @@ -248,7 +250,7 @@ func TestFSx_TagLimit(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // TagResource's own switch (fsx@v1.68.4 deserializers.go // deserializeOpErrorTagResource) does not declare // ServiceLimitExceeded; BadRequest is the correct wire type @@ -269,7 +271,7 @@ func TestFSx_TagLimit(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arn := out["FileSystem"].(map[string]any)["ResourceARN"].(string) @@ -312,7 +314,7 @@ func TestListTagsForResource_EmptyIsArray(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["FileSystem"].(map[string]any)["ResourceARN"].(string) }, @@ -324,7 +326,7 @@ func TestListTagsForResource_EmptyIsArray(t *testing.T) { rec := doFSxRequest(t, h, "CreateBackup", map[string]any{"FileSystemId": fsID}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["Backup"].(map[string]any)["ResourceARN"].(string) }, @@ -342,8 +344,8 @@ func TestListTagsForResource_EmptyIsArray(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) // Parse the raw JSON to verify "Tags" is an array, not null. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) tagsRaw, ok := raw["Tags"] require.True(t, ok, "Tags key must be present") @@ -398,7 +400,7 @@ func Test_TagOps_UnknownARN_ReturnsResourceNotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ResourceNotFound", errBody["__type"], "%s on an unknown ARN must return the generic ResourceNotFound code", tt.op) }) diff --git a/services/fsx/handler_test.go b/services/fsx/handler_test.go index 1147cc1db1..4e83ca551a 100644 --- a/services/fsx/handler_test.go +++ b/services/fsx/handler_test.go @@ -2,7 +2,7 @@ package fsx_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -33,7 +34,7 @@ func doFSxRequest(t *testing.T, h *fsx.Handler, op string, body any) *httptest.R if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -76,7 +77,7 @@ func createFS(t *testing.T, h *fsx.Handler, fsType string) string { rec := doFSxRequest(t, h, "CreateFileSystem", fileSystemCreateBody(fsType)) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["FileSystem"].(map[string]any)["FileSystemId"].(string) } @@ -87,7 +88,7 @@ func createFSandBackup(t *testing.T, h *fsx.Handler, fsType string) string { rec := doFSxRequest(t, h, "CreateBackup", map[string]any{"FileSystemId": fsID}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["Backup"].(map[string]any)["BackupId"].(string) } @@ -123,7 +124,7 @@ func createVolume(t *testing.T, h *fsx.Handler, fsID, volType, name string) stri rec := doFSxRequest(t, h, "CreateVolume", body) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["Volume"].(map[string]any)["VolumeId"].(string) } @@ -136,7 +137,7 @@ func openZFSRootVolumeID(t *testing.T, h *fsx.Handler, fsID string) string { rec := doFSxRequest(t, h, "DescribeFileSystems", map[string]any{"FileSystemIds": []string{fsID}}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fileSystems, ok := out["FileSystems"].([]any) require.True(t, ok) require.Len(t, fileSystems, 1) @@ -156,7 +157,7 @@ func createSVM(t *testing.T, h *fsx.Handler, fsID, name string) string { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["StorageVirtualMachine"].(map[string]any)["StorageVirtualMachineId"].(string) } @@ -172,7 +173,7 @@ func createFileCache(t *testing.T, h *fsx.Handler, cacheType string) string { // }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["FileCache"].(map[string]any)["FileCacheId"].(string) } @@ -196,7 +197,7 @@ func decodeField(t *testing.T, rec *httptest.ResponseRecorder, field string) map require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) obj, ok := out[field].(map[string]any) require.True(t, ok, "response must contain object field %q, got %v", field, out) diff --git a/services/fsx/handler_volumes_test.go b/services/fsx/handler_volumes_test.go index 3d2cc3ff14..1fc4af2f55 100644 --- a/services/fsx/handler_volumes_test.go +++ b/services/fsx/handler_volumes_test.go @@ -1,7 +1,7 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -74,7 +75,7 @@ func TestFSx_Volume(t *testing.T) { if !tc.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) v := out["Volume"].(map[string]any) assert.Contains(t, v["VolumeId"].(string), "fsvol-") assert.Equal(t, "AVAILABLE", v["Lifecycle"]) @@ -102,7 +103,7 @@ func TestFSx_VolumeLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var dr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dr, jsonx.V1())) assert.Len(t, dr["Volumes"].([]any), 1) // update @@ -112,14 +113,14 @@ func TestFSx_VolumeLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &ur, jsonx.V1())) assert.Equal(t, "vol-renamed", ur["Volume"].(map[string]any)["Name"]) // delete rec3 := doFSxRequest(t, h, "DeleteVolume", map[string]any{"VolumeId": volID}) require.Equal(t, http.StatusOK, rec3.Code) var del map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &del, jsonx.V1())) assert.Equal(t, volID, del["VolumeId"]) assert.Equal(t, "DELETING", del["Lifecycle"]) assert.Equal(t, 0, fsx.VolumeCount(b)) @@ -142,7 +143,7 @@ func TestFSx_CreateVolumeFromBackup(t *testing.T) { }) require.Equal(t, http.StatusOK, svmRec.Code) var svmOut map[string]any - require.NoError(t, json.Unmarshal(svmRec.Body.Bytes(), &svmOut)) + require.NoError(t, json.Unmarshal(svmRec.Body.Bytes(), &svmOut, jsonx.V1())) svmID := svmOut["StorageVirtualMachine"].(map[string]any)["StorageVirtualMachineId"].(string) rec := doFSxRequest(t, h, "CreateVolumeFromBackup", map[string]any{ @@ -155,7 +156,7 @@ func TestFSx_CreateVolumeFromBackup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) v := out["Volume"].(map[string]any) assert.Contains(t, v["VolumeId"].(string), "fsvol-") assert.Equal(t, "restored-vol", v["Name"]) @@ -176,7 +177,7 @@ func TestFSx_CreateVolumeFromBackup(t *testing.T) { }) require.Equal(t, http.StatusOK, svmRec.Code) var svmOut map[string]any - require.NoError(t, json.Unmarshal(svmRec.Body.Bytes(), &svmOut)) + require.NoError(t, json.Unmarshal(svmRec.Body.Bytes(), &svmOut, jsonx.V1())) svmID := svmOut["StorageVirtualMachine"].(map[string]any)["StorageVirtualMachineId"].(string) rec := doFSxRequest(t, h, "CreateVolumeFromBackup", map[string]any{ @@ -219,7 +220,7 @@ func TestFSx_RestoreVolumeFromSnapshot(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) snapID := cr["Snapshot"].(map[string]any)["SnapshotId"].(string) // restore @@ -229,7 +230,7 @@ func TestFSx_RestoreVolumeFromSnapshot(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, volID, out["VolumeId"]) assert.NotEmpty(t, out["Lifecycle"]) actions := out["AdministrativeActions"].([]any) diff --git a/services/fsx/idempotency.go b/services/fsx/idempotency.go index 624a8e1e28..4a5374c793 100644 --- a/services/fsx/idempotency.go +++ b/services/fsx/idempotency.go @@ -1,9 +1,11 @@ package fsx import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const clientRequestTokenField = "ClientRequestToken" @@ -11,19 +13,19 @@ const clientRequestTokenField = "ClientRequestToken" // tokenFingerprint canonicalises input minus its ClientRequestToken so a retry // with the same parameters hashes equal and a changed one does not. func tokenFingerprint(input any) (string, error) { - raw, err := json.Marshal(input) + raw, err := json.Marshal(input, jsonx.V1()) if err != nil { return "", fmt.Errorf("canonicalize request: %w", err) } var fields map[string]any - if err = json.Unmarshal(raw, &fields); err != nil { + if err = json.Unmarshal(raw, &fields, jsonx.V1()); err != nil { return "", fmt.Errorf("canonicalize request: %w", err) } delete(fields, clientRequestTokenField) - out, err := json.Marshal(fields) + out, err := json.Marshal(fields, jsonx.V1()) if err != nil { return "", fmt.Errorf("canonicalize request: %w", err) } diff --git a/services/fsx/interfaces.go b/services/fsx/interfaces.go index 9471a45ac5..b78437d688 100644 --- a/services/fsx/interfaces.go +++ b/services/fsx/interfaces.go @@ -2,7 +2,7 @@ package fsx import ( "context" - "encoding/json" + "encoding/json/jsontext" "strconv" "time" ) @@ -230,10 +230,10 @@ type LustreConfiguration struct { DriveCacheType string `json:"DriveCacheType,omitempty"` MountName string `json:"MountName,omitempty"` WeeklyMaintenanceStartTime string `json:"WeeklyMaintenanceStartTime,omitempty"` - RootSquashConfiguration json.RawMessage `json:"RootSquashConfiguration,omitempty"` - DataReadCacheConfiguration json.RawMessage `json:"DataReadCacheConfiguration,omitempty"` - MetadataConfiguration json.RawMessage `json:"MetadataConfiguration,omitempty"` - LogConfiguration json.RawMessage `json:"LogConfiguration,omitempty"` + RootSquashConfiguration jsontext.Value `json:"RootSquashConfiguration,omitempty"` + DataReadCacheConfiguration jsontext.Value `json:"DataReadCacheConfiguration,omitempty"` + MetadataConfiguration jsontext.Value `json:"MetadataConfiguration,omitempty"` + LogConfiguration jsontext.Value `json:"LogConfiguration,omitempty"` AutomaticBackupRetentionDays int32 `json:"AutomaticBackupRetentionDays,omitempty"` ThroughputCapacity int32 `json:"ThroughputCapacity,omitempty"` PerUnitStorageThroughput int32 `json:"PerUnitStorageThroughput,omitempty"` diff --git a/services/fsx/lustre_config.go b/services/fsx/lustre_config.go index 12996df350..6b0380e39e 100644 --- a/services/fsx/lustre_config.go +++ b/services/fsx/lustre_config.go @@ -1,10 +1,13 @@ package fsx import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -25,41 +28,41 @@ var weeklyMaintenancePattern = regexp.MustCompile(`^[1-7]:([01]\d|2[0-3]):?([0-5 // createLustreConfiguration mirrors CreateFileSystemLustreConfiguration. type createLustreConfiguration struct { - DriveCacheType string `json:"DriveCacheType,omitempty"` - ImportPath string `json:"ImportPath,omitempty"` - WeeklyMaintenanceStartTime string `json:"WeeklyMaintenanceStartTime,omitempty"` - ExportPath string `json:"ExportPath,omitempty"` - DeploymentType string `json:"DeploymentType,omitempty"` - AutoImportPolicy string `json:"AutoImportPolicy,omitempty"` - DailyAutomaticBackupStartTime string `json:"DailyAutomaticBackupStartTime,omitempty"` - DataCompressionType string `json:"DataCompressionType,omitempty"` - DataReadCacheConfiguration json.RawMessage `json:"DataReadCacheConfiguration,omitempty"` - RootSquashConfiguration json.RawMessage `json:"RootSquashConfiguration,omitempty"` - LogConfiguration json.RawMessage `json:"LogConfiguration,omitempty"` - MetadataConfiguration json.RawMessage `json:"MetadataConfiguration,omitempty"` - AutomaticBackupRetentionDays int32 `json:"AutomaticBackupRetentionDays,omitempty"` - ImportedFileChunkSize int32 `json:"ImportedFileChunkSize,omitempty"` - PerUnitStorageThroughput int32 `json:"PerUnitStorageThroughput,omitempty"` - ThroughputCapacity int32 `json:"ThroughputCapacity,omitempty"` - CopyTagsToBackups bool `json:"CopyTagsToBackups,omitempty"` - EfaEnabled bool `json:"EfaEnabled,omitempty"` + DriveCacheType string `json:"DriveCacheType,omitempty"` + ImportPath string `json:"ImportPath,omitempty"` + WeeklyMaintenanceStartTime string `json:"WeeklyMaintenanceStartTime,omitempty"` + ExportPath string `json:"ExportPath,omitempty"` + DeploymentType string `json:"DeploymentType,omitempty"` + AutoImportPolicy string `json:"AutoImportPolicy,omitempty"` + DailyAutomaticBackupStartTime string `json:"DailyAutomaticBackupStartTime,omitempty"` + DataCompressionType string `json:"DataCompressionType,omitempty"` + DataReadCacheConfiguration jsontext.Value `json:"DataReadCacheConfiguration,omitempty"` + RootSquashConfiguration jsontext.Value `json:"RootSquashConfiguration,omitempty"` + LogConfiguration jsontext.Value `json:"LogConfiguration,omitempty"` + MetadataConfiguration jsontext.Value `json:"MetadataConfiguration,omitempty"` + AutomaticBackupRetentionDays int32 `json:"AutomaticBackupRetentionDays,omitempty"` + ImportedFileChunkSize int32 `json:"ImportedFileChunkSize,omitempty"` + PerUnitStorageThroughput int32 `json:"PerUnitStorageThroughput,omitempty"` + ThroughputCapacity int32 `json:"ThroughputCapacity,omitempty"` + CopyTagsToBackups bool `json:"CopyTagsToBackups,omitempty"` + EfaEnabled bool `json:"EfaEnabled,omitempty"` } // storedLustreExtras keeps the Lustre members that have no counterpart on the shared // storedFileSystem fields. type storedLustreExtras struct { - AutoImportPolicy string `json:"autoImportPolicy,omitempty"` - DataCompressionType string `json:"dataCompression,omitempty"` - DriveCacheType string `json:"driveCache,omitempty"` - ExportPath string `json:"exportPath,omitempty"` - ImportPath string `json:"importPath,omitempty"` - RootSquashConfiguration json.RawMessage `json:"rootSquash,omitempty"` - DataReadCacheConfiguration json.RawMessage `json:"dataReadCache,omitempty"` - MetadataConfiguration json.RawMessage `json:"metadata,omitempty"` - LogConfiguration json.RawMessage `json:"log,omitempty"` - ImportedFileChunkSize int32 `json:"importedChunk,omitempty"` - PerUnitStorageThroughput int32 `json:"perUnitThroughput,omitempty"` - EfaEnabled bool `json:"efa,omitempty"` + AutoImportPolicy string `json:"autoImportPolicy,omitempty"` + DataCompressionType string `json:"dataCompression,omitempty"` + DriveCacheType string `json:"driveCache,omitempty"` + ExportPath string `json:"exportPath,omitempty"` + ImportPath string `json:"importPath,omitempty"` + RootSquashConfiguration jsontext.Value `json:"rootSquash,omitempty"` + DataReadCacheConfiguration jsontext.Value `json:"dataReadCache,omitempty"` + MetadataConfiguration jsontext.Value `json:"metadata,omitempty"` + LogConfiguration jsontext.Value `json:"log,omitempty"` + ImportedFileChunkSize int32 `json:"importedChunk,omitempty"` + PerUnitStorageThroughput int32 `json:"perUnitThroughput,omitempty"` + EfaEnabled bool `json:"efa,omitempty"` } func validLustreThroughputs(deployment, storageType string) []int32 { @@ -230,12 +233,12 @@ func applyLustreConfig(fs *storedFileSystem, cfg *createLustreConfiguration) err } // metadataMode extracts MetadataConfiguration.Mode for defaultLustreVersion. -func metadataMode(raw json.RawMessage) string { +func metadataMode(raw jsontext.Value) string { var m struct { Mode string `json:"Mode"` } - if len(raw) == 0 || json.Unmarshal(raw, &m) != nil { + if len(raw) == 0 || json.Unmarshal(raw, &m, jsonx.V1()) != nil { return "" } @@ -244,17 +247,17 @@ func metadataMode(raw json.RawMessage) string { // updateLustreConfiguration mirrors UpdateFileSystemLustreConfiguration. type updateLustreConfiguration struct { - AutoImportPolicy string `json:"AutoImportPolicy,omitempty"` - DailyAutomaticBackupStartTime string `json:"DailyAutomaticBackupStartTime,omitempty"` - DataCompressionType string `json:"DataCompressionType,omitempty"` - WeeklyMaintenanceStartTime string `json:"WeeklyMaintenanceStartTime,omitempty"` - RootSquashConfiguration json.RawMessage `json:"RootSquashConfiguration,omitempty"` - DataReadCacheConfiguration json.RawMessage `json:"DataReadCacheConfiguration,omitempty"` - MetadataConfiguration json.RawMessage `json:"MetadataConfiguration,omitempty"` - LogConfiguration json.RawMessage `json:"LogConfiguration,omitempty"` - AutomaticBackupRetentionDays int32 `json:"AutomaticBackupRetentionDays,omitempty"` - PerUnitStorageThroughput int32 `json:"PerUnitStorageThroughput,omitempty"` - ThroughputCapacity int32 `json:"ThroughputCapacity,omitempty"` + AutoImportPolicy string `json:"AutoImportPolicy,omitempty"` + DailyAutomaticBackupStartTime string `json:"DailyAutomaticBackupStartTime,omitempty"` + DataCompressionType string `json:"DataCompressionType,omitempty"` + WeeklyMaintenanceStartTime string `json:"WeeklyMaintenanceStartTime,omitempty"` + RootSquashConfiguration jsontext.Value `json:"RootSquashConfiguration,omitempty"` + DataReadCacheConfiguration jsontext.Value `json:"DataReadCacheConfiguration,omitempty"` + MetadataConfiguration jsontext.Value `json:"MetadataConfiguration,omitempty"` + LogConfiguration jsontext.Value `json:"LogConfiguration,omitempty"` + AutomaticBackupRetentionDays int32 `json:"AutomaticBackupRetentionDays,omitempty"` + PerUnitStorageThroughput int32 `json:"PerUnitStorageThroughput,omitempty"` + ThroughputCapacity int32 `json:"ThroughputCapacity,omitempty"` } func applyLustreUpdate(fs *storedFileSystem, cfg *updateLustreConfiguration) error { @@ -321,7 +324,7 @@ func setIf(dst *string, v string) { } } -func setRaw(dst *json.RawMessage, v json.RawMessage) { +func setRaw(dst *jsontext.Value, v jsontext.Value) { if len(v) > 0 { *dst = v } diff --git a/services/fsx/multi_region_test.go b/services/fsx/multi_region_test.go index 56fcdbcae1..14cd76ff19 100644 --- a/services/fsx/multi_region_test.go +++ b/services/fsx/multi_region_test.go @@ -2,7 +2,7 @@ package fsx_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/fsx/openzfs_volume_config_test.go b/services/fsx/openzfs_volume_config_test.go index a54ba9047b..cc246b9227 100644 --- a/services/fsx/openzfs_volume_config_test.go +++ b/services/fsx/openzfs_volume_config_test.go @@ -1,7 +1,7 @@ package fsx_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -70,7 +71,7 @@ func (f zfsFixture) describe(t *testing.T, volID string) map[string]any { } `json:"Volumes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Volumes, 1) return out.Volumes[0].OpenZFSConfiguration @@ -200,7 +201,7 @@ func TestOpenZFSVolume_CopyTagsToSnapshots(t *testing.T) { } `json:"Snapshot"` } - require.NoError(t, json.Unmarshal(snap.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(snap.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Snapshot.Tags, tt.want) }) } diff --git a/services/fsx/page_token.go b/services/fsx/page_token.go index b611d04f14..f7466c22dc 100644 --- a/services/fsx/page_token.go +++ b/services/fsx/page_token.go @@ -3,8 +3,11 @@ package fsx import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -13,18 +16,18 @@ const ( ) // tokenFromBody returns the body's decoded fields and its non-empty NextToken, if any. -func tokenFromBody(body []byte) (map[string]json.RawMessage, string, bool) { +func tokenFromBody(body []byte) (map[string]jsontext.Value, string, bool) { if !bytes.Contains(body, []byte(`"`+nextTokenField+`"`)) { return nil, "", false } - var m map[string]json.RawMessage - if err := json.Unmarshal(body, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { return nil, "", false } var tok string - if err := json.Unmarshal(m[nextTokenField], &tok); err != nil || tok == "" { + if err := json.Unmarshal(m[nextTokenField], &tok, jsonx.V1()); err != nil || tok == "" { return nil, "", false } @@ -44,9 +47,9 @@ func decodeNextToken(body []byte) ([]byte, error) { return nil, fmt.Errorf("%w: NextToken is not valid", ErrValidation) } - m[nextTokenField], _ = json.Marshal(string(key)) + m[nextTokenField], _ = json.Marshal(string(key), jsonx.V1()) - out, err := json.Marshal(m) + out, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: %w", ErrValidation, err) } @@ -61,9 +64,9 @@ func encodeNextToken(body []byte) []byte { return body } - m[nextTokenField], _ = json.Marshal(base64.RawURLEncoding.EncodeToString([]byte(key))) + m[nextTokenField], _ = json.Marshal(base64.RawURLEncoding.EncodeToString([]byte(key)), jsonx.V1()) - out, err := json.Marshal(m) + out, err := json.Marshal(m, jsonx.V1()) if err != nil { return body } diff --git a/services/fsx/persistence.go b/services/fsx/persistence.go index 7a860acf75..c3388c7700 100644 --- a/services/fsx/persistence.go +++ b/services/fsx/persistence.go @@ -2,7 +2,7 @@ package fsx import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -36,7 +36,7 @@ const fsxSnapshotVersion = 2 // store_setup.go's file doc comment for why they do not fit store.Table's // keyed-by-identity-value shape. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` Aliases map[string][]string `json:"aliases"` CreateFileSystemTokens map[string]fsCreateTokenEntry `json:"createFileSystemTokens"` diff --git a/services/fsx/persistence_test.go b/services/fsx/persistence_test.go index 6e2f86ae3a..6b3f85a752 100644 --- a/services/fsx/persistence_test.go +++ b/services/fsx/persistence_test.go @@ -8,7 +8,7 @@ package fsx_test // sharedVpcEnabled scalar, and a snapshot-version-mismatch test. import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/fsx" ) @@ -148,7 +149,7 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { require.Equal(t, http.StatusOK, dedupRec.Code) var dedupOut map[string]any - require.NoError(t, json.Unmarshal(dedupRec.Body.Bytes(), &dedupOut)) + require.NoError(t, json.Unmarshal(dedupRec.Body.Bytes(), &dedupOut, jsonx.V1())) assert.Equal(t, fsID, dedupOut["FileSystem"].(map[string]any)["FileSystemId"]) assert.Equal( t, fsx.FileSystemCount(b), fsx.FileSystemCount(fresh), "dedup replay must not create a new file system", @@ -190,7 +191,7 @@ func assertFullStateRestored(t *testing.T, h *fsx.Handler, ids fullStateIDs) { require.Equal(t, http.StatusOK, rec.Code) var fsOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fsOut, jsonx.V1())) fsList, ok := fsOut["FileSystems"].([]any) require.True(t, ok) require.Len(t, fsList, 1) @@ -212,7 +213,7 @@ func assertFullStateRestored(t *testing.T, h *fsx.Handler, ids fullStateIDs) { require.Equal(t, http.StatusOK, rec.Code) var aliasOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aliasOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aliasOut, jsonx.V1())) aliasList, ok := aliasOut["Aliases"].([]any) require.True(t, ok) require.Len(t, aliasList, 1) @@ -260,7 +261,7 @@ func assertFullStateRestored(t *testing.T, h *fsx.Handler, ids fullStateIDs) { require.Equal(t, http.StatusOK, rec.Code) var vpcOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vpcOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vpcOut, jsonx.V1())) assert.Equal(t, "true", vpcOut["EnableFsxRouteTableUpdatesFromParticipantAccounts"]) rec = doFSxRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": fsRecord["ResourceARN"]}) @@ -275,7 +276,7 @@ func assertOutputLen(t *testing.T, rec *httptest.ResponseRecorder, field string) t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out[field].([]any) require.True(t, ok, "field %q missing or not an array in response %s", field, rec.Body.String()) @@ -305,7 +306,7 @@ func createTaggedFS(t *testing.T, h *fsx.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fs, ok := out["FileSystem"].(map[string]any) require.True(t, ok) @@ -327,7 +328,7 @@ func createBackupForFS(t *testing.T, h *fsx.Handler, fsID string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) backup, ok := out["Backup"].(map[string]any) require.True(t, ok) @@ -349,7 +350,7 @@ func createDRA(t *testing.T, h *fsx.Handler, fsID string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assoc, ok := out["Association"].(map[string]any) require.True(t, ok) @@ -377,7 +378,7 @@ func createDRT(t *testing.T, h *fsx.Handler, fsID string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) task, ok := out["DataRepositoryTask"].(map[string]any) require.True(t, ok) @@ -398,7 +399,7 @@ func createVolSnapshot(t *testing.T, h *fsx.Handler, volumeID string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) snap, ok := out["Snapshot"].(map[string]any) require.True(t, ok) @@ -423,7 +424,7 @@ func createS3AP(t *testing.T, h *fsx.Handler, volumeID string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ap, ok := out["S3AccessPointAttachment"].(map[string]any) require.True(t, ok) diff --git a/services/gcpapigateway/auth.go b/services/gcpapigateway/auth.go index 87c6616941..cc898179a7 100644 --- a/services/gcpapigateway/auth.go +++ b/services/gcpapigateway/auth.go @@ -8,7 +8,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "errors" "fmt" @@ -21,6 +21,7 @@ import ( "github.com/golang-jwt/jwt/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -143,7 +144,7 @@ func parseKeySet(raw []byte) map[string]crypto.PublicKey { Keys []jwk `json:"keys"` } - if json.Unmarshal(raw, &set) == nil && len(set.Keys) > 0 { + if json.Unmarshal(raw, &set, jsonx.V1()) == nil && len(set.Keys) > 0 { for _, k := range set.Keys { if pub, err := k.public(); err == nil { keys[k.Kid] = pub @@ -154,7 +155,7 @@ func parseKeySet(raw []byte) map[string]crypto.PublicKey { } var certs map[string]string - if json.Unmarshal(raw, &certs) != nil { + if json.Unmarshal(raw, &certs, jsonx.V1()) != nil { return keys } diff --git a/services/gcpapigateway/dataplane.go b/services/gcpapigateway/dataplane.go index cf425f8731..b00fb6eb35 100644 --- a/services/gcpapigateway/dataplane.go +++ b/services/gcpapigateway/dataplane.go @@ -3,7 +3,8 @@ package gcpapigateway import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "time" "github.com/golang-jwt/jwt/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -156,7 +159,7 @@ func apiOfConfig(config string) string { func writeErr(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]any{"code": status, "message": msg}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{"code": status, "message": msg}) } // DataPlane serves requests to gateways. @@ -288,7 +291,7 @@ func (b *InMemoryBackend) forward( } if claims != nil { - if raw, mErr := json.Marshal(claims); mErr == nil { + if raw, mErr := json.Marshal(claims, jsonx.V1()); mErr == nil { out.Header.Set(userinfoHeader, base64.RawURLEncoding.EncodeToString(raw)) } } diff --git a/services/gcpapigateway/helpers_test.go b/services/gcpapigateway/helpers_test.go index f582c07536..27ed8dbee5 100644 --- a/services/gcpapigateway/helpers_test.go +++ b/services/gcpapigateway/helpers_test.go @@ -2,7 +2,8 @@ package gcpapigateway_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpapigateway" ) @@ -169,7 +171,7 @@ func (e *env) do(t *testing.T, req *http.Request) reply { require.NoError(t, err) r := reply{status: resp.StatusCode, raw: string(raw)} - _ = json.Unmarshal(raw, &r.body) + _ = json.Unmarshal(raw, &r.body, jsonx.V1()) return r } @@ -188,7 +190,7 @@ func echoBackend(t *testing.T) *httptest.Server { w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Backend", "echo") - _ = json.NewEncoder(w).Encode(map[string]any{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{ "method": r.Method, "path": r.URL.Path, "query": r.URL.RawQuery, "headers": hdr, "body": string(body), }) })) diff --git a/services/gcpapigateway/persistence.go b/services/gcpapigateway/persistence.go index 10278b5993..f9b41fb9c4 100644 --- a/services/gcpapigateway/persistence.go +++ b/services/gcpapigateway/persistence.go @@ -2,7 +2,7 @@ package gcpapigateway import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/apigateway/apiv1/apigatewaypb" @@ -18,9 +18,9 @@ import ( const snapshotVersion = 1 type recSnap struct { - Resource json.RawMessage `json:"resource"` - Policy json.RawMessage `json:"policy,omitempty"` - Life life `json:"life"` + Resource jsontext.Value `json:"resource"` + Policy jsontext.Value `json:"policy,omitempty"` + Life life `json:"life"` } type backendSnapshot struct { @@ -32,7 +32,7 @@ type backendSnapshot struct { Seq uint64 `json:"seq"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { if m == nil || !m.ProtoReflect().IsValid() { return nil } @@ -49,7 +49,7 @@ func unmarshalPB(raw []byte, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } -func restorePolicy(raw json.RawMessage) (*iampb.Policy, error) { +func restorePolicy(raw jsontext.Value) (*iampb.Policy, error) { if len(raw) == 0 { return nil, nil //nolint:nilnil // no policy stored } diff --git a/services/gcpapigateway/security_test.go b/services/gcpapigateway/security_test.go index f7949bc4e5..1d0ea11ee0 100644 --- a/services/gcpapigateway/security_test.go +++ b/services/gcpapigateway/security_test.go @@ -6,7 +6,8 @@ import ( "crypto/rand" "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "math/big" @@ -19,6 +20,8 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -110,7 +113,7 @@ func newSigner(t *testing.T) *signer { s := &signer{rsa: sharedRSA(), ec: ec} s.jwks = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(map[string]any{"keys": []map[string]string{ + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{"keys": []map[string]string{ {"kty": "RSA", "kid": "rsa-1", "n": b64u(s.rsa.N), "e": b64u(big.NewInt(int64(s.rsa.E)))}, { "kty": "EC", @@ -396,7 +399,7 @@ func TestJWTUserinfoForwarded(t *testing.T) { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) assert.Equal(t, "user-1", claims["sub"]) assert.Equal(t, "u@example.com", claims["email"]) assert.Equal(t, "Bearer "+tok, hdr["X-Forwarded-Authorization"]) diff --git a/services/gcpartifactregistry/filehttp.go b/services/gcpartifactregistry/filehttp.go index 1a9f3515a9..65307a2410 100644 --- a/services/gcpartifactregistry/filehttp.go +++ b/services/gcpartifactregistry/filehttp.go @@ -3,7 +3,8 @@ package gcpartifactregistry import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "mime" @@ -18,6 +19,7 @@ import ( "google.golang.org/protobuf/encoding/protojson" "github.com/blackbirdworks/gopherstack/pkgs/gcprest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -49,7 +51,7 @@ func writeRESTError(w http.ResponseWriter, err error) { body, _ := json.Marshal(map[string]any{"error": map[string]any{ "code": gcprest.HTTPStatus(st.Code()), "message": st.Message(), "status": codeName(st.Code()), - }}) + }}, jsonx.V1()) w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(gcprest.HTTPStatus(st.Code())) @@ -95,7 +97,7 @@ func readMultipart(r *http.Request) (uploadReq, []byte, error) { } var req uploadReq - if err = json.NewDecoder(meta).Decode(&req); err != nil { + if err = json.UnmarshalDecode(jsontext.NewDecoder(meta), &req, jsonx.V1()); err != nil { return uploadReq{}, nil, errInvalid("The upload metadata is not valid JSON: %v", err) } diff --git a/services/gcpartifactregistry/files.go b/services/gcpartifactregistry/files.go index 4dac4c9b5f..d53a7d8b87 100644 --- a/services/gcpartifactregistry/files.go +++ b/services/gcpartifactregistry/files.go @@ -4,7 +4,7 @@ import ( "cmp" "context" "encoding/hex" - "encoding/json" + "encoding/json/v2" "maps" "regexp" "slices" @@ -15,6 +15,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -77,7 +78,7 @@ func (b *InMemoryBackend) dockerFiles(ctx context.Context, ref repoRef) []*artif add(d, int64(len(m.Body)), src) var doc manifestBlobs - if json.Unmarshal(m.Body, &doc) != nil { + if json.Unmarshal(m.Body, &doc, jsonx.V1()) != nil { continue } diff --git a/services/gcpartifactregistry/files_test.go b/services/gcpartifactregistry/files_test.go index 3423f1fb37..740847e795 100644 --- a/services/gcpartifactregistry/files_test.go +++ b/services/gcpartifactregistry/files_test.go @@ -1,7 +1,7 @@ package gcpartifactregistry_test import ( - "encoding/json" + "encoding/json/v2" "testing" "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb" @@ -12,6 +12,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/fieldmaskpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" ) @@ -54,7 +55,7 @@ func configDigest(t *testing.T, img ocitest.Image) string { } `json:"config"` } - require.NoError(t, json.Unmarshal(img.Body, &doc)) + require.NoError(t, json.Unmarshal(img.Body, &doc, jsonx.V1())) return doc.Config.Digest } diff --git a/services/gcpartifactregistry/helpers_test.go b/services/gcpartifactregistry/helpers_test.go index a1f4158d7f..e6ad4a7b77 100644 --- a/services/gcpartifactregistry/helpers_test.go +++ b/services/gcpartifactregistry/helpers_test.go @@ -4,7 +4,7 @@ import ( "context" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net" "net/http" "slices" @@ -17,6 +17,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" "github.com/blackbirdworks/gopherstack/services/gcpartifactregistry" ) @@ -108,7 +109,7 @@ func decodeJSON(t *testing.T, body []byte) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out } diff --git a/services/gcpartifactregistry/npm.go b/services/gcpartifactregistry/npm.go index 68ccc38146..472a7a190b 100644 --- a/services/gcpartifactregistry/npm.go +++ b/services/gcpartifactregistry/npm.go @@ -5,13 +5,16 @@ import ( "crypto/sha512" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "maps" "net/http" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -28,10 +31,10 @@ type npmAttachment struct { } type npmPublishDoc struct { - DistTags map[string]string `json:"dist-tags"` - Versions map[string]json.RawMessage `json:"versions"` - Attachments map[string]npmAttachment `json:"_attachments"` - Name string `json:"name"` + DistTags map[string]string `json:"dist-tags"` + Versions map[string]jsontext.Value `json:"versions"` + Attachments map[string]npmAttachment `json:"_attachments"` + Name string `json:"name"` } func writeNPMError(w http.ResponseWriter, code int, msg string) { @@ -123,7 +126,7 @@ func readNPMDoc(w http.ResponseWriter, r *http.Request) (npmPublishDoc, bool) { var doc npmPublishDoc body := http.MaxBytesReader(w, r.Body, maxNPMDocBytes) - if err := json.NewDecoder(body).Decode(&doc); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(body), &doc, jsonx.V1()); err != nil { writeNPMError(w, http.StatusBadRequest, "Invalid package document: "+err.Error()) return doc, false @@ -201,7 +204,7 @@ func (b *InMemoryBackend) publishNPM(w http.ResponseWriter, r *http.Request, rc } func npmVersionFile( - pkg, version string, manifest json.RawMessage, atts map[string]npmAttachment, + pkg, version string, manifest jsontext.Value, atts map[string]npmAttachment, ) (pkgFile, int, string) { if !npmVersionRe.MatchString(version) { return pkgFile{}, http.StatusBadRequest, "Invalid semver version " + version + "." @@ -278,7 +281,7 @@ func (b *InMemoryBackend) serveNPMPackument(w http.ResponseWriter, r *http.Reque } base := baseURL(r, npmRoute, rc) - docs := map[string]json.RawMessage{} + docs := map[string]jsontext.Value{} times := map[string]string{} rev := sha1.New() //nolint:gosec // opaque revision token @@ -305,10 +308,10 @@ func (b *InMemoryBackend) serveNPMPackument(w http.ResponseWriter, r *http.Reque } // withDist adds the dist block npm clients install from to a stored version manifest. -func withDist(f *genFile, manifest []byte, tarballURL string) json.RawMessage { - var m map[string]json.RawMessage - if json.Unmarshal(manifest, &m) != nil { - m = map[string]json.RawMessage{} +func withDist(f *genFile, manifest []byte, tarballURL string) jsontext.Value { + var m map[string]jsontext.Value + if json.Unmarshal(manifest, &m, jsonx.V1()) != nil { + m = map[string]jsontext.Value{} } s1 := sha1.Sum(f.Data) //nolint:gosec // npm dist.shasum is SHA-1 by protocol @@ -316,10 +319,10 @@ func withDist(f *genFile, manifest []byte, tarballURL string) json.RawMessage { dist, _ := json.Marshal(map[string]any{ "tarball": tarballURL, "shasum": hex.EncodeToString(s1[:]), "integrity": "sha512-" + base64.StdEncoding.EncodeToString(s512[:]), - }) + }, jsonx.V1()) m["dist"] = dist - out, _ := json.Marshal(m) + out, _ := json.Marshal(m, jsonx.V1()) return out } @@ -414,7 +417,11 @@ func (b *InMemoryBackend) putNPMTag( w http.ResponseWriter, r *http.Request, rc repoCtx, pkg, tag string, existing map[string]bool, ) { var version string - if err := json.NewDecoder(io.LimitReader(r.Body, maxTagBody)).Decode(&version); err != nil { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(io.LimitReader(r.Body, maxTagBody)), + &version, + jsonx.V1(), + ); err != nil { writeNPMError(w, http.StatusBadRequest, "The body must be a JSON string holding a version.") return diff --git a/services/gcpartifactregistry/persistence.go b/services/gcpartifactregistry/persistence.go index 0e7f7ff00d..e52d39a859 100644 --- a/services/gcpartifactregistry/persistence.go +++ b/services/gcpartifactregistry/persistence.go @@ -2,7 +2,7 @@ package gcpartifactregistry import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb" @@ -19,8 +19,8 @@ import ( const snapshotVersion = 1 type repoSnap struct { - Repository json.RawMessage `json:"repository"` - IAM json.RawMessage `json:"iam,omitempty"` + Repository jsontext.Value `json:"repository"` + IAM jsontext.Value `json:"iam,omitempty"` } type backendSnapshot struct { @@ -31,14 +31,14 @@ type backendSnapshot struct { GenericVers map[string]*resMeta `json:"genericVersions,omitempty"` GenericFiles map[string]*genFile `json:"genericFiles,omitempty"` PackageTags map[string]map[string]string `json:"packageTags,omitempty"` - Rules map[string]json.RawMessage `json:"rules,omitempty"` - Settings map[string]json.RawMessage `json:"projectSettings,omitempty"` - VPCSC map[string]json.RawMessage `json:"vpcscConfigs,omitempty"` - Operations map[string]json.RawMessage `json:"operations"` + Rules map[string]jsontext.Value `json:"rules,omitempty"` + Settings map[string]jsontext.Value `json:"projectSettings,omitempty"` + VPCSC map[string]jsontext.Value `json:"vpcscConfigs,omitempty"` + Operations map[string]jsontext.Value `json:"operations"` Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -47,7 +47,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } @@ -133,8 +133,8 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { return nil } -func marshalAll[T proto.Message](in map[string]T) map[string]json.RawMessage { - out := make(map[string]json.RawMessage, len(in)) +func marshalAll[T proto.Message](in map[string]T) map[string]jsontext.Value { + out := make(map[string]jsontext.Value, len(in)) for k, v := range in { out[k] = marshalPB(v) } @@ -142,7 +142,7 @@ func marshalAll[T proto.Message](in map[string]T) map[string]json.RawMessage { return out } -func unmarshalAll[T proto.Message](in map[string]json.RawMessage, newMsg func() T) (map[string]T, error) { +func unmarshalAll[T proto.Message](in map[string]jsontext.Value, newMsg func() T) (map[string]T, error) { out := make(map[string]T, len(in)) for k, raw := range in { diff --git a/services/gcpartifactregistry/pkgfmt.go b/services/gcpartifactregistry/pkgfmt.go index 6f49fd5eb2..aa15364dd0 100644 --- a/services/gcpartifactregistry/pkgfmt.go +++ b/services/gcpartifactregistry/pkgfmt.go @@ -2,7 +2,7 @@ package gcpartifactregistry import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "regexp" @@ -13,6 +13,7 @@ import ( "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb" "github.com/blackbirdworks/gopherstack/pkgs/gcprest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -160,7 +161,7 @@ func writePlain(w http.ResponseWriter, code int, msg string) { } func writeJSONBody(w http.ResponseWriter, code int, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { writePlain(w, http.StatusInternalServerError, err.Error()) diff --git a/services/gcpartifactregistry/pkgfmt_test.go b/services/gcpartifactregistry/pkgfmt_test.go index 2c6edfd9a6..d8b383d88e 100644 --- a/services/gcpartifactregistry/pkgfmt_test.go +++ b/services/gcpartifactregistry/pkgfmt_test.go @@ -7,7 +7,7 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "io" "mime/multipart" @@ -17,6 +17,7 @@ import ( "testing" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "cloud.google.com/go/artifactregistry/apiv1/artifactregistrypb" "github.com/stretchr/testify/assert" @@ -160,7 +161,7 @@ func npmPublishBody(t *testing.T, name, version, tag string, tarball []byte) []b }}, } - raw, err := json.Marshal(doc) + raw, err := json.Marshal(doc, jsonx.V1()) require.NoError(t, err) return raw @@ -392,7 +393,7 @@ func TestNPMProtocol(t *testing.T) { "name": "left-pad", "dist-tags": map[string]string{"latest": "1.0.0"}, "versions": map[string]any{"1.0.0": map[string]any{}}, } - raw, _ := json.Marshal(unpub) + raw, _ := json.Marshal(unpub, jsonx.V1()) require.Equal( t, diff --git a/services/gcpartifactregistry/python.go b/services/gcpartifactregistry/python.go index b3bc5ab05e..7f4f78b99c 100644 --- a/services/gcpartifactregistry/python.go +++ b/services/gcpartifactregistry/python.go @@ -4,13 +4,15 @@ import ( "crypto/md5" //nolint:gosec // twine sends md5_digest by protocol "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "html" "io" "net/http" "net/url" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -100,7 +102,12 @@ func (b *InMemoryBackend) uploadPython(w http.ResponseWriter, r *http.Request, r norm := pyNormalize(name) doc, _ := json.Marshal( - pyDoc{Name: name, RequiresPython: r.FormValue("requires_python"), SHA256: hex.EncodeToString(sum[:])}, + pyDoc{ + Name: name, + RequiresPython: r.FormValue("requires_python"), + SHA256: hex.EncodeToString(sum[:]), + }, + jsonx.V1(), ) err = b.putPackageFile(rc.ref, pkgFile{ @@ -194,7 +201,7 @@ func (b *InMemoryBackend) pyProject(w http.ResponseWriter, r *http.Request, rc r for _, v := range versions { for _, nf := range v.files { var d pyDoc - _ = json.Unmarshal(nf.file.Doc, &d) + _ = json.Unmarshal(nf.file.Doc, &d, jsonx.V1()) filename := nf.id[strings.LastIndex(nf.id, ":")+1:] href := "/python/" + rc.ref.project + "/" + rc.ref.id + "/packages/" + url.PathEscape(norm) + "/" + diff --git a/services/gcpartifactregistry/util.go b/services/gcpartifactregistry/util.go index 5e365bba1a..97383fc0ed 100644 --- a/services/gcpartifactregistry/util.go +++ b/services/gcpartifactregistry/util.go @@ -1,10 +1,12 @@ package gcpartifactregistry import ( - "encoding/json" + "encoding/json/v2" "os" "sort" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func sortStrings(s []string) { sort.Strings(s) } @@ -13,4 +15,4 @@ func removeAll(path string) { _ = os.RemoveAll(path) } func timeFromNanos(n int64) time.Time { return time.Unix(0, n).UTC() } -func marshalJSON(v any) ([]byte, error) { return json.Marshal(v) } +func marshalJSON(v any) ([]byte, error) { return json.Marshal(v, jsonx.V1()) } diff --git a/services/gcpbatch/persistence.go b/services/gcpbatch/persistence.go index ca47826643..5a34accd44 100644 --- a/services/gcpbatch/persistence.go +++ b/services/gcpbatch/persistence.go @@ -2,7 +2,7 @@ package gcpbatch import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -17,8 +17,8 @@ import ( const snapshotVersion = 1 type jobSnap struct { - Rec *jobRec `json:"rec"` - Job json.RawMessage `json:"job"` + Rec *jobRec `json:"rec"` + Job jsontext.Value `json:"job"` } type backendSnapshot struct { diff --git a/services/gcpbigquery/autodetect.go b/services/gcpbigquery/autodetect.go index 3f72ac8a6f..f9d31b3f41 100644 --- a/services/gcpbigquery/autodetect.go +++ b/services/gcpbigquery/autodetect.go @@ -2,7 +2,8 @@ package gcpbigquery import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -10,6 +11,7 @@ import ( "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -176,15 +178,14 @@ func dedupeNames(fs []bqsql.Field) []bqsql.Field { } func detectJSON(lc loadConfig, data []byte) ([]bqsql.Field, error) { - dec := json.NewDecoder(bytes.NewReader(decodeText(data, lc.Encoding))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(decodeText(data, lc.Encoding))) var fields []bqsql.Field for range detectSample { var obj map[string]any - err := dec.Decode(&obj) + err := json.UnmarshalDecode(dec, &obj, jsonx.V1(), jsonx.UseNumber()) if errors.Is(err, io.EOF) { break } @@ -281,7 +282,7 @@ func inferJSONField(name string, v any) (bqsql.Field, bool) { return f, len(f.Fields) > 0 case bool: f.Type = litBoolean - case json.Number: + case jsonx.Number: f.Type = litInteger if _, err := t.Int64(); err != nil { f.Type = litFloat diff --git a/services/gcpbigquery/bqsql/engine_test.go b/services/gcpbigquery/bqsql/engine_test.go index a75559cf3a..121244e995 100644 --- a/services/gcpbigquery/bqsql/engine_test.go +++ b/services/gcpbigquery/bqsql/engine_test.go @@ -1,13 +1,15 @@ package bqsql_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -28,7 +30,7 @@ func fieldsJSON(t *testing.T, s string) []bqsql.Field { var fs []bqsql.Field - require.NoError(t, json.Unmarshal([]byte(s), &fs)) + require.NoError(t, json.Unmarshal([]byte(s), &fs, jsonx.V1())) require.NoError(t, bqsql.NormalizeSchema(fs)) return fs @@ -37,12 +39,11 @@ func fieldsJSON(t *testing.T, s string) []bqsql.Field { func rowsJSON(t *testing.T, fs []bqsql.Field, s string) [][]any { t.Helper() - dec := json.NewDecoder(strings.NewReader(s)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(s)) var raw []map[string]any - require.NoError(t, dec.Decode(&raw)) + require.NoError(t, json.UnmarshalDecode(dec, &raw, jsonx.V1(), jsonx.UseNumber())) out := make([][]any, len(raw)) @@ -222,7 +223,7 @@ func TestSelectQueries(t *testing.T) { return } - got, merr := json.Marshal(wireRows(res)) + got, merr := json.Marshal(wireRows(res), jsonx.V1()) require.NoError(t, merr) assert.JSONEq(t, tt.want, string(got)) }) diff --git a/services/gcpbigquery/bqsql/sqlvalues.go b/services/gcpbigquery/bqsql/sqlvalues.go index 1e9c7ff135..5be3ab2179 100644 --- a/services/gcpbigquery/bqsql/sqlvalues.go +++ b/services/gcpbigquery/bqsql/sqlvalues.go @@ -4,10 +4,13 @@ import ( "bytes" "database/sql/driver" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ToSQLite converts a stored value into the value bound into SQLite: scalars map to INTEGER, REAL or TEXT and @@ -111,7 +114,7 @@ func writeJSONRecord(buf *bytes.Buffer, fields []Field, stored any) { buf.WriteByte(',') } - name, _ := json.Marshal(fl.Name) + name, _ := json.Marshal(fl.Name, jsonx.V1()) buf.Write(name) buf.WriteByte(':') @@ -133,7 +136,7 @@ func writeJSONScalar(buf *bytes.Buffer, k Kind, s string) { case KindFloat: f, _ := parseFloatText(s) if math.IsNaN(f) || math.IsInf(f, 0) { - q, _ := json.Marshal(s) + q, _ := json.Marshal(s, jsonx.V1()) buf.Write(q) return @@ -142,7 +145,7 @@ func writeJSONScalar(buf *bytes.Buffer, k Kind, s string) { buf.WriteString(strconv.FormatFloat(f, 'g', -1, bitSize)) case KindUnknown, KindString, KindBytes, KindTimestamp, KindDate, KindTime, KindDatetime, KindNumeric, KindBigNumeric, KindJSON, KindGeography, KindStruct, KindArray: - q, _ := json.Marshal(s) + q, _ := json.Marshal(s, jsonx.V1()) buf.Write(q) } } @@ -177,11 +180,10 @@ func fromJSONText(f Field, v any) (any, error) { return nil, valueErr("expected JSON text for %s, got %T", f.Name, v) } - dec := json.NewDecoder(strings.NewReader(text)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(text)) var node any - if err := dec.Decode(&node); err != nil { + if err := json.UnmarshalDecode(dec, &node, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, valueErr("invalid JSON for %s: %v", f.Name, err) } diff --git a/services/gcpbigquery/bqsql/udf_array.go b/services/gcpbigquery/bqsql/udf_array.go index e4e7f7db7d..418a096ffe 100644 --- a/services/gcpbigquery/bqsql/udf_array.go +++ b/services/gcpbigquery/bqsql/udf_array.go @@ -3,16 +3,18 @@ package bqsql import ( "bytes" "database/sql/driver" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func decodeJSONArray(v driver.Value) ([]any, error) { - dec := json.NewDecoder(strings.NewReader(toStr(v))) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(toStr(v))) var arr []any - if err := dec.Decode(&arr); err != nil { + if err := json.UnmarshalDecode(dec, &arr, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, errBQ("expected an ARRAY value") } @@ -22,9 +24,8 @@ func decodeJSONArray(v driver.Value) ([]any, error) { func encodeJSON(v any) string { var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) - _ = enc.Encode(v) + enc := jsontext.NewEncoder(&buf, jsonx.V1(), jsontext.EscapeForHTML(false)) + _ = json.MarshalEncode(enc, v) return strings.TrimSpace(buf.String()) } @@ -34,7 +35,7 @@ func jsonToSQL(v any) driver.Value { switch t := v.(type) { case nil: return nil - case json.Number: + case jsonx.Number: if n, err := t.Int64(); err == nil { return n } diff --git a/services/gcpbigquery/bqsql/udf_json.go b/services/gcpbigquery/bqsql/udf_json.go index 7b95dd8105..5d52d43200 100644 --- a/services/gcpbigquery/bqsql/udf_json.go +++ b/services/gcpbigquery/bqsql/udf_json.go @@ -3,11 +3,14 @@ package bqsql import ( "crypto/rand" "database/sql/driver" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -56,7 +59,7 @@ func udfParseJSON(a []driver.Value) (driver.Value, error) { } s := toStr(a[0]) - if !json.Valid([]byte(s)) { + if !jsontext.Value([]byte(s)).IsValid(jsonx.V1()) { return nil, errBQ("syntax error while parsing value in JSON") } @@ -75,7 +78,7 @@ func udfJSONValue(scalarOnly bool) udfFn { } var doc any - if err := json.Unmarshal([]byte(toStr(a[0])), &doc); err != nil { + if err := json.Unmarshal([]byte(toStr(a[0])), &doc, jsonx.V1()); err != nil { return nil, errBQ("Invalid JSON: %v", err) } @@ -103,7 +106,7 @@ func jsonNodeText(node any, scalarOnly bool) driver.Value { } } - b, _ := json.Marshal(node) + b, _ := json.Marshal(node, jsonx.V1()) return string(b) } diff --git a/services/gcpbigquery/bqsql/udf_regex.go b/services/gcpbigquery/bqsql/udf_regex.go index 043783687c..9d163c95be 100644 --- a/services/gcpbigquery/bqsql/udf_regex.go +++ b/services/gcpbigquery/bqsql/udf_regex.go @@ -2,9 +2,11 @@ package bqsql import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func compileRe(v driver.Value) (*regexp.Regexp, error) { @@ -138,7 +140,7 @@ func udfRegexExtractAll(a []driver.Value) (driver.Value, error) { out = append(out, firstGroup(m)) } - b, _ := json.Marshal(out) + b, _ := json.Marshal(out, jsonx.V1()) return string(b), nil } diff --git a/services/gcpbigquery/bqsql/udf_str.go b/services/gcpbigquery/bqsql/udf_str.go index e2b5d95efd..c53a33b5c0 100644 --- a/services/gcpbigquery/bqsql/udf_str.go +++ b/services/gcpbigquery/bqsql/udf_str.go @@ -8,10 +8,12 @@ import ( "database/sql/driver" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "strings" "unicode" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func strFn(n int32, f func(s string, a []driver.Value) (driver.Value, error)) udfDef { @@ -379,7 +381,7 @@ func udfSplit(a []driver.Value) (driver.Value, error) { parts = []string{""} } - b, _ := json.Marshal(parts) + b, _ := json.Marshal(parts, jsonx.V1()) return string(b), nil } diff --git a/services/gcpbigquery/bqsql/udf_time.go b/services/gcpbigquery/bqsql/udf_time.go index 61a24c0acf..bbd2e08888 100644 --- a/services/gcpbigquery/bqsql/udf_time.go +++ b/services/gcpbigquery/bqsql/udf_time.go @@ -2,10 +2,12 @@ package bqsql import ( "database/sql/driver" - "encoding/json" + "encoding/json/v2" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseAny parses a stored temporal text value and reports which family it belongs to. @@ -661,7 +663,7 @@ func udfGenerateDates(a []driver.Value) (driver.Value, error) { t, _, _ = parseAny(next) } - b, _ := json.Marshal(out) + b, _ := json.Marshal(out, jsonx.V1()) return string(b), nil } diff --git a/services/gcpbigquery/bqsql/values.go b/services/gcpbigquery/bqsql/values.go index 6213d6f74c..eb1b9f81ac 100644 --- a/services/gcpbigquery/bqsql/values.go +++ b/services/gcpbigquery/bqsql/values.go @@ -2,7 +2,8 @@ package bqsql import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "math" @@ -10,6 +11,8 @@ import ( "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Canonical stored encodings of temporal values. @@ -168,7 +171,7 @@ func storedString(v any) (string, error) { return t, nil case []byte: return string(t), nil - case json.Number: + case jsonx.Number: return t.String(), nil case bool: return strconv.FormatBool(t), nil @@ -183,14 +186,14 @@ func storedString(v any) (string, error) { func storedJSON(v any) (string, error) { if s, ok := v.(string); ok { - if !json.Valid([]byte(s)) { + if !jsontext.Value([]byte(s)).IsValid(jsonx.V1()) { return "", valueErr("invalid JSON text") } return s, nil } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "", valueErr("cannot encode JSON: %v", err) } @@ -204,7 +207,7 @@ func numberText(v any) (string, bool) { return strings.TrimSpace(t), true case []byte: return strings.TrimSpace(string(t)), true - case json.Number: + case jsonx.Number: return t.String(), true case int64: return strconv.FormatInt(t, decimalBase), true @@ -293,7 +296,7 @@ func storedBool(v any) (string, error) { return strconv.FormatBool(t), nil case int64: return strconv.FormatBool(t != 0), nil - case json.Number: + case jsonx.Number: return strconv.FormatBool(t.String() != "0"), nil case string: switch strings.ToLower(strings.TrimSpace(t)) { @@ -449,7 +452,7 @@ func epochTime(v any) (time.Time, bool) { var f float64 switch t := v.(type) { - case json.Number: + case jsonx.Number: g, err := t.Float64() if err != nil { return time.Time{}, false diff --git a/services/gcpbigquery/handler.go b/services/gcpbigquery/handler.go index c880a3bed4..7abd7bb8cf 100644 --- a/services/gcpbigquery/handler.go +++ b/services/gcpbigquery/handler.go @@ -2,7 +2,8 @@ package gcpbigquery import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -179,11 +181,10 @@ func decodeObject(data []byte) (map[string]any, error) { return map[string]any{}, nil } - dec := json.NewDecoder(strings.NewReader(string(data))) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(string(data))) var body map[string]any - if err := dec.Decode(&body); err != nil { + if err := json.UnmarshalDecode(dec, &body, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, errInvalid("Invalid JSON payload received: %v", err) } @@ -366,10 +367,9 @@ func (h *Handler) insertAll(c *echo.Context, rt route) error { return newErr(http.StatusRequestEntityTooLarge, "uploadTooLarge", "Request body is too large or unreadable.") } - dec := json.NewDecoder(strings.NewReader(string(data))) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(string(data))) - if err := dec.Decode(&req); err != nil { + if err := json.UnmarshalDecode(dec, &req, jsonx.V1(), jsonx.UseNumber()); err != nil { return errInvalid("Invalid JSON payload received: %v", err) } diff --git a/services/gcpbigquery/jobs_extract.go b/services/gcpbigquery/jobs_extract.go index 6bc1f41408..8429e0fd9f 100644 --- a/services/gcpbigquery/jobs_extract.go +++ b/services/gcpbigquery/jobs_extract.go @@ -3,13 +3,15 @@ package gcpbigquery import ( "bytes" "compress/gzip" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -214,30 +216,30 @@ func encodeCSV(ec extractConfig, t *Table) ([]byte, error) { return buf.Bytes(), nil } -func jsonCell(f bqsql.Field, v any) json.RawMessage { +func jsonCell(f bqsql.Field, v any) jsontext.Value { if v == nil && f.Mode != bqsql.ModeRepeated { - return json.RawMessage("null") + return jsontext.Value("null") } k, _ := bqsql.KindOf(f.Type) if k == bqsql.KindStruct || f.Mode == bqsql.ModeRepeated { text, _ := bqsql.ToSQLite(f, v).(string) - return json.RawMessage(text) + return jsontext.Value(text) } s, _ := v.(string) switch k { case bqsql.KindInt, bqsql.KindBool, bqsql.KindNumeric, bqsql.KindBigNumeric: - return json.RawMessage(s) + return jsontext.Value(s) case bqsql.KindFloat: if f, err := strconv.ParseFloat( s, 64, ); err == nil && !math.IsNaN(f) && !math.IsInf(f, 0) { - return json.RawMessage(strconv.FormatFloat(f, 'g', -1, 64)) + return jsontext.Value(strconv.FormatFloat(f, 'g', -1, 64)) } case bqsql.KindTimestamp: if t, err := time.Parse(bqsql.TimestampLayout, s); err == nil { @@ -249,7 +251,7 @@ func jsonCell(f bqsql.Field, v any) json.RawMessage { bqsql.KindStruct, bqsql.KindArray: } - q, _ := json.Marshal(s) + q, _ := json.Marshal(s, jsonx.V1()) return q } @@ -265,7 +267,7 @@ func encodeNDJSON(t *Table) []byte { buf.WriteByte(',') } - name, _ := json.Marshal(f.Name) + name, _ := json.Marshal(f.Name, jsonx.V1()) buf.Write(name) buf.WriteByte(':') buf.Write(jsonCell(f, row[i])) diff --git a/services/gcpbigquery/jobs_query.go b/services/gcpbigquery/jobs_query.go index db8ea1c476..89cb8d3a03 100644 --- a/services/gcpbigquery/jobs_query.go +++ b/services/gcpbigquery/jobs_query.go @@ -2,13 +2,14 @@ package gcpbigquery import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "regexp" "strconv" "strings" "sync" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -38,10 +39,10 @@ type datasetRef struct { func intOf(v any) (int, bool) { switch t := v.(type) { - case json.Number: - n, err := t.Int64() + case jsonx.Number: + n, err := t.Int() - return int(n), err == nil + return n, err == nil case float64: return int(t), true case string: @@ -56,12 +57,12 @@ func intOf(v any) (int, bool) { func decodeQueryConfig(raw any) (queryConfig, error) { var qc queryConfig - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return qc, errInvalid("Invalid query configuration") } - if err = json.Unmarshal(b, &qc); err != nil { + if err = json.Unmarshal(b, &qc, jsonx.V1()); err != nil { return qc, errInvalid("Invalid query configuration: %v", err) } diff --git a/services/gcpbigquery/load_formats.go b/services/gcpbigquery/load_formats.go index 37fe198667..f37c8221cc 100644 --- a/services/gcpbigquery/load_formats.go +++ b/services/gcpbigquery/load_formats.go @@ -3,12 +3,14 @@ package gcpbigquery import ( "bytes" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -133,8 +135,7 @@ func csvRow(lc loadConfig, fields []bqsql.Field, rec []string) ([]any, error) { } func parseJSONInput(lc loadConfig, fields []bqsql.Field, data []byte) ([][]any, []error, error) { - dec := json.NewDecoder(bytes.NewReader(decodeText(data, lc.Encoding))) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(decodeText(data, lc.Encoding)), jsonx.V1()) var ( rows [][]any @@ -144,7 +145,7 @@ func parseJSONInput(lc loadConfig, fields []bqsql.Field, data []byte) ([][]any, for n := 1; ; n++ { var obj map[string]any - err := dec.Decode(&obj) + err := json.UnmarshalDecode(dec, &obj, jsonx.V1(), jsonx.UseNumber()) if errors.Is(err, io.EOF) { return rows, bad, nil } diff --git a/services/gcpbigquery/tables.go b/services/gcpbigquery/tables.go index cbaba8067d..c784afe0f9 100644 --- a/services/gcpbigquery/tables.go +++ b/services/gcpbigquery/tables.go @@ -2,10 +2,11 @@ package gcpbigquery import ( "context" - "encoding/json" + "encoding/json/v2" "sort" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/services/gcpbigquery/bqsql" ) @@ -24,7 +25,7 @@ func decodeSchema(raw any) ([]bqsql.Field, error) { return nil, nil } - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, errInvalid("Invalid schema") } @@ -33,7 +34,7 @@ func decodeSchema(raw any) ([]bqsql.Field, error) { Fields []bqsql.Field `json:"fields"` } - if err = json.Unmarshal(b, &s); err != nil { + if err = json.Unmarshal(b, &s, jsonx.V1()); err != nil { return nil, errInvalid("Invalid schema: %v", err) } diff --git a/services/gcpbigtable/persistence.go b/services/gcpbigtable/persistence.go index bab8da7450..d62b30f428 100644 --- a/services/gcpbigtable/persistence.go +++ b/services/gcpbigtable/persistence.go @@ -2,7 +2,7 @@ package gcpbigtable import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/bigtable/admin/apiv2/adminpb" @@ -34,35 +34,35 @@ type rowSnap struct { } type tableSnap struct { - Table json.RawMessage `json:"table"` - Policy json.RawMessage `json:"policy,omitempty"` - Splits [][]byte `json:"splits,omitempty"` - Rows []rowSnap `json:"rows,omitempty"` + Table jsontext.Value `json:"table"` + Policy jsontext.Value `json:"policy,omitempty"` + Splits [][]byte `json:"splits,omitempty"` + Rows []rowSnap `json:"rows,omitempty"` } type backupSnap struct { - Backup json.RawMessage `json:"backup"` - Policy json.RawMessage `json:"policy,omitempty"` - Table tableSnap `json:"table"` + Backup jsontext.Value `json:"backup"` + Policy jsontext.Value `json:"policy,omitempty"` + Table tableSnap `json:"table"` } type instSnap struct { - Clusters map[string]json.RawMessage `json:"clusters,omitempty"` - Profiles map[string]json.RawMessage `json:"profiles,omitempty"` - Tables map[string]tableSnap `json:"tables,omitempty"` - Backups map[string]backupSnap `json:"backups,omitempty"` - Instance json.RawMessage `json:"instance"` - Policy json.RawMessage `json:"policy,omitempty"` + Clusters map[string]jsontext.Value `json:"clusters,omitempty"` + Profiles map[string]jsontext.Value `json:"profiles,omitempty"` + Tables map[string]tableSnap `json:"tables,omitempty"` + Backups map[string]backupSnap `json:"backups,omitempty"` + Instance jsontext.Value `json:"instance"` + Policy jsontext.Value `json:"policy,omitempty"` } type backendSnapshot struct { - Instances map[string]instSnap `json:"instances"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` - Seq uint64 `json:"seq"` + Instances map[string]instSnap `json:"instances"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` + Seq uint64 `json:"seq"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { if m == nil || !m.ProtoReflect().IsValid() { return nil } @@ -124,7 +124,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { for name, r := range b.instances { s := instSnap{ Instance: marshalPB(r.pb), Policy: marshalPB(r.policy), - Clusters: map[string]json.RawMessage{}, Profiles: map[string]json.RawMessage{}, + Clusters: map[string]jsontext.Value{}, Profiles: map[string]jsontext.Value{}, Tables: map[string]tableSnap{}, Backups: map[string]backupSnap{}, } diff --git a/services/gcpcloudbuild/bindings.go b/services/gcpcloudbuild/bindings.go index 7f89ff16b4..33c0905648 100644 --- a/services/gcpcloudbuild/bindings.go +++ b/services/gcpcloudbuild/bindings.go @@ -2,11 +2,14 @@ package gcpcloudbuild import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "regexp" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errBindingMissing = errors.New("not present in the payload") @@ -32,10 +35,9 @@ func bindPayload(subs map[string]string, body []byte) (map[string]string, error) } if !parsed { - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) - if err := dec.Decode(&doc); err != nil { + if err := json.UnmarshalDecode(dec, &doc, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, errInvalid( "The webhook payload is not valid JSON, so $(body...) bindings cannot resolve: %v.", err) } @@ -79,13 +81,13 @@ func lookupBinding(doc any, path string) (string, error) { return "", nil case string: return v, nil - case json.Number: + case jsonx.Number: return v.String(), nil case bool: return strconv.FormatBool(v), nil } - raw, err := json.Marshal(cur) + raw, err := json.Marshal(cur, jsonx.V1()) if err != nil { return "", errBindingMissing } diff --git a/services/gcpcloudbuild/persistence.go b/services/gcpcloudbuild/persistence.go index 9106fb5ead..f5a5c75439 100644 --- a/services/gcpcloudbuild/persistence.go +++ b/services/gcpcloudbuild/persistence.go @@ -2,7 +2,7 @@ package gcpcloudbuild import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" @@ -19,20 +19,20 @@ import ( const snapshotVersion = 1 type buildSnap struct { - OpName string `json:"opName"` - Build json.RawMessage `json:"build"` - Orig json.RawMessage `json:"orig"` + OpName string `json:"opName"` + Build jsontext.Value `json:"build"` + Orig jsontext.Value `json:"orig"` } type backendSnapshot struct { - Builds map[string]buildSnap `json:"builds"` - Triggers map[string]json.RawMessage `json:"triggers"` - Pools map[string]json.RawMessage `json:"workerPools"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` + Builds map[string]buildSnap `json:"builds"` + Triggers map[string]jsontext.Value `json:"triggers"` + Pools map[string]jsontext.Value `json:"workerPools"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -41,7 +41,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } @@ -54,8 +54,8 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { snap := backendSnapshot{ Version: snapshotVersion, Operations: b.ops.Snapshot(), Builds: make(map[string]buildSnap, len(b.builds)), - Triggers: make(map[string]json.RawMessage, len(b.triggers)), - Pools: make(map[string]json.RawMessage, len(b.pools)), + Triggers: make(map[string]jsontext.Value, len(b.triggers)), + Pools: make(map[string]jsontext.Value, len(b.pools)), } for k, rec := range b.builds { @@ -170,7 +170,7 @@ func restoreBuilds(in map[string]buildSnap) (map[string]*buildRec, error) { return out, nil } -func restoreMap[T proto.Message](in map[string]json.RawMessage, mk func() T) (map[string]T, error) { +func restoreMap[T proto.Message](in map[string]jsontext.Value, mk func() T) (map[string]T, error) { out := make(map[string]T, len(in)) for k, raw := range in { diff --git a/services/gcpcloudbuild/publish.go b/services/gcpcloudbuild/publish.go index 1a64bad914..04762ccf67 100644 --- a/services/gcpcloudbuild/publish.go +++ b/services/gcpcloudbuild/publish.go @@ -4,7 +4,7 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/url" @@ -17,6 +17,7 @@ import ( "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -221,7 +222,7 @@ func (r *runner) writeManifest(bucket, prefix string, lines []manifestLine) (str var sb strings.Builder for _, l := range lines { - raw, err := json.Marshal(l) + raw, err := json.Marshal(l, jsonx.V1()) if err != nil { return "", fmt.Errorf("encode manifest: %w", err) } diff --git a/services/gcpcloudbuild/pubsubtrigger.go b/services/gcpcloudbuild/pubsubtrigger.go index d7f49dc36a..d7959f72db 100644 --- a/services/gcpcloudbuild/pubsubtrigger.go +++ b/services/gcpcloudbuild/pubsubtrigger.go @@ -2,11 +2,12 @@ package gcpcloudbuild import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "time" "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -65,5 +66,5 @@ func pubsubPayload( "publishTime": published.UTC().Format(time.RFC3339Nano), }, "subscription": subscription, - }) + }, jsonx.V1()) } diff --git a/services/gcpcloudtasks/helpers_test.go b/services/gcpcloudtasks/helpers_test.go index 0b69aabd85..47c1299021 100644 --- a/services/gcpcloudtasks/helpers_test.go +++ b/services/gcpcloudtasks/helpers_test.go @@ -3,7 +3,7 @@ package gcpcloudtasks_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -23,6 +23,7 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpcloudtasks" ) @@ -201,7 +202,7 @@ func decodeJWT(t *testing.T, token string) map[string]any { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) return claims } diff --git a/services/gcpcloudtasks/persistence.go b/services/gcpcloudtasks/persistence.go index 2d1123f88e..c171328803 100644 --- a/services/gcpcloudtasks/persistence.go +++ b/services/gcpcloudtasks/persistence.go @@ -2,7 +2,7 @@ package gcpcloudtasks import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -20,22 +20,22 @@ import ( const snapshotVersion = 1 type queueSnap struct { - Tasks map[string]json.RawMessage `json:"tasks,omitempty"` - Queue json.RawMessage `json:"queue"` + Tasks map[string]jsontext.Value `json:"tasks,omitempty"` + Queue jsontext.Value `json:"queue"` } type backendSnapshot struct { - Queues map[string]queueSnap `json:"queues"` - Tombstones map[string]time.Time `json:"tombstones"` - Policies map[string]json.RawMessage `json:"policies"` - Cmek map[string]json.RawMessage `json:"cmek"` - Ops map[string]json.RawMessage `json:"operations,omitempty"` - EtagSeq uint64 `json:"etagSeq"` - TaskSeq uint64 `json:"taskSeq"` - Version int `json:"version"` + Queues map[string]queueSnap `json:"queues"` + Tombstones map[string]time.Time `json:"tombstones"` + Policies map[string]jsontext.Value `json:"policies"` + Cmek map[string]jsontext.Value `json:"cmek"` + Ops map[string]jsontext.Value `json:"operations,omitempty"` + EtagSeq uint64 `json:"etagSeq"` + TaskSeq uint64 `json:"taskSeq"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -44,7 +44,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } @@ -59,12 +59,12 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { TaskSeq: b.taskSeq, Queues: make(map[string]queueSnap, len(b.queues)), Tombstones: make(map[string]time.Time, len(b.tombstones)), - Policies: make(map[string]json.RawMessage, len(b.policies)), - Cmek: make(map[string]json.RawMessage, len(b.cmek)), + Policies: make(map[string]jsontext.Value, len(b.policies)), + Cmek: make(map[string]jsontext.Value, len(b.cmek)), } for name, q := range b.queues { - qs := queueSnap{Queue: marshalPB(q.pb), Tasks: make(map[string]json.RawMessage, len(q.tasks))} + qs := queueSnap{Queue: marshalPB(q.pb), Tasks: make(map[string]jsontext.Value, len(q.tasks))} for tn, t := range q.tasks { qs.Tasks[tn] = marshalPB(t.pb) } diff --git a/services/gcpcompute/filter.go b/services/gcpcompute/filter.go index 79100c77d0..ecba84d2cd 100644 --- a/services/gcpcompute/filter.go +++ b/services/gcpcompute/filter.go @@ -1,11 +1,13 @@ package gcpcompute import ( - "encoding/json" + "encoding/json/v2" "regexp" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const quotePair = 2 @@ -325,13 +327,13 @@ func parseFilter(expr string) (filterNode, *APIError) { } func toDoc(item any) map[string]any { - data, err := json.Marshal(item) + data, err := json.Marshal(item, jsonx.V1()) if err != nil { return nil } var m map[string]any - _ = json.Unmarshal(data, &m) + _ = json.Unmarshal(data, &m, jsonx.V1()) return m } diff --git a/services/gcpcompute/handler.go b/services/gcpcompute/handler.go index 3af0e8daa2..9b21fbfb22 100644 --- a/services/gcpcompute/handler.go +++ b/services/gcpcompute/handler.go @@ -2,7 +2,7 @@ package gcpcompute import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" @@ -264,11 +265,11 @@ func decode[T any](rq *request) (*T, map[string]any, *APIError) { return v, raw, nil } - if err := json.Unmarshal(rq.body, v); err != nil { + if err := json.Unmarshal(rq.body, v, jsonx.V1()); err != nil { return nil, nil, errInvalid("Invalid JSON payload received. %v", err) } - if err := json.Unmarshal(rq.body, &raw); err != nil { + if err := json.Unmarshal(rq.body, &raw, jsonx.V1()); err != nil { return nil, nil, errInvalid("Invalid JSON payload received. %v", err) } diff --git a/services/gcpcompute/labels.go b/services/gcpcompute/labels.go index 1e4dab58ca..d8e7d5aa23 100644 --- a/services/gcpcompute/labels.go +++ b/services/gcpcompute/labels.go @@ -1,10 +1,12 @@ package gcpcompute import ( - "encoding/json" + "encoding/json/v2" "maps" "regexp" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -74,7 +76,7 @@ func labelFP(labels map[string]string) string { pairs[i] = [2]string{k, labels[k]} } - data, _ := json.Marshal(pairs) + data, _ := json.Marshal(pairs, jsonx.V1()) return fingerprint(string(data)) } diff --git a/services/gcpcompute/lb_neg_test.go b/services/gcpcompute/lb_neg_test.go index c9f5e27464..6267b871a5 100644 --- a/services/gcpcompute/lb_neg_test.go +++ b/services/gcpcompute/lb_neg_test.go @@ -1,7 +1,7 @@ package gcpcompute_test import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "testing" @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/api/iterator" "google.golang.org/protobuf/proto" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type globalAttach = computepb.GlobalNetworkEndpointGroupsAttachEndpointsRequest @@ -416,7 +418,7 @@ func TestNEGAsBackend(t *testing.T) { } `json:"items"` } - require.NoError(t, json.Unmarshal([]byte(body), &listed)) + require.NoError(t, json.Unmarshal([]byte(body), &listed, jsonx.V1())) require.Len(t, listed.Items, 1) require.Len(t, listed.Items[0].Healths, 1) assert.Equal(t, "HEALTHY", listed.Items[0].Healths[0].HealthState) diff --git a/services/gcpcompute/meta.go b/services/gcpcompute/meta.go index 229f954476..57029e4b95 100644 --- a/services/gcpcompute/meta.go +++ b/services/gcpcompute/meta.go @@ -5,8 +5,10 @@ import ( "crypto/sha256" "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -95,13 +97,13 @@ func clone[T any](v *T) *T { return nil } - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } out := new(T) - if err = json.Unmarshal(data, out); err != nil { + if err = json.Unmarshal(data, out, jsonx.V1()); err != nil { return v } @@ -127,13 +129,13 @@ func regionOfZone(zone string) string { } func fromDoc[T any](m map[string]any) (*T, error) { - data, err := json.Marshal(m) + data, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil, err } out := new(T) - if err = json.Unmarshal(data, out); err != nil { + if err = json.Unmarshal(data, out, jsonx.V1()); err != nil { return nil, err } diff --git a/services/gcpcompute/rest_test.go b/services/gcpcompute/rest_test.go index 566dea5e28..8aa9b5749e 100644 --- a/services/gcpcompute/rest_test.go +++ b/services/gcpcompute/rest_test.go @@ -1,7 +1,7 @@ package gcpcompute_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/api/iterator" "google.golang.org/protobuf/proto" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listDoc struct { @@ -29,7 +31,7 @@ func getJSON[T any](t *testing.T, e *env, path string, want int) T { require.Equal(t, want, status, body) var out T - require.NoError(t, json.Unmarshal([]byte(body), &out), body) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1()), body) return out } @@ -209,7 +211,7 @@ func TestListQueryErrors(t *testing.T) { } `json:"error"` } - require.NoError(t, json.Unmarshal([]byte(body), &doc)) + require.NoError(t, json.Unmarshal([]byte(body), &doc, jsonx.V1())) assert.Equal(t, http.StatusBadRequest, doc.Error.Code) require.NotEmpty(t, doc.Error.Errors) assert.Equal(t, "invalid", doc.Error.Errors[0].Reason) @@ -473,7 +475,7 @@ func TestErrorBodyShape(t *testing.T) { } `json:"error"` } - require.NoError(t, json.Unmarshal([]byte(body), &doc)) + require.NoError(t, json.Unmarshal([]byte(body), &doc, jsonx.V1())) assert.Equal(t, 404, doc.Error.Code) assert.Equal(t, "The resource 'projects/p/global/networks/ghost' was not found", doc.Error.Message) require.Len(t, doc.Error.Errors, 1) diff --git a/services/gcpcontainer/persistence.go b/services/gcpcontainer/persistence.go index d65acfa1d8..2bc0a871d5 100644 --- a/services/gcpcontainer/persistence.go +++ b/services/gcpcontainer/persistence.go @@ -2,7 +2,7 @@ package gcpcontainer import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -18,32 +18,32 @@ import ( const snapshotVersion = 1 type poolSnap struct { - PrevVersion string `json:"prevVersion"` - DeleteOp string `json:"deleteOp"` - Pool json.RawMessage `json:"pool"` - Size int32 `json:"size"` + PrevVersion string `json:"prevVersion"` + DeleteOp string `json:"deleteOp"` + Pool jsontext.Value `json:"pool"` + Size int32 `json:"size"` } type clusterSnap struct { - Rotation *rotationRec `json:"rotation,omitempty"` - Cluster json.RawMessage `json:"cluster"` - Project string `json:"project"` - Failure string `json:"failure"` - DeleteOp string `json:"deleteOp"` - Pools []poolSnap `json:"pools"` + Rotation *rotationRec `json:"rotation,omitempty"` + Cluster jsontext.Value `json:"cluster"` + Project string `json:"project"` + Failure string `json:"failure"` + DeleteOp string `json:"deleteOp"` + Pools []poolSnap `json:"pools"` } type opSnap struct { - EndAt time.Time `json:"endAt"` - Project string `json:"project"` - Location string `json:"location"` - Cluster string `json:"cluster"` - Pool string `json:"pool"` - Failure string `json:"failure"` - Op json.RawMessage `json:"op"` - Kind opKind `json:"kind"` - Manual bool `json:"manual"` - Finished bool `json:"finished"` + EndAt time.Time `json:"endAt"` + Project string `json:"project"` + Location string `json:"location"` + Cluster string `json:"cluster"` + Pool string `json:"pool"` + Failure string `json:"failure"` + Op jsontext.Value `json:"op"` + Kind opKind `json:"kind"` + Manual bool `json:"manual"` + Finished bool `json:"finished"` } type backendSnapshot struct { @@ -53,7 +53,7 @@ type backendSnapshot struct { Seq uint64 `json:"seq"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { raw, err := protojson.Marshal(m) if err != nil { return nil diff --git a/services/gcpcontainer/rest_test.go b/services/gcpcontainer/rest_test.go index ca4c1aa1b4..c9c5fbb218 100644 --- a/services/gcpcontainer/rest_test.go +++ b/services/gcpcontainer/rest_test.go @@ -3,7 +3,7 @@ package gcpcontainer_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -115,7 +116,7 @@ func (e *env) raw(t *testing.T, method, path string, body any) (int, map[string] var rdr io.Reader if body != nil { - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rdr = bytes.NewReader(raw) @@ -136,7 +137,7 @@ func (e *env) raw(t *testing.T, method, path string, body any) (int, map[string] require.NoError(t, err) if len(raw) > 0 { - require.NoError(t, json.Unmarshal(raw, &out), string(raw)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1()), string(raw)) } return resp.StatusCode, out diff --git a/services/gcpdatastore/persistence.go b/services/gcpdatastore/persistence.go index 093551b1af..2b6c07b43b 100644 --- a/services/gcpdatastore/persistence.go +++ b/services/gcpdatastore/persistence.go @@ -2,7 +2,7 @@ package gcpdatastore import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -18,20 +18,20 @@ import ( const snapshotVersion = 1 type entitySnap struct { - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Entity json.RawMessage `json:"entity"` - Version int64 `json:"version"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + Entity jsontext.Value `json:"entity"` + Version int64 `json:"version"` } type backendSnapshot struct { - LastCommit time.Time `json:"lastCommit"` - Entities map[string]entitySnap `json:"entities"` - UsedIDs map[string][]int64 `json:"usedIds"` - Indexes map[string]json.RawMessage `json:"indexes"` - Operations map[string]json.RawMessage `json:"operations"` - NextIndex int64 `json:"nextIndex"` - Version int `json:"version"` + LastCommit time.Time `json:"lastCommit"` + Entities map[string]entitySnap `json:"entities"` + UsedIDs map[string][]int64 `json:"usedIds"` + Indexes map[string]jsontext.Value `json:"indexes"` + Operations map[string]jsontext.Value `json:"operations"` + NextIndex int64 `json:"nextIndex"` + Version int `json:"version"` } // Snapshot serialises the current state of every entity, reserved ids, indexes and operations. @@ -46,7 +46,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NextIndex: b.nextIndex, Entities: make(map[string]entitySnap, len(b.ents)), UsedIDs: make(map[string][]int64, len(b.used)), - Indexes: make(map[string]json.RawMessage, len(b.indexes)), + Indexes: make(map[string]jsontext.Value, len(b.indexes)), Operations: b.ops.Snapshot(), } diff --git a/services/gcpdns/handler.go b/services/gcpdns/handler.go index 885c5f6025..be0ef10350 100644 --- a/services/gcpdns/handler.go +++ b/services/gcpdns/handler.go @@ -2,7 +2,7 @@ package gcpdns import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,7 @@ import ( pkgdns "github.com/blackbirdworks/gopherstack/pkgs/dns" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" @@ -185,11 +186,11 @@ func decode[T any](c *echo.Context) (*T, map[string]any, error) { return v, raw, nil } - if err = json.Unmarshal(data, v); err != nil { + if err = json.Unmarshal(data, v, jsonx.V1()); err != nil { return nil, nil, errInvalid("Invalid JSON payload received. %v", err) } - if err = json.Unmarshal(data, &raw); err != nil { + if err = json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return nil, nil, errInvalid("Invalid JSON payload received. %v", err) } diff --git a/services/gcpdns/handler_test.go b/services/gcpdns/handler_test.go index 37543094fe..c612f2887f 100644 --- a/services/gcpdns/handler_test.go +++ b/services/gcpdns/handler_test.go @@ -1,7 +1,8 @@ package gcpdns_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "strings" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpdns" ) @@ -65,7 +67,7 @@ func TestWireErrorsAndRouting(t *testing.T) { } `json:"error"` } - require.NoError(t, json.Unmarshal(raw, &body), string(raw)) + require.NoError(t, json.Unmarshal(raw, &body, jsonx.V1()), string(raw)) assert.Equal(t, tt.status, body.Error.Code) assert.NotEmpty(t, body.Error.Message) require.Len(t, body.Error.Errors, 1) @@ -89,7 +91,7 @@ func TestWireShapes(t *testing.T) { var m map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &m, jsonx.V1())) return m } diff --git a/services/gcpdns/rrsets.go b/services/gcpdns/rrsets.go index 2e2b5209ad..c32c30fb1b 100644 --- a/services/gcpdns/rrsets.go +++ b/services/gcpdns/rrsets.go @@ -1,7 +1,7 @@ package gcpdns import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" @@ -10,6 +10,7 @@ import ( "github.com/miekg/dns" dnsapi "google.golang.org/api/dns/v1" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -52,8 +53,8 @@ func sameRRSet(a, b *dnsapi.ResourceRecordSet) bool { return false } - ar, _ := json.Marshal(a.RoutingPolicy) - br, _ := json.Marshal(b.RoutingPolicy) + ar, _ := json.Marshal(a.RoutingPolicy, jsonx.V1()) + br, _ := json.Marshal(b.RoutingPolicy, jsonx.V1()) return string(ar) == string(br) } diff --git a/services/gcpdns/store.go b/services/gcpdns/store.go index a42edd648f..9dbfce8f08 100644 --- a/services/gcpdns/store.go +++ b/services/gcpdns/store.go @@ -3,13 +3,14 @@ package gcpdns import ( "crypto/rand" "encoding/binary" - "encoding/json" + "encoding/json/v2" "strings" "time" "github.com/miekg/dns" dnsapi "google.golang.org/api/dns/v1" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -109,13 +110,13 @@ func clone[T any](v *T) *T { return nil } - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } out := new(T) - if err = json.Unmarshal(data, out); err != nil { + if err = json.Unmarshal(data, out, jsonx.V1()); err != nil { return v } @@ -154,25 +155,25 @@ func mergePatch(dst, patch map[string]any) { } func patched[T any](cur *T, patch map[string]any) (*T, error) { - data, err := json.Marshal(cur) + data, err := json.Marshal(cur, jsonx.V1()) if err != nil { return nil, errInvalid("Invalid JSON payload received.") } var m map[string]any - if err = json.Unmarshal(data, &m); err != nil { + if err = json.Unmarshal(data, &m, jsonx.V1()); err != nil { return nil, errInvalid("Invalid JSON payload received.") } mergePatch(m, patch) - data, err = json.Marshal(m) + data, err = json.Marshal(m, jsonx.V1()) if err != nil { return nil, errInvalid("Invalid JSON payload received.") } out := new(T) - if err = json.Unmarshal(data, out); err != nil { + if err = json.Unmarshal(data, out, jsonx.V1()); err != nil { return nil, errInvalid("Invalid JSON payload received: %v", err) } diff --git a/services/gcpeventarc/delivery.go b/services/gcpeventarc/delivery.go index 3e0c681290..9ebb6d54af 100644 --- a/services/gcpeventarc/delivery.go +++ b/services/gcpeventarc/delivery.go @@ -4,7 +4,8 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/gcpdispatch" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -96,7 +98,11 @@ func (b *InMemoryBackend) servePush(w http.ResponseWriter, r *http.Request) { } var env pushedEnvelope - if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxPushBytes)).Decode(&env); err != nil { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(w, r.Body, maxPushBytes)), + &env, + jsonx.V1(), + ); err != nil { http.Error(w, "invalid push envelope: "+err.Error(), http.StatusBadRequest) return @@ -162,20 +168,20 @@ func (b *InMemoryBackend) deliverToWorkflow( } type structuredEvent struct { - SpecVersion string `json:"specversion"` - ID string `json:"id"` - Source string `json:"source"` - Type string `json:"type"` - DataContentType string `json:"datacontenttype"` - Subject string `json:"subject,omitempty"` - Time string `json:"time,omitempty"` - Data json.RawMessage `json:"data"` + SpecVersion string `json:"specversion"` + ID string `json:"id"` + Source string `json:"source"` + Type string `json:"type"` + DataContentType string `json:"datacontenttype"` + Subject string `json:"subject,omitempty"` + Time string `json:"time,omitempty"` + Data jsontext.Value `json:"data"` } func workflowArgument(ev cloudEvent) ([]byte, error) { - data := json.RawMessage(ev.body) - if !json.Valid(data) { - quoted, err := json.Marshal(string(ev.body)) + data := jsontext.Value(ev.body) + if !data.IsValid(jsonx.V1()) { + quoted, err := json.Marshal(string(ev.body), jsonx.V1()) if err != nil { return nil, err } @@ -186,12 +192,12 @@ func workflowArgument(ev cloudEvent) ([]byte, error) { return json.Marshal(structuredEvent{ SpecVersion: "1.0", ID: ev.id, Source: ev.source, Type: ev.typ, Subject: ev.subject, Time: ev.time, DataContentType: contentTypeJSON, Data: data, - }) + }, jsonx.V1()) } func buildEvent(t *eventarcpb.Trigger, env pushedEnvelope) (cloudEvent, bool) { if eventType(t.GetEventFilters()) == typePubsubPublished { - body, err := json.Marshal(pushedEnvelope{Subscription: env.Subscription, Message: env.Message}) + body, err := json.Marshal(pushedEnvelope{Subscription: env.Subscription, Message: env.Message}, jsonx.V1()) if err != nil { return cloudEvent{}, false } diff --git a/services/gcpeventarc/delivery_test.go b/services/gcpeventarc/delivery_test.go index 5e9ea1f2c4..ce9465c500 100644 --- a/services/gcpeventarc/delivery_test.go +++ b/services/gcpeventarc/delivery_test.go @@ -2,7 +2,7 @@ package gcpeventarc_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -14,6 +14,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/protobuf/types/known/fieldmaskpb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPubsubDelivery(t *testing.T) { @@ -89,7 +91,7 @@ func TestPubsubDelivery(t *testing.T) { } `json:"message"` Subscription string `json:"subscription"` } - require.NoError(t, json.Unmarshal(got.body, &body)) + require.NoError(t, json.Unmarshal(got.body, &body, jsonx.V1())) raw, err := base64.StdEncoding.DecodeString(body.Message.Data) require.NoError(t, err) @@ -231,7 +233,7 @@ func TestStorageDelivery(t *testing.T) { assert.Equal(t, "application/json", got.header.Get("Content-Type")) var data map[string]any - require.NoError(t, json.Unmarshal(got.body, &data)) + require.NoError(t, json.Unmarshal(got.body, &data, jsonx.V1())) assert.Equal(t, "photos", data["bucket"]) assert.Equal(t, strings.TrimPrefix(tt.subject, "objects/"), data["name"]) }) diff --git a/services/gcpeventarc/persistence.go b/services/gcpeventarc/persistence.go index 245db08d77..3d34b43ba1 100644 --- a/services/gcpeventarc/persistence.go +++ b/services/gcpeventarc/persistence.go @@ -2,7 +2,7 @@ package gcpeventarc import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/eventarc/apiv1/eventarcpb" @@ -18,27 +18,27 @@ import ( const snapshotVersion = 1 type triggerSnap struct { - Trigger json.RawMessage `json:"trigger"` - Policy json.RawMessage `json:"policy,omitempty"` - OwnedTopic bool `json:"ownedTopic,omitempty"` + Trigger jsontext.Value `json:"trigger"` + Policy jsontext.Value `json:"policy,omitempty"` + OwnedTopic bool `json:"ownedTopic,omitempty"` } type channelSnap struct { - Channel json.RawMessage `json:"channel"` - Policy json.RawMessage `json:"policy,omitempty"` + Channel jsontext.Value `json:"channel"` + Policy jsontext.Value `json:"policy,omitempty"` } type backendSnapshot struct { - Triggers map[string]triggerSnap `json:"triggers"` - Channels map[string]channelSnap `json:"channels"` - Connections map[string]json.RawMessage `json:"connections"` - Configs map[string]json.RawMessage `json:"configs"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` - Seq uint64 `json:"seq"` + Triggers map[string]triggerSnap `json:"triggers"` + Channels map[string]channelSnap `json:"channels"` + Connections map[string]jsontext.Value `json:"connections"` + Configs map[string]jsontext.Value `json:"configs"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` + Seq uint64 `json:"seq"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { if m == nil || !m.ProtoReflect().IsValid() { return nil } @@ -55,7 +55,7 @@ func unmarshalPB(raw []byte, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } -func restorePolicy(raw json.RawMessage) (*iampb.Policy, error) { +func restorePolicy(raw jsontext.Value) (*iampb.Policy, error) { if len(raw) == 0 { return nil, nil //nolint:nilnil // no policy stored } @@ -77,8 +77,8 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Version: snapshotVersion, Seq: b.seq, Triggers: make(map[string]triggerSnap, len(b.triggers)), Channels: make(map[string]channelSnap, len(b.channels)), - Connections: make(map[string]json.RawMessage, len(b.conns)), - Configs: make(map[string]json.RawMessage, len(b.configs)), + Connections: make(map[string]jsontext.Value, len(b.conns)), + Configs: make(map[string]jsontext.Value, len(b.configs)), Operations: b.ops.Snapshot(), } diff --git a/services/gcpeventarc/workflow_delivery_test.go b/services/gcpeventarc/workflow_delivery_test.go index d32e207427..a194103177 100644 --- a/services/gcpeventarc/workflow_delivery_test.go +++ b/services/gcpeventarc/workflow_delivery_test.go @@ -2,7 +2,7 @@ package gcpeventarc_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "testing" "cloud.google.com/go/eventarc/apiv1/eventarcpb" @@ -13,6 +13,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/protobuf/types/known/fieldmaskpb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const eventWorkflow = ` @@ -94,7 +96,7 @@ func TestPubsubEventStartsWorkflowExecution(t *testing.T) { require.Equal(t, executionspb.Execution_SUCCEEDED, execs[0].GetState(), execs[0].GetError().GetPayload()) var arg map[string]any - require.NoError(t, json.Unmarshal([]byte(execs[0].GetArgument()), &arg)) + require.NoError(t, json.Unmarshal([]byte(execs[0].GetArgument()), &arg, jsonx.V1())) assert.Equal(t, "1.0", arg["specversion"]) assert.Equal(t, "google.cloud.pubsub.topic.v1.messagePublished", arg["type"]) @@ -111,7 +113,7 @@ func TestPubsubEventStartsWorkflowExecution(t *testing.T) { Subscription string `json:"subscription"` } `json:"data"` } - require.NoError(t, json.Unmarshal([]byte(execs[0].GetResult()), &res)) + require.NoError(t, json.Unmarshal([]byte(execs[0].GetResult()), &res, jsonx.V1())) assert.Equal(t, "1.0", res.Specversion) assert.Equal(t, "//pubsub.googleapis.com/"+topic, res.Source) @@ -148,7 +150,7 @@ func TestStorageEventStartsWorkflowExecution(t *testing.T) { Name string `json:"name"` } `json:"data"` } - require.NoError(t, json.Unmarshal([]byte(execs[0].GetResult()), &res)) + require.NoError(t, json.Unmarshal([]byte(execs[0].GetResult()), &res, jsonx.V1())) assert.Equal(t, "google.cloud.storage.object.v1.finalized", res.Type) assert.Equal(t, "events-bkt", res.Data.Bucket) assert.Equal(t, "dir/a.txt", res.Data.Name) diff --git a/services/gcpfirestore/persistence.go b/services/gcpfirestore/persistence.go index 9604e8a4a9..1609f61c9f 100644 --- a/services/gcpfirestore/persistence.go +++ b/services/gcpfirestore/persistence.go @@ -2,7 +2,7 @@ package gcpfirestore import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -17,9 +17,9 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - LastCommit time.Time `json:"lastCommit"` - Docs map[string]json.RawMessage `json:"docs"` - Version int `json:"version"` + LastCommit time.Time `json:"lastCommit"` + Docs map[string]jsontext.Value `json:"docs"` + Version int `json:"version"` } // Snapshot serialises the current state of every document; version history and transactions are not persisted. @@ -30,7 +30,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { snap := backendSnapshot{ Version: snapshotVersion, LastCommit: b.lastCommit, - Docs: make(map[string]json.RawMessage, len(b.docs)), + Docs: make(map[string]jsontext.Value, len(b.docs)), } for name, rec := range b.docs { diff --git a/services/gcpfunctions/docker_test.go b/services/gcpfunctions/docker_test.go index ec796726d6..a04e097978 100644 --- a/services/gcpfunctions/docker_test.go +++ b/services/gcpfunctions/docker_test.go @@ -4,7 +4,7 @@ import ( "context" "crypto/rand" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" "log/slog" "net/http" "os" @@ -109,7 +109,7 @@ func TestDocker_StorageTriggeredFunction(t *testing.T) { } gcs := gcpstorage.NewInMemoryBackend() - _, err = gcs.CreateBucket("test-project", gcpstorage.Patch{"name": json.RawMessage(`"inbox"`)}) + _, err = gcs.CreateBucket("test-project", gcpstorage.Patch{"name": jsontext.Value(`"inbox"`)}) require.NoError(t, err) e := newEnv(t, false) diff --git a/services/gcpfunctions/fake_runtime_test.go b/services/gcpfunctions/fake_runtime_test.go index a103f6fc09..be4924b62b 100644 --- a/services/gcpfunctions/fake_runtime_test.go +++ b/services/gcpfunctions/fake_runtime_test.go @@ -2,7 +2,8 @@ package gcpfunctions_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type received struct { @@ -118,7 +120,7 @@ func (f *fakeRuntime) CreateAndStart(_ context.Context, spec container.Spec) (st } w.WriteHeader(int(f.reply.Load())) - _ = json.NewEncoder(w).Encode(rec) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), rec) })} go f.serve(c, l, addr) diff --git a/services/gcpfunctions/helpers_test.go b/services/gcpfunctions/helpers_test.go index d733c2e9e1..d3d2de1596 100644 --- a/services/gcpfunctions/helpers_test.go +++ b/services/gcpfunctions/helpers_test.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpfunctions" ) @@ -222,7 +223,7 @@ func decode(t *testing.T, body string) received { var r received - require.NoError(t, json.Unmarshal([]byte(body), &r)) + require.NoError(t, json.Unmarshal([]byte(body), &r, jsonx.V1())) return r } diff --git a/services/gcpfunctions/ingress.go b/services/gcpfunctions/ingress.go index 2aea6a1ad2..324e7681e2 100644 --- a/services/gcpfunctions/ingress.go +++ b/services/gcpfunctions/ingress.go @@ -2,7 +2,8 @@ package gcpfunctions import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net" @@ -17,6 +18,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/gcpevents" "github.com/blackbirdworks/gopherstack/pkgs/gcpscale" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -303,7 +305,11 @@ func (b *InMemoryBackend) serveEvent(w http.ResponseWriter, r *http.Request) { } var env pushEnvelope - if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxEventBytes)).Decode(&env); err != nil { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(http.MaxBytesReader(w, r.Body, maxEventBytes)), + &env, + jsonx.V1(), + ); err != nil { http.Error(w, "invalid push envelope: "+err.Error(), http.StatusBadRequest) return @@ -315,7 +321,7 @@ func (b *InMemoryBackend) serveEvent(w http.ResponseWriter, r *http.Request) { return } - body, err := json.Marshal(env) + body, err := json.Marshal(env, jsonx.V1()) if err != nil { http.Error(w, "encode event: "+err.Error(), http.StatusInternalServerError) diff --git a/services/gcpfunctions/persistence.go b/services/gcpfunctions/persistence.go index bdb3c5eff6..5241b6f699 100644 --- a/services/gcpfunctions/persistence.go +++ b/services/gcpfunctions/persistence.go @@ -2,7 +2,7 @@ package gcpfunctions import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/functions/apiv2/functionspb" @@ -18,11 +18,11 @@ import ( const snapshotVersion = 1 type fnSnap struct { - Function json.RawMessage `json:"function"` - Failure string `json:"failure"` - SubName string `json:"subName"` - Zip []byte `json:"zip"` - RevSeq int `json:"revSeq"` + Function jsontext.Value `json:"function"` + Failure string `json:"failure"` + SubName string `json:"subName"` + Zip []byte `json:"zip"` + RevSeq int `json:"revSeq"` } type uploadSnap struct { @@ -31,10 +31,10 @@ type uploadSnap struct { } type backendSnapshot struct { - Functions map[string]fnSnap `json:"functions"` - Uploads map[string]uploadSnap `json:"uploads"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` + Functions map[string]fnSnap `json:"functions"` + Uploads map[string]uploadSnap `json:"uploads"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` } // Snapshot serialises all functions and sources; containers are not part of it. diff --git a/services/gcpiam/keymat.go b/services/gcpiam/keymat.go index 9f65dac486..155e3a52b2 100644 --- a/services/gcpiam/keymat.go +++ b/services/gcpiam/keymat.go @@ -6,7 +6,8 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "math/big" "net/url" @@ -14,6 +15,8 @@ import ( "cloud.google.com/go/iam/admin/apiv1/adminpb" pkcs12 "software.sslmate.com/src/go-pkcs12" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -118,7 +121,7 @@ func (m *material) credentials(acct *adminpb.ServiceAccount, keyID string) ([]by return nil, errInternal(err) } - out, err := json.MarshalIndent(credentialsFile{ + out, err := json.Marshal(credentialsFile{ Type: credentialsType, ProjectID: acct.GetProjectId(), PrivateKeyID: keyID, @@ -130,7 +133,7 @@ func (m *material) credentials(acct *adminpb.ServiceAccount, keyID string) ([]by AuthProviderX509CertURL: authCertsURL, ClientX509CertURL: clientCertPrefix + url.PathEscape(acct.GetEmail()), UniverseDomain: universeDomain, - }, "", " ") + }, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) if err != nil { return nil, errInternal(err) } diff --git a/services/gcpiam/keys_test.go b/services/gcpiam/keys_test.go index d1577c53c1..992af2dfb7 100644 --- a/services/gcpiam/keys_test.go +++ b/services/gcpiam/keys_test.go @@ -6,7 +6,7 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "math/big" "testing" @@ -17,6 +17,8 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" pkcs12 "software.sslmate.com/src/go-pkcs12" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type keyFile struct { @@ -39,7 +41,7 @@ func createKey(t *testing.T, e *env, sa *adminpb.ServiceAccount) (*adminpb.Servi require.NoError(t, err) var kf keyFile - require.NoError(t, json.Unmarshal(key.GetPrivateKeyData(), &kf)) + require.NoError(t, json.Unmarshal(key.GetPrivateKeyData(), &kf, jsonx.V1())) return key, kf } @@ -123,7 +125,11 @@ func TestKeyAlgorithmsAndFormats(t *testing.T) { assert.Equal(t, adminpb.ServiceAccountKeyAlgorithm_KEY_ALG_RSA_2048, small.GetKeyAlgorithm()) var kf keyFile - require.NoError(t, json.Unmarshal(small.GetPrivateKeyData(), &kf), "default type is the credentials file") + require.NoError( + t, + json.Unmarshal(small.GetPrivateKeyData(), &kf, jsonx.V1()), + "default type is the credentials file", + ) assert.Equal(t, 2048, parsePrivate(t, kf.PrivateKey).N.BitLen()) p12, err := e.client.CreateServiceAccountKey(t.Context(), &adminpb.CreateServiceAccountKeyRequest{ diff --git a/services/gcpiam/persistence.go b/services/gcpiam/persistence.go index ac740127b7..fd93423ac8 100644 --- a/services/gcpiam/persistence.go +++ b/services/gcpiam/persistence.go @@ -2,7 +2,7 @@ package gcpiam import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -19,23 +19,23 @@ import ( const snapshotVersion = 1 type keySnap struct { - Key json.RawMessage `json:"key"` - Cert []byte `json:"cert,omitempty"` - Public []byte `json:"public,omitempty"` - Private []byte `json:"private,omitempty"` + Key jsontext.Value `json:"key"` + Cert []byte `json:"cert,omitempty"` + Public []byte `json:"public,omitempty"` + Private []byte `json:"private,omitempty"` } type accountSnap struct { Keys map[string]keySnap `json:"keys,omitempty"` System *keySnap `json:"system,omitempty"` DeletedAt time.Time `json:"deletedAt"` - Account json.RawMessage `json:"account"` - Policy json.RawMessage `json:"policy,omitempty"` + Account jsontext.Value `json:"account"` + Policy jsontext.Value `json:"policy,omitempty"` } type roleSnap struct { - DeletedAt time.Time `json:"deletedAt"` - Role json.RawMessage `json:"role"` + DeletedAt time.Time `json:"deletedAt"` + Role jsontext.Value `json:"role"` } type backendSnapshot struct { @@ -45,7 +45,7 @@ type backendSnapshot struct { Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -54,7 +54,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } diff --git a/services/gcpiam/sign.go b/services/gcpiam/sign.go index 803846aaaa..a9c38d98c3 100644 --- a/services/gcpiam/sign.go +++ b/services/gcpiam/sign.go @@ -7,12 +7,15 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "time" "cloud.google.com/go/iam/admin/apiv1/adminpb" "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -160,7 +163,7 @@ func (b *InMemoryBackend) SignJwt(name, payload string) (string, string, error) return "", "", err } - header, err := json.Marshal(map[string]string{"alg": "RS256", "typ": "JWT", "kid": keyID}) + header, err := json.Marshal(map[string]string{"alg": "RS256", "typ": "JWT", "kid": keyID}, jsonx.V1()) if err != nil { return "", "", errInternal(err) } @@ -179,17 +182,16 @@ func (b *InMemoryBackend) SignJwt(name, payload string) (string, string, error) func (b *InMemoryBackend) jwtClaims(payload string) ([]byte, error) { var claims map[string]any - dec := json.NewDecoder(strings.NewReader(payload)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(payload)) - if err := dec.Decode(&claims); err != nil || claims == nil { + if err := json.UnmarshalDecode(dec, &claims, jsonx.V1(), jsonx.UseNumber()); err != nil || claims == nil { return nil, errInvalid("The JWT payload must be a serialized JSON object.") } now := b.now() if raw, ok := claims["exp"]; ok { - num, isNum := raw.(json.Number) + num, isNum := raw.(jsonx.Number) exp, err := num.Int64() if !isNum || err != nil { @@ -203,7 +205,7 @@ func (b *InMemoryBackend) jwtClaims(payload string) ([]byte, error) { claims["exp"] = now.Add(jwtDefaultTTL).Unix() } - out, err := json.Marshal(claims) + out, err := json.Marshal(claims, jsonx.V1()) if err != nil { return nil, errInternal(err) } diff --git a/services/gcpiam/sign_test.go b/services/gcpiam/sign_test.go index fdcc93cdf7..a485f5b042 100644 --- a/services/gcpiam/sign_test.go +++ b/services/gcpiam/sign_test.go @@ -2,7 +2,7 @@ package gcpiam_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func systemPublicKey(t *testing.T, e *env, sa *adminpb.ServiceAccount, keyID string) []byte { @@ -61,14 +63,14 @@ func TestSignJwt(t *testing.T) { require.NoError(t, err) var header map[string]string - require.NoError(t, json.Unmarshal(hdr, &header)) + require.NoError(t, json.Unmarshal(hdr, &header, jsonx.V1())) assert.Equal(t, map[string]string{"alg": "RS256", "typ": "JWT", "kid": keyID}, header) body, err := enc.DecodeString(parts[1]) require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(body, &claims)) + require.NoError(t, json.Unmarshal(body, &claims, jsonx.V1())) assert.Equal(t, "someone", claims["sub"]) assert.InDelta(t, float64(now+3600), claims["exp"], 0, "exp defaults to one hour") @@ -137,7 +139,7 @@ func (e *env) signJwt(t *testing.T, name, payload string) (string, string, error } func itoa(n int64) string { - b, _ := json.Marshal(n) + b, _ := json.Marshal(n, jsonx.V1()) return string(b) } diff --git a/services/gcpidentitytoolkit/handler.go b/services/gcpidentitytoolkit/handler.go index 5f46d821da..75ff9a0c93 100644 --- a/services/gcpidentitytoolkit/handler.go +++ b/services/gcpidentitytoolkit/handler.go @@ -4,7 +4,7 @@ import ( "context" "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -21,6 +21,7 @@ import ( idtkv2 "google.golang.org/api/identitytoolkit/v2" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -159,11 +160,11 @@ func readJSON[T any](c *echo.Context) (*T, map[string]any, error) { return v, raw, nil } - if err = json.Unmarshal(data, v); err != nil { + if err = json.Unmarshal(data, v, jsonx.V1()); err != nil { return nil, nil, errInvalidArg("Invalid JSON payload received. %v", err) } - if err = json.Unmarshal(data, &raw); err != nil { + if err = json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return nil, nil, errInvalidArg("Invalid JSON payload received. %v", err) } diff --git a/services/gcpidentitytoolkit/helpers_test.go b/services/gcpidentitytoolkit/helpers_test.go index a15f4771e1..66619c080e 100644 --- a/services/gcpidentitytoolkit/helpers_test.go +++ b/services/gcpidentitytoolkit/helpers_test.go @@ -3,7 +3,8 @@ package gcpidentitytoolkit_test import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpidentitytoolkit" ) @@ -106,7 +108,7 @@ func (e *env) do(t *testing.T, method, path, auth string, body any) result { var rdr *bytes.Reader if body != nil { - data, err := json.Marshal(body) + data, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rdr = bytes.NewReader(data) @@ -129,7 +131,7 @@ func (e *env) do(t *testing.T, method, path, auth string, body any) result { defer resp.Body.Close() out := result{status: resp.StatusCode} - _ = json.NewDecoder(resp.Body).Decode(&out.body) + _ = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out.body, jsonx.V1()) return out } diff --git a/services/gcpidentitytoolkit/store.go b/services/gcpidentitytoolkit/store.go index 8a290cc0f5..b9899279d3 100644 --- a/services/gcpidentitytoolkit/store.go +++ b/services/gcpidentitytoolkit/store.go @@ -7,7 +7,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/hex" - "encoding/json" + "encoding/json/v2" "encoding/pem" "math/big" "sort" @@ -16,6 +16,7 @@ import ( idtkv2 "google.golang.org/api/identitytoolkit/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -365,13 +366,13 @@ func cloneJSON[T any](v *T) *T { return nil } - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } out := new(T) - if json.Unmarshal(data, out) != nil { + if json.Unmarshal(data, out, jsonx.V1()) != nil { return v } @@ -401,25 +402,25 @@ func mergePatch(dst, patch map[string]any) { } func patched[T any](cur *T, patch map[string]any) (*T, error) { - data, err := json.Marshal(cur) + data, err := json.Marshal(cur, jsonx.V1()) if err != nil { return nil, errInvalidArg("Invalid JSON payload received.") } var m map[string]any - if err = json.Unmarshal(data, &m); err != nil { + if err = json.Unmarshal(data, &m, jsonx.V1()); err != nil { return nil, errInvalidArg("Invalid JSON payload received.") } mergePatch(m, patch) - data, err = json.Marshal(m) + data, err = json.Marshal(m, jsonx.V1()) if err != nil { return nil, errInvalidArg("Invalid JSON payload received.") } out := new(T) - if err = json.Unmarshal(data, out); err != nil { + if err = json.Unmarshal(data, out, jsonx.V1()); err != nil { return nil, errInvalidArg("Invalid JSON payload received: %v", err) } diff --git a/services/gcpidentitytoolkit/tokens.go b/services/gcpidentitytoolkit/tokens.go index ee7ff38336..da1efa44bf 100644 --- a/services/gcpidentitytoolkit/tokens.go +++ b/services/gcpidentitytoolkit/tokens.go @@ -5,13 +5,15 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "maps" "strings" "time" "github.com/golang-jwt/jwt/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -48,7 +50,7 @@ func (b *InMemoryBackend) signRefreshLocked(p refreshPayload) (string, error) { return "", err } - body, err := json.Marshal(p) + body, err := json.Marshal(p, jsonx.V1()) if err != nil { return "", err //nolint:wrapcheck // unreachable } @@ -84,7 +86,7 @@ func (b *InMemoryBackend) parseRefreshLocked(tok string) (*refreshPayload, bool) } var p refreshPayload - if json.Unmarshal(body, &p) != nil { + if json.Unmarshal(body, &p, jsonx.V1()) != nil { return nil, false } @@ -126,7 +128,7 @@ func (b *InMemoryBackend) tokenClaims( if u.CustomAttrs != "" { var custom map[string]any - if json.Unmarshal([]byte(u.CustomAttrs), &custom) == nil { + if json.Unmarshal([]byte(u.CustomAttrs), &custom, jsonx.V1()) == nil { maps.Copy(claims, custom) } } diff --git a/services/gcpidentitytoolkit/users.go b/services/gcpidentitytoolkit/users.go index f80ddb0c4b..8d330c2f02 100644 --- a/services/gcpidentitytoolkit/users.go +++ b/services/gcpidentitytoolkit/users.go @@ -2,7 +2,7 @@ package gcpidentitytoolkit import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/mail" "regexp" "strconv" @@ -12,6 +12,7 @@ import ( idtkv2 "google.golang.org/api/identitytoolkit/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -286,7 +287,7 @@ func parseClaims(raw string) (string, error) { } var m map[string]any - if err := json.Unmarshal([]byte(raw), &m); err != nil { + if err := json.Unmarshal([]byte(raw), &m, jsonx.V1()); err != nil { return "", idtk("INVALID_CLAIMS") } diff --git a/services/gcpidentitytoolkit/wire.go b/services/gcpidentitytoolkit/wire.go index f7d20370bc..7c553ed636 100644 --- a/services/gcpidentitytoolkit/wire.go +++ b/services/gcpidentitytoolkit/wire.go @@ -2,8 +2,10 @@ package gcpidentitytoolkit import ( "bytes" - "encoding/json" + "encoding/json/v2" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // flexInt decodes a JSON number or a quoted number, the two forms Google clients send for int64 fields. @@ -84,7 +86,7 @@ func (u userWire) MarshalJSON() ([]byte, error) { aux.ValidSince = strconv.FormatInt(int64(u.ValidSince), 10) } - return json.Marshal(aux) + return json.Marshal(aux, jsonx.V1()) } // authRequest is the union of the request fields the account operations read. @@ -128,14 +130,14 @@ type flexStrings []string func (f *flexStrings) UnmarshalJSON(data []byte) error { var one string - if json.Unmarshal(data, &one) == nil { + if json.Unmarshal(data, &one, jsonx.V1()) == nil { *f = flexStrings{one} return nil } var many []string - if err := json.Unmarshal(data, &many); err != nil { + if err := json.Unmarshal(data, &many, jsonx.V1()); err != nil { return err //nolint:wrapcheck // surfaced as a JSON decode error } diff --git a/services/gcpkms/persistence.go b/services/gcpkms/persistence.go index dd23f7ede1..b756ce28c3 100644 --- a/services/gcpkms/persistence.go +++ b/services/gcpkms/persistence.go @@ -2,7 +2,7 @@ package gcpkms import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/iam/apiv1/iampb" @@ -19,34 +19,34 @@ const snapshotVersion = 1 // Key material is stored unencrypted in the snapshot: this is a local emulator, not a secure key store. type versionSnap struct { - Version json.RawMessage `json:"version"` - Material []byte `json:"material,omitempty"` - Deleted bool `json:"deleted,omitempty"` + Version jsontext.Value `json:"version"` + Material []byte `json:"material,omitempty"` + Deleted bool `json:"deleted,omitempty"` } type keySnap struct { - Key json.RawMessage `json:"key"` - Versions []versionSnap `json:"versions,omitempty"` - Primary int64 `json:"primary"` + Key jsontext.Value `json:"key"` + Versions []versionSnap `json:"versions,omitempty"` + Primary int64 `json:"primary"` } type jobSnap struct { - Job json.RawMessage `json:"job"` - Private []byte `json:"private,omitempty"` + Job jsontext.Value `json:"job"` + Private []byte `json:"private,omitempty"` } type backendSnapshot struct { - Rings map[string]json.RawMessage `json:"rings"` - Keys map[string]keySnap `json:"keys"` - Jobs map[string]jobSnap `json:"jobs"` - Retired map[string]json.RawMessage `json:"retired,omitempty"` - Ops map[string]json.RawMessage `json:"operations,omitempty"` - Policies map[string]json.RawMessage `json:"policies"` - EtagSeq uint64 `json:"etagSeq"` - Version int `json:"version"` + Rings map[string]jsontext.Value `json:"rings"` + Keys map[string]keySnap `json:"keys"` + Jobs map[string]jobSnap `json:"jobs"` + Retired map[string]jsontext.Value `json:"retired,omitempty"` + Ops map[string]jsontext.Value `json:"operations,omitempty"` + Policies map[string]jsontext.Value `json:"policies"` + EtagSeq uint64 `json:"etagSeq"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -55,7 +55,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } @@ -67,11 +67,11 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { snap := backendSnapshot{ Version: snapshotVersion, EtagSeq: b.etagSeq, - Rings: make(map[string]json.RawMessage, len(b.rings)), + Rings: make(map[string]jsontext.Value, len(b.rings)), Keys: make(map[string]keySnap, len(b.keys)), Jobs: make(map[string]jobSnap, len(b.jobs)), - Retired: make(map[string]json.RawMessage, len(b.retired)), - Policies: make(map[string]json.RawMessage, len(b.policies)), + Retired: make(map[string]jsontext.Value, len(b.retired)), + Policies: make(map[string]jsontext.Value, len(b.policies)), } for name, r := range b.rings { diff --git a/services/gcplogging/export_bq.go b/services/gcplogging/export_bq.go index 0998eb4867..a417ec3a09 100644 --- a/services/gcplogging/export_bq.go +++ b/services/gcplogging/export_bq.go @@ -1,12 +1,14 @@ package gcplogging import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" "cloud.google.com/go/logging/apiv2/loggingpb" "google.golang.org/protobuf/encoding/protojson" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func bqField(name, typ string) map[string]any { @@ -31,7 +33,7 @@ func bqRow(e *loggingpb.LogEntry) (map[string]any, error) { } doc := map[string]any{} - if err = json.Unmarshal(raw, &doc); err != nil { + if err = json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("decode entry: %w", err) } @@ -46,7 +48,7 @@ func bqRow(e *loggingpb.LogEntry) (map[string]any, error) { } if f["type"] == "JSON" { - enc, _ := json.Marshal(v) + enc, _ := json.Marshal(v, jsonx.V1()) v = string(enc) } diff --git a/services/gcplogging/filter_eval.go b/services/gcplogging/filter_eval.go index 43aa799498..72bb834a8e 100644 --- a/services/gcplogging/filter_eval.go +++ b/services/gcplogging/filter_eval.go @@ -1,7 +1,7 @@ package gcplogging import ( - "encoding/json" + "encoding/json/v2" "hash/fnv" "math" "net/url" @@ -13,6 +13,8 @@ import ( ltype "google.golang.org/genproto/googleapis/logging/type" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // entryDoc lazily renders a log entry as JSON-shaped data for filter evaluation. @@ -42,7 +44,7 @@ func (d *entryDoc) doc() map[string]any { raw, _ = protojson.Marshal(stripped) } - _ = json.Unmarshal(raw, &d.m) + _ = json.Unmarshal(raw, &d.m, jsonx.V1()) return d.m } @@ -199,7 +201,7 @@ func scalarString(v any) string { return "" } - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return string(b) } diff --git a/services/gcplogging/persistence.go b/services/gcplogging/persistence.go index e6f4ca289e..ecadec7139 100644 --- a/services/gcplogging/persistence.go +++ b/services/gcplogging/persistence.go @@ -2,7 +2,7 @@ package gcplogging import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "time" @@ -21,9 +21,9 @@ var errBadSnapshot = errors.New("gcplogging: corrupt snapshot") const snapshotVersion = 1 type bucketSnap struct { - DeleteRequested time.Time `json:"deleteRequested"` - Bucket json.RawMessage `json:"bucket"` - Entries []int `json:"entries,omitempty"` + DeleteRequested time.Time `json:"deleteRequested"` + Bucket jsontext.Value `json:"bucket"` + Entries []int `json:"entries,omitempty"` } type seriesSnap struct { @@ -36,26 +36,26 @@ type seriesSnap struct { } type metricSnap struct { - Metric json.RawMessage `json:"metric"` - Series []seriesSnap `json:"series,omitempty"` + Metric jsontext.Value `json:"metric"` + Series []seriesSnap `json:"series,omitempty"` } type backendSnapshot struct { - Buckets map[string]bucketSnap `json:"buckets"` - Views map[string]json.RawMessage `json:"views"` - Sinks map[string]json.RawMessage `json:"sinks"` - Exclusions map[string]json.RawMessage `json:"exclusions"` - Metrics map[string]metricSnap `json:"metrics"` - Settings map[string]json.RawMessage `json:"settings"` - Cmek map[string]json.RawMessage `json:"cmek"` - Ops map[string]json.RawMessage `json:"ops"` - Entries []json.RawMessage `json:"entries"` - Seeded []string `json:"seeded"` - Seq uint64 `json:"seq"` - Version int `json:"version"` + Buckets map[string]bucketSnap `json:"buckets"` + Views map[string]jsontext.Value `json:"views"` + Sinks map[string]jsontext.Value `json:"sinks"` + Exclusions map[string]jsontext.Value `json:"exclusions"` + Metrics map[string]metricSnap `json:"metrics"` + Settings map[string]jsontext.Value `json:"settings"` + Cmek map[string]jsontext.Value `json:"cmek"` + Ops map[string]jsontext.Value `json:"ops"` + Entries []jsontext.Value `json:"entries"` + Seeded []string `json:"seeded"` + Seq uint64 `json:"seq"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -64,12 +64,12 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } -func snapMap[T proto.Message](in map[string]T) map[string]json.RawMessage { - out := make(map[string]json.RawMessage, len(in)) +func snapMap[T proto.Message](in map[string]T) map[string]jsontext.Value { + out := make(map[string]jsontext.Value, len(in)) for k, v := range in { out[k] = marshalPB(v) } @@ -136,7 +136,7 @@ func snapMetric(rec *metricRec) metricSnap { return ms } -func restoreMap[T proto.Message](raw map[string]json.RawMessage, newT func() T) (map[string]T, error) { +func restoreMap[T proto.Message](raw map[string]jsontext.Value, newT func() T) (map[string]T, error) { out := make(map[string]T, len(raw)) for k, r := range raw { @@ -235,7 +235,7 @@ func (b *InMemoryBackend) load(snap *backendSnapshot) error { return b.loadMetrics(snap.Metrics) } -func decodeEntries(raw []json.RawMessage) ([]*loggingpb.LogEntry, error) { +func decodeEntries(raw []jsontext.Value) ([]*loggingpb.LogEntry, error) { out := make([]*loggingpb.LogEntry, len(raw)) for i, r := range raw { diff --git a/services/gcpmonitoring/alerts_test.go b/services/gcpmonitoring/alerts_test.go index cca9d3d8c2..b04e2746b3 100644 --- a/services/gcpmonitoring/alerts_test.go +++ b/services/gcpmonitoring/alerts_test.go @@ -1,7 +1,7 @@ package gcpmonitoring_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" @@ -11,6 +11,8 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/wrapperspb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const alertTopic = "projects/" + testProject + "/topics/alerts" @@ -90,7 +92,7 @@ func decode(t *testing.T, msg published) payload { t.Helper() var p payload - require.NoError(t, json.Unmarshal([]byte(msg.data), &p)) + require.NoError(t, json.Unmarshal([]byte(msg.data), &p, jsonx.V1())) return p } diff --git a/services/gcpmonitoring/helpers_test.go b/services/gcpmonitoring/helpers_test.go index a901703408..3e1859f32c 100644 --- a/services/gcpmonitoring/helpers_test.go +++ b/services/gcpmonitoring/helpers_test.go @@ -2,7 +2,7 @@ package gcpmonitoring_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync" "sync/atomic" @@ -22,6 +22,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpmonitoring" ) @@ -265,7 +266,7 @@ func mustLabels(t *testing.T, msg published) map[string]string { } `json:"metric"` } `json:"incident"` } - require.NoError(t, json.Unmarshal([]byte(msg.data), &p)) + require.NoError(t, json.Unmarshal([]byte(msg.data), &p, jsonx.V1())) return p.Incident.Metric.Labels } diff --git a/services/gcpmonitoring/notify.go b/services/gcpmonitoring/notify.go index 8d8f9d4294..d0e79d7eab 100644 --- a/services/gcpmonitoring/notify.go +++ b/services/gcpmonitoring/notify.go @@ -3,7 +3,7 @@ package gcpmonitoring import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -15,6 +15,7 @@ import ( "cloud.google.com/go/monitoring/apiv3/v2/monitoringpb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -83,7 +84,7 @@ func (b *InMemoryBackend) record(d Delivery) { // deliver sends the notification to every target; failures are logged and recorded, never fatal. func (b *InMemoryBackend) deliver(ctx context.Context, n notification) { - payload, err := json.Marshal(incidentPayload(n)) + payload, err := json.Marshal(incidentPayload(n), jsonx.V1()) if err != nil { return } diff --git a/services/gcpmonitoring/persistence.go b/services/gcpmonitoring/persistence.go index 4fb4a96f72..f4617c9ab3 100644 --- a/services/gcpmonitoring/persistence.go +++ b/services/gcpmonitoring/persistence.go @@ -2,7 +2,7 @@ package gcpmonitoring import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -19,38 +19,38 @@ import ( const snapshotVersion = 1 type seriesSnap struct { - Project string `json:"project"` - Unit string `json:"unit,omitempty"` - Series json.RawMessage `json:"series"` + Project string `json:"project"` + Unit string `json:"unit,omitempty"` + Series jsontext.Value `json:"series"` } type channelSnap struct { CodeAt time.Time `json:"codeAt"` Sensitive map[string]string `json:"sensitive,omitempty"` Code string `json:"code,omitempty"` - Channel json.RawMessage `json:"channel"` + Channel jsontext.Value `json:"channel"` } type uptimeSnap struct { - LastRun time.Time `json:"lastRun"` - Config json.RawMessage `json:"config"` + LastRun time.Time `json:"lastRun"` + Config jsontext.Value `json:"config"` } type backendSnapshot struct { - Descriptors map[string]json.RawMessage `json:"descriptors"` - Policies map[string]json.RawMessage `json:"policies"` - Groups map[string]json.RawMessage `json:"groups"` - Snoozes map[string]json.RawMessage `json:"snoozes,omitempty"` - Services map[string]json.RawMessage `json:"services,omitempty"` - SLOs map[string]json.RawMessage `json:"slos,omitempty"` - Channels map[string]channelSnap `json:"channels"` - Uptime map[string]uptimeSnap `json:"uptime"` - Series []seriesSnap `json:"series"` - Incidents []Incident `json:"incidents"` - Version int `json:"version"` + Descriptors map[string]jsontext.Value `json:"descriptors"` + Policies map[string]jsontext.Value `json:"policies"` + Groups map[string]jsontext.Value `json:"groups"` + Snoozes map[string]jsontext.Value `json:"snoozes,omitempty"` + Services map[string]jsontext.Value `json:"services,omitempty"` + SLOs map[string]jsontext.Value `json:"slos,omitempty"` + Channels map[string]channelSnap `json:"channels"` + Uptime map[string]uptimeSnap `json:"uptime"` + Series []seriesSnap `json:"series"` + Incidents []Incident `json:"incidents"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -59,12 +59,12 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } -func snapMap[T proto.Message](in map[string]T) map[string]json.RawMessage { - out := make(map[string]json.RawMessage, len(in)) +func snapMap[T proto.Message](in map[string]T) map[string]jsontext.Value { + out := make(map[string]jsontext.Value, len(in)) for k, v := range in { out[k] = marshalPB(v) } @@ -72,7 +72,7 @@ func snapMap[T proto.Message](in map[string]T) map[string]json.RawMessage { return out } -func restoreMap[T proto.Message](raw map[string]json.RawMessage, newT func() T) (map[string]T, error) { +func restoreMap[T proto.Message](raw map[string]jsontext.Value, newT func() T) (map[string]T, error) { out := make(map[string]T, len(raw)) for k, r := range raw { diff --git a/services/gcpmonitoring/promql_http.go b/services/gcpmonitoring/promql_http.go index 4923a48982..dba68777cd 100644 --- a/services/gcpmonitoring/promql_http.go +++ b/services/gcpmonitoring/promql_http.go @@ -1,7 +1,8 @@ package gcpmonitoring import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/amp/promql" ) @@ -69,7 +71,7 @@ type promResponse struct { func writePromJSON(w http.ResponseWriter, code int, body promResponse) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - _ = json.NewEncoder(w).Encode(body) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), body) } func promError(w http.ResponseWriter, code int, errType, msg string) { diff --git a/services/gcpmonitoring/promql_test.go b/services/gcpmonitoring/promql_test.go index 89b6e5c8dc..296f9dabdb 100644 --- a/services/gcpmonitoring/promql_test.go +++ b/services/gcpmonitoring/promql_test.go @@ -1,7 +1,7 @@ package gcpmonitoring_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -18,6 +18,7 @@ import ( "google.golang.org/genproto/googleapis/api/monitoredres" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -67,7 +68,7 @@ func (e *env) promQuery(t *testing.T, q string) promReply { require.Equal(t, http.StatusOK, code, string(body)) var r promReply - require.NoError(t, json.Unmarshal(body, &r)) + require.NoError(t, json.Unmarshal(body, &r, jsonx.V1())) return r } @@ -165,7 +166,7 @@ func TestPromQLRangeQuery(t *testing.T) { require.Equal(t, http.StatusOK, code, string(body)) var r promReply - require.NoError(t, json.Unmarshal(body, &r)) + require.NoError(t, json.Unmarshal(body, &r, jsonx.V1())) require.Equal(t, "matrix", r.Data.ResultType) require.Len(t, r.Data.Result, 1) @@ -294,7 +295,7 @@ func TestPromQLProjectIsolation(t *testing.T) { require.Equal(t, http.StatusOK, code) var r promReply - require.NoError(t, json.Unmarshal(body, &r)) + require.NoError(t, json.Unmarshal(body, &r, jsonx.V1())) assert.Empty(t, r.Data.Result) } @@ -363,7 +364,7 @@ func TestPromQLErrors(t *testing.T) { assert.Equal(t, tt.status, code, string(body)) var r promReply - require.NoError(t, json.Unmarshal(body, &r)) + require.NoError(t, json.Unmarshal(body, &r, jsonx.V1())) assert.Equal(t, "error", r.Status) assert.Equal(t, tt.errType, r.ErrorType) }) diff --git a/services/gcppubsub/avro.go b/services/gcppubsub/avro.go index a45e90ab47..dfd8b71890 100644 --- a/services/gcppubsub/avro.go +++ b/services/gcppubsub/avro.go @@ -2,13 +2,15 @@ package gcppubsub import ( "encoding/binary" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "math" "slices" "strings" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errAvro = errors.New("avro") @@ -71,7 +73,7 @@ func primitiveKind(s string) (avroKind, bool) { func parseAvroSchema(def string) (*avroType, error) { var raw any - if err := json.Unmarshal([]byte(def), &raw); err != nil { + if err := json.Unmarshal([]byte(def), &raw, jsonx.V1()); err != nil { return nil, avroErr("schema is not valid JSON: %v", err) } diff --git a/services/gcppubsub/persistence.go b/services/gcppubsub/persistence.go index 01bd7669ca..702eb7c9a3 100644 --- a/services/gcppubsub/persistence.go +++ b/services/gcppubsub/persistence.go @@ -2,7 +2,7 @@ package gcppubsub import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "time" @@ -22,34 +22,34 @@ var errNullMessage = errors.New("gcppubsub: snapshot has a null message") const snapshotVersion = 1 type topicSnap struct { - Topic json.RawMessage `json:"topic"` - Retained []*message `json:"retained,omitempty"` + Topic jsontext.Value `json:"topic"` + Retained []*message `json:"retained,omitempty"` } type subSnap struct { - LastActive time.Time `json:"lastActive"` - Sub json.RawMessage `json:"sub"` - Msgs []*subMsg `json:"msgs,omitempty"` + LastActive time.Time `json:"lastActive"` + Sub jsontext.Value `json:"sub"` + Msgs []*subMsg `json:"msgs,omitempty"` } type pbSnapSnap struct { - Created time.Time `json:"created"` - Snapshot json.RawMessage `json:"snapshot"` - Unacked []string `json:"unacked,omitempty"` + Created time.Time `json:"created"` + Snapshot jsontext.Value `json:"snapshot"` + Unacked []string `json:"unacked,omitempty"` } type backendSnapshot struct { - Topics map[string]topicSnap `json:"topics"` - Subs map[string]subSnap `json:"subs"` - Snapshots map[string]pbSnapSnap `json:"snapshots"` - Schemas map[string]*schemaRec `json:"schemas"` - Policies map[string]json.RawMessage `json:"policies,omitempty"` - LastMsg int64 `json:"lastMsg"` - EtagSeq uint64 `json:"etagSeq,omitempty"` - Version int `json:"version"` + Topics map[string]topicSnap `json:"topics"` + Subs map[string]subSnap `json:"subs"` + Snapshots map[string]pbSnapSnap `json:"snapshots"` + Schemas map[string]*schemaRec `json:"schemas"` + Policies map[string]jsontext.Value `json:"policies,omitempty"` + LastMsg int64 `json:"lastMsg"` + EtagSeq uint64 `json:"etagSeq,omitempty"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -58,7 +58,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } @@ -73,7 +73,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Version: snapshotVersion, LastMsg: b.lastMsg, EtagSeq: iamSeq, - Policies: make(map[string]json.RawMessage, len(iamPolicies)), + Policies: make(map[string]jsontext.Value, len(iamPolicies)), Schemas: b.schemas, Topics: make(map[string]topicSnap, len(b.topics)), Subs: make(map[string]subSnap, len(b.subs)), @@ -153,7 +153,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { return nil } -func decodePolicies(raw map[string]json.RawMessage) (map[string]*iampb.Policy, error) { +func decodePolicies(raw map[string]jsontext.Value) (map[string]*iampb.Policy, error) { out := make(map[string]*iampb.Policy, len(raw)) for n, r := range raw { diff --git a/services/gcppubsub/push.go b/services/gcppubsub/push.go index e5fb82157e..2a9e6c23ee 100644 --- a/services/gcppubsub/push.go +++ b/services/gcppubsub/push.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "sync" "time" @@ -12,6 +12,7 @@ import ( "cloud.google.com/go/pubsub/v2/apiv1/pubsubpb" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -196,7 +197,7 @@ func (b *InMemoryBackend) sendPush( } var err error - if body, err = json.Marshal(env); err != nil { + if body, err = json.Marshal(env, jsonx.V1()); err != nil { return false } diff --git a/services/gcppubsub/schema.go b/services/gcppubsub/schema.go index b28c9953d8..a7d4dad0bc 100644 --- a/services/gcppubsub/schema.go +++ b/services/gcppubsub/schema.go @@ -2,7 +2,8 @@ package gcppubsub import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "time" @@ -12,6 +13,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -110,9 +112,10 @@ func validateData(typ pubsubpb.Schema_Type, def string, enc pubsubpb.Encoding, d } var v any - dec := json.NewDecoder(bytes.NewReader(data)) + dec := jsontext.NewDecoder(bytes.NewReader(data), jsonx.V1()) - return dec.Decode(&v) == nil && !dec.More() && t.validJSON(v) + return json.UnmarshalDecode(dec, &v, jsonx.V1()) == nil && !moreJSON(data, dec.InputOffset()) && + t.validJSON(v) case pubsubpb.Schema_PROTOCOL_BUFFER: if enc == pubsubpb.Encoding_BINARY { return validWire(data) @@ -120,7 +123,7 @@ func validateData(typ pubsubpb.Schema_Type, def string, enc pubsubpb.Encoding, d var v map[string]any - return json.Unmarshal(data, &v) == nil + return json.Unmarshal(data, &v, jsonx.V1()) == nil case pubsubpb.Schema_TYPE_UNSPECIFIED: return false default: @@ -480,3 +483,11 @@ func (b *InMemoryBackend) ValidateMessage(req *pubsubpb.ValidateMessageRequest) return nil } + +// moreJSON mirrors encoding/json Decoder.More: data remains after offset and +// does not start with a closing bracket. +func moreJSON(data []byte, offset int64) bool { + rest := bytes.TrimLeft(data[offset:], " \t\r\n") + + return len(rest) > 0 && rest[0] != ']' && rest[0] != '}' +} diff --git a/services/gcppubsub/sdk_test.go b/services/gcppubsub/sdk_test.go index 5e9090b772..fd44b1bde1 100644 --- a/services/gcppubsub/sdk_test.go +++ b/services/gcppubsub/sdk_test.go @@ -3,7 +3,8 @@ package gcppubsub_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "sync" @@ -24,6 +25,7 @@ import ( "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcppubsub" ) @@ -840,7 +842,7 @@ func TestSDKPushSubscription(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var env envelope - _ = json.NewDecoder(r.Body).Decode(&env) + _ = json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &env, jsonx.V1()) mu.Lock() fail := first diff --git a/services/gcpredis/persistence.go b/services/gcpredis/persistence.go index 1a4410e3ed..03cc7db5da 100644 --- a/services/gcpredis/persistence.go +++ b/services/gcpredis/persistence.go @@ -2,7 +2,7 @@ package gcpredis import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/redis/apiv1/redispb" @@ -18,15 +18,15 @@ import ( const snapshotVersion = 1 type instSnap struct { - Instance json.RawMessage `json:"instance"` - AuthString string `json:"authString,omitempty"` - Keyspace []byte `json:"keyspace,omitempty"` + Instance jsontext.Value `json:"instance"` + AuthString string `json:"authString,omitempty"` + Keyspace []byte `json:"keyspace,omitempty"` } type backendSnapshot struct { - Instances map[string]instSnap `json:"instances"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` + Instances map[string]instSnap `json:"instances"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` } // Snapshot serialises every instance with its keyspace; Redis servers and TLS CAs are rebuilt on restore. diff --git a/services/gcpredis/rest_test.go b/services/gcpredis/rest_test.go index 96f602d482..cde3957b77 100644 --- a/services/gcpredis/rest_test.go +++ b/services/gcpredis/rest_test.go @@ -1,7 +1,7 @@ package gcpredis_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/gcprest/resttest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -69,7 +70,7 @@ func TestRESTFlow(t *testing.T) { Auth bool `json:"authEnabled"` } - return json.Unmarshal(body, &inst) == nil && inst.State == "READY" && inst.Host != "" && + return json.Unmarshal(body, &inst, jsonx.V1()) == nil && inst.State == "READY" && inst.Host != "" && assert.True(t, inst.Auth) }, 10*time.Second, 5*time.Millisecond) } diff --git a/services/gcpresourcemanager/helpers_test.go b/services/gcpresourcemanager/helpers_test.go index 02982e6659..f9138a8792 100644 --- a/services/gcpresourcemanager/helpers_test.go +++ b/services/gcpresourcemanager/helpers_test.go @@ -2,7 +2,7 @@ package gcpresourcemanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync/atomic" "testing" @@ -19,6 +19,7 @@ import ( "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpresourcemanager" ) @@ -200,7 +201,8 @@ func codeOf(err error) codes.Code { var c codes.Code - if json.Unmarshal([]byte(ge.Body), &body) == nil && c.UnmarshalJSON([]byte(`"`+body.Error.Status+`"`)) == nil { + if json.Unmarshal([]byte(ge.Body), &body, jsonx.V1()) == nil && + c.UnmarshalJSON([]byte(`"`+body.Error.Status+`"`)) == nil { return c } } diff --git a/services/gcpresourcemanager/persistence.go b/services/gcpresourcemanager/persistence.go index 5d4455e118..8d71f07a1d 100644 --- a/services/gcpresourcemanager/persistence.go +++ b/services/gcpresourcemanager/persistence.go @@ -2,7 +2,7 @@ package gcpresourcemanager import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -23,8 +23,8 @@ type backendSnapshot struct { TagValues map[string]*tagValueRec `json:"tagValues"` Bindings map[string]*bindingRec `json:"tagBindings"` Enabled map[string]map[string]time.Time `json:"enabledServices"` - Policies map[string]json.RawMessage `json:"policies"` - Ops map[string]json.RawMessage `json:"operations"` + Policies map[string]jsontext.Value `json:"policies"` + Ops map[string]jsontext.Value `json:"operations"` Seed string `json:"seed"` Version int `json:"version"` PolicySeq uint64 `json:"policySeq"` @@ -39,7 +39,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { snap := backendSnapshot{ Version: snapshotVersion, Seed: b.seed, Projects: b.projects, Folders: b.folders, TagKeys: b.tagKeys, TagValues: b.tagValues, Bindings: b.bindings, Enabled: b.enabled, PolicySeq: seq, - Policies: make(map[string]json.RawMessage, len(policies)), Ops: b.lro.Snapshot(), + Policies: make(map[string]jsontext.Value, len(policies)), Ops: b.lro.Snapshot(), } for n, p := range policies { diff --git a/services/gcpresourcemanager/v1.go b/services/gcpresourcemanager/v1.go index b55c7012f6..05efa055c2 100644 --- a/services/gcpresourcemanager/v1.go +++ b/services/gcpresourcemanager/v1.go @@ -1,7 +1,8 @@ package gcpresourcemanager import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "regexp" @@ -16,6 +17,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/blackbirdworks/gopherstack/pkgs/gcprest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -102,7 +104,7 @@ func writeV1Error(w http.ResponseWriter, err error) { st, _ := status.FromError(err) httpCode := gcprest.HTTPStatus(st.Code()) - details := make([]json.RawMessage, 0, len(st.Proto().GetDetails())) + details := make([]jsontext.Value, 0, len(st.Proto().GetDetails())) for _, d := range st.Proto().GetDetails() { if raw, mErr := protojson.Marshal(d); mErr == nil { @@ -140,7 +142,7 @@ func codeName(c codes.Code) string { } func writeV1JSON(w http.ResponseWriter, code int, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -153,7 +155,7 @@ func writeV1JSON(w http.ResponseWriter, code int, v any) { func writeV1Raw(w http.ResponseWriter, code int, raw []byte) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.WriteHeader(code) - _, _ = w.Write(raw) + _, _ = w.Write(raw) // #nosec G705 } func writeV1Proto(w http.ResponseWriter, m proto.Message) { @@ -177,7 +179,7 @@ func readBody(r *http.Request, into any) error { return nil } - if err = json.Unmarshal(raw, into); err != nil { + if err = json.Unmarshal(raw, into, jsonx.V1()); err != nil { return errInvalid("Invalid JSON payload: %v", err) } diff --git a/services/gcprun/persistence.go b/services/gcprun/persistence.go index b1a1db2f8a..5f554717b2 100644 --- a/services/gcprun/persistence.go +++ b/services/gcprun/persistence.go @@ -2,7 +2,7 @@ package gcprun import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/iam/apiv1/iampb" @@ -20,38 +20,38 @@ import ( const snapshotVersion = 1 type svcSnap struct { - Failure string `json:"failure"` - Service json.RawMessage `json:"service"` - RevSeq int `json:"revSeq"` + Failure string `json:"failure"` + Service jsontext.Value `json:"service"` + RevSeq int `json:"revSeq"` } type revSnap struct { - StartErr string `json:"startErr"` - Revision json.RawMessage `json:"revision"` - Seq int `json:"seq"` + StartErr string `json:"startErr"` + Revision jsontext.Value `json:"revision"` + Seq int `json:"seq"` } type taskSnap struct { - State string `json:"state"` - Task json.RawMessage `json:"task"` + State string `json:"state"` + Task jsontext.Value `json:"task"` } type backendSnapshot struct { - Services map[string]svcSnap `json:"services"` - Revisions map[string]revSnap `json:"revisions"` - Jobs map[string]json.RawMessage `json:"jobs"` - Executions map[string]json.RawMessage `json:"executions"` - Tasks map[string]taskSnap `json:"tasks"` - Operations map[string]json.RawMessage `json:"operations"` - DeletedSvc map[string]json.RawMessage `json:"deletedServices"` - DeletedJob map[string]json.RawMessage `json:"deletedJobs"` - DeletedExe map[string]json.RawMessage `json:"deletedExecutions"` - IAM map[string]json.RawMessage `json:"iamPolicies"` - IAMSeq uint64 `json:"iamSeq"` - Version int `json:"version"` + Services map[string]svcSnap `json:"services"` + Revisions map[string]revSnap `json:"revisions"` + Jobs map[string]jsontext.Value `json:"jobs"` + Executions map[string]jsontext.Value `json:"executions"` + Tasks map[string]taskSnap `json:"tasks"` + Operations map[string]jsontext.Value `json:"operations"` + DeletedSvc map[string]jsontext.Value `json:"deletedServices"` + DeletedJob map[string]jsontext.Value `json:"deletedJobs"` + DeletedExe map[string]jsontext.Value `json:"deletedExecutions"` + IAM map[string]jsontext.Value `json:"iamPolicies"` + IAMSeq uint64 `json:"iamSeq"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { raw, err := protojson.Marshal(m) if err != nil { return nil @@ -60,8 +60,8 @@ func marshalPB(m proto.Message) json.RawMessage { return raw } -func marshalMap[T proto.Message](m map[string]T) map[string]json.RawMessage { - out := make(map[string]json.RawMessage, len(m)) +func marshalMap[T proto.Message](m map[string]T) map[string]jsontext.Value { + out := make(map[string]jsontext.Value, len(m)) for k, v := range m { out[k] = marshalPB(v) } @@ -69,7 +69,7 @@ func marshalMap[T proto.Message](m map[string]T) map[string]json.RawMessage { return out } -func unmarshalMap[T proto.Message](raw map[string]json.RawMessage, mk func() T, what string) (map[string]T, error) { +func unmarshalMap[T proto.Message](raw map[string]jsontext.Value, mk func() T, what string) (map[string]T, error) { out := make(map[string]T, len(raw)) for k, r := range raw { @@ -84,7 +84,7 @@ func unmarshalMap[T proto.Message](raw map[string]json.RawMessage, mk func() T, return out, nil } -func unmarshalPB[T proto.Message](raw json.RawMessage, m T) (T, error) { +func unmarshalPB[T proto.Message](raw jsontext.Value, m T) (T, error) { err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(raw, m) return m, err @@ -99,8 +99,8 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Version: snapshotVersion, Services: make(map[string]svcSnap, len(b.services)), Revisions: make(map[string]revSnap, len(b.revisions)), - Jobs: make(map[string]json.RawMessage, len(b.jobs)), - Executions: make(map[string]json.RawMessage, len(b.executions)), + Jobs: make(map[string]jsontext.Value, len(b.jobs)), + Executions: make(map[string]jsontext.Value, len(b.executions)), Tasks: make(map[string]taskSnap, len(b.tasks)), Operations: b.ops.Snapshot(), } diff --git a/services/gcpscheduler/helpers_test.go b/services/gcpscheduler/helpers_test.go index 239ab77283..2c6e9a7c50 100644 --- a/services/gcpscheduler/helpers_test.go +++ b/services/gcpscheduler/helpers_test.go @@ -3,7 +3,7 @@ package gcpscheduler_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpscheduler" ) @@ -165,7 +166,7 @@ func decodeJWT(t *testing.T, token string) map[string]any { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) return claims } diff --git a/services/gcpscheduler/persistence.go b/services/gcpscheduler/persistence.go index 46a777a29c..25fff0d21a 100644 --- a/services/gcpscheduler/persistence.go +++ b/services/gcpscheduler/persistence.go @@ -2,7 +2,7 @@ package gcpscheduler import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -17,10 +17,10 @@ import ( const snapshotVersion = 1 type jobSnap struct { - RetryAt time.Time `json:"retryAt"` - ExecStart time.Time `json:"execStart"` - Job json.RawMessage `json:"job"` - Failures int32 `json:"failures"` + RetryAt time.Time `json:"retryAt"` + ExecStart time.Time `json:"execStart"` + Job jsontext.Value `json:"job"` + Failures int32 `json:"failures"` } type backendSnapshot struct { diff --git a/services/gcpsecretmanager/persistence.go b/services/gcpsecretmanager/persistence.go index ad3b5fc1ff..8da62144cb 100644 --- a/services/gcpsecretmanager/persistence.go +++ b/services/gcpsecretmanager/persistence.go @@ -2,7 +2,7 @@ package gcpsecretmanager import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/iam/apiv1/iampb" @@ -18,16 +18,16 @@ import ( const snapshotVersion = 1 type versionSnap struct { - Version json.RawMessage `json:"version"` - Data []byte `json:"data,omitempty"` - Num int64 `json:"num"` + Version jsontext.Value `json:"version"` + Data []byte `json:"data,omitempty"` + Num int64 `json:"num"` } type secretSnap struct { - Secret json.RawMessage `json:"secret"` - Policy json.RawMessage `json:"policy,omitempty"` - Versions []versionSnap `json:"versions,omitempty"` - NextVer int64 `json:"nextVer"` + Secret jsontext.Value `json:"secret"` + Policy jsontext.Value `json:"policy,omitempty"` + Versions []versionSnap `json:"versions,omitempty"` + NextVer int64 `json:"nextVer"` } type backendSnapshot struct { @@ -36,7 +36,7 @@ type backendSnapshot struct { Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { out, err := protojson.Marshal(m) if err != nil { return nil @@ -45,7 +45,7 @@ func marshalPB(m proto.Message) json.RawMessage { return out } -func unmarshalPB(raw json.RawMessage, m proto.Message) error { +func unmarshalPB(raw jsontext.Value, m proto.Message) error { return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, m) } diff --git a/services/gcpspanner/persistence.go b/services/gcpspanner/persistence.go index 1932704bba..54e7645c85 100644 --- a/services/gcpspanner/persistence.go +++ b/services/gcpspanner/persistence.go @@ -2,7 +2,7 @@ package gcpspanner import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "cloud.google.com/go/iam/apiv1/iampb" @@ -20,21 +20,21 @@ import ( const snapshotVersion = 1 type dbSnap struct { - Database json.RawMessage `json:"database"` - DDL []string `json:"ddl,omitempty"` + Database jsontext.Value `json:"database"` + DDL []string `json:"ddl,omitempty"` } type instSnap struct { Databases map[string]dbSnap `json:"databases,omitempty"` - Instance json.RawMessage `json:"instance"` - Policy json.RawMessage `json:"policy,omitempty"` + Instance jsontext.Value `json:"instance"` + Policy jsontext.Value `json:"policy,omitempty"` } type backendSnapshot struct { - Instances map[string]instSnap `json:"instances"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` - EtagSeq uint64 `json:"etagSeq"` + Instances map[string]instSnap `json:"instances"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` + EtagSeq uint64 `json:"etagSeq"` } // Snapshot serialises instances, policies, databases and their DDL. Table data lives in the emulator and is not diff --git a/services/gcpspanner/rest_test.go b/services/gcpspanner/rest_test.go index fac931f18d..882b835182 100644 --- a/services/gcpspanner/rest_test.go +++ b/services/gcpspanner/rest_test.go @@ -1,7 +1,7 @@ package gcpspanner_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -32,7 +33,7 @@ func restDo(t *testing.T, base, method, path, body string) (int, map[string]any) out := map[string]any{} if len(raw) > 0 { - require.NoError(t, json.Unmarshal(raw, &out), string(raw)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1()), string(raw)) } return resp.StatusCode, out diff --git a/services/gcpsql/backups.go b/services/gcpsql/backups.go index 9f3a93e350..c41d603633 100644 --- a/services/gcpsql/backups.go +++ b/services/gcpsql/backups.go @@ -2,17 +2,18 @@ package gcpsql import ( "context" - "encoding/json" "slices" "sort" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // RestoreBackupInput is the restoreBackupContext of instances.restoreBackup. type RestoreBackupInput struct { - BackupRunID json.Number `json:"backupRunId"` - InstanceID string `json:"instanceId"` - Project string `json:"project"` + BackupRunID jsonx.Number `json:"backupRunId"` + InstanceID string `json:"instanceId"` + Project string `json:"project"` } func (b *InMemoryBackend) nextBackupID() string { diff --git a/services/gcpsql/handler.go b/services/gcpsql/handler.go index 2c2d6f5983..a794dc6986 100644 --- a/services/gcpsql/handler.go +++ b/services/gcpsql/handler.go @@ -2,7 +2,8 @@ package gcpsql import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/gcpenforce" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -160,7 +162,7 @@ func decode[T any](c *echo.Context) (T, error) { return v, nil } - if err = json.Unmarshal(data, &v); err != nil { + if err = json.Unmarshal(data, &v, jsonx.V1()); err != nil { return v, errInvalid("Invalid JSON payload received: %v", err) } @@ -171,7 +173,7 @@ func decode[T any](c *echo.Context) (T, error) { func decodeContext[T any](c *echo.Context, key string) (T, map[string]any, error) { var typed T - wrapper, err := decode[map[string]json.RawMessage](c) + wrapper, err := decode[map[string]jsontext.Value](c) if err != nil { return typed, nil, err } @@ -181,13 +183,13 @@ func decodeContext[T any](c *echo.Context, key string) (T, map[string]any, error return typed, nil, errInvalid("Missing parameter: %s.", key) } - if err = json.Unmarshal(raw, &typed); err != nil { + if err = json.Unmarshal(raw, &typed, jsonx.V1()); err != nil { return typed, nil, errInvalid("Invalid %s: %v", key, err) } var echoed map[string]any - _ = json.Unmarshal(raw, &echoed) + _ = json.Unmarshal(raw, &echoed, jsonx.V1()) return typed, echoed, nil } diff --git a/services/gcpsql/settingsdef.go b/services/gcpsql/settingsdef.go index 4c41d0337e..f0f4047144 100644 --- a/services/gcpsql/settingsdef.go +++ b/services/gcpsql/settingsdef.go @@ -3,8 +3,10 @@ package gcpsql import ( "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const retainDays = 7 @@ -133,7 +135,7 @@ func flagsSig(f []Flag) string { } func etagOf(v any) string { - data, _ := json.Marshal(v) + data, _ := json.Marshal(v, jsonx.V1()) sum := sha256.Sum256(data) return base64.RawURLEncoding.EncodeToString(sum[:16]) diff --git a/services/gcpstorage/acl.go b/services/gcpstorage/acl.go index 906c352014..80e3594ae1 100644 --- a/services/gcpstorage/acl.go +++ b/services/gcpstorage/acl.go @@ -1,11 +1,13 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ACLEntry is one access-control entry; the derived fields (email, domain, projectTeam) are rendered from Entity. @@ -239,7 +241,7 @@ func (b *Bucket) ubla() bool { } raw, ok := b.Config["iamConfiguration"] - if !ok || json.Unmarshal(raw, &cfg) != nil { + if !ok || json.Unmarshal(raw, &cfg, jsonx.V1()) != nil { return false } diff --git a/services/gcpstorage/events.go b/services/gcpstorage/events.go index 5e4892699a..a51cf868a7 100644 --- a/services/gcpstorage/events.go +++ b/services/gcpstorage/events.go @@ -1,8 +1,11 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ObjectEventType names a change to an object, matching the suffix of the google.cloud.storage.object.v1 event types. @@ -27,8 +30,8 @@ type ObjectEvent struct { } // Data is the event payload: the object resource, as the Cloud Storage JSON API renders it. -func (e ObjectEvent) Data() json.RawMessage { - raw, err := json.Marshal(objectResource("", e.Object)) +func (e ObjectEvent) Data() jsontext.Value { + raw, err := json.Marshal(objectResource("", e.Object), jsonx.V1()) if err != nil { return nil } diff --git a/services/gcpstorage/events_test.go b/services/gcpstorage/events_test.go index 2bf146afdd..01b8456be6 100644 --- a/services/gcpstorage/events_test.go +++ b/services/gcpstorage/events_test.go @@ -1,7 +1,7 @@ package gcpstorage_test import ( - "encoding/json" + "encoding/json/v2" "sync" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/gcpstorage" ) @@ -146,7 +147,7 @@ func TestObjectEventData(t *testing.T) { require.Len(t, rec.got, 1) var data map[string]any - require.NoError(t, json.Unmarshal(rec.got[0].Data(), &data)) + require.NoError(t, json.Unmarshal(rec.got[0].Data(), &data, jsonx.V1())) assert.Equal(t, "ev-data", data["bucket"]) assert.Equal(t, "dir/a.txt", data["name"]) assert.Equal(t, "5", data["size"]) diff --git a/services/gcpstorage/handler_acl.go b/services/gcpstorage/handler_acl.go index 480d59e4a4..fefeaa9016 100644 --- a/services/gcpstorage/handler_acl.go +++ b/services/gcpstorage/handler_acl.go @@ -1,10 +1,12 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type aclBody struct { @@ -96,7 +98,7 @@ func (h *Handler) aclWrite(c *echo.Context, rt route, t aclTarget, base string) } var in aclBody - if jsonErr := json.Unmarshal(body, &in); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &in, jsonx.V1()); jsonErr != nil { return errInvalid("Invalid JSON payload received.") } diff --git a/services/gcpstorage/handler_bucket.go b/services/gcpstorage/handler_bucket.go index 6ebae2633f..1b2afd6421 100644 --- a/services/gcpstorage/handler_bucket.go +++ b/services/gcpstorage/handler_bucket.go @@ -1,13 +1,14 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/gcpiampolicy" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func errRequired(param string) *APIError { @@ -121,7 +122,7 @@ func (h *Handler) setIamPolicy(c *echo.Context, rt route) error { } var in IAMPolicy - if jsonErr := json.Unmarshal(body, &in); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &in, jsonx.V1()); jsonErr != nil { return errInvalid("Invalid JSON payload received.") } diff --git a/services/gcpstorage/handler_object.go b/services/gcpstorage/handler_object.go index cf7d82a198..a0273ae1f6 100644 --- a/services/gcpstorage/handler_object.go +++ b/services/gcpstorage/handler_object.go @@ -1,12 +1,14 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type objectsJSON struct { @@ -229,7 +231,7 @@ func (h *Handler) composeObject(c *echo.Context, rt route) error { } var req composeRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return errInvalid("Invalid JSON payload received.") } diff --git a/services/gcpstorage/handler_test.go b/services/gcpstorage/handler_test.go index 169d835165..64217a38e8 100644 --- a/services/gcpstorage/handler_test.go +++ b/services/gcpstorage/handler_test.go @@ -2,7 +2,7 @@ package gcpstorage_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpstorage" @@ -129,7 +130,7 @@ func TestErrorEnvelope(t *testing.T) { } `json:"error"` } - require.NoError(t, json.Unmarshal([]byte(body), &env), body) + require.NoError(t, json.Unmarshal([]byte(body), &env, jsonx.V1()), body) assert.Equal(t, tt.status, env.Error.Code) assert.NotEmpty(t, env.Error.Message) require.Len(t, env.Error.Errors, 1) @@ -200,7 +201,7 @@ func TestUploadsAndDownloads(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode, body) var obj map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &obj)) + require.NoError(t, json.Unmarshal([]byte(body), &obj, jsonx.V1())) assert.Equal(t, "a/b.bin", obj["name"]) assert.Equal(t, "6", obj["size"]) assert.Equal(t, "application/x-bin", obj["contentType"]) @@ -297,7 +298,7 @@ func TestRewriteAndPatch(t *testing.T) { Done bool `json:"done"` } - require.NoError(t, json.Unmarshal([]byte(body), &rw)) + require.NoError(t, json.Unmarshal([]byte(body), &rw, jsonx.V1())) assert.Equal(t, "storage#rewriteResponse", rw.Kind) assert.True(t, rw.Done) assert.Equal(t, "4", rw.Total) @@ -318,7 +319,7 @@ func TestRewriteAndPatch(t *testing.T) { Metageneration string `json:"metageneration"` } - require.NoError(t, json.Unmarshal([]byte(body), &obj)) + require.NoError(t, json.Unmarshal([]byte(body), &obj, jsonx.V1())) assert.Equal(t, map[string]string{"b": "2"}, obj.Metadata) assert.Empty(t, obj.ContentLang) assert.Equal(t, "3", obj.Metageneration) diff --git a/services/gcpstorage/iam.go b/services/gcpstorage/iam.go index 5257307624..07e18c0d14 100644 --- a/services/gcpstorage/iam.go +++ b/services/gcpstorage/iam.go @@ -3,12 +3,14 @@ package gcpstorage import ( "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "cloud.google.com/go/iam/apiv1/iampb" "google.golang.org/genproto/googleapis/type/expr" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type conditionJSON struct { @@ -36,7 +38,7 @@ func policyToPB(p IAMPolicy) *iampb.Policy { if len(bd.Condition) > 0 && string(bd.Condition) != "null" { var c conditionJSON - _ = json.Unmarshal(bd.Condition, &c) + _ = json.Unmarshal(bd.Condition, &c, jsonx.V1()) pb.Condition = &expr.Expr{Expression: c.Expression, Title: c.Title, Description: c.Description} } @@ -53,7 +55,10 @@ func policyFromPB(p *iampb.Policy) IAMPolicy { ib := IAMBinding{Role: bd.GetRole(), Members: bd.GetMembers()} if c := bd.GetCondition(); c != nil { - ib.Condition, _ = json.Marshal(conditionJSON{c.GetExpression(), c.GetTitle(), c.GetDescription()}) + ib.Condition, _ = json.Marshal( + conditionJSON{c.GetExpression(), c.GetTitle(), c.GetDescription()}, + jsonx.V1(), + ) } out.Bindings = append(out.Bindings, ib) diff --git a/services/gcpstorage/iam_policy_test.go b/services/gcpstorage/iam_policy_test.go index ea03211981..0724dcf0e7 100644 --- a/services/gcpstorage/iam_policy_test.go +++ b/services/gcpstorage/iam_policy_test.go @@ -1,12 +1,14 @@ package gcpstorage_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -72,7 +74,7 @@ func TestBucketIAMConditions(t *testing.T) { Etag string `json:"etag"` } - require.NoError(t, json.Unmarshal([]byte(seeded), &cur)) + require.NoError(t, json.Unmarshal([]byte(seeded), &cur, jsonx.V1())) if tt.freshEtag { body = `{"etag":"` + cur.Etag + `","version":3}` @@ -87,7 +89,7 @@ func TestBucketIAMConditions(t *testing.T) { Version int `json:"version"` } - require.NoError(t, json.Unmarshal([]byte(out), &got)) + require.NoError(t, json.Unmarshal([]byte(out), &got, jsonx.V1())) assert.Equal(t, tt.wantPolVer, got.Version) } }) diff --git a/services/gcpstorage/models.go b/services/gcpstorage/models.go index af57c686f1..a609e891ed 100644 --- a/services/gcpstorage/models.go +++ b/services/gcpstorage/models.go @@ -1,7 +1,7 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/jsontext" "maps" "slices" "time" @@ -9,9 +9,9 @@ import ( // IAMBinding is one role binding of a bucket IAM policy. type IAMBinding struct { - Condition json.RawMessage `json:"condition,omitempty"` - Role string `json:"role"` - Members []string `json:"members"` + Condition jsontext.Value `json:"condition,omitempty"` + Role string `json:"role"` + Members []string `json:"members"` } // IAMPolicy is a bucket IAM policy. @@ -70,21 +70,21 @@ func (o *Object) clone() *Object { // Bucket is a bucket with all of its object versions. type Bucket struct { - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` - Objects map[string][]*Object `json:"objects"` - Labels map[string]string `json:"labels,omitempty"` - Config map[string]json.RawMessage `json:"config,omitempty"` - Project string `json:"project"` - Name string `json:"name"` - Location string `json:"location"` - StorageClass string `json:"storageClass"` - ACL []ACLEntry `json:"acl,omitempty"` - DefaultObjectACL []ACLEntry `json:"defaultObjectAcl,omitempty"` - IAM IAMPolicy `json:"iam"` - Metageneration int64 `json:"metageneration"` - Versioning bool `json:"versioning,omitempty"` - DefaultEventBasedHold bool `json:"defaultEventBasedHold,omitempty"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` + Objects map[string][]*Object `json:"objects"` + Labels map[string]string `json:"labels,omitempty"` + Config map[string]jsontext.Value `json:"config,omitempty"` + Project string `json:"project"` + Name string `json:"name"` + Location string `json:"location"` + StorageClass string `json:"storageClass"` + ACL []ACLEntry `json:"acl,omitempty"` + DefaultObjectACL []ACLEntry `json:"defaultObjectAcl,omitempty"` + IAM IAMPolicy `json:"iam"` + Metageneration int64 `json:"metageneration"` + Versioning bool `json:"versioning,omitempty"` + DefaultEventBasedHold bool `json:"defaultEventBasedHold,omitempty"` } // clone copies the bucket metadata without its objects. diff --git a/services/gcpstorage/patch.go b/services/gcpstorage/patch.go index 74fd400833..332733f9e8 100644 --- a/services/gcpstorage/patch.go +++ b/services/gcpstorage/patch.go @@ -2,13 +2,16 @@ package gcpstorage import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Patch is a decoded JSON request body, keyed by field name. A null value // clears the field, as in a JSON merge patch. -type Patch map[string]json.RawMessage +type Patch map[string]jsontext.Value const ( keyContentType = "contentType" @@ -26,16 +29,16 @@ const ( defaultContentType = "application/octet-stream" ) -func isNull(raw json.RawMessage) bool { return string(bytes.TrimSpace(raw)) == "null" } +func isNull(raw jsontext.Value) bool { return string(bytes.TrimSpace(raw)) == "null" } -func decodeField[T any](raw json.RawMessage, field string) (T, *APIError) { +func decodeField[T any](raw jsontext.Value, field string) (T, *APIError) { var v T if isNull(raw) { return v, nil } - if err := json.Unmarshal(raw, &v); err != nil { + if err := json.Unmarshal(raw, &v, jsonx.V1()); err != nil { return v, errInvalid("Invalid value for field '" + field + "'.") } @@ -58,7 +61,7 @@ func validStorageClass(sc string) bool { } } -func mergeStringMap(raw json.RawMessage, dst map[string]string, field string) (map[string]string, *APIError) { +func mergeStringMap(raw jsontext.Value, dst map[string]string, field string) (map[string]string, *APIError) { if isNull(raw) { return nil, nil } @@ -233,7 +236,7 @@ func applyBucket(b *Bucket, p Patch, create bool) *APIError { } if b.Config == nil { - b.Config = map[string]json.RawMessage{} + b.Config = map[string]jsontext.Value{} } b.Config[k] = raw diff --git a/services/gcpstorage/util.go b/services/gcpstorage/util.go index 9783374d69..4ea8004312 100644 --- a/services/gcpstorage/util.go +++ b/services/gcpstorage/util.go @@ -1,15 +1,18 @@ package gcpstorage import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func itoa(n int) string { return strconv.Itoa(n) } -func marshalRaw(v any) json.RawMessage { - raw, _ := json.Marshal(v) //nolint:errchkjson // strings and string maps always marshal +func marshalRaw(v any) jsontext.Value { + raw, _ := json.Marshal(v, jsonx.V1()) //nolint:errchkjson // strings and string maps always marshal return raw } diff --git a/services/gcpstorage/wire.go b/services/gcpstorage/wire.go index 7d4e38d4dd..4535cda5a6 100644 --- a/services/gcpstorage/wire.go +++ b/services/gcpstorage/wire.go @@ -2,10 +2,12 @@ package gcpstorage import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type objectJSON struct { @@ -222,7 +224,7 @@ func decodePatch(body []byte) (Patch, *APIError) { return p, nil } - if err := json.Unmarshal(body, &p); err != nil || p == nil { + if err := json.Unmarshal(body, &p, jsonx.V1()); err != nil || p == nil { return nil, errInvalid("Invalid JSON payload received.") } diff --git a/services/gcpworkflows/callbacks.go b/services/gcpworkflows/callbacks.go index 4cd02ace24..d1fedf2525 100644 --- a/services/gcpworkflows/callbacks.go +++ b/services/gcpworkflows/callbacks.go @@ -2,7 +2,8 @@ package gcpworkflows import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/wfinterp" ) @@ -74,8 +76,10 @@ func IsCallbackPath(path string) bool { func writeJSONError(w http.ResponseWriter, code int, status, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - _ = json.NewEncoder(w). - Encode(map[string]any{"error": map[string]any{"code": code, "message": msg, "status": status}}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]any{"error": map[string]any{"code": code, "message": msg, "status": status}}, + ) } func (b *InMemoryBackend) serveCallback(w http.ResponseWriter, r *http.Request) { diff --git a/services/gcpworkflows/integration_test.go b/services/gcpworkflows/integration_test.go index 1754c5440a..3f46d248ca 100644 --- a/services/gcpworkflows/integration_test.go +++ b/services/gcpworkflows/integration_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/gcprest/resttest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/gcpfirestore" "github.com/blackbirdworks/gopherstack/services/gcppubsub" @@ -468,4 +469,6 @@ func TestRESTFlow(t *testing.T) { }) } -func jsonUnmarshal(raw []byte, v any) error { return json.Unmarshal(bytes.TrimSpace(raw), v) } +func jsonUnmarshal(raw []byte, v any) error { + return json.Unmarshal(bytes.TrimSpace(raw), v, jsonx.V1()) +} diff --git a/services/gcpworkflows/persistence.go b/services/gcpworkflows/persistence.go index 0a4bba082b..2cf0135f01 100644 --- a/services/gcpworkflows/persistence.go +++ b/services/gcpworkflows/persistence.go @@ -2,7 +2,7 @@ package gcpworkflows import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -23,17 +23,17 @@ var errNoRevisions = errors.New("no revisions") const snapshotVersion = 1 type workflowSnap struct { - Revisions []json.RawMessage `json:"revisions"` + Revisions []jsontext.Value `json:"revisions"` } type backendSnapshot struct { - Workflows map[string]workflowSnap `json:"workflows"` - Executions map[string]json.RawMessage `json:"executions"` - Operations map[string]json.RawMessage `json:"operations"` - Version int `json:"version"` + Workflows map[string]workflowSnap `json:"workflows"` + Executions map[string]jsontext.Value `json:"executions"` + Operations map[string]jsontext.Value `json:"operations"` + Version int `json:"version"` } -func marshalPB(m proto.Message) json.RawMessage { +func marshalPB(m proto.Message) jsontext.Value { raw, err := protojson.Marshal(m) if err != nil { return nil @@ -54,12 +54,12 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { snap := backendSnapshot{ Version: snapshotVersion, Workflows: make(map[string]workflowSnap, len(b.workflows)), - Executions: make(map[string]json.RawMessage, len(b.execs)), + Executions: make(map[string]jsontext.Value, len(b.execs)), Operations: b.ops.Snapshot(), } for n, rec := range b.workflows { - ws := workflowSnap{Revisions: make([]json.RawMessage, len(rec.revs))} + ws := workflowSnap{Revisions: make([]jsontext.Value, len(rec.revs))} for i, r := range rec.revs { ws.Revisions[i] = marshalPB(r) } diff --git a/services/glacier/handler_archives_test.go b/services/glacier/handler_archives_test.go index 1900a12e56..f46c33c4c0 100644 --- a/services/glacier/handler_archives_test.go +++ b/services/glacier/handler_archives_test.go @@ -3,7 +3,7 @@ package glacier_test import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -57,7 +58,7 @@ func TestUploadDeleteArchive(t *testing.T) { assert.Equal(t, http.StatusCreated, rec2.Code) var uploadResp map[string]any - err = json.Unmarshal(rec2.Body.Bytes(), &uploadResp) + err = json.Unmarshal(rec2.Body.Bytes(), &uploadResp, jsonx.V1()) require.NoError(t, err) archiveID := uploadResp["archiveId"].(string) @@ -190,7 +191,7 @@ func TestUploadArchive_TreeHashReturnedInResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, expectedHash, resp["checksum"]) assert.Equal(t, expectedHash, rec.Header().Get("X-Amz-Sha256-Tree-Hash")) } diff --git a/services/glacier/handler_data_retrieval_policy.go b/services/glacier/handler_data_retrieval_policy.go index 42d20a5879..f5054237ab 100644 --- a/services/glacier/handler_data_retrieval_policy.go +++ b/services/glacier/handler_data_retrieval_policy.go @@ -1,11 +1,13 @@ package glacier import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDataRetrievalPolicy handles GetDataRetrievalPolicy and SetDataRetrievalPolicy. @@ -30,7 +32,7 @@ func (h *Handler) handleGetDataRetrievalPolicy(c *echo.Context) error { } var parsed any - if err := json.Unmarshal([]byte(policy), &parsed); err == nil { + if err := json.Unmarshal([]byte(policy), &parsed, jsonx.V1()); err == nil { return c.JSON(http.StatusOK, parsed) } @@ -39,7 +41,7 @@ func (h *Handler) handleGetDataRetrievalPolicy(c *echo.Context) error { func (h *Handler) handleSetDataRetrievalPolicy(c *echo.Context, body []byte) error { var req dataRetrievalPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid data retrieval policy: "+err.Error()) } diff --git a/services/glacier/handler_data_retrieval_policy_test.go b/services/glacier/handler_data_retrieval_policy_test.go index 9811be5c4c..f6a85eeaba 100644 --- a/services/glacier/handler_data_retrieval_policy_test.go +++ b/services/glacier/handler_data_retrieval_policy_test.go @@ -1,13 +1,15 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDataRetrievalPolicy(t *testing.T) { @@ -43,7 +45,7 @@ func TestDataRetrievalPolicy(t *testing.T) { if tt.wantPolicy { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Policy") } }) @@ -86,7 +88,7 @@ func TestDataRetrievalPolicy_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.NotEmpty(t, got) }) } @@ -112,7 +114,7 @@ func TestDataRetrievalPolicy_DefaultFreeTier(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) policy, ok := got["Policy"].(map[string]any) require.True(t, ok) @@ -198,7 +200,7 @@ func TestDataRetrievalPolicy_GetDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) rules := policy["Rules"].([]any) @@ -269,7 +271,7 @@ func TestDataRetrievalPolicy_SetAndGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) rules := policy["Rules"].([]any) require.NotEmpty(t, rules) diff --git a/services/glacier/handler_job_output_test.go b/services/glacier/handler_job_output_test.go index 6046bdecfe..55d51dde09 100644 --- a/services/glacier/handler_job_output_test.go +++ b/services/glacier/handler_job_output_test.go @@ -2,7 +2,7 @@ package glacier_test import ( "encoding/csv" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -50,7 +51,7 @@ func TestGetJobOutput_ArchiveDescriptionHeader(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) rec = doRequest(t, h, http.MethodGet, @@ -203,7 +204,7 @@ func TestInventoryCSV_RFC4180QuotedDescriptions(t *testing.T) { require.Equal(t, http.StatusAccepted, jobRec.Code) var jobResp map[string]string - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp, jsonx.V1())) jobID := jobResp["jobId"] require.NotEmpty(t, jobID) @@ -242,7 +243,7 @@ func TestInventoryCSV_DescriptionWithDoubleQuote(t *testing.T) { require.Equal(t, http.StatusAccepted, jobRec.Code) var jobResp map[string]string - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp, jsonx.V1())) jobID := jobResp["jobId"] outRec := doRequest(t, h, http.MethodGet, @@ -278,7 +279,7 @@ func TestGetJobOutput_CompletedFilter_Integration(t *testing.T) { require.Equal(t, http.StatusAccepted, jobRec.Code) var jr map[string]string - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr, jsonx.V1())) jobID := jr["jobId"] require.NotEmpty(t, jobID) @@ -340,7 +341,7 @@ func TestGetJobOutput_SHA256Header(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) rec = doRequest(t, h, http.MethodGet, @@ -361,7 +362,7 @@ func uploadArchiveData(t *testing.T, h *glacier.Handler, vaultName string, data require.NoError(t, h.Handler()(c)) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["archiveId"] require.NotEmpty(t, id) @@ -375,7 +376,7 @@ func initiateJobWithBody(t *testing.T, h *glacier.Handler, vaultName, body strin rec := doRequestWithHeaders(t, h, http.MethodPost, "/"+testAccountID+"/vaults/"+vaultName+"/jobs", body, nil) require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["jobId"] require.NotEmpty(t, id) @@ -413,7 +414,7 @@ func TestArchiveRetrieval_FullLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/arch-lifecycle/jobs/"+jobID, "", nil) require.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "Succeeded", desc["StatusCode"]) assert.Equal(t, true, desc["Completed"]) @@ -551,7 +552,7 @@ func TestInventoryRetrieval_JSONLifecycle(t *testing.T) { assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) var inv map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inv)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inv, jsonx.V1())) archList, ok := inv["ArchiveList"].([]any) require.True(t, ok) assert.Len(t, archList, tt.archiveCount) @@ -634,7 +635,7 @@ func TestInventoryRetrieval_InventorySizeInBytes(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &desc, jsonx.V1())) raw, present := desc["InventorySizeInBytes"] assert.True(t, present, "InventorySizeInBytes must be present after GetJobOutput") got := int64(raw.(float64)) @@ -663,7 +664,7 @@ func TestInventoryRetrieval_LastInventoryDateUpdated(t *testing.T) { rec := doRequestWithHeaders(t, h, http.MethodGet, "/"+testAccountID+"/vaults/lastinv-vault", "", nil) require.Equal(t, http.StatusOK, rec.Code) var vaultDesc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vaultDesc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vaultDesc, jsonx.V1())) assert.Empty(t, vaultDesc["LastInventoryDate"]) initiateJobWithBody(t, h, "lastinv-vault", `{"Type":"inventory-retrieval"}`) @@ -671,7 +672,7 @@ func TestInventoryRetrieval_LastInventoryDateUpdated(t *testing.T) { // After job: LastInventoryDate set. rec = doRequestWithHeaders(t, h, http.MethodGet, "/"+testAccountID+"/vaults/lastinv-vault", "", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vaultDesc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vaultDesc, jsonx.V1())) assert.NotEmpty(t, vaultDesc["LastInventoryDate"], tt.name) }) } diff --git a/services/glacier/handler_jobs.go b/services/glacier/handler_jobs.go index cc0ec7f1a6..2e34357979 100644 --- a/services/glacier/handler_jobs.go +++ b/services/glacier/handler_jobs.go @@ -3,7 +3,7 @@ package glacier import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -11,11 +11,13 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleInitiateJob(c *echo.Context, vaultName string, body []byte) error { var req initiateJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -265,7 +267,7 @@ func (h *Handler) writeInventoryJSON(c *echo.Context, j *Job, vaultName string, "VaultARN": j.VaultARN, "InventoryDate": j.CompletionDate, "ArchiveList": items, - }) + }, jsonx.V1()) if err != nil { return h.writeError( c, diff --git a/services/glacier/handler_jobs_test.go b/services/glacier/handler_jobs_test.go index 4452779c4c..312e0c0cb5 100644 --- a/services/glacier/handler_jobs_test.go +++ b/services/glacier/handler_jobs_test.go @@ -1,7 +1,8 @@ package glacier_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -48,7 +50,7 @@ func TestInitiateListDescribeJob(t *testing.T) { assert.Equal(t, http.StatusAccepted, rec.Code) var jobResp map[string]any - err := json.Unmarshal(rec.Body.Bytes(), &jobResp) + err := json.Unmarshal(rec.Body.Bytes(), &jobResp, jsonx.V1()) require.NoError(t, err) jobID := jobResp["jobId"].(string) @@ -63,7 +65,7 @@ func TestInitiateListDescribeJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - err = json.Unmarshal(rec.Body.Bytes(), &descResp) + err = json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1()) require.NoError(t, err) assert.Equal(t, jobID, descResp["JobId"]) @@ -122,7 +124,7 @@ func TestJobTier_StoredInDescribeJob(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) rec = doRequest(t, h, http.MethodGet, @@ -130,7 +132,7 @@ func TestJobTier_StoredInDescribeJob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Bulk", descResp["Tier"]) } @@ -150,7 +152,7 @@ func TestInventoryJob_UpdatesLastInventoryDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) _, hasDate := desc["LastInventoryDate"] assert.False(t, hasDate, "LastInventoryDate should not be set before first inventory") @@ -165,7 +167,7 @@ func TestInventoryJob_UpdatesLastInventoryDate(t *testing.T) { "/"+testAccountID+"/vaults/inv-date-vault", "") require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) lastInventory, ok := desc["LastInventoryDate"] assert.True(t, ok, "LastInventoryDate should be set after inventory job") assert.NotEmpty(t, lastInventory) @@ -184,7 +186,7 @@ func TestArchiveJob_DoesNotUpdateLastInventoryDate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var archResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp, jsonx.V1())) archiveID := archResp["archiveId"].(string) // Initiate archive-retrieval job. @@ -244,7 +246,7 @@ func TestListJobs_LimitAndMarker(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobList := resp["JobList"] if jobList == nil { @@ -304,7 +306,7 @@ func TestArchiveRetrievalJob_StoresRetrievalByteRange(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var archResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp, jsonx.V1())) archiveID := archResp["archiveId"].(string) // Initiate archive retrieval with RetrievalByteRange. @@ -314,7 +316,7 @@ func TestArchiveRetrievalJob_StoresRetrievalByteRange(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) // DescribeJob should reflect the stored range. @@ -323,7 +325,7 @@ func TestArchiveRetrievalJob_StoresRetrievalByteRange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) // The RetrievalByteRange field is forwarded from the request. _ = descResp } @@ -468,7 +470,7 @@ func TestDescribeJob_ArchiveSizeInBytes(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) rec = doRequest( @@ -481,7 +483,7 @@ func TestDescribeJob_ArchiveSizeInBytes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var jobResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp, jsonx.V1())) if tt.wantSizePresent { assert.NotNil(t, jobResp["ArchiveSizeInBytes"]) } @@ -540,7 +542,7 @@ func TestDescribeJob_ArchiveSHA256TreeHash(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"].(string) rec = doRequest(t, h, http.MethodGet, @@ -548,7 +550,7 @@ func TestDescribeJob_ArchiveSHA256TreeHash(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var jobResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp, jsonx.V1())) if tt.wantArchiveSHA { assert.Equal(t, "cafebabe", jobResp["ArchiveSHA256TreeHash"], @@ -594,7 +596,7 @@ func initiateJob(t *testing.T, h *glacier.Handler, vaultName, jobType string) st require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobID, ok := resp["jobId"].(string) require.True(t, ok, "jobId missing from initiate response") @@ -635,7 +637,7 @@ func TestInitiateJob_StartsInProgress(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "InProgress", desc["StatusCode"]) }) } @@ -678,7 +680,7 @@ func TestInitiateJob_SucceedsAfterDelay(t *testing.T) { ) require.Equal(t, http.StatusOK, recEarly.Code) var earlyDesc map[string]any - require.NoError(t, json.Unmarshal(recEarly.Body.Bytes(), &earlyDesc)) + require.NoError(t, json.Unmarshal(recEarly.Body.Bytes(), &earlyDesc, jsonx.V1())) assert.Equal(t, "InProgress", earlyDesc["StatusCode"]) // After the delay elapses the job should be Succeeded. @@ -687,7 +689,7 @@ func TestInitiateJob_SucceedsAfterDelay(t *testing.T) { recLate := doRequest(t, h, http.MethodGet, "/"+testAccountID+"/vaults/"+tt.vaultName+"/jobs/"+jobID, "") require.Equal(t, http.StatusOK, recLate.Code) var lateDesc map[string]any - require.NoError(t, json.Unmarshal(recLate.Body.Bytes(), &lateDesc)) + require.NoError(t, json.Unmarshal(recLate.Body.Bytes(), &lateDesc, jsonx.V1())) assert.Equal(t, "Succeeded", lateDesc["StatusCode"]) }) }) @@ -721,7 +723,7 @@ func TestInitiateJob_ZeroDelay_ImmediateSucceeded(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "Succeeded", desc["StatusCode"]) }) } @@ -762,8 +764,8 @@ func TestListJobs_JobListAlwaysPresent(t *testing.T) { "/"+testAccountID+"/vaults/listjobs-audit-vault/jobs", "") require.Equal(t, http.StatusOK, rec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, present := resp["JobList"] assert.True(t, present, "JobList key must always be present in ListJobs response") @@ -821,7 +823,7 @@ func TestDescribeJob_SNSTopicPresent(t *testing.T) { require.Equal(t, http.StatusAccepted, initRec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"] require.NotEmpty(t, jobID) @@ -829,18 +831,18 @@ func TestDescribeJob_SNSTopicPresent(t *testing.T) { "/"+testAccountID+"/vaults/snstopic-audit-vault/jobs/"+jobID, "") require.Equal(t, http.StatusOK, descRec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) snsRaw, present := resp["SNSTopic"] if tt.wantPresent { assert.True(t, present, "SNSTopic must be present when job was initiated with SNSTopic") var got string - require.NoError(t, json.Unmarshal(snsRaw, &got)) + require.NoError(t, json.Unmarshal(snsRaw, &got, jsonx.V1())) assert.Equal(t, tt.wantSNSTopic, got) } else if present { var got string - require.NoError(t, json.Unmarshal(snsRaw, &got)) + require.NoError(t, json.Unmarshal(snsRaw, &got, jsonx.V1())) assert.Empty(t, got, "SNSTopic should be empty or absent when not set") } }) @@ -889,7 +891,7 @@ func TestDescribeJob_RetrievalByteRange(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var archResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &archResp, jsonx.V1())) archiveID := archResp["archiveId"] require.NotEmpty(t, archiveID) @@ -906,7 +908,7 @@ func TestDescribeJob_RetrievalByteRange(t *testing.T) { require.Equal(t, http.StatusAccepted, initRec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(initRec.Body.Bytes(), &initResp, jsonx.V1())) jobID := initResp["jobId"] require.NotEmpty(t, jobID) @@ -914,18 +916,18 @@ func TestDescribeJob_RetrievalByteRange(t *testing.T) { "/"+testAccountID+"/vaults/byterange-audit-vault/jobs/"+jobID, "") require.Equal(t, http.StatusOK, descRec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) rangeRaw, present := resp["RetrievalByteRange"] if tt.wantInResp { assert.True(t, present, "RetrievalByteRange must be present when set on the job") var got string - require.NoError(t, json.Unmarshal(rangeRaw, &got)) + require.NoError(t, json.Unmarshal(rangeRaw, &got, jsonx.V1())) assert.Equal(t, tt.byteRange, got) } else if present { var got string - require.NoError(t, json.Unmarshal(rangeRaw, &got)) + require.NoError(t, json.Unmarshal(rangeRaw, &got, jsonx.V1())) assert.Empty(t, got, "RetrievalByteRange should be empty or absent when not set") } }) @@ -1020,7 +1022,7 @@ func TestListJobs_CompletedFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs := resp["JobList"].([]any) assert.Len(t, jobs, tt.wantCount) }) @@ -1054,7 +1056,7 @@ func TestListJobs_StatusCodeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs := resp["JobList"].([]any) assert.Len(t, jobs, tt.wantCount) }) @@ -1127,7 +1129,7 @@ func TestDescribeJob_Fidelity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantAction, resp["Action"]) assert.Equal(t, tt.wantTier, resp["Tier"]) assert.NotEmpty(t, resp["JobId"]) @@ -1164,7 +1166,7 @@ func TestDescribeJob_ArchiveSizePopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) archiveSize, ok := resp["ArchiveSizeInBytes"].(float64) assert.True(t, ok, "ArchiveSizeInBytes must be present for archive-retrieval job") assert.InDelta(t, float64(len(tt.content)), archiveSize, 0) diff --git a/services/glacier/handler_multipart_uploads_test.go b/services/glacier/handler_multipart_uploads_test.go index 06b76914fa..12755b75b8 100644 --- a/services/glacier/handler_multipart_uploads_test.go +++ b/services/glacier/handler_multipart_uploads_test.go @@ -1,7 +1,8 @@ package glacier_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -83,7 +85,7 @@ func TestInitiateMultipartUpload(t *testing.T) { if tt.wantUploadID { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["uploadId"]) uploadID := rec.Header().Get("X-Amz-Multipart-Upload-Id") @@ -257,7 +259,7 @@ func TestCompleteMultipartUpload(t *testing.T) { if tt.wantArchive { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["archiveId"]) assert.NotEmpty(t, rec.Header().Get("X-Amz-Archive-Id")) } @@ -377,7 +379,7 @@ func TestListMultipartUploads(t *testing.T) { var resp struct { UploadsList []any `json:"UploadsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.UploadsList, tt.wantCount) }) } @@ -469,7 +471,7 @@ func TestListParts(t *testing.T) { var resp struct { Parts []any `json:"Parts"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Parts, tt.wantParts) } }) @@ -541,7 +543,7 @@ func TestMultipartUpload_FullRoundTrip(t *testing.T) { var partsResp struct { Parts []any `json:"Parts"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &partsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &partsResp, jsonx.V1())) assert.Len(t, partsResp.Parts, 1) // List uploads — should have 1 @@ -551,7 +553,7 @@ func TestMultipartUpload_FullRoundTrip(t *testing.T) { var uploadsResp struct { UploadsList []any `json:"UploadsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp, jsonx.V1())) assert.Len(t, uploadsResp.UploadsList, 1) // Complete the upload @@ -567,13 +569,13 @@ func TestMultipartUpload_FullRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var completeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &completeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &completeResp, jsonx.V1())) assert.NotEmpty(t, completeResp["archiveId"]) // After completion, list uploads should be empty rec = doRequest(t, h, http.MethodGet, "/-/vaults/"+tt.vaultName+"/multipart-uploads", "") require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp, jsonx.V1())) assert.Empty(t, uploadsResp.UploadsList) }) } @@ -818,7 +820,7 @@ func TestUploadMultipartPart_RequiresContentRange(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] require.NotEmpty(t, uploadID) @@ -893,7 +895,7 @@ func TestCompleteMultipartUpload_RequiresHeaders(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] require.NotEmpty(t, uploadID) @@ -953,7 +955,7 @@ func TestListMultipartUploads_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) uploads := resp["UploadsList"].([]any) assert.Len(t, uploads, 2) @@ -1007,7 +1009,7 @@ func TestListParts_Pagination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var upResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &upResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &upResp, jsonx.V1())) uploadID := upResp["uploadId"].(string) // Upload 3 parts. @@ -1029,7 +1031,7 @@ func TestListParts_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var partsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &partsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &partsResp, jsonx.V1())) parts := partsResp["Parts"].([]any) require.Len(t, parts, 3) @@ -1039,7 +1041,7 @@ func TestListParts_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var pPage1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pPage1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pPage1, jsonx.V1())) assert.Len(t, pPage1["Parts"].([]any), 1) assert.NotEmpty(t, pPage1["Marker"]) } @@ -1082,7 +1084,7 @@ func TestListParts_PartsAlwaysArray(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] require.NotEmpty(t, uploadID) @@ -1101,8 +1103,8 @@ func TestListParts_PartsAlwaysArray(t *testing.T) { "/"+testAccountID+"/vaults/listparts-audit-vault/multipart-uploads/"+uploadID, "") require.Equal(t, http.StatusOK, listRec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) partsRaw, present := resp["Parts"] assert.True(t, present, "Parts key must be present in ListParts response") @@ -1150,7 +1152,7 @@ func TestMultipartUpload_FullLifecycle(t *testing.T) { "", headers) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] require.NotEmpty(t, uploadID) // Location header must be set. @@ -1175,7 +1177,7 @@ func TestMultipartUpload_FullLifecycle(t *testing.T) { "", nil) require.Equal(t, http.StatusOK, rec2.Code) var listParts map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listParts)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listParts, jsonx.V1())) parts := listParts["Parts"].([]any) assert.Len(t, parts, 1) @@ -1189,7 +1191,7 @@ func TestMultipartUpload_FullLifecycle(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec3.Code) var completeResp map[string]string - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &completeResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &completeResp, jsonx.V1())) archiveID := completeResp["archiveId"] assert.NotEmpty(t, archiveID) assert.Equal(t, checksum, rec3.Header().Get("X-Amz-Sha256-Tree-Hash")) @@ -1199,7 +1201,7 @@ func TestMultipartUpload_FullLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/mp-lifecycle-"+tt.name+"/multipart-uploads", "", nil) require.Equal(t, http.StatusOK, rec4.Code) var listUploads map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &listUploads)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &listUploads, jsonx.V1())) uploads := listUploads["UploadsList"].([]any) assert.Empty(t, uploads) @@ -1215,7 +1217,7 @@ func TestMultipartUpload_FullLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/mp-lifecycle-"+tt.name, "", nil) require.Equal(t, http.StatusOK, descVault.Code) var vaultDesc map[string]any - require.NoError(t, json.Unmarshal(descVault.Body.Bytes(), &vaultDesc)) + require.NoError(t, json.Unmarshal(descVault.Body.Bytes(), &vaultDesc, jsonx.V1())) assert.EqualValues(t, 1, vaultDesc["NumberOfArchives"]) }) } @@ -1242,7 +1244,7 @@ func TestMultipartUpload_AbortLifecycle(t *testing.T) { "", map[string]string{"X-Amz-Part-Size": "1048576"}) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] // Abort. @@ -1255,7 +1257,7 @@ func TestMultipartUpload_AbortLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/mp-abort-vault/multipart-uploads", "", nil) require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) uploads := listResp["UploadsList"].([]any) assert.Empty(t, uploads, tt.name) @@ -1293,7 +1295,7 @@ func TestListParts_MarkerNotFound(t *testing.T) { map[string]string{"X-Amz-Part-Size": "1048576"}) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] require.NotEmpty(t, uploadID) @@ -1303,8 +1305,8 @@ func TestListParts_MarkerNotFound(t *testing.T) { "", nil) require.Equal(t, http.StatusOK, rec2.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) partsRaw := resp["Parts"] assert.Equal(t, "[]", string(partsRaw), tt.name) }) @@ -1338,7 +1340,7 @@ func TestListParts_MarkerPagination(t *testing.T) { "", map[string]string{"X-Amz-Part-Size": "1048576"}) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] // Upload 2 parts. @@ -1363,7 +1365,7 @@ func TestListParts_MarkerPagination(t *testing.T) { "", nil) require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) parts1 := page1["Parts"].([]any) require.Len(t, parts1, 1) markerVal, hasMarker := page1["Marker"].(string) @@ -1375,7 +1377,7 @@ func TestListParts_MarkerPagination(t *testing.T) { rec = doRequestWithHeaders(t, h, http.MethodGet, secondPage, "", nil) require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) parts2 := page2["Parts"].([]any) assert.Len(t, parts2, 1) _, hasMarker2 := page2["Marker"] diff --git a/services/glacier/handler_provisioned_capacity_test.go b/services/glacier/handler_provisioned_capacity_test.go index d59ad91ac9..9ea26411e5 100644 --- a/services/glacier/handler_provisioned_capacity_test.go +++ b/services/glacier/handler_provisioned_capacity_test.go @@ -1,7 +1,7 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -51,7 +52,7 @@ func TestListProvisionedCapacity(t *testing.T) { var resp struct { ProvisionedCapacityList []any `json:"ProvisionedCapacityList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ProvisionedCapacityList, tt.wantCount) }) } @@ -84,7 +85,7 @@ func TestPurchaseProvisionedCapacity(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["capacityId"]) assert.NotEmpty(t, rec.Header().Get("X-Amz-Capacity-Id")) }) @@ -113,7 +114,7 @@ func TestProvisionedCapacity_MaxTwoPerAccount(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "LimitExceededException", errResp["code"]) } @@ -139,7 +140,7 @@ func TestProvisionedCapacity_ExpiresAfterOneMonth(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) capList := resp["ProvisionedCapacityList"].([]any) require.Len(t, capList, 1) @@ -222,7 +223,7 @@ func TestProvisionedCapacity_PurchaseAndList(t *testing.T) { "/"+testAccountID+"/provisioned-capacity", "", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list := resp["ProvisionedCapacityList"].([]any) assert.Len(t, list, tt.wantCount) }) @@ -274,7 +275,7 @@ func TestProvisionedCapacity_DatesPresent(t *testing.T) { "/"+testAccountID+"/provisioned-capacity", "", nil) require.Equal(t, http.StatusCreated, rec.Code) var purchaseResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &purchaseResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &purchaseResp, jsonx.V1())) capID := purchaseResp["capacityId"] require.NotEmpty(t, capID) @@ -282,7 +283,7 @@ func TestProvisionedCapacity_DatesPresent(t *testing.T) { "/"+testAccountID+"/provisioned-capacity", "", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list := listResp["ProvisionedCapacityList"].([]any) require.Len(t, list, 1) capItem := list[0].(map[string]any) diff --git a/services/glacier/handler_tags.go b/services/glacier/handler_tags.go index 84acc34460..902f142035 100644 --- a/services/glacier/handler_tags.go +++ b/services/glacier/handler_tags.go @@ -1,15 +1,17 @@ package glacier import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddTagsToVault(c *echo.Context, vaultName string, body []byte) error { var req addTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -36,7 +38,7 @@ func (h *Handler) handleListTagsForVault(c *echo.Context, vaultName string) erro func (h *Handler) handleRemoveTagsFromVault(c *echo.Context, vaultName string, body []byte) error { var req removeTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/glacier/handler_tags_test.go b/services/glacier/handler_tags_test.go index af667371bd..0e88b8b20a 100644 --- a/services/glacier/handler_tags_test.go +++ b/services/glacier/handler_tags_test.go @@ -1,7 +1,7 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags(t *testing.T) { @@ -56,7 +58,7 @@ func TestTags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - err = json.Unmarshal(rec.Body.Bytes(), &tagsResp) + err = json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1()) require.NoError(t, err) tags := tagsResp["Tags"].(map[string]any) @@ -118,7 +120,7 @@ func TestTags_KeyValidation(t *testing.T) { h := newTestHandler() createVault(t, h, "tag-val-vault") - tagsJSON, _ := json.Marshal(tt.tags) + tagsJSON, _ := json.Marshal(tt.tags, jsonx.V1()) body := fmt.Sprintf(`{"Tags":%s}`, tagsJSON) rec := doRequest(t, h, http.MethodPost, "/"+testAccountID+"/vaults/tag-val-vault/tags?operation=add", body) @@ -155,7 +157,7 @@ func TestTags_ExactLimitAccepted(t *testing.T) { tags[fmt.Sprintf("key-%02d", i)] = fmt.Sprintf("val-%02d", i) } - body, err := json.Marshal(map[string]any{"Tags": tags}) + body, err := json.Marshal(map[string]any{"Tags": tags}, jsonx.V1()) require.NoError(t, err) rec := doRequestWithHeaders(t, h, http.MethodPost, @@ -248,7 +250,7 @@ func TestTags_ListTagsEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].(map[string]any) assert.True(t, ok, tt.name) assert.Empty(t, tags) diff --git a/services/glacier/handler_test.go b/services/glacier/handler_test.go index 6124101b8f..7a7653724d 100644 --- a/services/glacier/handler_test.go +++ b/services/glacier/handler_test.go @@ -1,7 +1,8 @@ package glacier_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -230,7 +232,7 @@ func TestHandlerReset(t *testing.T) { var out struct { VaultList []any `json:"VaultList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.VaultList, tt.wantAfter) }) } @@ -582,7 +584,7 @@ func TestErrorResponse_IncludesTypeField(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.NotEmpty(t, errBody["code"], "error body should have code field") assert.NotEmpty(t, errBody["__type"], "error body should have __type field") @@ -646,7 +648,7 @@ func TestErrorResponse_FormatFields(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) // Both "code" and "__type" must be present (SDK compatibility). assert.Equal(t, tt.wantCodeKey, errResp["code"]) assert.Equal(t, tt.wantCodeKey, errResp["__type"]) @@ -732,8 +734,8 @@ func TestPagination_MarkerNotFound_EmptyList(t *testing.T) { rec := doRequestWithHeaders(t, h, tt.method, tt.path, "", nil) require.Equal(t, http.StatusOK, rec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, ok := resp[tt.jsonKey] assert.True(t, ok, "%s key must be present", tt.jsonKey) assert.Equal(t, "[]", string(raw), "%s must be [] not null when marker not found", tt.jsonKey) diff --git a/services/glacier/handler_vault_access_policy.go b/services/glacier/handler_vault_access_policy.go index 3aae3be999..23ca7c2666 100644 --- a/services/glacier/handler_vault_access_policy.go +++ b/services/glacier/handler_vault_access_policy.go @@ -1,15 +1,17 @@ package glacier import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleSetVaultAccessPolicy(c *echo.Context, vaultName string, body []byte) error { var req vaultAccessPolicy - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/glacier/handler_vault_access_policy_test.go b/services/glacier/handler_vault_access_policy_test.go index f7a7293fd1..347c9dff80 100644 --- a/services/glacier/handler_vault_access_policy_test.go +++ b/services/glacier/handler_vault_access_policy_test.go @@ -1,13 +1,15 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAccessPolicy(t *testing.T) { @@ -87,7 +89,7 @@ func TestAccessPolicy_SetGetDelete(t *testing.T) { "/"+testAccountID+"/vaults/access-policy-vault/access-policy", "", nil) require.Equal(t, http.StatusOK, rec.Code) var policyResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp, jsonx.V1())) assert.NotEmpty(t, policyResp["Policy"]) // DELETE. diff --git a/services/glacier/handler_vault_lock.go b/services/glacier/handler_vault_lock.go index 8fe7ed7509..d15a1784c7 100644 --- a/services/glacier/handler_vault_lock.go +++ b/services/glacier/handler_vault_lock.go @@ -1,10 +1,12 @@ package glacier import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleVaultLock(c *echo.Context, op, resource string, body []byte) error { @@ -20,7 +22,7 @@ func (h *Handler) handleVaultLock(c *echo.Context, op, resource string, body []b case opInitiateVaultLock: var req vaultLockPolicyRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid request body: "+err.Error()) } diff --git a/services/glacier/handler_vault_lock_test.go b/services/glacier/handler_vault_lock_test.go index a0134caba4..8384329043 100644 --- a/services/glacier/handler_vault_lock_test.go +++ b/services/glacier/handler_vault_lock_test.go @@ -1,7 +1,7 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -65,7 +66,7 @@ func TestGetVaultLock(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantState, resp["State"]) } }) @@ -276,7 +277,7 @@ func TestCompleteVaultLock_Success(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) lockID := resp["lockId"] require.NotEmpty(t, lockID) @@ -316,7 +317,7 @@ func TestInitiateVaultLock_ReadsPolicy(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["lockId"]) }) } @@ -384,7 +385,7 @@ func TestVaultLock_CompleteClearsExpirationDate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var lockResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockResp, jsonx.V1())) lockID := lockResp["lockId"].(string) rec = doRequest(t, h, http.MethodPost, @@ -396,7 +397,7 @@ func TestVaultLock_CompleteClearsExpirationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "Locked", getResp["State"]) // ExpirationDate must be absent after completing a lock per AWS spec. _, hasExpiration := getResp["ExpirationDate"] @@ -420,7 +421,7 @@ func TestVaultLock_LockedStateHasNoExpirationDate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) lockID := initResp["lockId"].(string) // InProgress state should have ExpirationDate. @@ -429,7 +430,7 @@ func TestVaultLock_LockedStateHasNoExpirationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var inProgressResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inProgressResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inProgressResp, jsonx.V1())) assert.Equal(t, "InProgress", inProgressResp["State"]) assert.NotEmpty(t, inProgressResp["ExpirationDate"], "InProgress lock should have ExpirationDate") @@ -444,7 +445,7 @@ func TestVaultLock_LockedStateHasNoExpirationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var lockedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockedResp, jsonx.V1())) assert.Equal(t, "Locked", lockedResp["State"]) exp, hasExp := lockedResp["ExpirationDate"] @@ -533,7 +534,7 @@ func TestVaultLock_FullLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/lock-lifecycle-vault/lock-policy", body, nil) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) lockID := initResp["lockId"] require.NotEmpty(t, lockID) @@ -542,7 +543,7 @@ func TestVaultLock_FullLifecycle(t *testing.T) { "/"+testAccountID+"/vaults/lock-lifecycle-vault/lock-policy", "", nil) require.Equal(t, http.StatusOK, rec.Code) var lockState map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockState)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockState, jsonx.V1())) assert.Equal(t, "InProgress", lockState["State"]) assert.NotEmpty(t, lockState["ExpirationDate"]) @@ -555,7 +556,7 @@ func TestVaultLock_FullLifecycle(t *testing.T) { rec = doRequestWithHeaders(t, h, http.MethodGet, "/"+testAccountID+"/vaults/lock-lifecycle-vault/lock-policy", "", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockState)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lockState, jsonx.V1())) assert.Equal(t, "Locked", lockState["State"], tt.name) }) } diff --git a/services/glacier/handler_vault_notifications.go b/services/glacier/handler_vault_notifications.go index b582d32f86..7890decc67 100644 --- a/services/glacier/handler_vault_notifications.go +++ b/services/glacier/handler_vault_notifications.go @@ -1,10 +1,12 @@ package glacier import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleSetVaultNotifications( @@ -13,7 +15,7 @@ func (h *Handler) handleSetVaultNotifications( body []byte, ) error { var req vaultNotificationConfig - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/glacier/handler_vault_notifications_test.go b/services/glacier/handler_vault_notifications_test.go index c52373957e..68b2388ac3 100644 --- a/services/glacier/handler_vault_notifications_test.go +++ b/services/glacier/handler_vault_notifications_test.go @@ -1,13 +1,15 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestNotifications(t *testing.T) { @@ -96,7 +98,7 @@ func TestVaultNotifications_EventValidation(t *testing.T) { h := newTestHandler() createVault(t, h, "notif-vault") - eventsJSON, _ := json.Marshal(tt.events) + eventsJSON, _ := json.Marshal(tt.events, jsonx.V1()) body := fmt.Sprintf(`{"SNSTopic":"arn:aws:sns:us-east-1:000000000000:test","Events":%s}`, eventsJSON) rec := doRequest(t, h, http.MethodPut, "/"+testAccountID+"/vaults/notif-vault/notification-configuration", body) @@ -195,7 +197,7 @@ func TestVaultNotifications_SetGetDelete(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) // SET. - eventsJSON, _ := json.Marshal(tt.events) + eventsJSON, _ := json.Marshal(tt.events, jsonx.V1()) body := `{"SNSTopic":"` + tt.topic + `","Events":` + string(eventsJSON) + `}` rec = doRequestWithHeaders(t, h, http.MethodPut, "/"+testAccountID+"/vaults/notif-vault-"+tt.name+"/notification-configuration", body, nil) @@ -206,7 +208,7 @@ func TestVaultNotifications_SetGetDelete(t *testing.T) { "/"+testAccountID+"/vaults/notif-vault-"+tt.name+"/notification-configuration", "", nil) require.Equal(t, http.StatusOK, rec.Code) var notifResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ¬ifResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ¬ifResp, jsonx.V1())) assert.Equal(t, tt.topic, notifResp["SNSTopic"]) events, ok := notifResp["Events"].([]any) require.True(t, ok) diff --git a/services/glacier/handler_vaults_test.go b/services/glacier/handler_vaults_test.go index d478f9a5b7..7372536fe1 100644 --- a/services/glacier/handler_vaults_test.go +++ b/services/glacier/handler_vaults_test.go @@ -2,7 +2,8 @@ package glacier_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -46,7 +48,7 @@ func TestCreateDescribeDeleteVault(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var describeResp map[string]any - err := json.Unmarshal(rec.Body.Bytes(), &describeResp) + err := json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1()) require.NoError(t, err) assert.Equal(t, tt.vaultName, describeResp["VaultName"]) @@ -218,7 +220,7 @@ func TestListVaults_AfterReset(t *testing.T) { VaultList []any `json:"VaultList"` } - err := json.NewDecoder(rec.Body).Decode(&resp) + err := json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1()) require.NoError(t, err) assert.Len(t, resp.VaultList, tt.wantCount) @@ -237,7 +239,7 @@ func TestAccountIDDash_SubstitutedInListVaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vaultList := resp["VaultList"].([]any) assert.Len(t, vaultList, 1) } @@ -253,7 +255,7 @@ func TestAccountIDReal_WorksLikeDash(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vaultList := resp["VaultList"].([]any) assert.Len(t, vaultList, 1) } @@ -297,7 +299,7 @@ func TestListVaults_LimitValidation(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantLen > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vaultList := resp["VaultList"].([]any) assert.Len(t, vaultList, tt.wantLen) } @@ -320,7 +322,7 @@ func TestListVaults_MarkerPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) list1 := page1["VaultList"].([]any) assert.Len(t, list1, 2) @@ -334,7 +336,7 @@ func TestListVaults_MarkerPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) list2 := page2["VaultList"].([]any) assert.Len(t, list2, 2) @@ -465,7 +467,7 @@ func uploadArchiveHTTP(t *testing.T, h *glacier.Handler, vaultName string) strin require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp["archiveId"]) return resp["archiveId"] @@ -495,7 +497,7 @@ func assertVaultDeleteRejected(t *testing.T, h *glacier.Handler, vaultName strin require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["code"]) descRec := doRequest(t, h, http.MethodGet, "/"+testAccountID+"/vaults/"+vaultName, "") @@ -579,7 +581,7 @@ func TestDeleteVault_InventorySemantics(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["code"]) descRec := doRequest(t, h, http.MethodGet, "/"+testAccountID+"/vaults/"+vaultName, "") @@ -720,7 +722,7 @@ func TestDeleteVault_WithActiveMultipartUploads(t *testing.T) { "", map[string]string{"X-Amz-Part-Size": "1048576"}) require.Equal(t, http.StatusCreated, rec.Code) var initResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &initResp, jsonx.V1())) uploadID := initResp["uploadId"] // Delete vault (empty archives = ok, multipart uploads get cleaned). @@ -767,7 +769,7 @@ func TestCreateVault_Idempotent(t *testing.T) { rec := doRequestWithHeaders(t, h, http.MethodGet, "/"+testAccountID+"/vaults", "", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) vl := listResp["VaultList"].([]any) assert.Len(t, vl, 1) }) @@ -847,7 +849,7 @@ func TestDescribeVault_ArchiveStatsAsOfInventory(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/"+testAccountID+"/vaults/"+vaultName, "") require.Equal(t, http.StatusOK, rec.Code) var v map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &v)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &v, jsonx.V1())) if tt.wantAbsent { _, hasArchives := v["NumberOfArchives"] @@ -946,7 +948,7 @@ func TestVault_CrossAccountIsolation(t *testing.T) { listRecB := doRequestWithHeaders(t, hB, http.MethodGet, "/account-bbb/vaults", "", nil) require.Equal(t, http.StatusOK, listRecB.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRecB.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRecB.Body.Bytes(), &listResp, jsonx.V1())) vl := listResp["VaultList"].([]any) require.Len(t, vl, 1, tt.name) v := vl[0].(map[string]any) @@ -990,7 +992,7 @@ func TestVault_CrossRegionIsolation(t *testing.T) { recW := doRequestWithHeaders(t, hW, http.MethodGet, "/same-acct/vaults", "", nil) require.Equal(t, http.StatusOK, recW.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(recW.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recW.Body.Bytes(), &listResp, jsonx.V1())) vl := listResp["VaultList"].([]any) assert.Empty(t, vl, tt.name) }) @@ -1023,7 +1025,7 @@ func TestVaultARN_Format(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["VaultARN"].(string) assert.NotEmpty(t, arn) wantARN := "arn:aws:glacier:" + testRegion + ":" + testAccountID + ":vaults/" + tt.vaultName @@ -1092,7 +1094,7 @@ func TestListVaults_LimitAndMarkerCombined(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list := resp["VaultList"].([]any) require.Len(t, list, len(tt.wantNames)) for i, want := range tt.wantNames { diff --git a/services/glacier/inventory_retrieval_test.go b/services/glacier/inventory_retrieval_test.go index 80731a6bc9..e6fbb1123c 100644 --- a/services/glacier/inventory_retrieval_test.go +++ b/services/glacier/inventory_retrieval_test.go @@ -1,13 +1,14 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -51,7 +52,7 @@ func inventoryArchiveIDs(t *testing.T, body []byte) []string { ArchiveID string `json:"ArchiveId"` } `json:"ArchiveList"` } - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) ids := make([]string, len(resp.ArchiveList)) for i, a := range resp.ArchiveList { @@ -177,7 +178,7 @@ func TestInitiateJob_InventoryRetrieval_InvalidParameters(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["code"]) }) } @@ -200,7 +201,7 @@ func TestDescribeJob_InventoryRetrieval_EchoesParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasTopLevelFormat := resp["Format"] assert.False(t, hasTopLevelFormat, "top-level Format is not a real GlacierJobDescription field") @@ -231,7 +232,7 @@ func TestDescribeJob_ArchiveRetrieval_NoInventoryRetrievalParameters(t *testing. require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, has := resp["InventoryRetrievalParameters"] assert.False(t, has) diff --git a/services/glacier/oldshape_restore_test.go b/services/glacier/oldshape_restore_test.go index 8a4d015adc..cd1ded1d2d 100644 --- a/services/glacier/oldshape_restore_test.go +++ b/services/glacier/oldshape_restore_test.go @@ -1,12 +1,15 @@ package glacier_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // stripOldShapeVaultFields simulates a snapshot taken before gopherstack-x8em/ @@ -17,14 +20,14 @@ import ( func stripOldShapeVaultFields(t *testing.T, snap []byte) []byte { t.Helper() - var top map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &top)) + var top map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &top, jsonx.V1())) - var tables map[string]json.RawMessage - require.NoError(t, json.Unmarshal(top["tables"], &tables)) + var tables map[string]jsontext.Value + require.NoError(t, json.Unmarshal(top["tables"], &tables, jsonx.V1())) var vaults []map[string]any - require.NoError(t, json.Unmarshal(tables["vaults"], &vaults)) + require.NoError(t, json.Unmarshal(tables["vaults"], &vaults, jsonx.V1())) for _, v := range vaults { delete(v, "numberOfArchivesAtLastInventory") @@ -32,15 +35,15 @@ func stripOldShapeVaultFields(t *testing.T, snap []byte) []byte { delete(v, "writeSinceLastInventory") } - vaultsRaw, err := json.Marshal(vaults) + vaultsRaw, err := json.Marshal(vaults, jsonx.V1()) require.NoError(t, err) tables["vaults"] = vaultsRaw - tablesRaw, err := json.Marshal(tables) + tablesRaw, err := json.Marshal(tables, jsonx.V1()) require.NoError(t, err) top["tables"] = tablesRaw - out, err := json.Marshal(top) + out, err := json.Marshal(top, jsonx.V1()) require.NoError(t, err) return out @@ -75,7 +78,7 @@ func TestRestoreOldShapeSnapshot_DeleteVaultGuard(t *testing.T) { t.Logf("DescribeVault on restored old-shape vault: %s", rec.Body.String()) var describeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) assert.NotEmpty(t, describeResp["LastInventoryDate"], "LastInventoryDate must survive restore") _, hasNumArchives := describeResp["NumberOfArchives"] assert.False(t, hasNumArchives, diff --git a/services/glacier/persistence.go b/services/glacier/persistence.go index 5c35c7582b..07b1d5ab9d 100644 --- a/services/glacier/persistence.go +++ b/services/glacier/persistence.go @@ -2,7 +2,7 @@ package glacier import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -49,7 +49,7 @@ type provisionedCapacitySnapshot struct { // persisted directly alongside Tables, exactly as they were before this // conversion. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` DataRetrievalPolicies map[string]string `json:"dataRetrievalPolicies,omitempty"` MultipartParts []multipartPartSnapshot `json:"multipartParts"` ProvisionedCapacity []provisionedCapacitySnapshot `json:"provisionedCapacity"` diff --git a/services/glacier/persistence_test.go b/services/glacier/persistence_test.go index 901621a48f..7d23068436 100644 --- a/services/glacier/persistence_test.go +++ b/services/glacier/persistence_test.go @@ -1,13 +1,14 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -370,7 +371,7 @@ func TestPersistenceRoundTrip_DataRetrievalPolicy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.NotEmpty(t, got) }) } @@ -425,7 +426,7 @@ func TestPersistenceRoundTrip_MultipartAndCapacity(t *testing.T) { var uploadsResp struct { UploadsList []any `json:"UploadsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &uploadsResp, jsonx.V1())) assert.Len(t, uploadsResp.UploadsList, 1) // Verify provisioned capacity is restored @@ -435,7 +436,7 @@ func TestPersistenceRoundTrip_MultipartAndCapacity(t *testing.T) { var capsResp struct { ProvisionedCapacityList []any `json:"ProvisionedCapacityList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &capsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &capsResp, jsonx.V1())) assert.Len(t, capsResp.ProvisionedCapacityList, 1) }) } @@ -475,7 +476,7 @@ func TestPersistenceRoundTrip_SelectAndRangeInventoryJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var selectResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &selectResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &selectResp, jsonx.V1())) assert.Equal(t, "Select", selectResp["Action"]) assert.NotEmpty(t, selectResp["JobOutputPath"]) @@ -502,7 +503,7 @@ func TestPersistenceRoundTrip_SelectAndRangeInventoryJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var invResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &invResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &invResp, jsonx.V1())) invParams, ok := invResp["InventoryRetrievalParameters"].(map[string]any) require.True(t, ok, "InventoryRetrievalParameters must survive restore: %#v", invResp) diff --git a/services/glacier/select_output.go b/services/glacier/select_output.go index def34b2eab..7256b088ee 100644 --- a/services/glacier/select_output.go +++ b/services/glacier/select_output.go @@ -27,11 +27,12 @@ package glacier import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "github.com/aws/aws-sdk-go-v2/aws" s3sdk "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -155,7 +156,7 @@ func writeSelectS3Output( // the (unreachable in practice, since callers pass only structs/maps of strings) // marshal error -- a malformed manifest object is still safer than a panic. func marshalSelectOutputJSON(v any) []byte { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return []byte("{}") } diff --git a/services/glacier/select_output_test.go b/services/glacier/select_output_test.go index 7fc148e1ef..e7c002bf09 100644 --- a/services/glacier/select_output_test.go +++ b/services/glacier/select_output_test.go @@ -2,7 +2,7 @@ package glacier_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" s3backend "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -210,7 +211,7 @@ func TestGetJobOutput_Select_S3ErrorManifestOnQueryFailure(t *testing.T) { "OutputLocation": map[string]any{ "S3": map[string]any{"BucketName": testOutputBucket, "Prefix": "out/"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) jobID := initiateJobWithBody(t, h, "select-s3-error-vault", string(body)) diff --git a/services/glacier/select_test.go b/services/glacier/select_test.go index a7c885a340..17d798ef1d 100644 --- a/services/glacier/select_test.go +++ b/services/glacier/select_test.go @@ -1,13 +1,15 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const selectTestArchive = "1,alice,30\n2,bob,25\n3,carol,40\n" @@ -31,7 +33,7 @@ func basicSelectBody(archiveID, expression string) string { "OutputLocation": map[string]any{ "S3": map[string]any{"BucketName": "results-bucket", "Prefix": "out/"}, }, - }) + }, jsonx.V1()) return string(body) } @@ -56,7 +58,7 @@ func TestInitiateJob_Select_Success(t *testing.T) { assert.Contains(t, gotPathHeader, wantPath) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, gotPathHeader, resp["jobOutputPath"]) assert.NotEmpty(t, resp["jobId"]) } @@ -208,7 +210,7 @@ func TestInitiateJob_Select_Validation(t *testing.T) { maps.Copy(payload, tt.overrides) - body, err := json.Marshal(payload) + body, err := json.Marshal(payload, jsonx.V1()) require.NoError(t, err) rec := doRequest( @@ -218,7 +220,7 @@ func TestInitiateJob_Select_Validation(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code, rec.Body.String()) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantCode, errResp["code"]) }) } @@ -237,7 +239,7 @@ func TestInitiateJob_Select_ArchiveNotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code, rec.Body.String()) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["code"]) } @@ -336,7 +338,7 @@ func TestGetJobOutput_Select_HeaderNames(t *testing.T) { "OutputLocation": map[string]any{ "S3": map[string]any{"BucketName": "bucket"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) jobID := initiateJobWithBody(t, h, "select-header-vault", string(body)) @@ -372,7 +374,7 @@ func TestGetJobOutput_Select_QuoteFieldsAlways(t *testing.T) { "OutputLocation": map[string]any{ "S3": map[string]any{"BucketName": "bucket"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) jobID := initiateJobWithBody(t, h, "select-quote-vault", string(body)) @@ -423,7 +425,7 @@ func TestDescribeJob_Select_EchoesParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Select", resp["Action"]) assert.NotEmpty(t, resp["JobOutputPath"]) diff --git a/services/glacier/vault_lock_policy_eval.go b/services/glacier/vault_lock_policy_eval.go index 3ba465a6a2..ee836c2944 100644 --- a/services/glacier/vault_lock_policy_eval.go +++ b/services/glacier/vault_lock_policy_eval.go @@ -1,10 +1,13 @@ package glacier import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Vault Lock and vault access policy evaluation is transcribed from AWS's @@ -24,7 +27,7 @@ type vaultLockPolicyDocument struct { type vaultLockPolicyStatement struct { Condition map[string]map[string]glacierPolicyStringSet `json:"Condition"` - Principal json.RawMessage `json:"Principal"` + Principal jsontext.Value `json:"Principal"` Effect string `json:"Effect"` Action glacierPolicyStringSet `json:"Action"` Resource glacierPolicyStringSet `json:"Resource"` @@ -35,23 +38,23 @@ type vaultLockPolicyStatement struct { type glacierPolicyStringSet []string func (s *glacierPolicyStringSet) UnmarshalJSON(data []byte) error { - var multi []json.RawMessage - if err := json.Unmarshal(data, &multi); err != nil { - multi = []json.RawMessage{data} + var multi []jsontext.Value + if err := json.Unmarshal(data, &multi, jsonx.V1()); err != nil { + multi = []jsontext.Value{data} } out := make(glacierPolicyStringSet, 0, len(multi)) for _, raw := range multi { var str string - if err := json.Unmarshal(raw, &str); err == nil { + if err := json.Unmarshal(raw, &str, jsonx.V1()); err == nil { out = append(out, str) continue } - var num json.Number - if err := json.Unmarshal(raw, &num); err != nil { + var num jsonx.Number + if err := json.Unmarshal(raw, &num, jsonx.V1()); err != nil { return fmt.Errorf("unsupported policy value %s: %w", raw, err) } @@ -78,7 +81,7 @@ const ( // InitiateVaultLock time rather than silently never enforcing anything. func parseVaultLockPolicyDocument(policy string) (*vaultLockPolicyDocument, error) { var doc vaultLockPolicyDocument - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("malformed vault lock policy: %w", err) } @@ -135,7 +138,7 @@ func evaluateVaultPolicyDeny(policy string, req *policyRequest) (bool, error) { // principalMatches reports whether a statement's Principal covers the caller. A // missing Principal is treated as the wildcard. -func principalMatches(raw json.RawMessage, req *policyRequest) bool { +func principalMatches(raw jsontext.Value, req *policyRequest) bool { if len(raw) == 0 { return true } @@ -143,11 +146,11 @@ func principalMatches(raw json.RawMessage, req *policyRequest) bool { var set glacierPolicyStringSet var single string - if json.Unmarshal(raw, &single) == nil { + if json.Unmarshal(raw, &single, jsonx.V1()) == nil { set = glacierPolicyStringSet{single} } else { var obj map[string]glacierPolicyStringSet - if json.Unmarshal(raw, &obj) != nil { + if json.Unmarshal(raw, &obj, jsonx.V1()) != nil { return false } diff --git a/services/glacier/writesince_writesites_test.go b/services/glacier/writesince_writesites_test.go index 4089f8d181..523dd60f26 100644 --- a/services/glacier/writesince_writesites_test.go +++ b/services/glacier/writesince_writesites_test.go @@ -1,7 +1,7 @@ package glacier_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glacier" ) @@ -107,7 +108,7 @@ func TestDeleteVault_WriteSinceLastInventory_CompleteMultipartUpload(t *testing. require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var completeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &completeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &completeResp, jsonx.V1())) archiveID, _ := completeResp["archiveId"].(string) require.NotEmpty(t, archiveID) diff --git a/services/globalaccelerator/handler.go b/services/globalaccelerator/handler.go index fd9443325e..4c270da8df 100644 --- a/services/globalaccelerator/handler.go +++ b/services/globalaccelerator/handler.go @@ -2,7 +2,7 @@ package globalaccelerator import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -41,7 +42,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrInvalidArgument, "malformed request body: %v", err) } } diff --git a/services/globalaccelerator/handler_test.go b/services/globalaccelerator/handler_test.go index f0446ed961..cb83d3e9d5 100644 --- a/services/globalaccelerator/handler_test.go +++ b/services/globalaccelerator/handler_test.go @@ -1,7 +1,7 @@ package globalaccelerator_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/globalaccelerator" ) @@ -113,7 +114,7 @@ func TestHandler_ErrorEnvelope(t *testing.T) { assert.NotEmpty(t, rec.Header().Get("X-Amz-Crc32")) var got map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tc.code, got["__type"]) assert.NotEmpty(t, got["message"]) assert.Equal(t, got["message"], got["Message"]) @@ -141,7 +142,7 @@ func TestHandler_EmptyBodyAndResponseShapes(t *testing.T) { Accelerator map[string]any `json:"Accelerator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.IsType(t, float64(0), out.Accelerator["CreatedTime"], "timestamps are epoch-seconds numbers") assert.IsType(t, float64(0), out.Accelerator["LastModifiedTime"]) assert.Equal(t, true, out.Accelerator["Enabled"]) @@ -210,7 +211,7 @@ func TestErrorTable_CoversEverySentinel(t *testing.T) { require.NoError(t, globalaccelerator.WriteErrorForTest(c, tc.err)) var got map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tc.code, got["__type"]) }) } diff --git a/services/globalaccelerator/persistence.go b/services/globalaccelerator/persistence.go index c25b39eae7..eb869627db 100644 --- a/services/globalaccelerator/persistence.go +++ b/services/globalaccelerator/persistence.go @@ -2,7 +2,7 @@ package globalaccelerator import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,11 +12,11 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Seq int64 `json:"seq"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Seq int64 `json:"seq"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/glue/blueprint_include_flags_test.go b/services/glue/blueprint_include_flags_test.go index c43ac1c685..ea69aca524 100644 --- a/services/glue/blueprint_include_flags_test.go +++ b/services/glue/blueprint_include_flags_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -65,7 +66,7 @@ func TestGetBlueprint_IncludeFlags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) bp, _ := out["Blueprint"].(map[string]any) if tt.action == "BatchGetBlueprints" { diff --git a/services/glue/crawl_run.go b/services/glue/crawl_run.go index 830955d049..0a2b83900c 100644 --- a/services/glue/crawl_run.go +++ b/services/glue/crawl_run.go @@ -1,7 +1,7 @@ package glue import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -186,7 +187,7 @@ func configuredMergeNewColumns(cfg string) bool { } `json:"CrawlerOutput"` } - if err := json.Unmarshal([]byte(cfg), &doc); err != nil { + if err := json.Unmarshal([]byte(cfg), &doc, jsonx.V1()); err != nil { return false } diff --git a/services/glue/crawler_lake.go b/services/glue/crawler_lake.go index 5fd96f178c..c70e077482 100644 --- a/services/glue/crawler_lake.go +++ b/services/glue/crawler_lake.go @@ -3,7 +3,8 @@ package glue import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -13,6 +14,8 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -163,5 +166,5 @@ func moveToPartitionKeys(cols []Column, names []string) ([]Column, []Column) { } func decodeJSON(data []byte, v any) error { - return json.NewDecoder(bytes.NewReader(data)).Decode(v) + return json.UnmarshalDecode(jsontext.NewDecoder(bytes.NewReader(data)), v, jsonx.V1()) } diff --git a/services/glue/crawler_s3.go b/services/glue/crawler_s3.go index 5fce70e74f..39149ab9ea 100644 --- a/services/glue/crawler_s3.go +++ b/services/glue/crawler_s3.go @@ -4,7 +4,8 @@ import ( "bytes" "context" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "maps" @@ -18,6 +19,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -523,40 +525,41 @@ func mergeJSONTypes(a, b string) string { } } -func decodeJSONObject(line []byte) ([]string, []json.RawMessage, bool) { - dec := json.NewDecoder(bytes.NewReader(line)) - if tok, err := dec.Token(); err != nil || tok != json.Delim('{') { +func decodeJSONObject(line []byte) ([]string, []jsontext.Value, bool) { + dec := jsontext.NewDecoder(bytes.NewReader(line), jsonx.V1()) + if tok, err := dec.ReadToken(); err != nil || tok.Kind() != '{' { return nil, nil, false } var keys []string - var vals []json.RawMessage + var vals []jsontext.Value - for dec.More() { - kt, err := dec.Token() + for dec.PeekKind() != '}' { + kt, err := dec.ReadToken() if err != nil { return nil, nil, false } - var raw json.RawMessage - if err = dec.Decode(&raw); err != nil { + key := kt.String() + + raw, err := dec.ReadValue() + if err != nil { return nil, nil, false } - keys = append(keys, fmt.Sprint(kt)) - vals = append(vals, raw) + keys = append(keys, key) + vals = append(vals, raw.Clone()) } return keys, vals, true } -func jsonType(raw json.RawMessage) string { - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() +func jsonType(raw jsontext.Value) string { + dec := jsontext.NewDecoder(bytes.NewReader(raw)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return avroString } @@ -569,7 +572,7 @@ func jsonValueType(v any) string { return "" case bool: return avroBoolean - case json.Number: + case jsonx.Number: if _, err := x.Int64(); err == nil { return typeBigint } diff --git a/services/glue/get_partitions_index_test.go b/services/glue/get_partitions_index_test.go index bc03b9cad0..1563285533 100644 --- a/services/glue/get_partitions_index_test.go +++ b/services/glue/get_partitions_index_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -122,7 +123,7 @@ func TestPagination_GetPartitions_RoundTripOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out page - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) for _, p := range out.Partitions { seen = append(seen, p.Values[0]) diff --git a/services/glue/get_partitions_segment_test.go b/services/glue/get_partitions_segment_test.go index 8a5b8a780b..ca51a9a6a0 100644 --- a/services/glue/get_partitions_segment_test.go +++ b/services/glue/get_partitions_segment_test.go @@ -1,13 +1,15 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGetPartitions_Segment(t *testing.T) { @@ -57,7 +59,7 @@ func TestGetPartitions_Segment(t *testing.T) { var out struct { Partitions []map[string]any `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Partitions, tt.wantCount) }) } @@ -107,7 +109,7 @@ func TestGetPartitions_ExcludeColumnSchema(t *testing.T) { } `json:"StorageDescriptor"` } `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Partitions, 1) assert.Len(t, out.Partitions[0].StorageDescriptor.Columns, tt.wantColumns) assert.Equal(t, "s3://bucket/p", out.Partitions[0].StorageDescriptor.Location) diff --git a/services/glue/glossary_client_token_test.go b/services/glue/glossary_client_token_test.go index 1baa212713..eda8759ea8 100644 --- a/services/glue/glossary_client_token_test.go +++ b/services/glue/glossary_client_token_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateGlossary_ClientToken(t *testing.T) { @@ -34,7 +36,7 @@ func TestCreateGlossary_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["Id"].(string) } @@ -46,7 +48,7 @@ func TestCreateGlossary_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["Id"].(string) } diff --git a/services/glue/handler.go b/services/glue/handler.go index de6085a5b5..3b480a63b8 100644 --- a/services/glue/handler.go +++ b/services/glue/handler.go @@ -2,7 +2,7 @@ package glue import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -175,7 +176,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ConnectionName string `json:"ConnectionName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) switch { case req.ResourceArn != "": @@ -232,7 +233,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // glueErrorCodes maps every sentinel this package returns to its AWS error diff --git a/services/glue/handler_blueprints_test.go b/services/glue/handler_blueprints_test.go index acd16a293b..74a024aecc 100644 --- a/services/glue/handler_blueprints_test.go +++ b/services/glue/handler_blueprints_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/glue/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestBlueprint_CRUD(t *testing.T) { @@ -368,7 +370,7 @@ func TestBlueprint_Run_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, startBpRec1.Code) var startBpOut1 map[string]any - require.NoError(t, json.Unmarshal(startBpRec1.Body.Bytes(), &startBpOut1)) + require.NoError(t, json.Unmarshal(startBpRec1.Body.Bytes(), &startBpOut1, jsonx.V1())) assert.NotEmpty(t, startBpOut1["RunId"]) startRec := doGlueRequest(t, h, "StartBlueprintRun", map[string]any{ @@ -377,7 +379,7 @@ func TestBlueprint_Run_Lifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"].(string) getBpRunRec := doGlueRequest(t, h, "GetBlueprintRun", map[string]any{ @@ -390,7 +392,7 @@ func TestBlueprint_Run_Lifecycle(t *testing.T) { getBpRunsRec := doGlueRequest(t, h, "GetBlueprintRuns", map[string]any{"BlueprintName": "bp-run"}) require.Equal(t, http.StatusOK, getBpRunsRec.Code) var getBpRunsOut map[string]any - require.NoError(t, json.Unmarshal(getBpRunsRec.Body.Bytes(), &getBpRunsOut)) + require.NoError(t, json.Unmarshal(getBpRunsRec.Body.Bytes(), &getBpRunsOut, jsonx.V1())) bpRuns := getBpRunsOut["BlueprintRuns"].([]any) assert.GreaterOrEqual(t, len(bpRuns), 1) } diff --git a/services/glue/handler_catalogs_test.go b/services/glue/handler_catalogs_test.go index 8b1aa2ada9..5f777f3492 100644 --- a/services/glue/handler_catalogs_test.go +++ b/services/glue/handler_catalogs_test.go @@ -1,13 +1,15 @@ package glue_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -19,7 +21,7 @@ func dispatchNewOp(t *testing.T, h *glue.Handler, op string, body any) map[strin t.Fatalf("op %s: got status %d, body: %s", op, rr.Code, rr.Body.String()) } var out map[string]any - if err := json.NewDecoder(rr.Body).Decode(&out); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(rr.Body), &out, jsonx.V1()); err != nil { t.Fatalf("decode response: %v", err) } @@ -202,7 +204,7 @@ func TestCatalogImport_Lifecycle(t *testing.T) { ImportCompleted bool `json:"ImportCompleted"` } `json:"ImportStatus"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tc.wantCompleted, out.ImportStatus.ImportCompleted) if tc.wantCompleted { @@ -231,7 +233,7 @@ func TestCatalogImport_Idempotent(t *testing.T) { ImportCompleted bool `json:"ImportCompleted"` } `json:"ImportStatus"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.True(t, out.ImportStatus.ImportCompleted) } @@ -293,7 +295,7 @@ func TestCatalogImportStatus(t *testing.T) { ImportCompleted bool `json:"ImportCompleted"` } `json:"ImportStatus"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tc.wantCompleted, out.ImportStatus.ImportCompleted) if tc.wantCompleted { diff --git a/services/glue/handler_classifiers_test.go b/services/glue/handler_classifiers_test.go index 1e2da3a96e..1e30293212 100644 --- a/services/glue/handler_classifiers_test.go +++ b/services/glue/handler_classifiers_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestClassifier_CRUD(t *testing.T) { @@ -148,7 +150,7 @@ func TestClassifier_GetClassifiers(t *testing.T) { rec := doGlueRequest(t, h, "GetClassifiers", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) classifiers := out["Classifiers"].([]any) assert.Len(t, classifiers, 2) } diff --git a/services/glue/handler_column_statistics_test.go b/services/glue/handler_column_statistics_test.go index 521f3b8e84..408457fc6c 100644 --- a/services/glue/handler_column_statistics_test.go +++ b/services/glue/handler_column_statistics_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -53,7 +54,7 @@ func TestStopColumnStatisticsTaskRun_NotFound(t *testing.T) { var out struct { RunID string `json:"ColumnStatisticsTaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.RunID, "expected ColumnStatisticsTaskRunId in response") } @@ -355,7 +356,7 @@ func TestColumnStatisticsTaskSettings(t *testing.T) { }) require.Equal(t, http.StatusOK, startCSTRec.Code) var startCSTOut map[string]any - require.NoError(t, json.Unmarshal(startCSTRec.Body.Bytes(), &startCSTOut)) + require.NoError(t, json.Unmarshal(startCSTRec.Body.Bytes(), &startCSTOut, jsonx.V1())) assert.NotEmpty(t, startCSTOut["ColumnStatisticsTaskRunId"]) doGlueRequest(t, h, "StartColumnStatisticsTaskRun", map[string]any{ @@ -374,7 +375,7 @@ func TestColumnStatisticsTaskSettings(t *testing.T) { }) require.Equal(t, http.StatusOK, startCSTRec2.Code) var startCSTOut2 map[string]any - require.NoError(t, json.Unmarshal(startCSTRec2.Body.Bytes(), &startCSTOut2)) + require.NoError(t, json.Unmarshal(startCSTRec2.Body.Bytes(), &startCSTOut2, jsonx.V1())) cstRunID := startCSTOut2["ColumnStatisticsTaskRunId"].(string) getCSTRunRec := doGlueRequest(t, h, "GetColumnStatisticsTaskRun", map[string]any{ @@ -440,7 +441,7 @@ func TestColumnStatisticsTaskRunSchedule_RequiresExistingSettings(t *testing.T) } `json:"Schedule"` } `json:"ColumnStatisticsTaskSettings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "SCHEDULED", out.ColumnStatisticsTaskSettings.Schedule.State) rec = doGlueRequest(t, h, "StopColumnStatisticsTaskRunSchedule", map[string]any{ @@ -452,7 +453,7 @@ func TestColumnStatisticsTaskRunSchedule_RequiresExistingSettings(t *testing.T) "DatabaseName": "db", "TableName": "tbl", }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "NOT_SCHEDULED", out.ColumnStatisticsTaskSettings.Schedule.State) } diff --git a/services/glue/handler_connection_types_sdk_test.go b/services/glue/handler_connection_types_sdk_test.go index 73b8149286..b63bc711b4 100644 --- a/services/glue/handler_connection_types_sdk_test.go +++ b/services/glue/handler_connection_types_sdk_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -121,7 +122,7 @@ func TestConnectionTypeWireShape(t *testing.T) { SupportedDataOperations []string `json:"SupportedDataOperations"` } `json:"Capabilities"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.ElementsMatch(t, []string{"READ", "WRITE"}, out.Capabilities.SupportedDataOperations) }, }, @@ -133,7 +134,7 @@ func TestConnectionTypeWireShape(t *testing.T) { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.NotContains(t, out, "Category", "DescribeConnectionTypeOutput has no Category member on the real wire") }, @@ -151,7 +152,7 @@ func TestConnectionTypeWireShape(t *testing.T) { Categories []string `json:"Categories"` } `json:"ConnectionTypes"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) found := false diff --git a/services/glue/handler_connection_types_test.go b/services/glue/handler_connection_types_test.go index 27fabdd8ce..80fa171b3f 100644 --- a/services/glue/handler_connection_types_test.go +++ b/services/glue/handler_connection_types_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDeleteConnectionType verifies real state mutation: built-in types are @@ -75,7 +77,7 @@ func TestDeleteConnectionType_CustomRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, regRec.Code) var regOut map[string]any - require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Out)) + require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Out, jsonx.V1())) assert.NotEmpty(t, regOut["ConnectionTypeArn"], "RegisterConnectionType must return ConnectionTypeArn") delRec := doGlueRequest(t, h, "DeleteConnectionType", map[string]any{"ConnectionType": "MyCustomConn"}) diff --git a/services/glue/handler_connections_test.go b/services/glue/handler_connections_test.go index 9e26a7dfcc..6797de49fe 100644 --- a/services/glue/handler_connections_test.go +++ b/services/glue/handler_connections_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -41,7 +42,7 @@ func TestDescribeEntity_ValidConnection(t *testing.T) { var out struct { Fields []any `json:"Fields"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out.Fields) } @@ -77,7 +78,7 @@ func TestGetEntityRecords_ValidConnection(t *testing.T) { NextToken string `json:"NextToken"` Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Records) // Each record must conform to the Account schema. for _, r := range out.Records { @@ -286,7 +287,7 @@ func TestConnection_NetworkType(t *testing.T) { getRec := doGlueRequest(t, h, "GetConnection", map[string]any{"Name": "network-conn"}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) conn := out["Connection"].(map[string]any) assert.Equal(t, "NETWORK", conn["ConnectionType"]) props := conn["ConnectionProperties"].(map[string]any) @@ -323,7 +324,7 @@ func TestConnection_CredentialsPersistAfterUpdate(t *testing.T) { getRec := doGlueRequest(t, h, "GetConnection", map[string]any{"Name": "cred-conn"}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) conn := out["Connection"].(map[string]any) props := conn["ConnectionProperties"].(map[string]any) assert.Equal(t, "user2", props["USERNAME"]) @@ -405,7 +406,7 @@ func TestConnectionCRUD(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } }) @@ -455,7 +456,7 @@ func TestConnectionGetFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) conn := out["Connection"].(map[string]any) assert.Equal(t, tt.wantConnType, conn["ConnectionType"]) if tt.wantCreation { @@ -500,7 +501,7 @@ func TestGetConnections(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, _ := out["ConnectionList"].([]any) assert.Len(t, list, tt.wantLen) }) @@ -553,7 +554,7 @@ func TestConnectionTagging(t *testing.T) { rec := doGlueRequest(t, h, "GetTags", map[string]any{"ResourceArn": connARN}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } @@ -567,7 +568,7 @@ func TestConnectionTagging(t *testing.T) { rec2 := doGlueRequest(t, h, "GetTags", map[string]any{"ResourceArn": connARN}) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) tags, _ := out2["Tags"].(map[string]any) _, hasFoo := tags["foo"] assert.False(t, hasFoo) @@ -684,7 +685,7 @@ func TestUpdateConnection_Updates(t *testing.T) { ConnectionProperties map[string]string `json:"ConnectionProperties"` } `json:"Connection"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "jdbc:mysql://newhost:3306/db", out.Connection.ConnectionProperties["JDBC_CONNECTION_URL"]) } diff --git a/services/glue/handler_crawlers_test.go b/services/glue/handler_crawlers_test.go index 336d135c6d..11c981e032 100644 --- a/services/glue/handler_crawlers_test.go +++ b/services/glue/handler_crawlers_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -72,7 +73,7 @@ func Test_Crawler_CreateOptions_RoundTrip(t *testing.T) { } `json:"Targets"` } `json:"Crawler"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "cron(0 12 * * ? *)", out.Crawler.Schedule.ScheduleExpression) assert.Equal(t, `{"Version":1.0}`, out.Crawler.Configuration) @@ -86,7 +87,7 @@ func Test_Crawler_CreateOptions_RoundTrip(t *testing.T) { cat := out cat.Crawler.Targets.CatalogTargets = nil - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cat)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cat, jsonx.V1())) require.Len(t, cat.Crawler.Targets.CatalogTargets, 1) assert.Equal(t, "db1", cat.Crawler.Targets.CatalogTargets[0].DatabaseName) @@ -100,7 +101,7 @@ func Test_Crawler_CreateOptions_RoundTrip(t *testing.T) { rec = doGlueRequest(t, h, "GetCrawler", map[string]any{"Name": "crawler1"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "curated_", out.Crawler.TablePrefix) } @@ -265,7 +266,7 @@ func TestCrawler_Lifecycle_States(t *testing.T) { if tt.wantState != "" && tt.wantCode == http.StatusOK { getRec := doGlueRequest(t, h, "GetCrawler", map[string]any{"Name": "cr-lifecycle"}) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) cr := out["Crawler"].(map[string]any) assert.Equal(t, tt.wantState, cr["State"]) } @@ -412,7 +413,7 @@ func TestHTTPBatchGetCrawlers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) crawlers, ok := out["Crawlers"].([]any) require.True(t, ok) assert.Len(t, crawlers, 1) @@ -453,7 +454,7 @@ func TestListCrawlers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) names, _ := out["CrawlerNames"].([]any) assert.Len(t, names, tt.wantLen) @@ -515,7 +516,7 @@ func TestCrawlerErrors(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantErrorType, out["__type"]) }) } @@ -582,7 +583,7 @@ func TestCrawlerSchedule(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } }) @@ -660,7 +661,7 @@ func TestListCrawls_ReturnsRealCrawlHistory(t *testing.T) { var before struct { Crawls []map[string]any `json:"Crawls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before, jsonx.V1())) assert.Empty(t, before.Crawls, "no crawl has started yet") rec = doGlueRequest(t, h, "StartCrawler", map[string]any{"Name": "my-crawler"}) @@ -672,7 +673,7 @@ func TestListCrawls_ReturnsRealCrawlHistory(t *testing.T) { var after struct { Crawls []map[string]any `json:"Crawls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after, jsonx.V1())) require.Len(t, after.Crawls, 1) assert.NotEmpty(t, after.Crawls[0]["CrawlId"]) assert.NotEmpty(t, after.Crawls[0]["StartTime"]) diff --git a/services/glue/handler_custom_entity_types_test.go b/services/glue/handler_custom_entity_types_test.go index 7ddd30f446..c3db87a363 100644 --- a/services/glue/handler_custom_entity_types_test.go +++ b/services/glue/handler_custom_entity_types_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -181,7 +182,7 @@ func TestCustomEntityType_RegexAndContextWords_Persist(t *testing.T) { rec := doGlueRequest(t, h, "GetCustomEntityType", map[string]any{"Name": "CreditCard"}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, `\d{4}-\d{4}-\d{4}-\d{4}`, out["RegexString"]) ctxWords, ok := out["ContextWords"].([]any) require.True(t, ok) @@ -271,7 +272,7 @@ func TestGetCustomEntityType_Found(t *testing.T) { Name string `json:"Name"` RegexString string `json:"RegexString"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "MY_TYPE", out.Name) assert.Equal(t, `\d+`, out.RegexString) } diff --git a/services/glue/handler_data_quality_rulesets_test.go b/services/glue/handler_data_quality_rulesets_test.go index ab49f9baa6..8a86a34dc9 100644 --- a/services/glue/handler_data_quality_rulesets_test.go +++ b/services/glue/handler_data_quality_rulesets_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -56,7 +57,7 @@ func TestCancelDQRecommendationRun_NotFound(t *testing.T) { var out struct { RunID string `json:"RunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.RunID, "expected RunId in response") runID = out.RunID } @@ -177,13 +178,13 @@ func TestDataQuality_RuleRecommendationRun(t *testing.T) { startRec := doGlueRequest(t, h, "StartDataQualityRuleRecommendationRun", map[string]any{}) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"].(string) getRecommendRec := doGlueRequest(t, h, "GetDataQualityRuleRecommendationRun", map[string]any{"RunId": runID}) require.Equal(t, http.StatusOK, getRecommendRec.Code) var getRecommendOut map[string]any - require.NoError(t, json.Unmarshal(getRecommendRec.Body.Bytes(), &getRecommendOut)) + require.NoError(t, json.Unmarshal(getRecommendRec.Body.Bytes(), &getRecommendOut, jsonx.V1())) assert.NotEmpty(t, getRecommendOut["RunId"]) listRecommendRec := doGlueRequest(t, h, "ListDataQualityRuleRecommendationRuns", map[string]any{}) @@ -243,7 +244,7 @@ func TestDataQualityRulesets(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } }) @@ -293,7 +294,7 @@ func TestDataQualityRulesetTagging(t *testing.T) { if tt.check != nil { var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } }) @@ -380,7 +381,7 @@ func TestHandlerDataQuality_GetDataQualityRuleset(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "my-ruleset", out["Name"]) } }) @@ -496,7 +497,7 @@ func TestHandlerDataQuality_ListDataQualityRulesets(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) rulesets, _ := out["Rulesets"].([]any) assert.Len(t, rulesets, tt.wantLen) }) @@ -540,7 +541,7 @@ func TestHandlerDataQuality_StartDataQualityRulesetEvaluationRun(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["RunId"]) } }) @@ -585,7 +586,7 @@ func TestHandlerDataQuality_GetDataQualityRulesetEvaluationRun(t *testing.T) { ) require.Equal(t, http.StatusOK, startRec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &out, jsonx.V1())) runID = out["RunId"] } @@ -594,7 +595,7 @@ func TestHandlerDataQuality_GetDataQualityRulesetEvaluationRun(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["RunId"]) } }) @@ -618,7 +619,7 @@ func TestHandlerDataQuality_BatchGetDataQualityRulesetEvaluationRun(t *testing.T ) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"] require.NotEmpty(t, runID) @@ -631,7 +632,7 @@ func TestHandlerDataQuality_BatchGetDataQualityRulesetEvaluationRun(t *testing.T Runs []map[string]any `json:"Runs"` RunsNotFound []string `json:"RunsNotFound"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Runs, 1) assert.Equal(t, runID, out.Runs[0]["RunId"]) assert.Equal(t, []string{"no-such-run"}, out.RunsNotFound) @@ -671,7 +672,7 @@ func TestHandlerDataQuality_CancelDataQualityRulesetEvaluationRun(t *testing.T) ) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"] if tt.preCancel { diff --git a/services/glue/handler_data_quality_stats_test.go b/services/glue/handler_data_quality_stats_test.go index 8889a62100..a757772d4f 100644 --- a/services/glue/handler_data_quality_stats_test.go +++ b/services/glue/handler_data_quality_stats_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -23,7 +24,7 @@ func TestDataQualityRecommendationRun(t *testing.T) { var startOut struct { RunID string `json:"RunId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) require.NotEmpty(t, startOut.RunID) // List @@ -104,13 +105,13 @@ func TestDataQuality_EvaluationRun_GetAndCancel(t *testing.T) { ) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"].(string) getEvalRec := doGlueRequest(t, h, "GetDataQualityRulesetEvaluationRun", map[string]any{"RunId": runID}) require.Equal(t, http.StatusOK, getEvalRec.Code) var getEvalOut map[string]any - require.NoError(t, json.Unmarshal(getEvalRec.Body.Bytes(), &getEvalOut)) + require.NoError(t, json.Unmarshal(getEvalRec.Body.Bytes(), &getEvalOut, jsonx.V1())) assert.Equal(t, "RUNNING", getEvalOut["Status"]) cancelEvalRec := doGlueRequest(t, h, "CancelDataQualityRulesetEvaluationRun", map[string]any{"RunId": runID}) @@ -133,7 +134,7 @@ func TestGetDataQualityModel_RequiresProfileID(t *testing.T) { var out struct { Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "SUCCEEDED", out.Status) } @@ -155,7 +156,7 @@ func TestGetDataQualityModelResult_RequiresBothIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out, "Model") assert.NotContains(t, out, "Status", "GetDataQualityModelResult has no Status field in the real API") } @@ -174,7 +175,7 @@ func TestDataQualityStatisticAnnotations_RealStore(t *testing.T) { var empty struct { Annotations []map[string]any `json:"Annotations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty, jsonx.V1())) assert.Empty(t, empty.Annotations) rec = doGlueRequest(t, h, "PutDataQualityProfileAnnotation", map[string]any{}) @@ -196,7 +197,7 @@ func TestDataQualityStatisticAnnotations_RealStore(t *testing.T) { var batchOut struct { FailedInclusionAnnotations []map[string]any `json:"FailedInclusionAnnotations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchOut, jsonx.V1())) require.Len(t, batchOut.FailedInclusionAnnotations, 1) assert.Equal(t, "stat-2", batchOut.FailedInclusionAnnotations[0]["StatisticId"]) @@ -206,14 +207,14 @@ func TestDataQualityStatisticAnnotations_RealStore(t *testing.T) { var out struct { Annotations []map[string]any `json:"Annotations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Annotations, 2, "the profile-wide and the per-statistic annotation") rec = doGlueRequest(t, h, "ListDataQualityStatisticAnnotations", map[string]any{ "ProfileId": "profile-1", "StatisticId": "stat-1", }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Annotations, 1) assert.Equal(t, "stat-1", out.Annotations[0]["StatisticId"]) } @@ -232,7 +233,7 @@ func TestDataQualityEvaluationRuns_Stateful(t *testing.T) { var out struct { Runs []any `json:"Runs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Runs) }) @@ -260,7 +261,7 @@ func TestDataQualityEvaluationRuns_Stateful(t *testing.T) { var out struct { Runs []any `json:"Runs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Runs, 1) }) } @@ -279,7 +280,7 @@ func TestListDataQualityResults_Stateful(t *testing.T) { var out struct { Results []any `json:"Results"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Results) }) @@ -298,7 +299,7 @@ func TestListDataQualityResults_Stateful(t *testing.T) { var out struct { Results []any `json:"Results"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Results, 2) }) } @@ -351,7 +352,7 @@ func TestDataQualityRecommendationRun_Stateful(t *testing.T) { var startOut struct { RunID string `json:"RunId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut.RunID) getRec := doGlueRequest(t, h, "GetDataQualityRuleRecommendationRun", map[string]any{ @@ -363,7 +364,7 @@ func TestDataQualityRecommendationRun_Stateful(t *testing.T) { RunID string `json:"RunId"` Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, startOut.RunID, getOut.RunID) assert.NotEmpty(t, getOut.Status) } @@ -433,7 +434,7 @@ func TestGetDataQualityResult_Found(t *testing.T) { ResultID string `json:"ResultId"` Score float64 `json:"Score"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "result-abc", out.ResultID) assert.InDelta(t, 0.95, out.Score, 0.001) } diff --git a/services/glue/handler_databases_test.go b/services/glue/handler_databases_test.go index 6e4d77847b..41738babb1 100644 --- a/services/glue/handler_databases_test.go +++ b/services/glue/handler_databases_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -268,7 +269,7 @@ func TestTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) resource := out if tt.resourceKey != "" { diff --git a/services/glue/handler_dev_endpoints_test.go b/services/glue/handler_dev_endpoints_test.go index 651a1aeed0..606914b319 100644 --- a/services/glue/handler_dev_endpoints_test.go +++ b/services/glue/handler_dev_endpoints_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -140,7 +141,7 @@ func TestDevEndpoint_UpdateAndGet(t *testing.T) { getRec := doGlueRequest(t, h, "GetDevEndpoint", map[string]any{"EndpointName": "dep-upd"}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) dep := out["DevEndpoint"].(map[string]any) args, ok := dep["Arguments"].(map[string]any) require.True(t, ok) @@ -197,7 +198,7 @@ func TestUpdateDevEndpoint_PublicKeys(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) dep := out["DevEndpoint"].(map[string]any) keysRaw, ok := dep["PublicKeys"].([]any) require.True(t, ok) @@ -237,6 +238,6 @@ func TestDevEndpoint_ResourceNumberLimitExceeded(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ResourceNumberLimitExceededException", out["__type"]) } diff --git a/services/glue/handler_entities_test.go b/services/glue/handler_entities_test.go index bd9197dee3..344cad5ac0 100644 --- a/services/glue/handler_entities_test.go +++ b/services/glue/handler_entities_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -47,7 +48,7 @@ func TestHandler_DescribeEntity_Fields(t *testing.T) { FieldType string `json:"FieldType"` } `json:"Fields"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Fields) names := make([]string, 0, len(out.Fields)) @@ -129,7 +130,7 @@ func TestHandler_ListEntities(t *testing.T) { EntityName string `json:"EntityName"` } `json:"Entities"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.Entities), tc.wantMin) }) } @@ -154,7 +155,7 @@ func TestHandler_GetEntityRecords_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Records, 2) require.NotEmpty(t, page1.NextToken) @@ -169,7 +170,7 @@ func TestHandler_GetEntityRecords_Pagination(t *testing.T) { var page2 struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.NotEmpty(t, page2.Records) assert.NotEqual(t, page1.Records[0]["Id"], page2.Records[0]["Id"]) } @@ -201,7 +202,7 @@ func TestHandler_ConnectionTypeLifecycle(t *testing.T) { var regOut struct { ConnectionTypeArn string `json:"ConnectionTypeArn"` } - require.NoError(t, json.Unmarshal(reg.Body.Bytes(), ®Out)) + require.NoError(t, json.Unmarshal(reg.Body.Bytes(), ®Out, jsonx.V1())) assert.NotEmpty(t, regOut.ConnectionTypeArn, "RegisterConnectionType must return ConnectionTypeArn") // It appears in ListConnectionTypes alongside built-ins. @@ -213,7 +214,7 @@ func TestHandler_ConnectionTypeLifecycle(t *testing.T) { ConnectionType string `json:"ConnectionType"` } `json:"ConnectionTypes"` } - require.NoError(t, json.Unmarshal(list.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(list.Body.Bytes(), &listOut, jsonx.V1())) names := make([]string, 0, len(listOut.ConnectionTypes)) for _, ct := range listOut.ConnectionTypes { @@ -231,7 +232,7 @@ func TestHandler_ConnectionTypeLifecycle(t *testing.T) { var descOut struct { RestConfiguration map[string]any `json:"RestConfiguration"` } - require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(desc.Body.Bytes(), &descOut, jsonx.V1())) require.NotNil(t, descOut.RestConfiguration) validationCfg, _ := descOut.RestConfiguration["ValidationEndpointConfiguration"].(map[string]any) require.NotNil(t, validationCfg) @@ -386,7 +387,7 @@ func TestHandler_ListEntities_NativeCatalog(t *testing.T) { var out struct { Entities []glue.EntityDescriptor `json:"Entities"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Entities) }) @@ -402,7 +403,7 @@ func TestHandler_ListEntities_NativeCatalog(t *testing.T) { var out struct { Entities []glue.EntityDescriptor `json:"Entities"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Entities, 1) assert.Equal(t, "salesdb", out.Entities[0].EntityName) assert.Equal(t, "DATABASES", out.Entities[0].Category) @@ -421,7 +422,7 @@ func TestHandler_ListEntities_NativeCatalog(t *testing.T) { var out struct { Entities []glue.EntityDescriptor `json:"Entities"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Entities, 1) assert.Equal(t, "salesdb.orders", out.Entities[0].EntityName) assert.Equal(t, "TABLES", out.Entities[0].Category) @@ -451,7 +452,7 @@ func TestHandler_ListEntities_NativeCatalog(t *testing.T) { var out struct { Entities []glue.EntityDescriptor `json:"Entities"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) names := make([]string, 0, len(out.Entities)) for _, e := range out.Entities { @@ -485,7 +486,7 @@ func TestHandler_GetEntityRecords_NativeCatalog(t *testing.T) { var out struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Records, 3) for _, r := range out.Records { @@ -541,7 +542,7 @@ func TestHandler_GetEntityRecords_NativeCatalog(t *testing.T) { NextToken string `json:"NextToken"` Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Records, 2) require.NotEmpty(t, page1.NextToken) @@ -555,7 +556,7 @@ func TestHandler_GetEntityRecords_NativeCatalog(t *testing.T) { var page2 struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) require.NotEmpty(t, page2.Records) assert.NotEqual(t, page1.Records[0]["id"], page2.Records[0]["id"]) }) diff --git a/services/glue/handler_etl_test.go b/services/glue/handler_etl_test.go index 3c3d28944a..5cb7cf391f 100644 --- a/services/glue/handler_etl_test.go +++ b/services/glue/handler_etl_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateScript verifies that CreateScript generates non-empty ETL code from DagNodes/DagEdges. @@ -97,7 +99,7 @@ func TestCreateScript(t *testing.T) { PythonScript string `json:"PythonScript"` ScalaCode string `json:"ScalaCode"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.wantPythonNonEmpty { assert.NotEmpty(t, out.PythonScript) @@ -170,7 +172,7 @@ func TestGetDataflowGraph(t *testing.T) { DagNodes []any `json:"DagNodes"` DagEdges []any `json:"DagEdges"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.DagNodes, tc.wantNodeCount) assert.Len(t, out.DagEdges, tc.wantEdgeCount) }) @@ -213,7 +215,7 @@ func TestGetDataflowGraph_RoundTrip(t *testing.T) { var scriptOut struct { PythonScript string `json:"PythonScript"` } - require.NoError(t, json.Unmarshal(scriptRec.Body.Bytes(), &scriptOut)) + require.NoError(t, json.Unmarshal(scriptRec.Body.Bytes(), &scriptOut, jsonx.V1())) require.NotEmpty(t, scriptOut.PythonScript) // Parse the script back to a DAG. @@ -226,7 +228,7 @@ func TestGetDataflowGraph_RoundTrip(t *testing.T) { DagNodes []any `json:"DagNodes"` DagEdges []any `json:"DagEdges"` } - require.NoError(t, json.Unmarshal(graphRec.Body.Bytes(), &graphOut)) + require.NoError(t, json.Unmarshal(graphRec.Body.Bytes(), &graphOut, jsonx.V1())) // Should have recovered 2 nodes (DataSource + DataSink) and 1 edge. assert.Len(t, graphOut.DagNodes, 2) @@ -312,7 +314,7 @@ func TestGetMapping_DerivesFromRealSchema(t *testing.T) { var out struct { Mapping []map[string]any `json:"Mapping"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Mapping, tc.wantLen) assert.Equal(t, "id", out.Mapping[0]["SourcePath"]) }) @@ -365,7 +367,7 @@ func TestGetPlan_Languages(t *testing.T) { PythonScript string `json:"PythonScript"` ScalaCode string `json:"ScalaCode"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.wantPython { assert.NotEmpty(t, out.PythonScript) @@ -430,7 +432,7 @@ func TestGetPlan(t *testing.T) { PythonScript string `json:"PythonScript"` ScalaCode string `json:"ScalaCode"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.wantPython { assert.NotEmpty(t, out.PythonScript) diff --git a/services/glue/handler_identity_center_test.go b/services/glue/handler_identity_center_test.go index 920b6d3da9..1cdced9c1b 100644 --- a/services/glue/handler_identity_center_test.go +++ b/services/glue/handler_identity_center_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGlueIdentityCenter(t *testing.T) { @@ -48,7 +50,7 @@ func TestGlueIdentityCenter_Stateful(t *testing.T) { var getOut struct { InstanceArn string `json:"InstanceArn"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "arn:aws:sso:::instance/ssoins-1234", getOut.InstanceArn) updateRec := doGlueRequest(t, h, "UpdateGlueIdentityCenterConfiguration", map[string]any{ diff --git a/services/glue/handler_integrations_test.go b/services/glue/handler_integrations_test.go index 6d7a8bc653..c1a13bd091 100644 --- a/services/glue/handler_integrations_test.go +++ b/services/glue/handler_integrations_test.go @@ -1,13 +1,15 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDescribeInboundIntegrations verifies integrations are returned from the backend. @@ -63,7 +65,7 @@ func TestDescribeInboundIntegrations(t *testing.T) { var out struct { InboundIntegrations []any `json:"InboundIntegrations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.InboundIntegrations), tc.wantMinCount) }) } @@ -192,7 +194,7 @@ func TestIntegration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.Equal(t, "my-integration", createOut["IntegrationName"]) assert.Equal(t, "arn:aws:s3:::my-source-bucket", createOut["SourceArn"]) assert.Equal(t, "arn:aws:redshift:us-east-1:123456789012:cluster/my-target", createOut["TargetArn"]) @@ -213,7 +215,7 @@ func TestIntegration(t *testing.T) { var inboundOut struct { InboundIntegrations []any `json:"InboundIntegrations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inboundOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inboundOut, jsonx.V1())) assert.Len(t, inboundOut.InboundIntegrations, 1, "IntegrationArn filter should match the created integration") // ModifyIntegration, addressed by ARN like a real client would (see @@ -222,7 +224,7 @@ func TestIntegration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var modifyOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &modifyOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &modifyOut, jsonx.V1())) assert.Equal(t, "my-integration", modifyOut["IntegrationName"]) assert.Equal(t, integrationARN, modifyOut["IntegrationArn"]) assert.Equal(t, "arn:aws:s3:::my-source-bucket", modifyOut["SourceArn"]) @@ -235,7 +237,7 @@ func TestIntegration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteOut, jsonx.V1())) assert.Equal(t, "my-integration", deleteOut["IntegrationName"]) assert.Equal(t, integrationARN, deleteOut["IntegrationArn"]) assert.Equal(t, "arn:aws:s3:::my-source-bucket", deleteOut["SourceArn"]) @@ -261,7 +263,7 @@ func TestIntegration_ReachesActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.Equal(t, "CREATING", createOut["Status"]) require.Eventually(t, func() bool { @@ -277,7 +279,7 @@ func TestIntegration_ReachesActive(t *testing.T) { Status string `json:"Status"` } `json:"Integrations"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) return len(out.Integrations) == 1 && out.Integrations[0].Status == "ACTIVE" }, 2*time.Second, 10*time.Millisecond) @@ -297,7 +299,7 @@ func TestListIntegrationResourceProperties_ReturnsStoredEntries(t *testing.T) { var empty struct { IntegrationResourcePropertyList []map[string]any `json:"IntegrationResourcePropertyList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty, jsonx.V1())) assert.Empty(t, empty.IntegrationResourcePropertyList) doGlueRequest(t, h, "CreateIntegrationResourceProperty", map[string]any{ @@ -311,7 +313,7 @@ func TestListIntegrationResourceProperties_ReturnsStoredEntries(t *testing.T) { var out struct { IntegrationResourcePropertyList []map[string]any `json:"IntegrationResourcePropertyList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.IntegrationResourcePropertyList, 1) assert.Equal(t, "arn:aws:glue:us-east-1:123456789012:connection/conn1", out.IntegrationResourcePropertyList[0]["ResourceArn"]) @@ -348,7 +350,7 @@ func TestUpdateIntegrationResourceProperty_RequiresExistingEntry(t *testing.T) { var out struct { SourceProcessingProperties map[string]any `json:"SourceProcessingProperties"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "updated", out.SourceProcessingProperties["key"]) } @@ -387,7 +389,7 @@ func TestUpdateIntegrationTableProperties_RequiresExistingEntry(t *testing.T) { var out struct { SourceTableConfig map[string]string `json:"SourceTableConfig"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "updated", out.SourceTableConfig["key"]) } diff --git a/services/glue/handler_jobs.go b/services/glue/handler_jobs.go index e5a8fb4203..4a69daf35e 100644 --- a/services/glue/handler_jobs.go +++ b/services/glue/handler_jobs.go @@ -2,7 +2,7 @@ package glue import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" gluetypes "github.com/aws/aws-sdk-go-v2/service/glue/types" @@ -35,13 +35,13 @@ type createJobInput struct { // jobExtraMembers are the CreateJob/JobUpdate members shared by both inputs. type jobExtraMembers struct { - NonOverridableArguments map[string]string `json:"NonOverridableArguments,omitempty"` - CodeGenConfigurationNodes map[string]json.RawMessage `json:"CodeGenConfigurationNodes,omitempty"` - JobRunQueuingEnabled *bool `json:"JobRunQueuingEnabled,omitempty"` - LogURI string `json:"LogUri,omitempty"` - SecurityConfiguration string `json:"SecurityConfiguration,omitempty"` - ExecutionClass string `json:"ExecutionClass,omitempty"` - MaintenanceWindow string `json:"MaintenanceWindow,omitempty"` + NonOverridableArguments map[string]string `json:"NonOverridableArguments,omitempty"` + CodeGenConfigurationNodes map[string]jsontext.Value `json:"CodeGenConfigurationNodes,omitempty"` + JobRunQueuingEnabled *bool `json:"JobRunQueuingEnabled,omitempty"` + LogURI string `json:"LogUri,omitempty"` + SecurityConfiguration string `json:"SecurityConfiguration,omitempty"` + ExecutionClass string `json:"ExecutionClass,omitempty"` + MaintenanceWindow string `json:"MaintenanceWindow,omitempty"` } func (m jobExtraMembers) apply(j *Job) { diff --git a/services/glue/handler_jobs_test.go b/services/glue/handler_jobs_test.go index 896478fe47..887531f3e6 100644 --- a/services/glue/handler_jobs_test.go +++ b/services/glue/handler_jobs_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -49,7 +50,7 @@ func Test_StartTrigger_OnDemand_StaysCreatedAndFiresJobRun(t *testing.T) { JobRunState string `json:"JobRunState"` } `json:"JobRuns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &runsOut, jsonx.V1())) require.Len(t, runsOut.JobRuns, 1) assert.NotEmpty(t, runsOut.JobRuns[0].JobRunState) } @@ -158,7 +159,7 @@ func Test_Job_MaxCapacity(t *testing.T) { MaxCapacity float64 `json:"MaxCapacity"` } `json:"Job"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.InDelta(t, tc.body["MaxCapacity"], out.Job.MaxCapacity, 0.0001) }) } @@ -448,7 +449,7 @@ func TestJobLifecycle(t *testing.T) { if tt.check != nil && rec.Code == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.check(t, out) } }) @@ -534,7 +535,7 @@ func TestBatchStopJobRun(t *testing.T) { runRec := doGlueRequest(t, h, "StartJobRun", map[string]any{"JobName": "j"}) var runOut map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &runOut, jsonx.V1())) runID := runOut["JobRunId"].(string) doGlueRequest(t, h, "BatchStopJobRun", map[string]any{ @@ -547,7 +548,7 @@ func TestBatchStopJobRun(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) errs, _ := out["Errors"].([]any) assert.Len(t, errs, tt.wantErrors) }) @@ -606,7 +607,7 @@ func TestUpdateJobFromSourceControl_ValidatesJob(t *testing.T) { } `json:"SourceControlDetails"` } `json:"Job"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "GITHUB", out.Job.SourceControlDetails.Provider) assert.Equal(t, "my-repo", out.Job.SourceControlDetails.Repository) assert.Equal(t, "main", out.Job.SourceControlDetails.Branch) @@ -699,7 +700,7 @@ func TestHandlerJobRuns_StartJobRun(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["JobRunId"]) } }) @@ -749,7 +750,7 @@ func TestHandlerJobRuns_GetJobRun(t *testing.T) { rec := doGlueRequest(t, h, "StartJobRun", map[string]any{"JobName": "my-job"}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) runID = out["JobRunId"] } @@ -758,7 +759,7 @@ func TestHandlerJobRuns_GetJobRun(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["JobRun"]) } }) @@ -805,7 +806,7 @@ func TestHandlerJobRuns_GetJobRuns(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) runs, ok := out["JobRuns"].([]any) require.True(t, ok) assert.Len(t, runs, tt.numRuns) @@ -855,7 +856,7 @@ func TestHandlerJobRuns_BatchStopJobRun(t *testing.T) { startRec := doGlueRequest(t, h, "StartJobRun", map[string]any{"JobName": "my-job"}) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) realRunID := startOut["JobRunId"] stopIDs := []string{realRunID} @@ -867,7 +868,7 @@ func TestHandlerJobRuns_BatchStopJobRun(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) errs, _ := out["Errors"].([]any) assert.Len(t, errs, tt.wantErrors) }) @@ -910,7 +911,7 @@ func TestHandlerJobRuns_GetJobBookmark(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["JobBookmarkEntry"]) }) } diff --git a/services/glue/handler_materialized_views_test.go b/services/glue/handler_materialized_views_test.go index 19c98be9bf..1df4994549 100644 --- a/services/glue/handler_materialized_views_test.go +++ b/services/glue/handler_materialized_views_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestStopMaterializedViewRefreshTaskRun_NotFound verifies that @@ -53,7 +55,7 @@ func TestStopMaterializedViewRefreshTaskRun_NotFound(t *testing.T) { var out struct { RunID string `json:"MaterializedViewRefreshTaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.RunID, "expected MaterializedViewRefreshTaskRunId in response") } @@ -99,7 +101,7 @@ func TestMaterializedViewRefreshTaskRun(t *testing.T) { }) require.Equal(t, http.StatusOK, startRefreshRec1.Code) var startRefreshOut1 map[string]any - require.NoError(t, json.Unmarshal(startRefreshRec1.Body.Bytes(), &startRefreshOut1)) + require.NoError(t, json.Unmarshal(startRefreshRec1.Body.Bytes(), &startRefreshOut1, jsonx.V1())) assert.NotEmpty(t, startRefreshOut1["MaterializedViewRefreshTaskRunId"]) startRec := doGlueRequest(t, h, "StartMaterializedViewRefreshTaskRun", map[string]any{ @@ -108,7 +110,7 @@ func TestMaterializedViewRefreshTaskRun(t *testing.T) { }) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["MaterializedViewRefreshTaskRunId"].(string) getRefreshRec := doGlueRequest(t, h, "GetMaterializedViewRefreshTaskRun", map[string]any{ @@ -145,7 +147,7 @@ func TestMaterializedViewRefresh_Stateful(t *testing.T) { var startOut struct { RunID string `json:"MaterializedViewRefreshTaskRunId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut.RunID) getRec := doGlueRequest(t, h, "GetMaterializedViewRefreshTaskRun", map[string]any{ @@ -159,7 +161,7 @@ func TestMaterializedViewRefresh_Stateful(t *testing.T) { Status string `json:"Status"` } `json:"MaterializedViewRefreshTaskRun"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, startOut.RunID, getOut.Run.RunID) assert.Equal(t, "RUNNING", getOut.Run.Status) @@ -179,7 +181,7 @@ func TestMaterializedViewRefresh_Stateful(t *testing.T) { Status string `json:"Status"` } `json:"MaterializedViewRefreshTaskRun"` } - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2, jsonx.V1())) assert.Equal(t, "STOPPED", getOut2.Run.Status) }) diff --git a/services/glue/handler_ml_test.go b/services/glue/handler_ml_test.go index 9026881424..d83a300070 100644 --- a/services/glue/handler_ml_test.go +++ b/services/glue/handler_ml_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -84,7 +85,7 @@ func TestMLTransform_CRUD(t *testing.T) { createRec := doGlueRequest(t, h, "CreateMLTransform", createBody) if createRec.Code == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) transformID, _ = out["TransformId"].(string) } @@ -106,7 +107,7 @@ func TestMLTransform_Delete(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) transformID := out["TransformId"].(string) rec := doGlueRequest(t, h, "DeleteMLTransform", map[string]any{"TransformId": transformID}) @@ -232,7 +233,7 @@ func createTestMLTransform(t *testing.T, h *glue.Handler, name string) string { var out struct { TransformID string `json:"TransformId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.TransformID) return out.TransformID @@ -296,7 +297,7 @@ func TestMLLabelingSetGenerationTaskRun(t *testing.T) { var out struct { TaskRunID string `json:"TaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.TaskRunID) } @@ -337,7 +338,7 @@ func TestMLExportImportLabels(t *testing.T) { var out struct { TaskRunID string `json:"TaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.TaskRunID) }) } @@ -380,7 +381,7 @@ func TestGetMLTaskRun(t *testing.T) { var startOut struct { TaskRunID string `json:"TaskRunId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) getRec := doGlueRequest(t, h, "GetMLTaskRun", map[string]any{ "TransformId": transformID, @@ -393,7 +394,7 @@ func TestGetMLTaskRun(t *testing.T) { TaskRunID string `json:"TaskRunId"` Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, transformID, getOut.TransformID) assert.Equal(t, startOut.TaskRunID, getOut.TaskRunID) assert.Equal(t, "RUNNING", getOut.Status) @@ -413,7 +414,7 @@ func TestGetMLTaskRun(t *testing.T) { var getOut2 struct { Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getOut2, jsonx.V1())) assert.Equal(t, "STOPPED", getOut2.Status) }) } @@ -432,7 +433,7 @@ func TestGetMLTaskRuns(t *testing.T) { var out struct { TaskRuns []any `json:"TaskRuns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.TaskRuns) }) @@ -457,7 +458,7 @@ func TestGetMLTaskRuns(t *testing.T) { var out struct { TaskRuns []any `json:"TaskRuns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.TaskRuns, 3) }) } @@ -491,7 +492,7 @@ func TestGetMLTaskRuns_SDKPagination_TotalOrderNoTiesLost(t *testing.T) { var out struct { TaskRunID string `json:"TaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.TaskRunID) wantIDs[out.TaskRunID] = true } @@ -543,7 +544,7 @@ func TestGetMLTaskRuns_SDKFilter_ByStatus(t *testing.T) { var started struct { TaskRunID string `json:"TaskRunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) cancelRec := doGlueRequest(t, h, "CancelMLTaskRun", map[string]any{ "TransformId": transformID, @@ -625,7 +626,7 @@ func TestListMLTransforms_Stateful(t *testing.T) { var out struct { TransformIDs []string `json:"TransformIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.TransformIDs, tc.wantLen) }) } diff --git a/services/glue/handler_pagination_test.go b/services/glue/handler_pagination_test.go index e086978989..fe552b090c 100644 --- a/services/glue/handler_pagination_test.go +++ b/services/glue/handler_pagination_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPagination_GetDatabases(t *testing.T) { @@ -90,7 +92,7 @@ func TestPagination_GetDatabases(t *testing.T) { NextToken string `json:"NextToken"` DatabaseList []any `json:"DatabaseList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.DatabaseList, tc.wantCount) if tc.wantHasNext { assert.NotEmpty(t, out.NextToken) @@ -188,7 +190,7 @@ func TestPagination_GetTables(t *testing.T) { NextToken string `json:"NextToken"` TableList []any `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.TableList, tc.wantCount) if tc.wantHasNext { assert.NotEmpty(t, out.NextToken) @@ -281,7 +283,7 @@ func TestPagination_GetPartitions(t *testing.T) { NextToken string `json:"NextToken"` Partitions []any `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Partitions, tc.wantCount) if tc.wantHasNext { assert.NotEmpty(t, out.NextToken) diff --git a/services/glue/handler_partition_indexes_test.go b/services/glue/handler_partition_indexes_test.go index 56b271151a..e1b020a005 100644 --- a/services/glue/handler_partition_indexes_test.go +++ b/services/glue/handler_partition_indexes_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -120,7 +121,7 @@ func TestPartitionIndexLifecycle(t *testing.T) { } `json:"Keys"` } `json:"PartitionIndexDescriptorList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Indexes, 2) assert.Equal(t, "year_idx", out.Indexes[0].IndexName) assert.Equal(t, "ACTIVE", out.Indexes[0].IndexStatus) diff --git a/services/glue/handler_partitions_test.go b/services/glue/handler_partitions_test.go index 16c0881527..5f1057c7ed 100644 --- a/services/glue/handler_partitions_test.go +++ b/services/glue/handler_partitions_test.go @@ -2,7 +2,7 @@ package glue_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -112,7 +113,7 @@ func Test_BatchGetPartition_ReturnsRealPartitions(t *testing.T) { Values []string `json:"Values"` } `json:"UnprocessedKeys"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Partitions, 1) assert.Equal(t, []string{"2024-01-01"}, out.Partitions[0].Values) @@ -303,7 +304,7 @@ func TestAuditGlue_GetPartitions_Expression(t *testing.T) { Values []string `json:"Values"` } `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) got := make([][]string, len(out.Partitions)) for i, p := range out.Partitions { @@ -390,7 +391,7 @@ func TestHTTPBatchCreatePartition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["Partitions"]) } @@ -398,7 +399,7 @@ func TestHTTPBatchCreatePartition(t *testing.T) { func postGlue(t *testing.T, h *glue.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bs, err := json.Marshal(body) + bs, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bs)) @@ -467,7 +468,7 @@ func TestGetPartition_Found(t *testing.T) { Values []string `json:"Values"` } `json:"Partition"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, []string{"2024", "01"}, out.Partition.Values) } @@ -503,7 +504,7 @@ func TestGetPartitions_List(t *testing.T) { Values []string `json:"Values"` } `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Partitions, 2) } @@ -553,7 +554,7 @@ func TestUpdatePartition_Updates(t *testing.T) { } `json:"StorageDescriptor"` } `json:"Partition"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "s3://updated-bucket/tbl3", out.Partition.StorageDescriptor.Location) } @@ -608,7 +609,7 @@ func TestBatchUpdatePartition_Updates(t *testing.T) { var out struct { Errors []any `json:"Errors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Errors) } diff --git a/services/glue/handler_resource_limits_test.go b/services/glue/handler_resource_limits_test.go index 5fe9433883..5ceba77b3b 100644 --- a/services/glue/handler_resource_limits_test.go +++ b/services/glue/handler_resource_limits_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -26,7 +27,7 @@ func assertResourceLimitExceeded(t *testing.T, h *glue.Handler, action string, b assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ResourceNumberLimitExceededException", out["__type"]) } @@ -327,7 +328,7 @@ func TestResourceNumberLimitExceeded_PartitionsPerTable(t *testing.T) { } `json:"Errors"` Partitions []map[string]any `json:"Partitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Partitions, testResourceCap) require.Len(t, out.Errors, 1) diff --git a/services/glue/handler_resource_policies_test.go b/services/glue/handler_resource_policies_test.go index bc41a2ee10..68fcdf8395 100644 --- a/services/glue/handler_resource_policies_test.go +++ b/services/glue/handler_resource_policies_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func Test_PutResourcePolicy_ExistsConditions(t *testing.T) { @@ -58,7 +60,7 @@ func Test_PutResourcePolicy_HashCondition(t *testing.T) { var out struct { PolicyHash string `json:"PolicyHash"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.PolicyHash) // Stale hash -> ConditionCheckFailureException, and the policy is untouched. @@ -90,7 +92,7 @@ func Test_DeleteResourcePolicy_HashCondition(t *testing.T) { var out struct { PolicyHash string `json:"PolicyHash"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Wrong hash -> rejected, policy stays. rec = doGlueRequest(t, h, "DeleteResourcePolicy", map[string]any{ @@ -126,7 +128,7 @@ func TestGetResourcePolicies_ReturnsStoredPolicies(t *testing.T) { var empty struct { GetResourcePoliciesResponseList []map[string]any `json:"GetResourcePoliciesResponseList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty, jsonx.V1())) assert.Empty(t, empty.GetResourcePoliciesResponseList) doGlueRequest(t, h, "PutResourcePolicy", map[string]any{ @@ -143,7 +145,7 @@ func TestGetResourcePolicies_ReturnsStoredPolicies(t *testing.T) { var out struct { GetResourcePoliciesResponseList []map[string]any `json:"GetResourcePoliciesResponseList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.GetResourcePoliciesResponseList, 2) for _, p := range out.GetResourcePoliciesResponseList { diff --git a/services/glue/handler_schemas.go b/services/glue/handler_schemas.go index 3cb0620f32..34d7f691df 100644 --- a/services/glue/handler_schemas.go +++ b/services/glue/handler_schemas.go @@ -2,7 +2,7 @@ package glue import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -11,6 +11,8 @@ import ( "time" gluetypes "github.com/aws/aws-sdk-go-v2/service/glue/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validateSchemaDefinition checks a schema definition string against its DataFormat. @@ -611,7 +613,7 @@ type schemaVersionNumberInput struct { func (s *schemaVersionNumberInput) UnmarshalJSON(data []byte) error { var n int64 - if err := json.Unmarshal(data, &n); err == nil { + if err := json.Unmarshal(data, &n, jsonx.V1()); err == nil { s.Number = n return nil @@ -621,7 +623,7 @@ func (s *schemaVersionNumberInput) UnmarshalJSON(data []byte) error { VersionNumber int64 `json:"VersionNumber"` } - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &v, jsonx.V1()); err != nil { return err } diff --git a/services/glue/handler_schemas_test.go b/services/glue/handler_schemas_test.go index f11ff40776..c97b28113f 100644 --- a/services/glue/handler_schemas_test.go +++ b/services/glue/handler_schemas_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -123,7 +124,7 @@ func TestCheckSchemaVersionValidity(t *testing.T) { Error string `json:"Error"` Valid bool `json:"Valid"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tc.wantValid, out.Valid) if tc.wantError != "" { @@ -187,7 +188,7 @@ func TestDeleteSchemaVersions(t *testing.T) { VersionNumber int64 `json:"VersionNumber"` } `json:"SchemaVersionErrors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SchemaVersionErrors, tc.wantErrCount) }) } @@ -219,7 +220,7 @@ func TestDeleteSchemaVersions_Stateful(t *testing.T) { VersionNumber int64 `json:"VersionNumber"` } `json:"SchemaVersionErrors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Version 9 doesn't exist → 1 error. require.Len(t, out.SchemaVersionErrors, 1) @@ -413,7 +414,7 @@ func TestSchemaRegistry_SchemaVersion_CRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, registerRec.Code) var registerOut map[string]any - require.NoError(t, json.Unmarshal(registerRec.Body.Bytes(), ®isterOut)) + require.NoError(t, json.Unmarshal(registerRec.Body.Bytes(), ®isterOut, jsonx.V1())) assert.NotEmpty(t, registerOut["SchemaVersionId"]) getSchemaVersionRec := doGlueRequest(t, h, "GetSchemaVersion", map[string]any{ @@ -422,7 +423,7 @@ func TestSchemaRegistry_SchemaVersion_CRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, getSchemaVersionRec.Code) var getSchemaVersionOut map[string]any - require.NoError(t, json.Unmarshal(getSchemaVersionRec.Body.Bytes(), &getSchemaVersionOut)) + require.NoError(t, json.Unmarshal(getSchemaVersionRec.Body.Bytes(), &getSchemaVersionOut, jsonx.V1())) assert.EqualValues(t, 1, getSchemaVersionOut["VersionNumber"]) listSchemaVersionsRec := doGlueRequest(t, h, "ListSchemaVersions", map[string]any{ @@ -437,7 +438,7 @@ func TestSchemaRegistry_SchemaVersion_CRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, checkValidityRec.Code) var checkValidityOut map[string]any - require.NoError(t, json.Unmarshal(checkValidityRec.Body.Bytes(), &checkValidityOut)) + require.NoError(t, json.Unmarshal(checkValidityRec.Body.Bytes(), &checkValidityOut, jsonx.V1())) assert.True(t, checkValidityOut["Valid"].(bool)) } @@ -483,7 +484,7 @@ func TestCreateSchema_WithDefinition_PopulatesVersionFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.InDelta(t, tt.wantLatest, out["LatestSchemaVersion"], 0) assert.InDelta(t, tt.wantNext, out["NextSchemaVersion"], 0) @@ -554,7 +555,7 @@ func setupTestSchema(t *testing.T, h *glue.Handler) string { var sv1Out struct { SchemaVersionID string `json:"SchemaVersionId"` } - require.NoError(t, json.Unmarshal(reg1Rec.Body.Bytes(), &sv1Out)) + require.NoError(t, json.Unmarshal(reg1Rec.Body.Bytes(), &sv1Out, jsonx.V1())) reg2Rec := doGlueRequest(t, h, "RegisterSchemaVersion", map[string]any{ "SchemaId": map[string]any{"RegistryName": "reg1", "SchemaName": "schema1"}, @@ -565,7 +566,7 @@ func setupTestSchema(t *testing.T, h *glue.Handler) string { var sv2Out struct { SchemaVersionID string `json:"SchemaVersionId"` } - require.NoError(t, json.Unmarshal(reg2Rec.Body.Bytes(), &sv2Out)) + require.NoError(t, json.Unmarshal(reg2Rec.Body.Bytes(), &sv2Out, jsonx.V1())) return sv1Out.SchemaVersionID } @@ -632,7 +633,7 @@ func TestSchemaVersionMetadata_Lifecycle(t *testing.T) { } `json:"MetadataInfoMap"` SchemaVersionID string `json:"SchemaVersionId"` } - require.NoError(t, json.Unmarshal(queryRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(queryRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, v1ID, out.SchemaVersionID) got := make(map[string]string, len(out.MetadataInfo)) @@ -659,7 +660,7 @@ func TestSchemaVersionMetadata_EmptyQuery(t *testing.T) { var out struct { MetadataInfo map[string]any `json:"MetadataInfoMap"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.MetadataInfo) } @@ -714,7 +715,7 @@ func TestGetSchemaByDefinition_Stateful(t *testing.T) { SchemaVersionID string `json:"SchemaVersionId"` Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.SchemaVersionID) assert.Equal(t, "AVAILABLE", out.Status) } @@ -764,7 +765,7 @@ func TestGetSchemaVersionsDiff_Stateful(t *testing.T) { var out struct { Diff string `json:"Diff"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.wantDiff { assert.NotEmpty(t, out.Diff) @@ -884,7 +885,7 @@ func TestGetSchemaVersionsDiff(t *testing.T) { var out struct { Diff string `json:"Diff"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.wantDiff { assert.NotEmpty(t, out.Diff) @@ -917,7 +918,7 @@ func TestListRegistries_OmitsGetOnlyFields(t *testing.T) { var out struct { Registries []map[string]any `json:"Registries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Registries, 1) item := out.Registries[0] @@ -950,7 +951,7 @@ func TestListSchemas_OmitsGetOnlyFields(t *testing.T) { var out struct { Schemas []map[string]any `json:"Schemas"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Schemas, 1) item := out.Schemas[0] @@ -990,7 +991,7 @@ func TestListSchemaVersions_OmitsGetOnlyFields(t *testing.T) { var out struct { Schemas []map[string]any `json:"Schemas"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Schemas, 1) item := out.Schemas[0] diff --git a/services/glue/handler_smoke_test.go b/services/glue/handler_smoke_test.go index 63ef170d4d..8d4e1c97eb 100644 --- a/services/glue/handler_smoke_test.go +++ b/services/glue/handler_smoke_test.go @@ -2,7 +2,7 @@ package glue_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,13 +10,14 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) // doGlueOp is a helper to POST a JSON body to the Glue handler via echo. func doGlueOp(t *testing.T, h *glue.Handler, op string, body any) *httptest.ResponseRecorder { t.Helper() - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) if err != nil { t.Fatalf("marshal request: %v", err) } diff --git a/services/glue/handler_table_optimizers_test.go b/services/glue/handler_table_optimizers_test.go index ba5c7a0d33..247c94f20e 100644 --- a/services/glue/handler_table_optimizers_test.go +++ b/services/glue/handler_table_optimizers_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListTableOptimizerRuns_ReturnsRealHistory verifies ListTableOptimizerRuns @@ -50,7 +52,7 @@ func TestListTableOptimizerRuns_ReturnsRealHistory(t *testing.T) { var out struct { TableOptimizerRuns []map[string]any `json:"TableOptimizerRuns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.TableOptimizerRuns, 1) assert.Equal(t, "completed", out.TableOptimizerRuns[0]["eventType"]) } diff --git a/services/glue/handler_tables_test.go b/services/glue/handler_tables_test.go index 00d754b65e..b23a365ebc 100644 --- a/services/glue/handler_tables_test.go +++ b/services/glue/handler_tables_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -100,7 +101,7 @@ func Test_TableStorageDescriptor_RoundTrip(t *testing.T) { Retention int `json:"Retention"` } `json:"Table"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "hive", out.Table.Owner) assert.Equal(t, 7, out.Table.Retention) @@ -212,7 +213,7 @@ func TestAuditGlue_GetTables_Expression(t *testing.T) { Name string `json:"Name"` } `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) gotNames := make([]string, len(out.TableList)) for i, tbl := range out.TableList { @@ -253,7 +254,7 @@ func TestHTTPBatchDeleteTable(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) errs, ok := out["Errors"].([]any) require.True(t, ok) assert.Empty(t, errs) @@ -299,7 +300,7 @@ func TestCloneTableWithColumns(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) assert.NotNil(t, tbl) @@ -356,7 +357,7 @@ func TestSearchTables_EmptyFilter(t *testing.T) { Name string `json:"Name"` } `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.TableList, 2) } @@ -382,7 +383,7 @@ func TestSearchTables_WithFilter(t *testing.T) { Name string `json:"Name"` } `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.TableList, 1) assert.Equal(t, "orders", out.TableList[0].Name) } diff --git a/services/glue/handler_test.go b/services/glue/handler_test.go index f11b70cd22..f5db9da77e 100644 --- a/services/glue/handler_test.go +++ b/services/glue/handler_test.go @@ -2,7 +2,7 @@ package glue_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -43,7 +44,7 @@ func dqEvalRunRequiredFields(rulesetNames []string) map[string]any { func doGlueRequest(t *testing.T, h *glue.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -229,7 +230,7 @@ func TestGlue_DatabaseLifecycle(t *testing.T) { CatalogID string `json:"CatalogId"` } `json:"Database"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "mydb", getOut.Database.Name) assert.Equal(t, testAccountID, getOut.Database.CatalogID) @@ -242,7 +243,7 @@ func TestGlue_DatabaseLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"DatabaseList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.DatabaseList, 1) // Delete @@ -289,7 +290,7 @@ func TestGlue_TableLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"Table"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "mytable", getOut.Table.Name) // Update table @@ -311,7 +312,7 @@ func TestGlue_TableLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.TableList, 1) // Delete table @@ -364,7 +365,7 @@ func TestGlue_CrawlerLifecycle(t *testing.T) { State string `json:"State"` } `json:"Crawler"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "my-crawler", getOut.Crawler.Name) assert.Equal(t, "READY", getOut.Crawler.State) @@ -377,7 +378,7 @@ func TestGlue_CrawlerLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"Crawlers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Crawlers, 1) // UpdateCrawler @@ -420,7 +421,7 @@ func TestGlue_JobLifecycle(t *testing.T) { var createOut struct { Name string `json:"Name"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.Equal(t, "my-job", createOut.Name) // GetJob @@ -432,7 +433,7 @@ func TestGlue_JobLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"Job"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "my-job", getOut.Job.Name) // GetJobs @@ -444,7 +445,7 @@ func TestGlue_JobLifecycle(t *testing.T) { Name string `json:"Name"` } `json:"Jobs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Jobs, 1) // UpdateJob @@ -460,7 +461,7 @@ func TestGlue_JobLifecycle(t *testing.T) { var updateOut struct { JobName string `json:"JobName"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateOut, jsonx.V1())) assert.Equal(t, "my-job", updateOut.JobName) // DeleteJob @@ -502,7 +503,7 @@ func TestGlue_Tags(t *testing.T) { var tagsOut struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut, jsonx.V1())) assert.Equal(t, "value1", tagsOut.Tags["key1"]) // TagResource @@ -527,7 +528,7 @@ func TestGlue_Tags(t *testing.T) { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &finalTagsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &finalTagsOut, jsonx.V1())) assert.NotContains(t, finalTagsOut.Tags, "key1") assert.Equal(t, "value2", finalTagsOut.Tags["key2"]) } @@ -635,7 +636,7 @@ func TestGlue_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -716,7 +717,7 @@ func TestGlue_CrawlerTagsLifecycle(t *testing.T) { var tagsOut struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut, jsonx.V1())) assert.Equal(t, "tag", tagsOut.Tags["extra"]) assert.Equal(t, "test", tagsOut.Tags["env"]) @@ -767,7 +768,7 @@ func TestGlue_JobTagsLifecycle(t *testing.T) { var tagsOut struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsOut, jsonx.V1())) assert.Equal(t, "prod", tagsOut.Tags["env"]) assert.Equal(t, "data", tagsOut.Tags["team"]) @@ -882,7 +883,7 @@ func idFromResponse(t *testing.T, body []byte) string { var out struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) require.NotEmpty(t, out.ID) return out.ID @@ -1202,7 +1203,7 @@ func TestGlue_AssociateGlossaryTerms_TableDriven(t *testing.T) { var assocOut struct { GlossaryTerms []string `json:"GlossaryTerms"` } - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocOut)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocOut, jsonx.V1())) require.Contains(t, assocOut.GlossaryTerms, termID) return map[string]any{"AssetIdentifier": "asset-2", "GlossaryTermIdentifiers": []string{termID}} @@ -1213,7 +1214,7 @@ func TestGlue_AssociateGlossaryTerms_TableDriven(t *testing.T) { var out struct { GlossaryTerms []string `json:"GlossaryTerms"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Empty(t, out.GlossaryTerms) }, }, @@ -1246,7 +1247,7 @@ func TestGlue_AssociateGlossaryTerms_TableDriven(t *testing.T) { var out struct { GlossaryTerms []string `json:"GlossaryTerms"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Empty(t, out.GlossaryTerms, "deleted glossary term must be disassociated from the asset") }, }, diff --git a/services/glue/handler_triggers_test.go b/services/glue/handler_triggers_test.go index eaad998da5..a38b9cd53d 100644 --- a/services/glue/handler_triggers_test.go +++ b/services/glue/handler_triggers_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -22,7 +23,7 @@ func triggerState(t *testing.T, h *glue.Handler, name string) string { State string `json:"State"` } `json:"Trigger"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.Trigger.State } @@ -174,7 +175,7 @@ func TestTrigger_GetTrigger(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if !tt.wantErr { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) trig, ok := out["Trigger"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-trigger", trig["Name"]) @@ -278,7 +279,7 @@ func TestTrigger_StartStopActivate(t *testing.T) { if tt.wantCode == http.StatusOK && tt.wantState != "" { get := doGlueRequest(t, h, "GetTrigger", map[string]any{"Name": "t1"}) var out map[string]any - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) trig := out["Trigger"].(map[string]any) assert.Equal(t, tt.wantState, trig["State"]) } @@ -305,7 +306,7 @@ func TestTrigger_BatchGetAndList(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) triggers := out["Triggers"].([]any) assert.Len(t, triggers, 2) missing := out["TriggersNotFound"].([]any) @@ -352,7 +353,7 @@ func TestTrigger_WithActions(t *testing.T) { getRec := doGlueRequest(t, h, "GetTrigger", map[string]any{"Name": "trig-actions"}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) trig := out["Trigger"].(map[string]any) actions := trig["Actions"].([]any) assert.Len(t, actions, 2) diff --git a/services/glue/handler_unfiltered_test.go b/services/glue/handler_unfiltered_test.go index d5e73e56a6..e07b763d15 100644 --- a/services/glue/handler_unfiltered_test.go +++ b/services/glue/handler_unfiltered_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetUnfilteredTableMetadata exercises the GetUnfilteredTableMetadata handler. @@ -26,7 +28,7 @@ func TestGetUnfilteredTableMetadata(t *testing.T) { var out struct { AuthorizedColumns []string `json:"AuthorizedColumns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out.AuthorizedColumns) }) @@ -59,7 +61,7 @@ func TestGetUnfilteredTableMetadata(t *testing.T) { } `json:"Table"` AuthorizedColumns []string `json:"AuthorizedColumns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "uftbl", out.Table.Name) assert.NotNil(t, out.AuthorizedColumns) }) @@ -79,7 +81,7 @@ func TestGetUnfilteredTableMetadata(t *testing.T) { var out struct { IsRegisteredWithLakeFormation bool `json:"IsRegisteredWithLakeFormation"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.False(t, out.IsRegisteredWithLakeFormation) }) } @@ -102,7 +104,7 @@ func TestGetUnfilteredPartitionMetadata(t *testing.T) { var out struct { AuthorizedColumns []string `json:"AuthorizedColumns"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out.AuthorizedColumns) }) @@ -141,7 +143,7 @@ func TestGetUnfilteredPartitionMetadata(t *testing.T) { AuthorizedColumns []string `json:"AuthorizedColumns"` IsRegisteredWithLakeFormation bool `json:"IsRegisteredWithLakeFormation"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, []string{"2024", "01"}, out.Partition.Values) assert.NotNil(t, out.AuthorizedColumns) assert.False(t, out.IsRegisteredWithLakeFormation) @@ -165,7 +167,7 @@ func TestGetUnfilteredPartitionsMetadata(t *testing.T) { var out struct { UnfilteredPartitions []any `json:"UnfilteredPartitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.UnfilteredPartitions) }) @@ -195,7 +197,7 @@ func TestGetUnfilteredPartitionsMetadata(t *testing.T) { var out struct { UnfilteredPartitions []any `json:"UnfilteredPartitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.UnfilteredPartitions) }) @@ -216,7 +218,7 @@ func TestGetUnfilteredPartitionsMetadata(t *testing.T) { var out struct { UnfilteredPartitions []any `json:"UnfilteredPartitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.UnfilteredPartitions, 2) }) @@ -241,7 +243,7 @@ func TestGetUnfilteredPartitionsMetadata(t *testing.T) { AuthorizedColumns []string `json:"AuthorizedColumns"` } `json:"UnfilteredPartitions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.UnfilteredPartitions, 1) assert.Equal(t, []string{"2024"}, out.UnfilteredPartitions[0].Partition.Values) assert.NotNil(t, out.UnfilteredPartitions[0].AuthorizedColumns) @@ -287,7 +289,7 @@ func TestUnfilteredMetadata_IntegrationFlow(t *testing.T) { Name string `json:"Name"` } `json:"Table"` } - require.NoError(t, json.Unmarshal(tblMetaRec.Body.Bytes(), &tblMeta)) + require.NoError(t, json.Unmarshal(tblMetaRec.Body.Bytes(), &tblMeta, jsonx.V1())) assert.Equal(t, "integ-uf-tbl", tblMeta.Table.Name) // GetUnfilteredPartitionsMetadata lists all partitions. @@ -300,7 +302,7 @@ func TestUnfilteredMetadata_IntegrationFlow(t *testing.T) { var partsMeta struct { UnfilteredPartitions []any `json:"UnfilteredPartitions"` } - require.NoError(t, json.Unmarshal(partsMetaRec.Body.Bytes(), &partsMeta)) + require.NoError(t, json.Unmarshal(partsMetaRec.Body.Bytes(), &partsMeta, jsonx.V1())) assert.Len(t, partsMeta.UnfilteredPartitions, 1) // GetUnfilteredPartitionMetadata retrieves the specific partition. @@ -316,6 +318,6 @@ func TestUnfilteredMetadata_IntegrationFlow(t *testing.T) { Values []string `json:"Values"` } `json:"Partition"` } - require.NoError(t, json.Unmarshal(partMetaRec.Body.Bytes(), &partMeta)) + require.NoError(t, json.Unmarshal(partMetaRec.Body.Bytes(), &partMeta, jsonx.V1())) assert.Equal(t, []string{"2024", "06"}, partMeta.Partition.Values) } diff --git a/services/glue/handler_usage_profiles_test.go b/services/glue/handler_usage_profiles_test.go index 451b071fc3..f0be268bd7 100644 --- a/services/glue/handler_usage_profiles_test.go +++ b/services/glue/handler_usage_profiles_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDeleteUsageProfile_NotFound verifies that DeleteUsageProfile raises @@ -203,7 +205,7 @@ func TestGetUsageProfile_FullFields(t *testing.T) { CreatedOn float64 `json:"CreatedOn"` LastModifiedOn float64 `json:"LastModifiedOn"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "full-profile", out.Name) assert.Equal(t, "test description", out.Description) assert.NotZero(t, out.CreatedOn) diff --git a/services/glue/handler_user_defined_functions_test.go b/services/glue/handler_user_defined_functions_test.go index 7352992ed1..4d6d5dd5ea 100644 --- a/services/glue/handler_user_defined_functions_test.go +++ b/services/glue/handler_user_defined_functions_test.go @@ -1,7 +1,8 @@ package glue_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -139,7 +141,7 @@ func TestGetUserDefinedFunctions_Pattern(t *testing.T) { FunctionName string `json:"FunctionName"` } `json:"UserDefinedFunctions"` } - require.NoError(t, json.NewDecoder(rr.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rr.Body), &out, jsonx.V1())) got := make([]string, 0, len(out.UserDefinedFunctions)) for _, u := range out.UserDefinedFunctions { diff --git a/services/glue/handler_workflows_test.go b/services/glue/handler_workflows_test.go index fcd4aa65cd..4a1d2bd030 100644 --- a/services/glue/handler_workflows_test.go +++ b/services/glue/handler_workflows_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -74,7 +75,7 @@ func TestWorkflow_CRUD(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if tt.op == "GetWorkflow" && tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) wf := out["Workflow"].(map[string]any) assert.Equal(t, "wf1", wf["Name"]) } @@ -131,7 +132,7 @@ func TestWorkflow_DeleteAndList(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) wfs := out["Workflows"].([]any) assert.Len(t, wfs, 2) missing := out["MissingWorkflows"].([]any) @@ -150,7 +151,7 @@ func TestWorkflow_RunLifecycle(t *testing.T) { rec := doGlueRequest(t, h, "StartWorkflowRun", map[string]any{"Name": "wf-run"}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["RunId"]) }) @@ -171,20 +172,20 @@ func TestWorkflow_GetRunAndProperties(t *testing.T) { startRec := doGlueRequest(t, h, "StartWorkflowRun", map[string]any{"Name": "wf-props"}) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"].(string) getRunRec := doGlueRequest(t, h, "GetWorkflowRun", map[string]any{"Name": "wf-props", "RunId": runID}) require.Equal(t, http.StatusOK, getRunRec.Code) var getRunOut map[string]any - require.NoError(t, json.Unmarshal(getRunRec.Body.Bytes(), &getRunOut)) + require.NoError(t, json.Unmarshal(getRunRec.Body.Bytes(), &getRunOut, jsonx.V1())) run := getRunOut["Run"].(map[string]any) assert.Equal(t, runID, run["WorkflowRunId"]) getRunsRec := doGlueRequest(t, h, "GetWorkflowRuns", map[string]any{"Name": "wf-props"}) require.Equal(t, http.StatusOK, getRunsRec.Code) var getRunsOut map[string]any - require.NoError(t, json.Unmarshal(getRunsRec.Body.Bytes(), &getRunsOut)) + require.NoError(t, json.Unmarshal(getRunsRec.Body.Bytes(), &getRunsOut, jsonx.V1())) runs := getRunsOut["Runs"].([]any) assert.Len(t, runs, 1) @@ -206,7 +207,7 @@ func TestWorkflow_GetRunAndProperties(t *testing.T) { }) require.Equal(t, http.StatusOK, getPropsRec.Code) var getPropsOut map[string]any - require.NoError(t, json.Unmarshal(getPropsRec.Body.Bytes(), &getPropsOut)) + require.NoError(t, json.Unmarshal(getPropsRec.Body.Bytes(), &getPropsOut, jsonx.V1())) props := getPropsOut["RunProperties"].(map[string]any) assert.Equal(t, "staging", props["env"]) @@ -235,7 +236,7 @@ func TestWorkflow_UpdateDefaultRunProperties(t *testing.T) { getRec := doGlueRequest(t, h, "GetWorkflow", map[string]any{"Name": "wf-drp"}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) wf, ok := out["Workflow"].(map[string]any) require.True(t, ok) props, ok := wf["DefaultRunProperties"].(map[string]any) @@ -351,7 +352,7 @@ func TestResumeWorkflowRun_Stateful(t *testing.T) { RunID string `json:"RunId"` NodeIDs []string `json:"NodeIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.RunID) assert.NotEqual(t, runID, out.RunID, "resume must mint a new RunId, not echo the original") assert.Equal(t, []string{"node-a"}, out.NodeIDs) @@ -373,7 +374,7 @@ func startedWorkflowRun(t *testing.T, h *glue.Handler, name string) (string, str var startOut struct { RunID string `json:"RunId"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) return name, startOut.RunID } @@ -407,7 +408,7 @@ func TestResumeWorkflowRun_EchoesRequestedNodes(t *testing.T) { RunID string `json:"RunId"` NodeIDs []string `json:"NodeIds"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, []string{"node-a", "node-b"}, out.NodeIDs) assert.NotEqual(t, runID, out.RunID) } @@ -502,7 +503,7 @@ func TestWorkflow_Graph(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) wf := out["Workflow"].(map[string]any) if !tt.includeGraph { @@ -547,19 +548,19 @@ func TestWorkflow_LastRun(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out["Workflow"].(map[string]any), "LastRun") startRec := doGlueRequest(t, h, "StartWorkflowRun", map[string]any{"Name": "graphwf"}) require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"].(string) rec = doGlueRequest(t, h, "GetWorkflow", map[string]any{"Name": "graphwf"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) wf := out["Workflow"].(map[string]any) assert.NotContains(t, wf, "Graph") @@ -587,7 +588,7 @@ func TestStopWorkflowRun_StateGuard(t *testing.T) { getRec := doGlueRequest(t, h, "GetWorkflowRun", map[string]any{"Name": wfName, "RunId": runID}) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "RUNNING", getOut["Run"].(map[string]any)["Status"]) stopRec := doGlueRequest(t, h, "StopWorkflowRun", map[string]any{"Name": wfName, "RunId": runID}) @@ -595,20 +596,20 @@ func TestStopWorkflowRun_StateGuard(t *testing.T) { getRec = doGlueRequest(t, h, "GetWorkflowRun", map[string]any{"Name": wfName, "RunId": runID}) require.Equal(t, http.StatusOK, getRec.Code) - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "STOPPED", getOut["Run"].(map[string]any)["Status"]) // Stopping again must fail: the run is no longer RUNNING. stopAgainRec := doGlueRequest(t, h, "StopWorkflowRun", map[string]any{"Name": wfName, "RunId": runID}) assert.Equal(t, http.StatusBadRequest, stopAgainRec.Code) var errOut map[string]string - require.NoError(t, json.Unmarshal(stopAgainRec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(stopAgainRec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "IllegalWorkflowStateException", errOut["__type"]) // Stopping a nonexistent run is EntityNotFoundException, not the state error. missingRec := doGlueRequest(t, h, "StopWorkflowRun", map[string]any{"Name": wfName, "RunId": "no-such-run"}) assert.Equal(t, http.StatusBadRequest, missingRec.Code) - require.NoError(t, json.Unmarshal(missingRec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(missingRec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "EntityNotFoundException", errOut["__type"]) } @@ -667,7 +668,7 @@ func TestSDKRoundTrip_GetWorkflowRuns_Pagination(t *testing.T) { var out struct { RunID string `json:"RunId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) wantIDs[out.RunID] = true } require.Len(t, wantIDs, numRuns) diff --git a/services/glue/models.go b/services/glue/models.go index 54ce6dec3d..012eef0f24 100644 --- a/services/glue/models.go +++ b/services/glue/models.go @@ -1,8 +1,11 @@ package glue import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // DatabaseInput is the input for creating or updating a Glue database. @@ -359,13 +362,13 @@ type Job struct { // NonOverridableArguments are job arguments a run cannot override. NonOverridableArguments map[string]string `json:"NonOverridableArguments,omitempty"` // CodeGenConfigurationNodes is the visual-job DAG, stored verbatim. - CodeGenConfigurationNodes map[string]json.RawMessage `json:"CodeGenConfigurationNodes,omitempty"` - JobRunQueuingEnabled *bool `json:"JobRunQueuingEnabled,omitempty"` - Command JobCommand `json:"Command,omitzero"` - LogURI string `json:"LogUri,omitempty"` - SecurityConfiguration string `json:"SecurityConfiguration,omitempty"` - ExecutionClass string `json:"ExecutionClass,omitempty"` - MaintenanceWindow string `json:"MaintenanceWindow,omitempty"` + CodeGenConfigurationNodes map[string]jsontext.Value `json:"CodeGenConfigurationNodes,omitempty"` + JobRunQueuingEnabled *bool `json:"JobRunQueuingEnabled,omitempty"` + Command JobCommand `json:"Command,omitzero"` + LogURI string `json:"LogUri,omitempty"` + SecurityConfiguration string `json:"SecurityConfiguration,omitempty"` + ExecutionClass string `json:"ExecutionClass,omitempty"` + MaintenanceWindow string `json:"MaintenanceWindow,omitempty"` // JobMode describes how the job was created (SCRIPT/VISUAL/NOTEBOOK); // missing or null defaults to SCRIPT (glue@v1.157.0 api_op_CreateJob.go). JobMode string `json:"JobMode,omitempty"` @@ -651,7 +654,7 @@ func (b *JobBookmark) UnmarshalJSON(data []byte) error { ActiveRun string `json:"ActiveRun,omitempty"` }{alias: (*alias)(b)} - if err := json.Unmarshal(data, aux); err != nil { + if err := json.Unmarshal(data, aux, jsonx.V1()); err != nil { return err } @@ -926,7 +929,7 @@ func (p *IntegrationResourceProperty) UnmarshalJSON(data []byte) error { *alias }{alias: (*alias)(p)} - if err := json.Unmarshal(data, aux); err != nil { + if err := json.Unmarshal(data, aux, jsonx.V1()); err != nil { return err } diff --git a/services/glue/multi_region_test.go b/services/glue/multi_region_test.go index 3259d292f0..c7e76a9e1b 100644 --- a/services/glue/multi_region_test.go +++ b/services/glue/multi_region_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -40,7 +41,7 @@ func regionCall(t *testing.T, h *glue.Handler, region, op, body string) map[stri require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/glue/persistence.go b/services/glue/persistence.go index e33e76525b..615a28885e 100644 --- a/services/glue/persistence.go +++ b/services/glue/persistence.go @@ -2,7 +2,7 @@ package glue import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" @@ -47,7 +47,7 @@ const glueSnapshotVersion = 3 // singletons and JobRun*At timer maps, is not a map[string]*T at all) -- and // so is still persisted directly, exactly as before this refactor. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` PartitionIndexes map[string]*PartitionIndex `json:"partitionIndexes"` TableColumnStats map[string]*ColumnStatistics `json:"tableColumnStats"` PartitionColumnStats map[string]*ColumnStatistics `json:"partitionColumnStats"` diff --git a/services/glue/persistence_test.go b/services/glue/persistence_test.go index 344d5b58fc..07b3cf72e9 100644 --- a/services/glue/persistence_test.go +++ b/services/glue/persistence_test.go @@ -1,13 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -367,11 +368,11 @@ func TestInMemoryBackend_Restore_VersionMismatch(t *testing.T) { // Corrupt the version field so Restore hits the mismatch branch, without // hardcoding the rest of the snapshot's shape. var generic map[string]any - require.NoError(t, json.Unmarshal(snap, &generic)) + require.NoError(t, json.Unmarshal(snap, &generic, jsonx.V1())) require.Contains(t, generic, "version") generic["version"] = 999 - mutated, err := json.Marshal(generic) + mutated, err := json.Marshal(generic, jsonx.V1()) require.NoError(t, err) restored := glue.NewInMemoryBackend("123456789012", "us-east-1") diff --git a/services/glue/policy_shares.go b/services/glue/policy_shares.go index 5fca62dc3f..8f46ad7094 100644 --- a/services/glue/policy_shares.go +++ b/services/glue/policy_shares.go @@ -1,10 +1,12 @@ package glue import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // glueAccountIDLen is the number of digits in an AWS account ID. @@ -34,14 +36,14 @@ type stringOrSlice []string func (s *stringOrSlice) UnmarshalJSON(data []byte) error { var str string - if err := json.Unmarshal(data, &str); err == nil { + if err := json.Unmarshal(data, &str, jsonx.V1()); err == nil { *s = []string{str} return nil } var slice []string - if err := json.Unmarshal(data, &slice); err != nil { + if err := json.Unmarshal(data, &slice, jsonx.V1()); err != nil { return err } @@ -59,19 +61,19 @@ type policyPrincipal struct { func (p *policyPrincipal) UnmarshalJSON(data []byte) error { var str string - if err := json.Unmarshal(data, &str); err == nil { + if err := json.Unmarshal(data, &str, jsonx.V1()); err == nil { p.IsWildcard = str == "*" return nil } - var m map[string]json.RawMessage - if err := json.Unmarshal(data, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(data, &m, jsonx.V1()); err != nil { return err } if v, ok := m["AWS"]; ok { - _ = json.Unmarshal(v, &p.AWS) + _ = json.Unmarshal(v, &p.AWS, jsonx.V1()) } return nil @@ -112,7 +114,7 @@ func isAllDigits(s string) bool { // specific principals, so a public ("*") or same-account-only grant creates no share. func crossAccountGrant(policyJSON, ownerAccountID string) ([]string, []string) { var doc policyDoc - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return nil, nil } diff --git a/services/glue/schema_avro.go b/services/glue/schema_avro.go index b018904210..8248fb7ebc 100644 --- a/services/glue/schema_avro.go +++ b/services/glue/schema_avro.go @@ -1,10 +1,12 @@ package glue import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errAvroSchema = errors.New("invalid AVRO schema") @@ -64,7 +66,7 @@ func avroIsPrimitive(t string) bool { func parseAvro(def string) (*avroNode, error) { var v any - if err := json.Unmarshal([]byte(def), &v); err != nil { + if err := json.Unmarshal([]byte(def), &v, jsonx.V1()); err != nil { return nil, fmt.Errorf("schema is not valid JSON: %w", err) } diff --git a/services/glue/schema_json.go b/services/glue/schema_json.go index 673c0e7829..1ba220af8e 100644 --- a/services/glue/schema_json.go +++ b/services/glue/schema_json.go @@ -1,13 +1,15 @@ package glue import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" "reflect" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errJSONSchema = errors.New("invalid JSON schema") @@ -34,7 +36,7 @@ type jsonSchemaDoc map[string]any func parseJSONSchema(def string) (jsonSchemaDoc, error) { var v any - if err := json.Unmarshal([]byte(def), &v); err != nil { + if err := json.Unmarshal([]byte(def), &v, jsonx.V1()); err != nil { return nil, fmt.Errorf("schema is not valid JSON: %w", err) } diff --git a/services/glue/search_tables_sort_test.go b/services/glue/search_tables_sort_test.go index b6bc5a9beb..61b060b765 100644 --- a/services/glue/search_tables_sort_test.go +++ b/services/glue/search_tables_sort_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSearchTables_SortCriteria(t *testing.T) { @@ -76,7 +78,7 @@ func TestSearchTables_SortCriteria(t *testing.T) { Name string `json:"Name"` } `json:"TableList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) got := make([]string, 0, len(out.TableList)) for _, tbl := range out.TableList { diff --git a/services/glue/session_docker.go b/services/glue/session_docker.go index a113b7473b..e5f70d5ac1 100644 --- a/services/glue/session_docker.go +++ b/services/glue/session_docker.go @@ -3,7 +3,7 @@ package glue import ( "context" _ "embed" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" @@ -11,6 +11,8 @@ import ( "strconv" "sync/atomic" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed session_repl.pyrepl @@ -95,12 +97,12 @@ func (e *dockerSessionEngine) image(glueVersion string) string { func sparkCommand(name string) bool { return name == "glueetl" || name == "gluestreaming" } func sessionEnv(spec SessionSpec) ([]string, error) { - args, err := json.Marshal(spec.DefaultArguments) + args, err := json.Marshal(spec.DefaultArguments, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode default arguments: %w", err) } - conf, err := json.Marshal(parseSparkConf(spec.DefaultArguments["--conf"])) + conf, err := json.Marshal(parseSparkConf(spec.DefaultArguments["--conf"]), jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode spark conf: %w", err) } @@ -222,7 +224,7 @@ func (p *dockerSession) awaitReady(ctx context.Context, timeout time.Duration) e Status string `json:"status"` Message string `json:"message"` } - if json.Unmarshal(data, &r) == nil && r.Status == "ok" { + if json.Unmarshal(data, &r, jsonx.V1()) == nil && r.Status == "ok" { return nil } else if r.Status == "error" { return fmt.Errorf("%w: %s", errReplStart, r.Message) @@ -273,7 +275,7 @@ func (p *dockerSession) Run(ctx context.Context, code string) (StatementResult, p.seq++ n := p.seq - body, err := json.Marshal(map[string]string{"code": code}) + body, err := json.Marshal(map[string]string{"code": code}, jsonx.V1()) if err != nil { return StatementResult{}, fmt.Errorf("encode statement: %w", err) } @@ -306,7 +308,7 @@ func (p *dockerSession) awaitResponse(ctx context.Context, n int) (replResponse, for { if data, err := os.ReadFile(path); err == nil { var r replResponse - if err = json.Unmarshal(data, &r); err != nil { + if err = json.Unmarshal(data, &r, jsonx.V1()); err != nil { return r, fmt.Errorf("decode interpreter response: %w", err) } diff --git a/services/glue/timestamp_wire_shape_test.go b/services/glue/timestamp_wire_shape_test.go index 94f79c7c86..743d0dbfa0 100644 --- a/services/glue/timestamp_wire_shape_test.go +++ b/services/glue/timestamp_wire_shape_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestStartedOn_IsEpochSecondsNumber locks down a fix for a systemic bug @@ -37,7 +39,7 @@ func TestStartedOn_IsEpochSecondsNumber(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID, ok := startOut["RunId"].(string) require.True(t, ok) @@ -48,7 +50,7 @@ func TestStartedOn_IsEpochSecondsNumber(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) run, ok := getOut["BlueprintRun"].(map[string]any) require.True(t, ok) @@ -79,7 +81,7 @@ func TestStartedOn_IsEpochSecondsNumber(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID, ok := startOut["ColumnStatisticsTaskRunId"].(string) require.True(t, ok) @@ -89,7 +91,7 @@ func TestStartedOn_IsEpochSecondsNumber(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) run, ok := getOut["ColumnStatisticsTaskRun"].(map[string]any) require.True(t, ok) diff --git a/services/glue/trigger_conditional_test.go b/services/glue/trigger_conditional_test.go index 3dcd9465a7..c5fb701f96 100644 --- a/services/glue/trigger_conditional_test.go +++ b/services/glue/trigger_conditional_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -172,7 +173,7 @@ func TestConditionalTrigger_Fires(t *testing.T) { PredecessorRuns []pred `json:"PredecessorRuns"` } `json:"JobRun"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var got []string for _, p := range out.JobRun.PredecessorRuns { diff --git a/services/glue/wire_arn_no_leak_test.go b/services/glue/wire_arn_no_leak_test.go index 95da48ec01..41fbbb70e9 100644 --- a/services/glue/wire_arn_no_leak_test.go +++ b/services/glue/wire_arn_no_leak_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -21,7 +22,7 @@ func assertRawObjectHasNoArn(t *testing.T, body []byte, path ...string) map[stri t.Helper() var decoded map[string]any - require.NoError(t, json.Unmarshal(body, &decoded)) + require.NoError(t, json.Unmarshal(body, &decoded, jsonx.V1())) obj := decoded for i, key := range path { @@ -50,7 +51,7 @@ func assertNoArnInArray(t *testing.T, body []byte, containerKey string) { t.Helper() var decoded map[string]any - require.NoError(t, json.Unmarshal(body, &decoded)) + require.NoError(t, json.Unmarshal(body, &decoded, jsonx.V1())) arr, ok := decoded[containerKey].([]any) require.Truef(t, ok, "missing array key %q in response", containerKey) diff --git a/services/glue/wire_field_fixes_test.go b/services/glue/wire_field_fixes_test.go index a8e4044c95..bb8fb80f0f 100644 --- a/services/glue/wire_field_fixes_test.go +++ b/services/glue/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/glue" ) @@ -128,7 +129,7 @@ func TestCancelDataQualityRulesetEvaluationRun_StatusIsLegalEnumMember(t *testin ) require.Equal(t, 200, startRec.Code) var startOut map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) runID := startOut["RunId"] cancelRec := doGlueRequest(t, h, "CancelDataQualityRulesetEvaluationRun", map[string]any{"RunId": runID}) @@ -140,7 +141,7 @@ func TestCancelDataQualityRulesetEvaluationRun_StatusIsLegalEnumMember(t *testin var getOut struct { Status string `json:"Status"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, string(types.TaskStatusTypeStopped), getOut.Status) } diff --git a/services/glue/workflow_run_options_test.go b/services/glue/workflow_run_options_test.go index 57283d20f8..0a3f96a392 100644 --- a/services/glue/workflow_run_options_test.go +++ b/services/glue/workflow_run_options_test.go @@ -1,12 +1,14 @@ package glue_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestStartWorkflowRun_RunProperties(t *testing.T) { @@ -47,7 +49,7 @@ func TestStartWorkflowRun_RunProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var start map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) rec = doGlueRequest(t, h, "GetWorkflowRunProperties", map[string]any{ "Name": "wf", "RunId": start["RunId"], @@ -55,7 +57,7 @@ func TestStartWorkflowRun_RunProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.want, got["RunProperties"]) }) } @@ -86,7 +88,7 @@ func TestGetWorkflowRun_IncludeGraph(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var start map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) body := map[string]any{"Name": "graphwf", "IncludeGraph": tt.includeGraph} if tt.action == "GetWorkflowRun" { @@ -97,7 +99,7 @@ func TestGetWorkflowRun_IncludeGraph(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) run, _ := out["Run"].(map[string]any) if tt.action == "GetWorkflowRuns" { diff --git a/services/grafana/handler.go b/services/grafana/handler.go index b348173ece..9d3f4692d2 100644 --- a/services/grafana/handler.go +++ b/services/grafana/handler.go @@ -2,7 +2,7 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -378,7 +379,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { status, errType = http.StatusNotFound, "ResourceNotFoundException" } - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) diff --git a/services/grafana/handler_api_keys.go b/services/grafana/handler_api_keys.go index e7810bbf69..cffd003af5 100644 --- a/services/grafana/handler_api_keys.go +++ b/services/grafana/handler_api_keys.go @@ -2,8 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createWorkspaceAPIKeyRequest mirrors CreateWorkspaceApiKeyInput's JSON body. @@ -17,7 +19,7 @@ func (h *Handler) handleCreateWorkspaceAPIKey(_ context.Context, r *http.Request segs := rawPathSegments(r) var req createWorkspaceAPIKeyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -30,7 +32,7 @@ func (h *Handler) handleCreateWorkspaceAPIKey(_ context.Context, r *http.Request "key": key.Key, "keyName": key.KeyName, keyWorkspaceID: key.WorkspaceID, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteWorkspaceAPIKey(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -44,5 +46,5 @@ func (h *Handler) handleDeleteWorkspaceAPIKey(_ context.Context, r *http.Request return json.Marshal(map[string]any{ "keyName": keyName, keyWorkspaceID: workspaceID, - }) + }, jsonx.V1()) } diff --git a/services/grafana/handler_authentication.go b/services/grafana/handler_authentication.go index 8280c044b2..0168993276 100644 --- a/services/grafana/handler_authentication.go +++ b/services/grafana/handler_authentication.go @@ -2,8 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeWorkspaceAuthentication(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -14,14 +16,14 @@ func (h *Handler) handleDescribeWorkspaceAuthentication(_ context.Context, r *ht return nil, err } - return json.Marshal(map[string]any{"authentication": toAuthenticationDescriptionWire(w)}) + return json.Marshal(map[string]any{"authentication": toAuthenticationDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleUpdateWorkspaceAuthentication(_ context.Context, r *http.Request, body []byte) ([]byte, error) { segs := rawPathSegments(r) var req updateWorkspaceAuthenticationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -30,5 +32,5 @@ func (h *Handler) handleUpdateWorkspaceAuthentication(_ context.Context, r *http return nil, err } - return json.Marshal(map[string]any{"authentication": toAuthenticationDescriptionWire(w)}) + return json.Marshal(map[string]any{"authentication": toAuthenticationDescriptionWire(w)}, jsonx.V1()) } diff --git a/services/grafana/handler_configuration.go b/services/grafana/handler_configuration.go index 7fae1d95b0..fe720481b2 100644 --- a/services/grafana/handler_configuration.go +++ b/services/grafana/handler_configuration.go @@ -2,8 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeWorkspaceConfiguration(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -17,14 +19,14 @@ func (h *Handler) handleDescribeWorkspaceConfiguration(_ context.Context, r *htt return json.Marshal(map[string]any{ "configuration": configuration, "grafanaVersion": grafanaVersion, - }) + }, jsonx.V1()) } func (h *Handler) handleUpdateWorkspaceConfiguration(_ context.Context, r *http.Request, body []byte) ([]byte, error) { segs := rawPathSegments(r) var req updateWorkspaceConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -32,5 +34,5 @@ func (h *Handler) handleUpdateWorkspaceConfiguration(_ context.Context, r *http. return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/grafana/handler_license.go b/services/grafana/handler_license.go index 4a80cb6095..5b698bf4db 100644 --- a/services/grafana/handler_license.go +++ b/services/grafana/handler_license.go @@ -2,8 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // grafanaTokenHeader is the HTTP header AssociateLicenseInput.GrafanaToken is @@ -24,7 +26,7 @@ func (h *Handler) handleAssociateLicense(_ context.Context, r *http.Request, _ [ return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleDisassociateLicense(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -36,5 +38,5 @@ func (h *Handler) handleDisassociateLicense(_ context.Context, r *http.Request, return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } diff --git a/services/grafana/handler_permissions.go b/services/grafana/handler_permissions.go index 8c1018978b..4473dfa450 100644 --- a/services/grafana/handler_permissions.go +++ b/services/grafana/handler_permissions.go @@ -2,9 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -31,14 +32,14 @@ func (h *Handler) handleListPermissions(_ context.Context, r *http.Request, _ [] out["nextToken"] = p.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } func (h *Handler) handleUpdatePermissions(ctx context.Context, r *http.Request, body []byte) ([]byte, error) { segs := rawPathSegments(r) var req updatePermissionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -51,5 +52,5 @@ func (h *Handler) handleUpdatePermissions(ctx context.Context, r *http.Request, errs = []updateErrorWire{} } - return json.Marshal(map[string]any{"errors": errs}) + return json.Marshal(map[string]any{"errors": errs}, jsonx.V1()) } diff --git a/services/grafana/handler_service_account_tokens.go b/services/grafana/handler_service_account_tokens.go index 58ec308346..9c4e0ab87d 100644 --- a/services/grafana/handler_service_account_tokens.go +++ b/services/grafana/handler_service_account_tokens.go @@ -2,9 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -22,7 +23,7 @@ func (h *Handler) handleCreateWorkspaceServiceAccountToken( workspaceID, serviceAccountID := segs[1], segs[3] var req createWorkspaceServiceAccountTokenRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -39,7 +40,7 @@ func (h *Handler) handleCreateWorkspaceServiceAccountToken( Name: tok.Name, Key: tok.Key, }, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteWorkspaceServiceAccountToken( @@ -56,7 +57,7 @@ func (h *Handler) handleDeleteWorkspaceServiceAccountToken( keyServiceAccountID: serviceAccountID, "tokenId": tokenID, keyWorkspaceID: workspaceID, - }) + }, jsonx.V1()) } func (h *Handler) handleListWorkspaceServiceAccountTokens( @@ -89,5 +90,5 @@ func (h *Handler) handleListWorkspaceServiceAccountTokens( out["nextToken"] = p.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } diff --git a/services/grafana/handler_service_accounts.go b/services/grafana/handler_service_accounts.go index 39e45561f3..de6440d641 100644 --- a/services/grafana/handler_service_accounts.go +++ b/services/grafana/handler_service_accounts.go @@ -2,9 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -19,7 +20,7 @@ func (h *Handler) handleCreateWorkspaceServiceAccount(_ context.Context, r *http segs := rawPathSegments(r) var req createWorkspaceServiceAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -33,7 +34,7 @@ func (h *Handler) handleCreateWorkspaceServiceAccount(_ context.Context, r *http "name": sa.Name, "grafanaRole": sa.GrafanaRole, keyWorkspaceID: sa.WorkspaceID, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteWorkspaceServiceAccount(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -47,7 +48,7 @@ func (h *Handler) handleDeleteWorkspaceServiceAccount(_ context.Context, r *http return json.Marshal(map[string]any{ keyServiceAccountID: serviceAccountID, keyWorkspaceID: workspaceID, - }) + }, jsonx.V1()) } func (h *Handler) handleListWorkspaceServiceAccounts(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -74,5 +75,5 @@ func (h *Handler) handleListWorkspaceServiceAccounts(_ context.Context, r *http. out["nextToken"] = p.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } diff --git a/services/grafana/handler_tags.go b/services/grafana/handler_tags.go index ffaed0350c..4f8ce3c970 100644 --- a/services/grafana/handler_tags.go +++ b/services/grafana/handler_tags.go @@ -2,8 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // tagResourceRequest mirrors TagResourceInput's JSON body. @@ -20,7 +22,7 @@ func (h *Handler) handleListTagsForResource(_ context.Context, r *http.Request, return nil, err } - return json.Marshal(map[string]any{"tags": tags}) + return json.Marshal(map[string]any{"tags": tags}, jsonx.V1()) } func (h *Handler) handleTagResource(ctx context.Context, r *http.Request, body []byte) ([]byte, error) { @@ -28,7 +30,7 @@ func (h *Handler) handleTagResource(ctx context.Context, r *http.Request, body [ resourceArn := segs[1] var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -36,7 +38,7 @@ func (h *Handler) handleTagResource(ctx context.Context, r *http.Request, body [ return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) handleUntagResource(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -48,5 +50,5 @@ func (h *Handler) handleUntagResource(ctx context.Context, r *http.Request, _ [] return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/grafana/handler_versions.go b/services/grafana/handler_versions.go index 56d8221680..659e630094 100644 --- a/services/grafana/handler_versions.go +++ b/services/grafana/handler_versions.go @@ -2,9 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -34,5 +35,5 @@ func (h *Handler) handleListVersions(_ context.Context, r *http.Request, _ []byt out["nextToken"] = p.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } diff --git a/services/grafana/handler_workspaces.go b/services/grafana/handler_workspaces.go index d84fb08e10..79adfa8f22 100644 --- a/services/grafana/handler_workspaces.go +++ b/services/grafana/handler_workspaces.go @@ -2,9 +2,10 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -12,7 +13,7 @@ const grafanaDefaultPageSize = 100 func (h *Handler) handleCreateWorkspace(_ context.Context, _ *http.Request, body []byte) ([]byte, error) { var req createWorkspaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -21,7 +22,7 @@ func (h *Handler) handleCreateWorkspace(_ context.Context, _ *http.Request, body return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleDescribeWorkspace(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -32,14 +33,14 @@ func (h *Handler) handleDescribeWorkspace(_ context.Context, r *http.Request, _ return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleUpdateWorkspace(_ context.Context, r *http.Request, body []byte) ([]byte, error) { segs := rawPathSegments(r) var req updateWorkspaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, validationError("invalid request body: " + err.Error()) } @@ -48,7 +49,7 @@ func (h *Handler) handleUpdateWorkspace(_ context.Context, r *http.Request, body return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleDeleteWorkspace(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -59,7 +60,7 @@ func (h *Handler) handleDeleteWorkspace(_ context.Context, r *http.Request, _ [] return nil, err } - return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}) + return json.Marshal(map[string]any{keyWorkspace: toWorkspaceDescriptionWire(w)}, jsonx.V1()) } func (h *Handler) handleListWorkspaces(_ context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -81,5 +82,5 @@ func (h *Handler) handleListWorkspaces(_ context.Context, r *http.Request, _ []b out["nextToken"] = p.Next } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } diff --git a/services/grafana/persistence.go b/services/grafana/persistence.go index 8425a6c15e..717c4eb000 100644 --- a/services/grafana/persistence.go +++ b/services/grafana/persistence.go @@ -2,7 +2,7 @@ package grafana import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -19,12 +19,12 @@ const grafanaSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Grafana backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` - NextServiceAccountID uint64 `json:"nextServiceAccountId"` - NextTokenID uint64 `json:"nextTokenId"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` + NextServiceAccountID uint64 `json:"nextServiceAccountId"` + NextTokenID uint64 `json:"nextTokenId"` } // Snapshot serialises the backend state to JSON. diff --git a/services/guardduty/coverage_statistics_test.go b/services/guardduty/coverage_statistics_test.go index f799cec38a..bc1241fafa 100644 --- a/services/guardduty/coverage_statistics_test.go +++ b/services/guardduty/coverage_statistics_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -30,7 +31,7 @@ func TestCoverage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.NotNil(t, listResp["resources"]) // GetCoverageStatistics requires statisticsType (gopherstack-h910: this @@ -45,7 +46,7 @@ func TestCoverage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var byTypeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byTypeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byTypeResp, jsonx.V1())) stats, ok := byTypeResp["coverageStatistics"].(map[string]any) require.True(t, ok) assert.Contains(t, stats, "countByResourceType") @@ -57,7 +58,7 @@ func TestCoverage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var byStatusResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byStatusResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byStatusResp, jsonx.V1())) stats, ok = byStatusResp["coverageStatistics"].(map[string]any) require.True(t, ok) assert.Contains(t, stats, "countByCoverageStatus") diff --git a/services/guardduty/cross_service_test.go b/services/guardduty/cross_service_test.go index 2176fbc93f..d68cffab95 100644 --- a/services/guardduty/cross_service_test.go +++ b/services/guardduty/cross_service_test.go @@ -1,12 +1,13 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/guardduty" organizationsbackend "github.com/blackbirdworks/gopherstack/services/organizations" @@ -97,7 +98,7 @@ func TestMemberOps_AutoEnableOrganizationMembersAll_AllowedAfterAccountLeavesOrg require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Empty(t, out["unprocessedAccounts"], "account should have been processed, not left unprocessed") }) } diff --git a/services/guardduty/detectors_test.go b/services/guardduty/detectors_test.go index 5a041c1cc8..2259eea74e 100644 --- a/services/guardduty/detectors_test.go +++ b/services/guardduty/detectors_test.go @@ -1,7 +1,7 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "regexp" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -40,7 +41,7 @@ func TestDetector_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp["detectorId"]) }, }, @@ -54,7 +55,7 @@ func TestDetector_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ENABLED", resp["status"]) assert.NotEmpty(t, resp["serviceRole"]) assert.Equal(t, "SIX_HOURS", resp["findingPublishingFrequency"]) @@ -73,7 +74,7 @@ func TestDetector_Lifecycle(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/detector/"+id, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "FIFTEEN_MINUTES", resp["findingPublishingFrequency"]) }, }, @@ -90,7 +91,7 @@ func TestDetector_Lifecycle(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/detector/"+id, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DISABLED", resp["status"]) }, }, @@ -117,7 +118,7 @@ func TestDetector_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, ok := resp["detectorIds"].([]any) require.True(t, ok) require.Len(t, ids, 1) @@ -155,12 +156,12 @@ func TestDetector_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["detectorId"].(string) rec = doRequest(t, h, http.MethodGet, "/detector/"+id, nil) var det map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &det)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &det, jsonx.V1())) tags, ok := det["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "test", tags["env"]) @@ -218,7 +219,7 @@ func TestDetector_Timestamps_Present(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range []string{"createdAt", "updatedAt"} { raw, ok := resp[field] @@ -241,7 +242,7 @@ func TestDetector_UpdatedAt_Advances(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var before map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &before, jsonx.V1())) createdAt := before["createdAt"].(string) updatedAt1 := before["updatedAt"].(string) @@ -256,7 +257,7 @@ func TestDetector_UpdatedAt_Advances(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var after map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &after, jsonx.V1())) createdAt2 := after["createdAt"].(string) updatedAt2 := after["updatedAt"].(string) @@ -279,7 +280,7 @@ func TestDetector_Tags_EmptyMap_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["tags"] require.True(t, exists, "GetDetector response must include 'tags' key") @@ -299,7 +300,7 @@ func TestListDetectors_Empty_State(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["detectorIds"] require.True(t, exists, "ListDetectors must include 'detectorIds' key") diff --git a/services/guardduty/entity_sets_test.go b/services/guardduty/entity_sets_test.go index 88108bde72..da9e2c28de 100644 --- a/services/guardduty/entity_sets_test.go +++ b/services/guardduty/entity_sets_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -36,7 +37,7 @@ func TestThreatEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) setID, _ := createResp["threatEntitySetId"].(string) require.NotEmpty(t, setID) @@ -45,7 +46,7 @@ func TestThreatEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-threat-set", getResp["name"]) assert.Equal(t, "ACTIVE", getResp["status"]) @@ -61,7 +62,7 @@ func TestThreatEntitySets(t *testing.T) { // Verify update rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/threatentityset/"+setID, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "updated-threat-set", getResp["name"]) assert.Equal(t, "INACTIVE", getResp["status"]) @@ -70,7 +71,7 @@ func TestThreatEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) ids, _ := listResp["threatEntitySetIds"].([]any) assert.Len(t, ids, 1) assert.Equal(t, setID, ids[0]) @@ -81,7 +82,7 @@ func TestThreatEntitySets(t *testing.T) { // List after delete rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/threatentityset", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) idsAfter, _ := listResp["threatEntitySetIds"].([]any) assert.Empty(t, idsAfter) }, @@ -133,7 +134,7 @@ func TestTrustedEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) setID, _ := createResp["trustedEntitySetId"].(string) require.NotEmpty(t, setID) @@ -142,7 +143,7 @@ func TestTrustedEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-trusted-set", getResp["name"]) assert.Equal(t, "ACTIVE", getResp["status"]) @@ -156,7 +157,7 @@ func TestTrustedEntitySets(t *testing.T) { // Verify update rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/trustedentityset/"+setID, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "updated-trusted-set", getResp["name"]) assert.Equal(t, "INACTIVE", getResp["status"]) @@ -165,7 +166,7 @@ func TestTrustedEntitySets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) ids, _ := listResp["trustedEntitySetIds"].([]any) assert.Len(t, ids, 1) assert.Equal(t, setID, ids[0]) @@ -176,7 +177,7 @@ func TestTrustedEntitySets(t *testing.T) { // List after delete rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/trustedentityset", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) idsAfter, _ := listResp["trustedEntitySetIds"].([]any) assert.Empty(t, idsAfter) }, diff --git a/services/guardduty/filters_test.go b/services/guardduty/filters_test.go index 3449d9e22f..1e71382880 100644 --- a/services/guardduty/filters_test.go +++ b/services/guardduty/filters_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -31,14 +32,14 @@ func TestFilter_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) assert.Equal(t, "my-filter", cr["name"]) rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/filter/my-filter", nil) require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "ARCHIVE", gr["action"]) assert.InDelta(t, 1.0, gr["rank"], 0.01) assert.Equal(t, "test filter", gr["description"]) @@ -62,7 +63,7 @@ func TestFilter_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/filter/upd-filter", nil) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "ARCHIVE", gr["action"]) assert.InDelta(t, 2.0, gr["rank"], 0.01) }, @@ -96,7 +97,7 @@ func TestFilter_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names, ok := resp["filterNames"].([]any) require.True(t, ok) assert.Len(t, names, 3) @@ -132,7 +133,7 @@ func TestListFilters_Empty_State(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["filterNames"] require.True(t, exists, "ListFilters must include 'filterNames' key") @@ -160,7 +161,7 @@ func TestFilter_Tags_EmptyMap_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["tags"] require.True(t, exists, "GetFilter response must include 'tags' key") diff --git a/services/guardduty/finding_criteria.go b/services/guardduty/finding_criteria.go index 7134ff90f9..266d64761b 100644 --- a/services/guardduty/finding_criteria.go +++ b/services/guardduty/finding_criteria.go @@ -1,12 +1,14 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "regexp" "slices" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Condition represents a single finding-criteria condition, matching @@ -45,13 +47,13 @@ func conditionsFromRaw(raw map[string]any) map[string]Condition { return nil } - data, err := json.Marshal(raw) + data, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil } var fc FindingCriteria - if unmarshalErr := json.Unmarshal(data, &fc); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &fc, jsonx.V1()); unmarshalErr != nil { return nil } @@ -71,13 +73,13 @@ type SortCriteria struct { // resolved the same way a real GuardDuty backend would resolve them against // the finding document. func findingToFieldMap(f *Finding) map[string]any { - data, marshalErr := json.Marshal(f) + data, marshalErr := json.Marshal(f, jsonx.V1()) if marshalErr != nil { return map[string]any{} } var m map[string]any - if unmarshalErr := json.Unmarshal(data, &m); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &m, jsonx.V1()); unmarshalErr != nil { return map[string]any{} } diff --git a/services/guardduty/finding_criteria_test.go b/services/guardduty/finding_criteria_test.go index 05dd1293cd..f2f531c127 100644 --- a/services/guardduty/finding_criteria_test.go +++ b/services/guardduty/finding_criteria_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -20,7 +21,7 @@ func listFindings(t *testing.T, h *guardduty.Handler, detectorID string, body an require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/guardduty/finding_statistics_test.go b/services/guardduty/finding_statistics_test.go index 026eaa1cc0..74429a52fe 100644 --- a/services/guardduty/finding_statistics_test.go +++ b/services/guardduty/finding_statistics_test.go @@ -1,12 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGetFindingsStatistics_GroupBy(t *testing.T) { @@ -67,7 +69,7 @@ func TestGetFindingsStatistics_GroupBy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs, ok := resp["findingStatistics"].(map[string]any) require.True(t, ok) @@ -109,7 +111,7 @@ func TestGetFindingsStatistics_GroupBy_Unset_ReturnsCountBySeverity(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["findingStatistics"].(map[string]any) assert.Contains(t, fs, "countBySeverity") @@ -139,7 +141,7 @@ func TestGetFindingsStatistics_FindingCriteria(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs := resp["findingStatistics"].(map[string]any) groups, ok := fs["groupedByFindingType"].([]any) diff --git a/services/guardduty/findings_test.go b/services/guardduty/findings_test.go index e0fd0bff5c..20efdb8eff 100644 --- a/services/guardduty/findings_test.go +++ b/services/guardduty/findings_test.go @@ -1,7 +1,7 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -32,7 +33,7 @@ func TestFindings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var lr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr, jsonx.V1())) ids, ok := lr["findingIds"].([]any) require.True(t, ok) assert.Len(t, ids, 1) @@ -49,7 +50,7 @@ func TestFindings(t *testing.T) { // List to get IDs rec := doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/findings", nil) var lr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr, jsonx.V1())) ids := lr["findingIds"].([]any) // Get by ID @@ -59,7 +60,7 @@ func TestFindings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) findings, ok := gr["findings"].([]any) require.True(t, ok) require.Len(t, findings, 1) @@ -75,7 +76,7 @@ func TestFindings(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/findings", nil) var lr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr, jsonx.V1())) ids := lr["findingIds"].([]any) fid := ids[0].(string) @@ -90,7 +91,7 @@ func TestFindings(t *testing.T) { "findingIds": []string{fid}, }) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) findings := gr["findings"].([]any) f := findings[0].(map[string]any) svc := f["service"].(map[string]any) @@ -105,7 +106,7 @@ func TestFindings(t *testing.T) { rec = doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/findings/get", map[string]any{ "findingIds": []string{fid}, }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) findings = gr["findings"].([]any) f = findings[0].(map[string]any) svc = f["service"].(map[string]any) @@ -127,7 +128,7 @@ func TestFindings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["findingStatistics"]) }, }, @@ -141,7 +142,7 @@ func TestFindings(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/findings", nil) var lr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lr, jsonx.V1())) ids := lr["findingIds"].([]any) rec = doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/findings/feedback", map[string]any{ @@ -173,7 +174,7 @@ func TestListFindings_Empty_State(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["findingIds"] require.True(t, exists, "ListFindings must include 'findingIds' key") @@ -195,7 +196,7 @@ func TestGetFindings_Empty_Input(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["findings"] require.True(t, exists, "GetFindings must include 'findings' key") @@ -425,7 +426,7 @@ func TestGetFindingsStatistics_Handler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs, ok := resp["findingStatistics"].(map[string]any) require.True(t, ok) diff --git a/services/guardduty/handler_coverage_statistics.go b/services/guardduty/handler_coverage_statistics.go index ee29e46cb5..3a12dd45be 100644 --- a/services/guardduty/handler_coverage_statistics.go +++ b/services/guardduty/handler_coverage_statistics.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetCoverageStatistics(detectorID string, body []byte) (any, int, error) { @@ -11,7 +13,7 @@ func (h *Handler) handleGetCoverageStatistics(detectorID string, body []byte) (a } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -35,7 +37,7 @@ func (h *Handler) handleListCoverage(detectorID string, body []byte) (any, int, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/guardduty/handler_detectors.go b/services/guardduty/handler_detectors.go index f3d49e70b6..6b6486f787 100644 --- a/services/guardduty/handler_detectors.go +++ b/services/guardduty/handler_detectors.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchDetectorOps(op, path, query string, body []byte) (any, int, bool, error) { @@ -49,7 +51,7 @@ func (h *Handler) handleCreateDetector(body []byte) (any, int, error) { Features []DetectorFeature `json:"features"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -105,7 +107,7 @@ func (h *Handler) handleUpdateDetector(detectorID string, body []byte) (int, err Features []DetectorFeature `json:"features"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_entity_sets.go b/services/guardduty/handler_entity_sets.go index 52b81316b7..ae4e515b69 100644 --- a/services/guardduty/handler_entity_sets.go +++ b/services/guardduty/handler_entity_sets.go @@ -1,10 +1,11 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchEntitySetOps(op, path, query string, body []byte) (any, int, bool, error) { @@ -87,7 +88,7 @@ func (h *Handler) handleCreateThreatEntitySet( //nolint:dupl // existing issue. ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -176,7 +177,7 @@ func (h *Handler) handleUpdateThreatEntitySet(detectorID, setID string, body []b ExpectedBucketOwner string `json:"expectedBucketOwner"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -212,7 +213,7 @@ func (h *Handler) handleCreateTrustedEntitySet( //nolint:dupl // existing issue. ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -298,7 +299,7 @@ func (h *Handler) handleUpdateTrustedEntitySet(detectorID, setID string, body [] ExpectedBucketOwner string `json:"expectedBucketOwner"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_filters.go b/services/guardduty/handler_filters.go index d02fceb97b..402cb3270e 100644 --- a/services/guardduty/handler_filters.go +++ b/services/guardduty/handler_filters.go @@ -1,10 +1,11 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchFilterOps(op, path, query string, body []byte) (any, int, bool, error) { @@ -54,7 +55,7 @@ func (h *Handler) handleCreateFilter(detectorID string, body []byte) (any, int, Rank int32 `json:"rank"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -122,7 +123,7 @@ func (h *Handler) handleUpdateFilter(detectorID, filterName string, body []byte) Rank int32 `json:"rank"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_findings.go b/services/guardduty/handler_findings.go index afc5307b33..10b05f6d92 100644 --- a/services/guardduty/handler_findings.go +++ b/services/guardduty/handler_findings.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchFindingOps(op, path string, body []byte) (any, int, bool, error) { @@ -54,7 +56,7 @@ func (h *Handler) handleGetFindings(detectorID string, body []byte) (any, int, e FindingIDs []string `json:"findingIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -79,7 +81,7 @@ func (h *Handler) handleListFindings(detectorID string, body []byte) (any, int, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -116,7 +118,7 @@ func (h *Handler) handleArchiveFindings(detectorID string, body []byte) (int, er FindingIDs []string `json:"findingIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -132,7 +134,7 @@ func (h *Handler) handleUnarchiveFindings(detectorID string, body []byte) (int, FindingIDs []string `json:"findingIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -149,7 +151,7 @@ func (h *Handler) handleCreateSampleFindings(detectorID string, body []byte) (in } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } @@ -170,7 +172,7 @@ func (h *Handler) handleGetFindingsStatistics(detectorID string, body []byte) (a } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -195,7 +197,7 @@ func (h *Handler) handleUpdateFindingsFeedback(detectorID string, body []byte) ( FindingIDs []string `json:"findingIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_investigations.go b/services/guardduty/handler_investigations.go index 61388fbc18..e6a40ba0d6 100644 --- a/services/guardduty/handler_investigations.go +++ b/services/guardduty/handler_investigations.go @@ -1,10 +1,11 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchInvestigationOps(op, path string, body []byte) (any, int, bool, error) { @@ -37,7 +38,7 @@ func (h *Handler) handleCreateInvestigation(detectorID string, body []byte) (any ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -80,7 +81,7 @@ func (h *Handler) handleListInvestigations(detectorID string, body []byte) (any, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/guardduty/handler_ip_and_threatintel_sets.go b/services/guardduty/handler_ip_and_threatintel_sets.go index 5f58e7e2d2..93f6a31efb 100644 --- a/services/guardduty/handler_ip_and_threatintel_sets.go +++ b/services/guardduty/handler_ip_and_threatintel_sets.go @@ -1,9 +1,11 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validSetFormats are the enum values IpSetFormat and ThreatIntelSetFormat @@ -60,7 +62,7 @@ func (h *Handler) handleCreateIPSet(detectorID string, body []byte) (any, int, e ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -126,7 +128,7 @@ func (h *Handler) handleUpdateIPSet(detectorID, ipSetID string, body []byte) (in ExpectedBucketOwner string `json:"expectedBucketOwner"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -210,7 +212,7 @@ func (h *Handler) handleCreateThreatIntelSet(detectorID string, body []byte) (an ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -276,7 +278,7 @@ func (h *Handler) handleUpdateThreatIntelSet(detectorID, setID string, body []by ExpectedBucketOwner string `json:"expectedBucketOwner"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_ip_and_threatintel_sets_test.go b/services/guardduty/handler_ip_and_threatintel_sets_test.go index eb264ecfe3..ee8744b8d9 100644 --- a/services/guardduty/handler_ip_and_threatintel_sets_test.go +++ b/services/guardduty/handler_ip_and_threatintel_sets_test.go @@ -1,12 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGuardDuty_CreateIPSet_FormatValidation(t *testing.T) { @@ -44,7 +46,7 @@ func TestGuardDuty_CreateIPSet_FormatValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "BadRequestException", errOut["__type"]) return @@ -90,7 +92,7 @@ func TestGuardDuty_CreateThreatIntelSet_FormatValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "BadRequestException", errOut["__type"]) return diff --git a/services/guardduty/handler_malware_protection.go b/services/guardduty/handler_malware_protection.go index 6fb7710bcf..3dcc6cb70d 100644 --- a/services/guardduty/handler_malware_protection.go +++ b/services/guardduty/handler_malware_protection.go @@ -1,12 +1,13 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "sync" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -188,7 +189,7 @@ func (h *Handler) handleDescribeMalwareScans(detectorID string, body []byte) (an var req malwareScanFilterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -210,7 +211,7 @@ func (h *Handler) handleListMalwareScans(query string, body []byte) (any, int, e var req malwareScanFilterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -239,7 +240,7 @@ func (h *Handler) handleStartMalwareScan(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -343,7 +344,7 @@ func (h *Handler) handleUpdateMalwareScanSettings(detectorID string, body []byte EbsSnapshotPreservation string `json:"ebsSnapshotPreservation"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -368,7 +369,7 @@ func (h *Handler) handleCreateMalwareProtectionPlan(body []byte) (any, int, erro ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -479,7 +480,7 @@ func (h *Handler) handleUpdateMalwareProtectionPlan(planID string, body []byte) Role string `json:"role"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -508,7 +509,7 @@ func (h *Handler) handleSendObjectMalwareScan(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/guardduty/handler_members.go b/services/guardduty/handler_members.go index dc2b230250..ad02a3ecf7 100644 --- a/services/guardduty/handler_members.go +++ b/services/guardduty/handler_members.go @@ -1,9 +1,11 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // keyMembers is the real required response key for GetMembers, ListMembers, @@ -157,7 +159,7 @@ func (h *Handler) handleCreateMembers(detectorID string, body []byte) (any, int, AccountDetails []map[string]any `json:"accountDetails"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -173,7 +175,7 @@ func (h *Handler) handleDeleteMembers(detectorID string, body []byte) (any, int, AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -190,7 +192,7 @@ func (h *Handler) handleGetMembers(detectorID string, body []byte) (any, int, er AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -215,7 +217,7 @@ func (h *Handler) handleInviteMembers(detectorID string, body []byte) (any, int, AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -253,7 +255,7 @@ func (h *Handler) handleStartMonitoringMembers(detectorID string, body []byte) ( AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -270,7 +272,7 @@ func (h *Handler) handleStopMonitoringMembers(detectorID string, body []byte) (a AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -287,7 +289,7 @@ func (h *Handler) handleDisassociateMembers(detectorID string, body []byte) (any AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -304,7 +306,7 @@ func (h *Handler) handleGetMemberDetectors(detectorID string, body []byte) (any, AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -326,7 +328,7 @@ func (h *Handler) handleUpdateMemberDetectors(detectorID string, body []byte) (a Features []DetectorFeature `json:"features"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -346,7 +348,7 @@ func (h *Handler) handleAcceptAdministratorInvitation(detectorID string, body [] InvitationID string `json:"invitationId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -363,7 +365,7 @@ func (h *Handler) handleAcceptInvitation(detectorID string, body []byte) (int, e InvitationID string `json:"invitationId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -379,7 +381,7 @@ func (h *Handler) handleDeclineInvitations(body []byte) (any, int, error) { AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -393,7 +395,7 @@ func (h *Handler) handleDeleteInvitations(body []byte) (any, int, error) { AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_organization.go b/services/guardduty/handler_organization.go index 9172447775..ab580c6313 100644 --- a/services/guardduty/handler_organization.go +++ b/services/guardduty/handler_organization.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchOrgOps(op, path, query string, body []byte) (any, int, bool, error) { @@ -70,7 +72,7 @@ func (h *Handler) handleEnableOrganizationAdminAccount(body []byte) (int, error) AdminAccountID string `json:"adminAccountId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -86,7 +88,7 @@ func (h *Handler) handleDisableOrganizationAdminAccount(body []byte) (int, error AdminAccountID string `json:"adminAccountId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -160,7 +162,7 @@ func (h *Handler) handleUpdateOrganizationConfiguration(detectorID string, body Features []OrgFeature `json:"features"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_publishing_destinations.go b/services/guardduty/handler_publishing_destinations.go index 261572bde8..1287f2effe 100644 --- a/services/guardduty/handler_publishing_destinations.go +++ b/services/guardduty/handler_publishing_destinations.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchPublishingDestOps(op, path, query string, body []byte) (any, int, bool, error) { @@ -49,7 +51,7 @@ func (h *Handler) handleCreatePublishingDestination(detectorID string, body []by ClientToken string `json:"clientToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -137,7 +139,7 @@ func (h *Handler) handleUpdatePublishingDestination(detectorID, destID string, b DestinationProperties DestinationProperties `json:"destinationProperties"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_tags.go b/services/guardduty/handler_tags.go index b23eebb641..b9eac5640a 100644 --- a/services/guardduty/handler_tags.go +++ b/services/guardduty/handler_tags.go @@ -1,10 +1,12 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) dispatchTagOps(op, path, query string, body []byte) (any, int, error) { @@ -44,7 +46,7 @@ func (h *Handler) handleTagResource(resourceARN string, body []byte) (int, error Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/guardduty/handler_tagsync_test.go b/services/guardduty/handler_tagsync_test.go index 9f50e7dfe9..7702bc45b1 100644 --- a/services/guardduty/handler_tagsync_test.go +++ b/services/guardduty/handler_tagsync_test.go @@ -14,7 +14,7 @@ package guardduty_test // regression coverage before now. import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -39,7 +40,7 @@ func doJSON(t *testing.T, h *guardduty.Handler, method, path string, body any) m require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/guardduty/handler_test.go b/services/guardduty/handler_test.go index 8549e3718c..a0139b9f15 100644 --- a/services/guardduty/handler_test.go +++ b/services/guardduty/handler_test.go @@ -2,7 +2,7 @@ package guardduty_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -30,7 +31,7 @@ func doRequest(t *testing.T, h *guardduty.Handler, method, path string, body any if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -54,7 +55,7 @@ func createTestDetector(t *testing.T, h *guardduty.Handler) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, ok := resp["detectorId"].(string) require.True(t, ok) require.NotEmpty(t, id) diff --git a/services/guardduty/handler_usage.go b/services/guardduty/handler_usage.go index e0354339af..16b3c694b3 100644 --- a/services/guardduty/handler_usage.go +++ b/services/guardduty/handler_usage.go @@ -1,8 +1,10 @@ package guardduty import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetUsageStatistics(detectorID string, body []byte) (any, int, error) { @@ -18,7 +20,7 @@ func (h *Handler) handleGetUsageStatistics(detectorID string, body []byte) (any, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -85,7 +87,7 @@ func (h *Handler) handleGetRemainingFreeTrialDays(detectorID string, body []byte } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/guardduty/handler_wireshape_test.go b/services/guardduty/handler_wireshape_test.go index 9a4b8a9a32..8fbd5f418b 100644 --- a/services/guardduty/handler_wireshape_test.go +++ b/services/guardduty/handler_wireshape_test.go @@ -19,13 +19,15 @@ package guardduty_test // scanStartedAt/scanCompletedAt epoch fields. import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -113,8 +115,8 @@ func TestWireShape_DescribePublishingDestination_UsesRealKeysAndTags(t *testing. rec := doRequest(t, h, http.MethodGet, "/detector/"+detID+"/publishingDestination/"+destID, nil) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasWrongKey := raw["publishingFailureStartedAt"] assert.False(t, hasWrongKey, "must not use the made-up publishingFailureStartedAt wire key") @@ -123,7 +125,7 @@ func TestWireShape_DescribePublishingDestination_UsesRealKeysAndTags(t *testing. assert.True(t, hasRightKey, "must use the real publishingFailureStartTimestamp wire key") var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) tags, _ := got["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"], "tags supplied at creation must be returned by DescribePublishingDestination") } @@ -149,8 +151,8 @@ func TestWireShape_GetMalwareScan_UsesRealFieldNames(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/malware-scan/"+scanID, nil) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) for _, badKey := range []string{"accountId", "resourceDetails", "findings", "scanStartTime", "scanEndTime"} { _, present := raw[badKey] @@ -171,7 +173,7 @@ func TestWireShape_GetMalwareScan_UsesRealFieldNames(t *testing.T) { } var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, detID, got["detectorId"], "detectorId must resolve to the account's own detector") assert.Equal(t, "EC2_INSTANCE", got["resourceType"], "resourceType must be inferred from the resource ARN") @@ -267,14 +269,14 @@ func TestWireShape_Investigation_NoFabricatedAnalysis(t *testing.T) { getRec := doRequest(t, h, http.MethodGet, "/detector/"+detID+"/investigation/"+invID, nil) require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) - var getRaw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw)) + var getRaw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw, jsonx.V1())) invRaw, ok := getRaw["investigation"] require.True(t, ok, "GetInvestigationOutput must have an investigation member") - var inv map[string]json.RawMessage - require.NoError(t, json.Unmarshal(invRaw, &inv)) + var inv map[string]jsontext.Value + require.NoError(t, json.Unmarshal(invRaw, &inv, jsonx.V1())) for _, fabricatable := range []string{ "cloud", "confidence", "endTime", "error", "metadata", "risk", "riskLevel", "summary", "title", @@ -285,18 +287,18 @@ func TestWireShape_Investigation_NoFabricatedAnalysis(t *testing.T) { } var got map[string]any - require.NoError(t, json.Unmarshal(invRaw, &got)) + require.NoError(t, json.Unmarshal(invRaw, &got, jsonx.V1())) assert.Equal(t, "RUNNING", got["status"], "an investigation this backend cannot analyze must stay RUNNING") assert.Equal(t, "Investigate finding in this account", got["triggerPrompt"]) listRec := doRequest(t, h, http.MethodPost, "/detector/"+detID+"/investigation/list", nil) require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) - var listRaw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listRaw)) + var listRaw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listRaw, jsonx.V1())) - var summaries []map[string]json.RawMessage - require.NoError(t, json.Unmarshal(listRaw["investigations"], &summaries)) + var summaries []map[string]jsontext.Value + require.NoError(t, json.Unmarshal(listRaw["investigations"], &summaries, jsonx.V1())) require.Len(t, summaries, 1) for _, fabricatable := range []string{"confidence", "endTime", "riskLevel", "title"} { @@ -305,7 +307,7 @@ func TestWireShape_Investigation_NoFabricatedAnalysis(t *testing.T) { } var summary map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &summary)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &summary, jsonx.V1())) invList, ok := summary["investigations"].([]any) require.True(t, ok) require.Len(t, invList, 1) diff --git a/services/guardduty/ip_sets_test.go b/services/guardduty/ip_sets_test.go index 5b8d1eead4..e07332274e 100644 --- a/services/guardduty/ip_sets_test.go +++ b/services/guardduty/ip_sets_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -31,7 +32,7 @@ func TestIPSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) ipSetID := cr["ipSetId"].(string) require.NotEmpty(t, ipSetID) @@ -39,7 +40,7 @@ func TestIPSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "my-ipset", gr["name"]) assert.Equal(t, "TXT", gr["format"]) assert.Equal(t, "ACTIVE", gr["status"]) @@ -53,7 +54,7 @@ func TestIPSet_CRUD(t *testing.T) { "name": "upd-ipset", "format": "TXT", "location": "s3://b/f.txt", }) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) ipSetID := cr["ipSetId"].(string) rec = doRequest(t, h, http.MethodPost, "/detector/"+detectorID+"/ipset/"+ipSetID, map[string]any{ @@ -64,7 +65,7 @@ func TestIPSet_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/ipset/"+ipSetID, nil) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "updated-ipset", gr["name"]) assert.Equal(t, "ACTIVE", gr["status"]) }, @@ -77,7 +78,7 @@ func TestIPSet_CRUD(t *testing.T) { "name": "del-ipset", "format": "TXT", "location": "s3://b/f.txt", }) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) ipSetID := cr["ipSetId"].(string) rec = doRequest(t, h, http.MethodDelete, "/detector/"+detectorID+"/ipset/"+ipSetID, nil) @@ -87,7 +88,7 @@ func TestIPSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "DELETED", gr["status"]) rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/ipset", nil) @@ -111,7 +112,7 @@ func TestIPSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, ok := resp["ipSetIds"].([]any) require.True(t, ok) assert.Len(t, ids, 3) @@ -125,12 +126,12 @@ func TestIPSet_CRUD(t *testing.T) { "name": "inactive-ipset", "format": "TXT", "location": "s3://b/f.txt", }) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) ipSetID := cr["ipSetId"].(string) rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/ipset/"+ipSetID, nil) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "INACTIVE", gr["status"]) }, }, @@ -156,7 +157,7 @@ func TestListIPSets_Empty_State(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["ipSetIds"] require.True(t, exists, "ListIPSets must include 'ipSetIds' key") @@ -182,14 +183,14 @@ func TestIPSet_Tags_EmptyMap_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) ipSetID := createResp["ipSetId"].(string) rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/ipset/"+ipSetID, nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["tags"] require.True(t, exists, "GetIPSet response must include 'tags' key") diff --git a/services/guardduty/malware_protection_plan_schema.go b/services/guardduty/malware_protection_plan_schema.go index 06032c603e..d9027ce7a3 100644 --- a/services/guardduty/malware_protection_plan_schema.go +++ b/services/guardduty/malware_protection_plan_schema.go @@ -1,6 +1,10 @@ package guardduty -import "encoding/json" +import ( + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // ProtectedResourceInput mirrors types.CreateProtectedResource (also reused // verbatim by GetMalwareProtectionPlanOutput.ProtectedResource -- Create and @@ -56,13 +60,13 @@ func decodeProtectedResource(raw map[string]any) (*ProtectedResourceInput, error return &ProtectedResourceInput{}, nil } - data, marshalErr := json.Marshal(raw) + data, marshalErr := json.Marshal(raw, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } var pr ProtectedResourceInput - if unmarshalErr := json.Unmarshal(data, &pr); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &pr, jsonx.V1()); unmarshalErr != nil { return nil, unmarshalErr } @@ -74,13 +78,13 @@ func decodeMalwareProtectionPlanActions(raw map[string]any) (*MalwareProtectionP return &MalwareProtectionPlanActionsInput{}, nil } - data, marshalErr := json.Marshal(raw) + data, marshalErr := json.Marshal(raw, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } var a MalwareProtectionPlanActionsInput - if unmarshalErr := json.Unmarshal(data, &a); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &a, jsonx.V1()); unmarshalErr != nil { return nil, unmarshalErr } diff --git a/services/guardduty/malware_protection_test.go b/services/guardduty/malware_protection_test.go index bcfdf87788..4ec144e155 100644 --- a/services/guardduty/malware_protection_test.go +++ b/services/guardduty/malware_protection_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -30,7 +31,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) scanID, _ := startResp["scanId"].(string) require.NotEmpty(t, scanID) @@ -39,7 +40,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, scanID, getResp["scanId"]) // ListMalwareScans @@ -47,7 +48,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) scans, _ := listResp["scans"].([]any) assert.Len(t, scans, 1) }, @@ -77,7 +78,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["scans"]) }, }, @@ -133,7 +134,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var settingsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settingsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &settingsResp, jsonx.V1())) assert.Equal(t, "RETENTION_WITH_FINDING", settingsResp["ebsSnapshotPreservation"]) }, }, @@ -167,7 +168,7 @@ func TestMalwareScanning(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["scanId"]) }, }, @@ -375,7 +376,7 @@ func TestMalwareProtectionPlans(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) planID, _ := createResp["malwareProtectionPlanId"].(string) require.NotEmpty(t, planID) @@ -384,7 +385,7 @@ func TestMalwareProtectionPlans(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, planID, getResp["malwareProtectionPlanId"]) // UpdateMalwareProtectionPlan uses PATCH on the real wire -- @@ -401,7 +402,7 @@ func TestMalwareProtectionPlans(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) plans, _ := listResp["malwareProtectionPlans"].([]any) assert.Len(t, plans, 1) @@ -411,7 +412,7 @@ func TestMalwareProtectionPlans(t *testing.T) { // List after delete rec = doRequest(t, h, http.MethodGet, "/malware-protection-plan", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) plansAfter, _ := listResp["malwareProtectionPlans"].([]any) assert.Empty(t, plansAfter) }, @@ -461,7 +462,7 @@ func TestUpdateMalwareProtectionPlan_PreservesBucketName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) planID, _ := createResp["malwareProtectionPlanId"].(string) require.NotEmpty(t, planID) @@ -481,7 +482,7 @@ func TestUpdateMalwareProtectionPlan_PreservesBucketName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) protectedResource, ok := getResp["protectedResource"].(map[string]any) require.True(t, ok, "protectedResource must survive the update") diff --git a/services/guardduty/members_test.go b/services/guardduty/members_test.go index 864eb9cc13..0ec28fdb60 100644 --- a/services/guardduty/members_test.go +++ b/services/guardduty/members_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" organizationsbackend "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -36,7 +37,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Empty(t, createResp["unprocessedAccounts"]) // ListMembers @@ -44,7 +45,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) members, _ := listResp["members"].([]any) assert.Len(t, members, 2) @@ -55,7 +56,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) got, _ := getResp["members"].([]any) assert.Len(t, got, 1) @@ -68,7 +69,7 @@ func TestMembers(t *testing.T) { // ListMembers after delete rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/member", nil) var listAfter map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listAfter)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listAfter, jsonx.V1())) membersAfter, _ := listAfter["members"].([]any) assert.Empty(t, membersAfter) }, @@ -99,7 +100,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members, _ := resp["members"].([]any) require.Len(t, members, 1) @@ -310,7 +311,7 @@ func TestMemberOps_AutoEnableOrganizationMembersAll_Rejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "BadRequestException", errOut["__type"]) }) } @@ -382,7 +383,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var countResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &countResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &countResp, jsonx.V1())) assert.NotNil(t, countResp["invitationsCount"]) // ListInvitations @@ -390,7 +391,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.NotNil(t, listResp["invitations"]) }, }, @@ -431,7 +432,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var adminResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp, jsonx.V1())) assert.NotNil(t, adminResp["administrator"]) // DisassociateFromAdministratorAccount @@ -458,7 +459,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var masterResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &masterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &masterResp, jsonx.V1())) assert.NotNil(t, masterResp["master"]) // DisassociateFromMasterAccount diff --git a/services/guardduty/multi_region_test.go b/services/guardduty/multi_region_test.go index 463e56d8fa..42983a102c 100644 --- a/services/guardduty/multi_region_test.go +++ b/services/guardduty/multi_region_test.go @@ -2,7 +2,7 @@ package guardduty_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/guardduty/organization_test.go b/services/guardduty/organization_test.go index 65dc7147ed..2e5a71b027 100644 --- a/services/guardduty/organization_test.go +++ b/services/guardduty/organization_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -34,7 +35,7 @@ func TestOrganization(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) accounts, _ := listResp["adminAccounts"].([]any) assert.Len(t, accounts, 1) @@ -46,7 +47,7 @@ func TestOrganization(t *testing.T) { // List after disable rec = doRequest(t, h, http.MethodGet, "/admin", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) accounts2, _ := listResp["adminAccounts"].([]any) assert.Empty(t, accounts2) }, @@ -63,7 +64,7 @@ func TestOrganization(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.NotNil(t, descResp["autoEnable"]) // UpdateOrganizationConfiguration @@ -74,7 +75,7 @@ func TestOrganization(t *testing.T) { // Verify update rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/admin", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, true, descResp["autoEnable"]) }, }, diff --git a/services/guardduty/persistence.go b/services/guardduty/persistence.go index cb0ae8037f..65e27f9826 100644 --- a/services/guardduty/persistence.go +++ b/services/guardduty/persistence.go @@ -2,7 +2,7 @@ package guardduty import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -110,7 +110,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // identity of their own -- see store_setup.go) and is persisted here // directly, as before. MemberSeq is a scalar counter, also carried directly. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountId"` Region string `json:"region"` @@ -308,7 +308,7 @@ func unwrapByDetectorDTOs[V any](dtos *store.Table[byDetectorDTO[V]], setDetecto // restoreDirtyTables rebuilds every "dirty" store.Table on b from tables via // the ephemeral DTO registry, factored out of Restore to keep Restore's own // cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("guardduty: restore snapshot DTO tables: %w", err) diff --git a/services/guardduty/publishing_destinations_test.go b/services/guardduty/publishing_destinations_test.go index 5414bca43f..6ae3fe265f 100644 --- a/services/guardduty/publishing_destinations_test.go +++ b/services/guardduty/publishing_destinations_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -36,7 +37,7 @@ func TestPublishingDestinations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) destID, _ := createResp["destinationId"].(string) require.NotEmpty(t, destID) @@ -45,7 +46,7 @@ func TestPublishingDestinations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "S3", descResp["destinationType"]) // UpdatePublishingDestination @@ -62,7 +63,7 @@ func TestPublishingDestinations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) dests, _ := listResp["destinations"].([]any) assert.Len(t, dests, 1) @@ -72,7 +73,7 @@ func TestPublishingDestinations(t *testing.T) { // List after delete rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/publishingDestination", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) destsAfter, _ := listResp["destinations"].([]any) assert.Empty(t, destsAfter) }, diff --git a/services/guardduty/tags_test.go b/services/guardduty/tags_test.go index e838b42fe9..f6f53eda4e 100644 --- a/services/guardduty/tags_test.go +++ b/services/guardduty/tags_test.go @@ -1,7 +1,7 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -34,7 +35,7 @@ func TestTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -56,7 +57,7 @@ func TestTags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/tags/"+arn, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].(map[string]any) assert.NotContains(t, tags, "k1") assert.Equal(t, "v2", tags["k2"]) @@ -72,7 +73,7 @@ func TestTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Empty(t, tags) @@ -101,7 +102,7 @@ func TestTagResource_Merges_Creation_Tags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["detectorId"].(string) arn := fmt.Sprintf("arn:aws:guardduty:us-east-1:123456789012:detector/%s", id) @@ -115,7 +116,7 @@ func TestTagResource_Merges_Creation_Tags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["tags"].(map[string]any) assert.Equal(t, "test", tags["env"], "creation-time tag must be preserved") @@ -134,7 +135,7 @@ func TestUntagResource_Removes_Only_Specified_Key(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["detectorId"].(string) arn := fmt.Sprintf("arn:aws:guardduty:us-east-1:123456789012:detector/%s", id) @@ -146,7 +147,7 @@ func TestUntagResource_Removes_Only_Specified_Key(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["tags"].(map[string]any) assert.Equal(t, "v1", tags["k1"], "k1 must be preserved") diff --git a/services/guardduty/threat_intel_sets_test.go b/services/guardduty/threat_intel_sets_test.go index ba34e2cb9e..5dc1b25b06 100644 --- a/services/guardduty/threat_intel_sets_test.go +++ b/services/guardduty/threat_intel_sets_test.go @@ -1,13 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/guardduty" ) @@ -31,7 +32,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) setID := cr["threatIntelSetId"].(string) require.NotEmpty(t, setID) @@ -39,7 +40,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "my-threatset", gr["name"]) assert.Equal(t, "ACTIVE", gr["status"]) }, @@ -52,7 +53,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { "name": "upd-threat", "format": "TXT", "location": "s3://b/t.txt", "activate": true, }) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) setID := cr["threatIntelSetId"].(string) activate := false @@ -63,7 +64,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/threatintelset/"+setID, nil) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "INACTIVE", gr["status"]) }, }, @@ -75,7 +76,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { "name": "del-threat", "format": "TXT", "location": "s3://b/t.txt", }) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) setID := cr["threatIntelSetId"].(string) rec = doRequest(t, h, http.MethodDelete, "/detector/"+detectorID+"/threatintelset/"+setID, nil) @@ -85,7 +86,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, "DELETED", gr["status"]) rec = doRequest(t, h, http.MethodGet, "/detector/"+detectorID+"/threatintelset", nil) @@ -109,7 +110,7 @@ func TestThreatIntelSet_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids, ok := resp["threatIntelSetIds"].([]any) require.True(t, ok) assert.Len(t, ids, 2) @@ -137,7 +138,7 @@ func TestListThreatIntelSets_Empty_State(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["threatIntelSetIds"] require.True(t, exists, "ListThreatIntelSets must include 'threatIntelSetIds' key") @@ -163,14 +164,14 @@ func TestThreatIntelSet_Tags_EmptyMap_Not_Null(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) setID := createResp["threatIntelSetId"].(string) rec = doRequest(t, h, http.MethodGet, "/detector/"+id+"/threatintelset/"+setID, nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) raw, exists := resp["tags"] require.True(t, exists, "GetThreatIntelSet response must include 'tags' key") diff --git a/services/guardduty/usage_test.go b/services/guardduty/usage_test.go index f9d4df400f..979d9e80ca 100644 --- a/services/guardduty/usage_test.go +++ b/services/guardduty/usage_test.go @@ -1,12 +1,14 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetUsageStatistics_WireShape locks the GetUsageStatistics fix: the @@ -24,7 +26,7 @@ func TestGetUsageStatistics_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) us, ok := resp["usageStatistics"].(map[string]any) require.True(t, ok, "response must be wrapped under usageStatistics") @@ -66,7 +68,7 @@ func TestGetUsageStatistics_UsageStatisticType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) us := resp["usageStatistics"].(map[string]any) assert.Contains(t, us, "sumByAccount") @@ -93,7 +95,7 @@ func TestGetUsageStatistics_SumByFeature_ReflectsEnabledFeatures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) detID := createResp["detectorId"].(string) rec = doRequest(t, h, http.MethodPost, "/detector/"+detID+"/usage/statistics", map[string]any{ @@ -102,7 +104,7 @@ func TestGetUsageStatistics_SumByFeature_ReflectsEnabledFeatures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) us := resp["usageStatistics"].(map[string]any) features, ok := us["sumByFeature"].([]any) @@ -140,7 +142,7 @@ func TestGetRemainingFreeTrialDays_HonorsAccountIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts, ok := resp["accounts"].([]any) require.True(t, ok) diff --git a/services/guardduty/wire_field_fixes_test.go b/services/guardduty/wire_field_fixes_test.go index 889c77444b..e9d3b40e2d 100644 --- a/services/guardduty/wire_field_fixes_test.go +++ b/services/guardduty/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package guardduty_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/guardduty" @@ -366,7 +367,7 @@ func TestListMalwareProtectionPlans_NoInventedArn(t *testing.T) { var raw struct { MalwareProtectionPlans []map[string]any `json:"malwareProtectionPlans"` } - require.NoError(t, json.Unmarshal(body, &raw)) + require.NoError(t, json.Unmarshal(body, &raw, jsonx.V1())) require.NotEmpty(t, raw.MalwareProtectionPlans) for _, entry := range raw.MalwareProtectionPlans { diff --git a/services/iam/accounts.go b/services/iam/accounts.go index 8ff7b4fb8f..95cd9aabff 100644 --- a/services/iam/accounts.go +++ b/services/iam/accounts.go @@ -2,7 +2,7 @@ package iam import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "strings" @@ -223,13 +223,13 @@ func (h *Handler) forAccount(root *InMemoryBackend, acct string) *Handler { return sh } -func (h *Handler) accountSnapshots(ctx context.Context) map[string]json.RawMessage { +func (h *Handler) accountSnapshots(ctx context.Context) map[string]jsontext.Value { root, ok := h.Backend.(*InMemoryBackend) if !ok { return nil } - var out map[string]json.RawMessage + var out map[string]jsontext.Value for _, id := range root.Accounts() { data := h.forAccount(root, id).Snapshot(ctx) @@ -238,7 +238,7 @@ func (h *Handler) accountSnapshots(ctx context.Context) map[string]json.RawMessa } if out == nil { - out = make(map[string]json.RawMessage) + out = make(map[string]jsontext.Value) } out[id] = data @@ -247,7 +247,7 @@ func (h *Handler) accountSnapshots(ctx context.Context) map[string]json.RawMessa return out } -func (h *Handler) restoreAccounts(ctx context.Context, snaps map[string]json.RawMessage) error { +func (h *Handler) restoreAccounts(ctx context.Context, snaps map[string]jsontext.Value) error { root, ok := h.Backend.(*InMemoryBackend) if !ok { return nil diff --git a/services/iam/aws_managed.go b/services/iam/aws_managed.go index d5b1ed86a7..0015de82c5 100644 --- a/services/iam/aws_managed.go +++ b/services/iam/aws_managed.go @@ -1,16 +1,18 @@ package iam import ( - "bytes" "crypto/sha256" _ "embed" "encoding/base32" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "strings" "sync" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed data/aws_managed_policies.json @@ -23,13 +25,13 @@ type awsManagedFile struct { } type awsManagedEntry struct { - Name string `json:"name"` - Path string `json:"path"` - PolicyID string `json:"policyId"` - DefaultVersionID string `json:"defaultVersionId"` - CreateDate time.Time `json:"createDate"` - UpdateDate time.Time `json:"updateDate"` - Document json.RawMessage `json:"document"` + Name string `json:"name"` + Path string `json:"path"` + PolicyID string `json:"policyId"` + DefaultVersionID string `json:"defaultVersionId"` + CreateDate time.Time `json:"createDate"` + UpdateDate time.Time `json:"updateDate"` + Document jsontext.Value `json:"document"` } type awsManagedCatalog struct { @@ -49,7 +51,7 @@ var loadAWSManagedCatalog = sync.OnceValue(func() *awsManagedCatalog { func parseAWSManagedCatalog(raw []byte) (*awsManagedCatalog, error) { var file awsManagedFile - if err := json.Unmarshal(raw, &file); err != nil { + if err := json.Unmarshal(raw, &file, jsonx.V1()); err != nil { return nil, fmt.Errorf("iam: parse AWS managed policy catalog: %w", err) } @@ -59,8 +61,8 @@ func parseAWSManagedCatalog(raw []byte) (*awsManagedCatalog, error) { } for _, e := range file.Policies { - var doc bytes.Buffer - if err := json.Compact(&doc, e.Document); err != nil { + doc := e.Document.Clone() + if err := doc.Compact(jsonx.V1()); err != nil { return nil, fmt.Errorf("iam: AWS managed policy %q document: %w", e.Name, err) } @@ -74,7 +76,7 @@ func parseAWSManagedCatalog(raw []byte) (*awsManagedCatalog, error) { PolicyID: e.PolicyID, Arn: arn, Path: e.Path, - PolicyDocument: doc.String(), + PolicyDocument: string(doc), DefaultVersionID: e.DefaultVersionID, CreateDate: e.CreateDate.UTC(), UpdateDate: e.UpdateDate.UTC(), diff --git a/services/iam/aws_managed_test.go b/services/iam/aws_managed_test.go index 4e6f6b0595..604de57460 100644 --- a/services/iam/aws_managed_test.go +++ b/services/iam/aws_managed_test.go @@ -1,7 +1,8 @@ package iam_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io/fs" "net/url" "os" @@ -17,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -475,13 +477,13 @@ func TestAWSManagedPolicyCatalog_Integrity(t *testing.T) { var file struct { Policies []struct { - Name string `json:"name"` - Path string `json:"path"` - DefaultVersionID string `json:"defaultVersionId"` - Document json.RawMessage `json:"document"` + Name string `json:"name"` + Path string `json:"path"` + DefaultVersionID string `json:"defaultVersionId"` + Document jsontext.Value `json:"document"` } `json:"policies"` } - require.NoError(t, json.Unmarshal(raw, &file)) + require.NoError(t, json.Unmarshal(raw, &file, jsonx.V1())) seen := map[string]bool{} @@ -496,7 +498,7 @@ func TestAWSManagedPolicyCatalog_Integrity(t *testing.T) { Version string `json:"Version"` Statement []any `json:"Statement"` } - require.NoError(t, json.Unmarshal(p.Document, &d), key) + require.NoError(t, json.Unmarshal(p.Document, &d, jsonx.V1()), key) assert.Equal(t, "2012-10-17", d.Version, key) assert.NotEmpty(t, d.Statement, key) } diff --git a/services/iam/conditions_test.go b/services/iam/conditions_test.go index e9dd5387ba..d0fd241e7f 100644 --- a/services/iam/conditions_test.go +++ b/services/iam/conditions_test.go @@ -2,11 +2,12 @@ package iam_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -1058,7 +1059,7 @@ func TestConditionTagKeyFallsThroughToExtra(t *testing.T) { // mustJSON marshals v to a compact JSON string, panicking on error (test-only). func mustJSON(v any) string { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { panic("marshal policy: " + err.Error()) } diff --git a/services/iam/enforcement_integration_test.go b/services/iam/enforcement_integration_test.go index b3e00d0241..da088c1515 100644 --- a/services/iam/enforcement_integration_test.go +++ b/services/iam/enforcement_integration_test.go @@ -2,7 +2,7 @@ package iam_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -19,6 +19,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -202,7 +203,7 @@ func TestEnforcement_ConditionOperators_SDKIntegration(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - policyBytes, marshalErr := json.Marshal(tt.policy) + policyBytes, marshalErr := json.Marshal(tt.policy, jsonx.V1()) require.NoError(t, marshalErr) const ( @@ -260,7 +261,7 @@ func TestEnforcement_MultiServiceSDKIntegration(t *testing.T) { }, }, } - analystPolicyBytes, marshalErr := json.Marshal(analystPolicy) + analystPolicyBytes, marshalErr := json.Marshal(analystPolicy, jsonx.V1()) require.NoError(t, marshalErr) analystPolicyDoc := string(analystPolicyBytes) diff --git a/services/iam/evaluator.go b/services/iam/evaluator.go index 7c7b2f870d..2970367b72 100644 --- a/services/iam/evaluator.go +++ b/services/iam/evaluator.go @@ -1,11 +1,12 @@ package iam import ( - "encoding/json" + "encoding/json/v2" "maps" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/safemap" ) @@ -68,7 +69,7 @@ func parsePolicyDocumentCached(expanded string) (PolicyDocument, bool) { var pd PolicyDocument - err := json.Unmarshal([]byte(expanded), &pd) + err := json.Unmarshal([]byte(expanded), &pd, jsonx.V1()) entry := &cachedPolicyDoc{doc: pd, ok: err == nil} parsedPolicyCache.Set(expanded, entry) diff --git a/services/iam/handler_inline_policies.go b/services/iam/handler_inline_policies.go index d0797fd8a2..93ca66236f 100644 --- a/services/iam/handler_inline_policies.go +++ b/services/iam/handler_inline_policies.go @@ -1,11 +1,14 @@ package iam import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/url" "sort" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) iamInlinePolicyDispatchTable() map[string]iamActionFn { @@ -284,10 +287,10 @@ func contextKeysFromPolicyDocuments(docs []string) []string { } var parsed struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if err := json.Unmarshal([]byte(doc), &parsed); err != nil { + if err := json.Unmarshal([]byte(doc), &parsed, jsonx.V1()); err != nil { // Malformed documents are skipped; GetContextKeys is best-effort. continue } diff --git a/services/iam/middleware.go b/services/iam/middleware.go index 547735ed14..a34c961c07 100644 --- a/services/iam/middleware.go +++ b/services/iam/middleware.go @@ -3,7 +3,7 @@ package iam import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/http" "strings" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/iampolicystream" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -752,7 +753,7 @@ func writeJSONRPCAccessDenied(c *echo.Context, action, msg string) error { Message: cmp.Or(msg, "User is not authorized to perform: "+action), } - body, err := json.Marshal(resp) + body, err := json.Marshal(resp, jsonx.V1()) if err != nil { return c.String(http.StatusBadRequest, `{"__type":"AccessDeniedException"}`) } diff --git a/services/iam/persistence.go b/services/iam/persistence.go index 6d65e24168..fca1c9f695 100644 --- a/services/iam/persistence.go +++ b/services/iam/persistence.go @@ -2,7 +2,7 @@ package iam import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -32,7 +32,7 @@ type backendSnapshot struct { PolicyVersions map[string][]StoredPolicyVersion `json:"policyVersions,omitempty"` RoleByARN map[string]string `json:"roleByARN,omitempty"` PolicyByARN map[string]string `json:"policyByARN,omitempty"` - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` PolicyAttachments map[string]policyAttachmentRefs `json:"policyAttachments,omitempty"` DeletedV1Policies map[string]bool `json:"deletedV1Policies,omitempty"` AccountProperties map[string]string `json:"accountProperties,omitempty"` @@ -329,8 +329,8 @@ func parseVersionNum(id string) int { type handlerSnapshot struct { Tags map[string]map[string]string `json:"tags,omitempty"` // Accounts holds each non-default account's handler snapshot. - Accounts map[string]json.RawMessage `json:"accounts,omitempty"` - Backend json.RawMessage `json:"backend,omitempty"` + Accounts map[string]jsontext.Value `json:"accounts,omitempty"` + Backend jsontext.Value `json:"backend,omitempty"` } // Snapshot implements persistence.Persistable. It combines the backend's own @@ -344,7 +344,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { if s, ok := h.Backend.(snapshotter); ok { if b := s.Snapshot(ctx); len(b) > 0 { - snap.Backend = json.RawMessage(b) + snap.Backend = jsontext.Value(b) } } diff --git a/services/iam/policy_validation.go b/services/iam/policy_validation.go index d8fb1f883f..8cc7a0511d 100644 --- a/services/iam/policy_validation.go +++ b/services/iam/policy_validation.go @@ -1,9 +1,12 @@ package iam import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // IAM policy-grammar validation. @@ -46,14 +49,14 @@ func isSupportedPolicyVersion(v string) bool { // rawStatement is a statement decoded with field presence preserved so that // "absent" can be distinguished from "present but empty". type rawStatement struct { - Effect *string `json:"Effect"` - Condition map[string]map[string]json.RawMessage `json:"Condition"` - Action json.RawMessage `json:"Action"` - NotAction json.RawMessage `json:"NotAction"` - Resource json.RawMessage `json:"Resource"` - NotResource json.RawMessage `json:"NotResource"` - Principal json.RawMessage `json:"Principal"` - NotPrincipal json.RawMessage `json:"NotPrincipal"` + Effect *string `json:"Effect"` + Condition map[string]map[string]jsontext.Value `json:"Condition"` + Action jsontext.Value `json:"Action"` + NotAction jsontext.Value `json:"NotAction"` + Resource jsontext.Value `json:"Resource"` + NotResource jsontext.Value `json:"NotResource"` + Principal jsontext.Value `json:"Principal"` + NotPrincipal jsontext.Value `json:"NotPrincipal"` } // validateIdentityPolicyDocument validates an identity-based policy document @@ -66,8 +69,8 @@ func validateIdentityPolicyDocument(policyDocument string) error { } // Top level must be a JSON object. - var top map[string]json.RawMessage - if err := json.Unmarshal([]byte(policyDocument), &top); err != nil { + var top map[string]jsontext.Value + if err := json.Unmarshal([]byte(policyDocument), &top, jsonx.V1()); err != nil { return fmt.Errorf("%w: policy document must be a JSON object", ErrMalformedPolicyDocument) } @@ -99,13 +102,13 @@ func validateIdentityPolicyDocument(policyDocument string) error { } // validatePolicyVersion checks the optional Version element. -func validatePolicyVersion(raw json.RawMessage) error { +func validatePolicyVersion(raw jsontext.Value) error { if raw == nil { return nil } var version string - if err := json.Unmarshal(raw, &version); err != nil { + if err := json.Unmarshal(raw, &version, jsonx.V1()); err != nil { return fmt.Errorf("%w: Version must be a string", ErrMalformedPolicyDocument) } @@ -121,11 +124,11 @@ func validatePolicyVersion(raw json.RawMessage) error { // decodeStatements accepts either a single statement object or an array of // statement objects, matching AWS's tolerance of both shapes. -func decodeStatements(raw json.RawMessage) ([]rawStatement, error) { +func decodeStatements(raw jsontext.Value) ([]rawStatement, error) { trimmed := strings.TrimSpace(string(raw)) if strings.HasPrefix(trimmed, "[") { var arr []rawStatement - if err := json.Unmarshal(raw, &arr); err != nil { + if err := json.Unmarshal(raw, &arr, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: Statement array is malformed", ErrMalformedPolicyDocument) } @@ -133,7 +136,7 @@ func decodeStatements(raw json.RawMessage) ([]rawStatement, error) { } var single rawStatement - if err := json.Unmarshal(raw, &single); err != nil { + if err := json.Unmarshal(raw, &single, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: Statement must be an object or array of objects", ErrMalformedPolicyDocument) } diff --git a/services/iam/request_scope.go b/services/iam/request_scope.go index 02e81702b2..3fddcddeec 100644 --- a/services/iam/request_scope.go +++ b/services/iam/request_scope.go @@ -2,7 +2,7 @@ package iam import ( "cmp" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -148,7 +149,7 @@ func filterPrincipalDocs(docs []string, condCtx ConditionContext) []string { func filterPrincipalDoc(doc string, condCtx ConditionContext) string { var raw map[string]any - if json.Unmarshal([]byte(doc), &raw) != nil { + if json.Unmarshal([]byte(doc), &raw, jsonx.V1()) != nil { return doc } @@ -162,7 +163,7 @@ func filterPrincipalDoc(doc string, condCtx ConditionContext) string { raw["Statement"] = kept - out, err := json.Marshal(raw) + out, err := json.Marshal(raw, jsonx.V1()) if err != nil { return doc } diff --git a/services/iam/resource_arn.go b/services/iam/resource_arn.go index 60d2c5fbdd..8b1bcaf65a 100644 --- a/services/iam/resource_arn.go +++ b/services/iam/resource_arn.go @@ -2,7 +2,7 @@ package iam import ( "context" - "encoding/json" + "encoding/json/v2" "net" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -230,7 +231,7 @@ func extractJSONTargetResourceARN(r *http.Request, target, accountID, region str } var payload jsonTargetResourcePayload - if jsonErr := json.Unmarshal(body, &payload); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &payload, jsonx.V1()); jsonErr != nil { return "" } diff --git a/services/iam/role_templates.go b/services/iam/role_templates.go index 2db30fd155..288582ab74 100644 --- a/services/iam/role_templates.go +++ b/services/iam/role_templates.go @@ -1,13 +1,14 @@ package iam import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "strconv" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func roleTemplateVersionKey(templateArn string, minorVersion int32) string { @@ -196,7 +197,7 @@ type acquiredRoleSpec struct { // returning an error without having mutated any backend state. func buildAcquiredRoleSpec(tv *RoleTemplateVersion, values map[string]string) (*acquiredRoleSpec, error) { assumeRolePolicy := substituteTemplateParams(tv.AssumeRolePolicyDocumentTemplate, values) - if assumeRolePolicy != "" && !json.Valid([]byte(assumeRolePolicy)) { + if assumeRolePolicy != "" && !jsontext.Value([]byte(assumeRolePolicy)).IsValid(jsonx.V1()) { return nil, fmt.Errorf("%w: invalid JSON in AssumeRolePolicyDocument", ErrMalformedPolicyDocument) } diff --git a/services/iam/roles.go b/services/iam/roles.go index 9fb017549e..2a13d95d57 100644 --- a/services/iam/roles.go +++ b/services/iam/roles.go @@ -1,7 +1,8 @@ package iam import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -31,7 +33,7 @@ func (b *InMemoryBackend) CreateRole( return nil, quotaExceeded("RolesPerAccount", limit) } - if assumeRolePolicyDocument != "" && !json.Valid([]byte(assumeRolePolicyDocument)) { + if assumeRolePolicyDocument != "" && !jsontext.Value([]byte(assumeRolePolicyDocument)).IsValid(jsonx.V1()) { return nil, fmt.Errorf( "%w: invalid JSON in AssumeRolePolicyDocument", ErrMalformedPolicyDocument, @@ -403,7 +405,7 @@ func (b *InMemoryBackend) UpdateAssumeRolePolicy(roleName, policyDocument string return fmt.Errorf("%w: role %q not found", ErrRoleNotFound, roleName) } - if policyDocument != "" && !json.Valid([]byte(policyDocument)) { + if policyDocument != "" && !jsontext.Value([]byte(policyDocument)).IsValid(jsonx.V1()) { return fmt.Errorf( "%w: invalid JSON in AssumeRolePolicyDocument", ErrMalformedPolicyDocument, @@ -451,7 +453,7 @@ func (b *InMemoryBackend) UpdateRole(roleName, description string) error { // parseJSON is a thin wrapper around json.Unmarshal for readability. func parseJSON(s string, v any) error { - return json.Unmarshal([]byte(s), v) //nolint:wrapcheck // thin delegation; callers handle the error + return json.Unmarshal([]byte(s), v, jsonx.V1()) //nolint:wrapcheck // thin delegation; callers handle the error } // TagRole merges the given key-value pairs into the role's Tags field. @@ -522,7 +524,7 @@ type trustPrincipal struct { func (p *trustPrincipal) UnmarshalJSON(data []byte) error { // Try string first. var s string - if err := json.Unmarshal(data, &s); err == nil { + if err := json.Unmarshal(data, &s, jsonx.V1()); err == nil { if s == "*" { p.AllowAll = true } else { @@ -533,8 +535,8 @@ func (p *trustPrincipal) UnmarshalJSON(data []byte) error { } // Try typed map. - var m map[string]json.RawMessage - if err := json.Unmarshal(data, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(data, &m, jsonx.V1()); err != nil { return err //nolint:wrapcheck // UnmarshalJSON interface; wrapping changes the type } @@ -543,10 +545,10 @@ func (p *trustPrincipal) UnmarshalJSON(data []byte) error { // Value can be a string or []string. var single string - if err := json.Unmarshal(v, &single); err == nil { + if err := json.Unmarshal(v, &single, jsonx.V1()); err == nil { vals = []string{single} } else { - if err2 := json.Unmarshal(v, &vals); err2 != nil { + if err2 := json.Unmarshal(v, &vals, jsonx.V1()); err2 != nil { continue } } @@ -685,11 +687,11 @@ func (b *InMemoryBackend) validateTrustPolicyPrincipal(policyJSON string) error var doc struct { Statement []struct { - Principal json.RawMessage `json:"Principal"` + Principal jsontext.Value `json:"Principal"` } `json:"Statement"` } - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return nil //nolint:nilerr // JSON validity confirmed upstream; this path handles schema mismatch } @@ -699,7 +701,7 @@ func (b *InMemoryBackend) validateTrustPolicyPrincipal(policyJSON string) error } var p trustPrincipal - if err := json.Unmarshal(stmt.Principal, &p); err != nil { + if err := json.Unmarshal(stmt.Principal, &p, jsonx.V1()); err != nil { return fmt.Errorf( "%w: statement[%d] Principal has unsupported shape", ErrMalformedPolicyDocument, i, diff --git a/services/identitystore/epoch_time_test.go b/services/identitystore/epoch_time_test.go index e3705776cd..73a5895e81 100644 --- a/services/identitystore/epoch_time_test.go +++ b/services/identitystore/epoch_time_test.go @@ -1,12 +1,14 @@ package identitystore //nolint:testpackage // epochTime is unexported; white-box unit tests of its wire encoding. import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestEpochTimeMarshalJSON verifies epochTime renders as an AWS @@ -43,7 +45,7 @@ func TestEpochTimeMarshalJSON(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := json.Marshal(epochTime(tt.in)) + got, err := json.Marshal(epochTime(tt.in), jsonx.V1()) require.NoError(t, err) assert.Equal(t, tt.want, string(got)) }) @@ -87,7 +89,7 @@ func TestEpochTimeUnmarshalJSON(t *testing.T) { var got epochTime - err := json.Unmarshal([]byte(tt.in), &got) + err := json.Unmarshal([]byte(tt.in), &got, jsonx.V1()) if tt.wantErr { require.Error(t, err) @@ -110,11 +112,11 @@ func TestEpochTimeRoundTrip(t *testing.T) { original := time.Now().UTC() - data, err := json.Marshal(epochTime(original)) + data, err := json.Marshal(epochTime(original), jsonx.V1()) require.NoError(t, err) var restored epochTime - require.NoError(t, json.Unmarshal(data, &restored)) + require.NoError(t, json.Unmarshal(data, &restored, jsonx.V1())) assert.WithinDuration(t, original, time.Time(restored), time.Millisecond) } diff --git a/services/identitystore/guard_sentinel_test.go b/services/identitystore/guard_sentinel_test.go index f54ff33709..cd7615a537 100644 --- a/services/identitystore/guard_sentinel_test.go +++ b/services/identitystore/guard_sentinel_test.go @@ -2,7 +2,7 @@ package identitystore //nolint:testpackage // pins errResponseWritten + unexport import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const internalTestStoreID = "d-1234567890" @@ -34,7 +35,7 @@ func newInternalTestContext(t *testing.T, body []byte) (*echo.Context, *httptest func mustJSON(t *testing.T, v any) []byte { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return b diff --git a/services/identitystore/handler.go b/services/identitystore/handler.go index 98477ab150..796a197084 100644 --- a/services/identitystore/handler.go +++ b/services/identitystore/handler.go @@ -3,7 +3,7 @@ package identitystore import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -129,7 +130,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { IdentityStoreID string `json:"IdentityStoreId"` } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -306,7 +307,7 @@ func (h *Handler) parseAlternateIDRequest(c *echo.Context, body []byte) (alterna IdentityStoreID string `json:"IdentityStoreId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { _ = h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") return alternateIDResult{}, errResponseWritten @@ -444,7 +445,7 @@ func validNextToken(body []byte) bool { NextToken string `json:"NextToken"` } - if json.Unmarshal(body, &req) != nil || req.NextToken == "" { + if json.Unmarshal(body, &req, jsonx.V1()) != nil || req.NextToken == "" { return true } diff --git a/services/identitystore/handler_group_memberships.go b/services/identitystore/handler_group_memberships.go index d6c1bd6b4f..6c1f343f42 100644 --- a/services/identitystore/handler_group_memberships.go +++ b/services/identitystore/handler_group_memberships.go @@ -2,12 +2,14 @@ package identitystore import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxIsMemberInGroupsIDs is the maximum number of GroupIds allowed per IsMemberInGroups request. @@ -74,7 +76,7 @@ type listGroupMembershipsRequest struct { func (h *Handler) handleCreateGroupMembership(ctx context.Context, c *echo.Context, body []byte) error { var req createGroupMembershipRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -103,7 +105,7 @@ func (h *Handler) handleCreateGroupMembership(ctx context.Context, c *echo.Conte func (h *Handler) handleDescribeGroupMembership(ctx context.Context, c *echo.Context, body []byte) error { var req describeGroupMembershipRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -125,7 +127,7 @@ func (h *Handler) handleDescribeGroupMembership(ctx context.Context, c *echo.Con func (h *Handler) handleListGroupMemberships(ctx context.Context, c *echo.Context, body []byte) error { var req listGroupMembershipsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -152,7 +154,7 @@ func (h *Handler) handleListGroupMemberships(ctx context.Context, c *echo.Contex func (h *Handler) handleDeleteGroupMembership(ctx context.Context, c *echo.Context, body []byte) error { var req deleteGroupMembershipRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -173,7 +175,7 @@ func (h *Handler) handleDeleteGroupMembership(ctx context.Context, c *echo.Conte func (h *Handler) handleGetGroupMembershipID(ctx context.Context, c *echo.Context, body []byte) error { var req getGroupMembershipIDRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -202,7 +204,7 @@ func (h *Handler) handleGetGroupMembershipID(ctx context.Context, c *echo.Contex func (h *Handler) handleListGroupMembershipsForMember(ctx context.Context, c *echo.Context, body []byte) error { var req listGroupMembershipsForMemberRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -229,7 +231,7 @@ func (h *Handler) handleListGroupMembershipsForMember(ctx context.Context, c *ec func (h *Handler) handleIsMemberInGroups(ctx context.Context, c *echo.Context, body []byte) error { var req isMemberInGroupsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/identitystore/handler_groups.go b/services/identitystore/handler_groups.go index 23253dd6e0..68fc6cbb66 100644 --- a/services/identitystore/handler_groups.go +++ b/services/identitystore/handler_groups.go @@ -2,11 +2,13 @@ package identitystore import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -56,7 +58,7 @@ type deleteGroupRequest struct { func (h *Handler) handleCreateGroup(ctx context.Context, c *echo.Context, body []byte) error { var req createGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -84,7 +86,7 @@ func (h *Handler) handleCreateGroup(ctx context.Context, c *echo.Context, body [ func (h *Handler) handleDescribeGroup(ctx context.Context, c *echo.Context, body []byte) error { var req describeGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -107,7 +109,7 @@ func (h *Handler) handleDescribeGroup(ctx context.Context, c *echo.Context, body //nolint:dupl // structurally parallel to handleListUsers; both validate MaxResults, filter, paginate func (h *Handler) handleListGroups(ctx context.Context, c *echo.Context, body []byte) error { var req listGroupsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -135,7 +137,7 @@ func (h *Handler) handleListGroups(ctx context.Context, c *echo.Context, body [] func (h *Handler) handleUpdateGroup(ctx context.Context, c *echo.Context, body []byte) error { var req updateGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -160,7 +162,7 @@ func (h *Handler) handleUpdateGroup(ctx context.Context, c *echo.Context, body [ func (h *Handler) handleDeleteGroup(ctx context.Context, c *echo.Context, body []byte) error { var req deleteGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/identitystore/handler_guard_test.go b/services/identitystore/handler_guard_test.go index fbd0dce956..180b04eba9 100644 --- a/services/identitystore/handler_guard_test.go +++ b/services/identitystore/handler_guard_test.go @@ -1,13 +1,14 @@ package identitystore_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/identitystore" ) @@ -303,7 +304,7 @@ func TestRequireIdentityStoreIDGuardReadsSingleWellFormedBody(t *testing.T) { var resp map[string]any require.NoError( t, - json.Unmarshal(rec.Body.Bytes(), &resp), + json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()), "response body must be a single well-formed JSON document, not two writes concatenated: %s", rec.Body.String(), ) diff --git a/services/identitystore/handler_test.go b/services/identitystore/handler_test.go index 024d1261e2..6acc68ac91 100644 --- a/services/identitystore/handler_test.go +++ b/services/identitystore/handler_test.go @@ -2,7 +2,7 @@ package identitystore_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/identitystore" ) @@ -37,7 +38,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -58,7 +59,7 @@ func parseResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -179,7 +180,7 @@ func TestExtractOperationAndResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) diff --git a/services/identitystore/handler_users.go b/services/identitystore/handler_users.go index 7b60c439a2..98b812f4fb 100644 --- a/services/identitystore/handler_users.go +++ b/services/identitystore/handler_users.go @@ -2,11 +2,13 @@ package identitystore import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -82,7 +84,7 @@ type listUsersRequest struct { func (h *Handler) handleCreateUser(ctx context.Context, c *echo.Context, body []byte) error { var req createUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -121,7 +123,7 @@ func (h *Handler) handleCreateUser(ctx context.Context, c *echo.Context, body [] func (h *Handler) handleDescribeUser(ctx context.Context, c *echo.Context, body []byte) error { var req describeUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -144,7 +146,7 @@ func (h *Handler) handleDescribeUser(ctx context.Context, c *echo.Context, body //nolint:dupl // structurally parallel to handleListGroups; both validate MaxResults, filter, paginate func (h *Handler) handleListUsers(ctx context.Context, c *echo.Context, body []byte) error { var req listUsersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -172,7 +174,7 @@ func (h *Handler) handleListUsers(ctx context.Context, c *echo.Context, body []b func (h *Handler) handleUpdateUser(ctx context.Context, c *echo.Context, body []byte) error { var req updateUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -197,7 +199,7 @@ func (h *Handler) handleUpdateUser(ctx context.Context, c *echo.Context, body [] func (h *Handler) handleDeleteUser(ctx context.Context, c *echo.Context, body []byte) error { var req deleteUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/identitystore/models.go b/services/identitystore/models.go index 09872ab585..f0b52becb9 100644 --- a/services/identitystore/models.go +++ b/services/identitystore/models.go @@ -1,10 +1,11 @@ package identitystore import ( - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---------------------------------------- @@ -23,13 +24,13 @@ type epochTime time.Time // MarshalJSON renders t as AWS JSON-protocol epoch seconds. func (t epochTime) MarshalJSON() ([]byte, error) { - return json.Marshal(awstime.Epoch(time.Time(t))) + return json.Marshal(awstime.Epoch(time.Time(t)), jsonx.V1()) } // UnmarshalJSON parses AWS JSON-protocol epoch seconds back into t. func (t *epochTime) UnmarshalJSON(data []byte) error { var seconds float64 - if err := json.Unmarshal(data, &seconds); err != nil { + if err := json.Unmarshal(data, &seconds, jsonx.V1()); err != nil { return err } diff --git a/services/identitystore/persistence.go b/services/identitystore/persistence.go index f3eee1f31c..2baeda14db 100644 --- a/services/identitystore/persistence.go +++ b/services/identitystore/persistence.go @@ -2,7 +2,7 @@ package identitystore import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -49,11 +49,11 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Counter int `json:"counter"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Counter int `json:"counter"` + Version int `json:"version"` } // persistenceDTOTables groups every ephemeral DTO table @@ -169,7 +169,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { diff --git a/services/inspector2/handler.go b/services/inspector2/handler.go index 7c8c6b3237..c11209e6ea 100644 --- a/services/inspector2/handler.go +++ b/services/inspector2/handler.go @@ -1,7 +1,7 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -390,7 +391,7 @@ func decodeFilterListRequest(c *echo.Context, maxResultsLimit int32) (filterList } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { _ = c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) return req, false diff --git a/services/inspector2/handler_cis_scans.go b/services/inspector2/handler_cis_scans.go index 7b282ed8b5..bc812b670b 100644 --- a/services/inspector2/handler_cis_scans.go +++ b/services/inspector2/handler_cis_scans.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -58,7 +59,7 @@ func (h *Handler) handleCreateCisScanConfiguration(c *echo.Context) error { SecurityLevel string `json:"securityLevel"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -89,7 +90,7 @@ func (h *Handler) handleDeleteCisScanConfiguration(c *echo.Context) error { ScanConfigurationArn string `json:"scanConfigurationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -114,7 +115,7 @@ func (h *Handler) handleUpdateCisScanConfiguration(c *echo.Context) error { SecurityLevel string `json:"securityLevel"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -171,7 +172,7 @@ func (h *Handler) handleStartCisSession(c *echo.Context) error { ScanJobID string `json:"scanJobId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -199,7 +200,7 @@ func (h *Handler) handleStopCisSession(c *echo.Context) error { ScanJobID string `json:"scanJobId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -221,7 +222,7 @@ func (h *Handler) handleSendCisSessionHealth(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -255,7 +256,7 @@ func (h *Handler) handleSendCisSessionTelemetry(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -281,7 +282,7 @@ func (h *Handler) handleGetCisScanReport(c *echo.Context) error { ReportFormat string `json:"reportFormat"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -314,7 +315,7 @@ func (h *Handler) handleGetCisScanResultDetails(c *echo.Context) error { MaxResults int32 `json:"maxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -415,7 +416,7 @@ func (h *Handler) listCisAggregation(c *echo.Context, l cisAggregationListing) e MaxResults int32 `json:"maxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_cis_scans_test.go b/services/inspector2/handler_cis_scans_test.go index c5b6175dc6..3101596ac8 100644 --- a/services/inspector2/handler_cis_scans_test.go +++ b/services/inspector2/handler_cis_scans_test.go @@ -5,13 +5,14 @@ package inspector2_test // canned data, and that unknown scan ARNs degrade benignly. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -27,7 +28,7 @@ func createCisConfig(t *testing.T, h *inspector2.Handler, name string, accountID require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["scanConfigurationArn"].(string) require.True(t, ok) require.NotEmpty(t, arn) @@ -43,7 +44,7 @@ func firstScanArn(t *testing.T, h *inspector2.Handler, cfgARN string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) scans, _ := resp["scans"].([]any) for _, raw := range scans { @@ -71,7 +72,7 @@ func TestCisScans_CreatedConfigMaterializesScan(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var empty map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty, jsonx.V1())) scans, _ := empty["scans"].([]any) assert.Empty(t, scans) @@ -79,7 +80,7 @@ func TestCisScans_CreatedConfigMaterializesScan(t *testing.T) { cfgARN := createCisConfig(t, h, "scan-a", []string{"111111111111"}) rec = auditDo(t, h, http.MethodPost, "/cis/scan/list", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &empty, jsonx.V1())) scans, _ = empty["scans"].([]any) require.Len(t, scans, 1) @@ -102,7 +103,7 @@ func TestCisScans_ResultDetailsReflectConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, _ := resp["scanResultDetails"].([]any) // Two target accounts x catalog checks => non-empty, and every result is @@ -137,7 +138,7 @@ func TestCisScans_ReportReflectsScan(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["status"]) assert.Equal(t, scanARN, resp["scanArn"]) // totalChecks reported as a JSON number. @@ -160,7 +161,7 @@ func TestCisScans_AggregationsConsistent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var byCheck map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byCheck)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byCheck, jsonx.V1())) checks, _ := byCheck["checkAggregations"].([]any) require.NotEmpty(t, checks) @@ -178,7 +179,7 @@ func TestCisScans_AggregationsConsistent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var byTarget map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byTarget)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &byTarget, jsonx.V1())) targets, _ := byTarget["targetResourceAggregations"].([]any) assert.Len(t, targets, 2) } @@ -198,7 +199,7 @@ func TestCisScans_DeleteConfigRemovesScans(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) scans, _ := resp["scans"].([]any) assert.Empty(t, scans) } @@ -227,7 +228,7 @@ func TestCisScans_UnknownScanArnDegradesBenignly(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp[tc.key] assert.True(t, ok) }) @@ -323,7 +324,7 @@ func TestCisScanConfigurationLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["scanConfigurationArn"]) }, }, @@ -336,7 +337,7 @@ func TestCisScanConfigurationLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) cfgs, _ := resp["scanConfigurations"].([]any) assert.Len(t, cfgs, 1) }, @@ -415,7 +416,7 @@ func TestCisScanConfigurationFullCycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) cfgARN, _ := createResp["scanConfigurationArn"].(string) require.NotEmpty(t, cfgARN) @@ -434,7 +435,7 @@ func TestCisScanConfigurationFullCycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) cfgs, _ := listResp["scanConfigurations"].([]any) assert.Empty(t, cfgs) } @@ -559,7 +560,7 @@ func TestCisSessionOps(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["status"]) }, }, @@ -572,7 +573,7 @@ func TestCisSessionOps(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) _, ok := resp["scanResultDetails"] assert.True(t, ok) }, @@ -586,7 +587,7 @@ func TestCisSessionOps(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) scans, _ := resp["scans"].([]any) assert.Empty(t, scans) }, @@ -600,7 +601,7 @@ func TestCisSessionOps(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) _, ok := resp["checkAggregations"] assert.True(t, ok) }, @@ -614,7 +615,7 @@ func TestCisSessionOps(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) _, ok := resp["targetResourceAggregations"] assert.True(t, ok) }, diff --git a/services/inspector2/handler_code_security.go b/services/inspector2/handler_code_security.go index 534a6cdced..d4daf8d4dd 100644 --- a/services/inspector2/handler_code_security.go +++ b/services/inspector2/handler_code_security.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -61,7 +62,7 @@ func (h *Handler) handleCreateCodeSecurityIntegration(c *echo.Context) error { Type string `json:"type"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -91,7 +92,7 @@ func (h *Handler) handleDeleteCodeSecurityIntegration(c *echo.Context) error { IntegrationArn string `json:"integrationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -115,7 +116,7 @@ func (h *Handler) handleGetCodeSecurityIntegration(c *echo.Context) error { IntegrationArn string `json:"integrationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -164,7 +165,7 @@ func (h *Handler) handleUpdateCodeSecurityIntegration(c *echo.Context) error { IntegrationArn string `json:"integrationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -226,7 +227,7 @@ func (h *Handler) handleCreateCodeSecurityScanConfiguration(c *echo.Context) err } `json:"configuration"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -252,7 +253,7 @@ func (h *Handler) handleDeleteCodeSecurityScanConfiguration(c *echo.Context) err ScanConfigurationArn string `json:"scanConfigurationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -277,7 +278,7 @@ func (h *Handler) handleGetCodeSecurityScanConfiguration(c *echo.Context) error ScanConfigurationArn string `json:"scanConfigurationArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -354,7 +355,7 @@ func (h *Handler) handleUpdateCodeSecurityScanConfiguration(c *echo.Context) err } `json:"configuration"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -483,7 +484,7 @@ func (h *Handler) handleBatchAssociateCodeSecurityScanConfiguration( AssociateConfigurationRequests []codeSecurityBatchItemRequest `json:"associateConfigurationRequests"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -554,7 +555,7 @@ func (h *Handler) handleBatchDisassociateCodeSecurityScanConfiguration( DisassociateConfigurationRequests []codeSecurityBatchItemRequest `json:"disassociateConfigurationRequests"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -594,7 +595,7 @@ func (h *Handler) handleListCodeSecurityScanConfigurationAssociations(c *echo.Co MaxResults int32 `json:"maxResults"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -637,7 +638,7 @@ func (h *Handler) handleStartCodeSecurityScan(c *echo.Context) error { } `json:"resource"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -667,7 +668,7 @@ func (h *Handler) handleGetCodeSecurityScan(c *echo.Context) error { ScanID string `json:"scanId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_code_security_test.go b/services/inspector2/handler_code_security_test.go index a93d07dbd0..43ec4ac15d 100644 --- a/services/inspector2/handler_code_security_test.go +++ b/services/inspector2/handler_code_security_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -25,7 +26,7 @@ func TestCodeSecurityIntegrationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) integARN, _ := createResp["integrationArn"].(string) require.NotEmpty(t, integARN) assert.Equal(t, "ACTIVE", createResp["status"]) @@ -37,7 +38,7 @@ func TestCodeSecurityIntegrationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, integARN, getResp["integrationArn"]) // List @@ -45,7 +46,7 @@ func TestCodeSecurityIntegrationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) integrations, _ := listResp["integrations"].([]any) assert.Len(t, integrations, 1) @@ -132,7 +133,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) cfgARN, _ := createResp["scanConfigurationArn"].(string) require.NotEmpty(t, cfgARN) @@ -143,7 +144,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "ACCOUNT", getResp["level"]) assert.NotContains(t, getResp, "status") configuration, ok := getResp["configuration"].(map[string]any) @@ -155,7 +156,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) cfgs, _ := listResp["configurations"].([]any) require.Len(t, cfgs, 1) summary, ok := cfgs[0].(map[string]any) @@ -191,7 +192,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var batchAssocResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchAssocResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchAssocResp, jsonx.V1())) successful, _ := batchAssocResp["successfulAssociations"].([]any) assert.Len(t, successful, 1) @@ -202,7 +203,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp, jsonx.V1())) assocs, _ := assocResp["associations"].([]any) assert.Len(t, assocs, 1) @@ -224,7 +225,7 @@ func TestCodeSecurityScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var assocResp2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp2, jsonx.V1())) assocs2, _ := assocResp2["associations"].([]any) assert.Empty(t, assocs2) @@ -380,7 +381,7 @@ func TestCodeSecurityScanConfigurationValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) cfgARN, _ := createResp["scanConfigurationArn"].(string) require.NotEmpty(t, cfgARN) @@ -403,7 +404,7 @@ func TestCodeSecurityScanLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) scanID, _ := startResp["scanId"].(string) require.NotEmpty(t, scanID) @@ -414,7 +415,7 @@ func TestCodeSecurityScanLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, scanID, getResp["scanId"]) } @@ -505,7 +506,7 @@ func TestConnectorLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -537,7 +538,7 @@ func TestConnectorLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["items"].([]any) assert.Len(t, items, 1) @@ -553,7 +554,7 @@ func TestConnectorLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, rec2.Body.String()) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) items2, _ := resp2["items"].([]any) assert.Empty(t, items2) }, @@ -572,12 +573,12 @@ func TestConnectorLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, connectorArn, resp["connectorArn"]) listRec := auditDo(t, h, http.MethodPost, "/connector/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["items"].([]any) require.Len(t, items, 1) @@ -612,7 +613,7 @@ func TestConnectorLifecycle(t *testing.T) { listRec := auditDo(t, h, http.MethodPost, "/connector/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp["items"].([]any)) }, }, @@ -693,7 +694,7 @@ func TestConnectorScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) cfgs, ok := listResp["scanConfigurations"].([]any) require.True(t, ok) require.Len(t, cfgs, 1) @@ -739,7 +740,7 @@ func TestConnectorScanConfigurationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfgs, _ := resp["scanConfigurations"].([]any) require.Len(t, cfgs, 1) assert.Equal(t, arnA, cfgs[0].(map[string]any)["awsConfigConnectorArn"]) diff --git a/services/inspector2/handler_connectors.go b/services/inspector2/handler_connectors.go index 6861ff880d..b238162dff 100644 --- a/services/inspector2/handler_connectors.go +++ b/services/inspector2/handler_connectors.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -98,7 +99,7 @@ func (h *Handler) handleCreateConnector(c *echo.Context) error { } var req createConnectorRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) } @@ -152,7 +153,7 @@ func (h *Handler) handleUpdateConnector(c *echo.Context) error { } var req updateConnectorRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) } @@ -190,7 +191,7 @@ func (h *Handler) handleDeleteConnector(c *echo.Context) error { ConnectorArn string `json:"connectorArn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) } @@ -405,7 +406,7 @@ func decodeListConnectorsRequest(c *echo.Context) (listConnectorsRequest, bool) } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { _ = c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) return req, false @@ -479,7 +480,7 @@ func (h *Handler) handleListConnectorScanConfigurations(c *echo.Context) error { var req listConnectorScanConfigurationsRequest if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) } } @@ -533,7 +534,7 @@ func (h *Handler) handleUpdateConnectorScanConfiguration(c *echo.Context) error } var req updateConnectorScanConfigurationRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse(errValidation, "invalid JSON")) } diff --git a/services/inspector2/handler_coverage_reporting.go b/services/inspector2/handler_coverage_reporting.go index 2ab081bcec..81142c5cdd 100644 --- a/services/inspector2/handler_coverage_reporting.go +++ b/services/inspector2/handler_coverage_reporting.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -90,7 +91,7 @@ func (h *Handler) handleListCoverageStatistics(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_coverage_reporting_test.go b/services/inspector2/handler_coverage_reporting_test.go index 9da706e235..7e98228367 100644 --- a/services/inspector2/handler_coverage_reporting_test.go +++ b/services/inspector2/handler_coverage_reporting_test.go @@ -1,7 +1,7 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -20,7 +21,7 @@ func TestListCoverageEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, ok := resp["coveredResources"].([]any) require.True(t, ok) assert.Empty(t, resources) @@ -53,7 +54,7 @@ func TestListCoverageSeeded(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, ok := resp["coveredResources"].([]any) require.True(t, ok) require.Len(t, resources, 2) @@ -71,7 +72,7 @@ func TestListCoverageSeeded(t *testing.T) { }, }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, ok = resp["coveredResources"].([]any) require.True(t, ok) require.Len(t, resources, 1) @@ -106,7 +107,7 @@ func TestListCoverageStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InDelta(t, float64(3), resp["totalCounts"], 0) groups, ok := resp["countsByGroup"].([]any) require.True(t, ok) @@ -117,7 +118,7 @@ func TestListCoverageStatistics(t *testing.T) { "groupBy": "RESOURCE_TYPE", }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InDelta(t, float64(3), resp["totalCounts"], 0) groups, ok = resp["countsByGroup"].([]any) @@ -231,7 +232,7 @@ func TestListCoverage_FilterCriteriaFacets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, ok := resp["coveredResources"].([]any) require.True(t, ok) require.Len(t, resources, 1) diff --git a/services/inspector2/handler_ec2_configuration.go b/services/inspector2/handler_ec2_configuration.go index 55a53aa2b9..c5d2d04398 100644 --- a/services/inspector2/handler_ec2_configuration.go +++ b/services/inspector2/handler_ec2_configuration.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -49,7 +50,7 @@ func (h *Handler) handleUpdateEc2DeepInspectionConfiguration(c *echo.Context) er } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -85,7 +86,7 @@ func (h *Handler) handleUpdateOrgEc2DeepInspectionConfiguration(c *echo.Context) } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -111,7 +112,7 @@ func (h *Handler) handleBatchGetMemberEc2DeepInspectionStatus(c *echo.Context) e } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -141,7 +142,7 @@ func (h *Handler) handleBatchUpdateMemberEc2DeepInspectionStatus(c *echo.Context } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_ec2_configuration_test.go b/services/inspector2/handler_ec2_configuration_test.go index 935b035207..77b8c98d0d 100644 --- a/services/inspector2/handler_ec2_configuration_test.go +++ b/services/inspector2/handler_ec2_configuration_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEc2DeepInspectionConfiguration(t *testing.T) { @@ -36,7 +38,7 @@ func TestEc2DeepInspectionConfiguration(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) _, ok := resp["status"] assert.True(t, ok) }, @@ -50,7 +52,7 @@ func TestEc2DeepInspectionConfiguration(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) paths, _ := resp["packagePaths"].([]any) assert.Len(t, paths, 2) }, @@ -79,7 +81,7 @@ func TestEc2DeepInspectionConfiguration(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) members, _ := resp["accountIds"].([]any) assert.Len(t, members, 1) }, @@ -100,7 +102,7 @@ func TestEc2DeepInspectionConfiguration(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) accounts, _ := resp["accountIds"].([]any) assert.Len(t, accounts, 1) }, diff --git a/services/inspector2/handler_ecr_configuration.go b/services/inspector2/handler_ecr_configuration.go index 47e53c0f36..aa2d71d0cc 100644 --- a/services/inspector2/handler_ecr_configuration.go +++ b/services/inspector2/handler_ecr_configuration.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -32,7 +33,7 @@ func (h *Handler) handleGetClustersForImage(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_ecr_configuration_test.go b/services/inspector2/handler_ecr_configuration_test.go index fab46cc0f3..811c78b1ae 100644 --- a/services/inspector2/handler_ecr_configuration_test.go +++ b/services/inspector2/handler_ecr_configuration_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetClustersForImage exercises the fixed request shape: real @@ -24,7 +26,7 @@ func TestGetClustersForImage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["cluster"] assert.True(t, ok) } diff --git a/services/inspector2/handler_enablement.go b/services/inspector2/handler_enablement.go index 1bc1177837..1d07e0857c 100644 --- a/services/inspector2/handler_enablement.go +++ b/services/inspector2/handler_enablement.go @@ -1,7 +1,7 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "slices" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -35,7 +36,7 @@ func (h *Handler) handleToggle(c *echo.Context, enable bool) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -123,7 +124,7 @@ func (h *Handler) handleBatchGetAccountStatus(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } @@ -196,7 +197,7 @@ func (h *Handler) handleGetConfiguration(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } } @@ -245,7 +246,7 @@ func (h *Handler) handleUpdateConfiguration(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -293,7 +294,7 @@ func (h *Handler) handleListAccountPermissions(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_enablement_test.go b/services/inspector2/handler_enablement_test.go index e577802608..fefed7d48f 100644 --- a/services/inspector2/handler_enablement_test.go +++ b/services/inspector2/handler_enablement_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -29,7 +30,7 @@ func TestEnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts, ok := resp["accounts"].([]any) require.True(t, ok) require.Len(t, accounts, 1) @@ -54,7 +55,7 @@ func TestEnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts, ok := resp["accounts"].([]any) require.True(t, ok) require.Len(t, accounts, 1) @@ -98,7 +99,7 @@ func TestBatchGetAccountStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts, ok := resp["accounts"].([]any) require.True(t, ok) require.Len(t, accounts, 1) @@ -119,7 +120,7 @@ func TestBatchGetAccountStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts := resp["accounts"].([]any) acc := accounts[0].(map[string]any) state := acc["state"].(map[string]any) @@ -152,7 +153,7 @@ func TestConfigurationGetUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasEC2 := resp["ec2Configuration"] assert.True(t, hasEC2) _, hasECR := resp["ecrConfiguration"] @@ -177,7 +178,7 @@ func TestConfigurationGetUpdate(t *testing.T) { // Verify via get getRec := auditDo(t, h, http.MethodPost, "/configuration/get", nil) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ec2Cfg := getResp["ec2Configuration"].(map[string]any) scanModeState := ec2Cfg["scanModeState"].(map[string]any) @@ -219,7 +220,7 @@ func TestAccountPermissions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) perms, ok := resp["permissions"].([]any) require.True(t, ok) @@ -244,7 +245,7 @@ func TestAccountPermissionsFilteredByService(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) perms, ok := resp["permissions"].([]any) require.True(t, ok) @@ -367,7 +368,7 @@ func TestEnableDisablePerResourceType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts := resp["accounts"].([]any) require.Len(t, accounts, 1) diff --git a/services/inspector2/handler_encryption.go b/services/inspector2/handler_encryption.go index 6f7c9c0a1f..cacb372695 100644 --- a/services/inspector2/handler_encryption.go +++ b/services/inspector2/handler_encryption.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -47,7 +48,7 @@ func (h *Handler) handleResetEncryptionKey(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -74,7 +75,7 @@ func (h *Handler) handleUpdateEncryptionKey(c *echo.Context) error { ScanType string `json:"scanType"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_encryption_test.go b/services/inspector2/handler_encryption_test.go index fd1f3ed103..4f249b92c8 100644 --- a/services/inspector2/handler_encryption_test.go +++ b/services/inspector2/handler_encryption_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEncryptionKeyLifecycle(t *testing.T) { @@ -36,7 +38,7 @@ func TestEncryptionKeyLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "AWS_OWNED_KEY", resp["kmsKeyId"]) }, }, diff --git a/services/inspector2/handler_filters.go b/services/inspector2/handler_filters.go index 0d7f8f6522..d7ed28d9c0 100644 --- a/services/inspector2/handler_filters.go +++ b/services/inspector2/handler_filters.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateFilter handles POST /filters/create. @@ -26,7 +27,7 @@ func (h *Handler) handleCreateFilter(c *echo.Context) error { Reason string `json:"reason"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -72,7 +73,7 @@ func (h *Handler) handleUpdateFilter(c *echo.Context) error { Name string `json:"name"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -109,7 +110,7 @@ func (h *Handler) handleDeleteFilter(c *echo.Context) error { Arn string `json:"arn"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -142,7 +143,7 @@ func (h *Handler) handleListFilters(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_filters_test.go b/services/inspector2/handler_filters_test.go index 3a807a37ac..f0590cb04b 100644 --- a/services/inspector2/handler_filters_test.go +++ b/services/inspector2/handler_filters_test.go @@ -1,7 +1,7 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -66,7 +67,7 @@ func TestFilterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) filters, ok := resp["filters"].([]any) require.True(t, ok) require.Len(t, filters, 1) @@ -89,7 +90,7 @@ func TestFilterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) filters := resp["filters"].([]any) require.Len(t, filters, 1) @@ -116,7 +117,7 @@ func TestFilterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) filters := resp["filters"].([]any) require.Len(t, filters, 1) @@ -139,13 +140,13 @@ func TestFilterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, filterARN, resp["arn"]) // Verify via list listRec := auditDo(t, h, http.MethodPost, "/filters/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) filters := listResp["filters"].([]any) require.Len(t, filters, 1) @@ -176,7 +177,7 @@ func TestFilterLifecycle(t *testing.T) { // Use a list check to verify filter was created listRec := auditDo(t, h, http.MethodPost, "/filters/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["filters"].([]any), 1) rec := auditDo(t, h, http.MethodPost, "/filters/delete", map[string]any{ @@ -186,7 +187,7 @@ func TestFilterLifecycle(t *testing.T) { listRec2 := auditDo(t, h, http.MethodPost, "/filters/list", map[string]any{}) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2, jsonx.V1())) assert.Empty(t, listResp2["filters"].([]any)) }, }, @@ -226,7 +227,7 @@ func TestFilterLifecycle(t *testing.T) { listRec := auditDo(t, h, http.MethodPost, "/filters/list", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) filters := listResp["filters"].([]any) require.Len(t, filters, 1) diff --git a/services/inspector2/handler_findings.go b/services/inspector2/handler_findings.go index 5299e98cb2..52bbba360b 100644 --- a/services/inspector2/handler_findings.go +++ b/services/inspector2/handler_findings.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -173,6 +174,7 @@ func resourceToWire(r FindingResource) map[string]any { return res } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleCreateFindingsReport(c *echo.Context) error { body, err := httputils.ReadBody(c.Request()) if err != nil { @@ -186,7 +188,7 @@ func (h *Handler) handleCreateFindingsReport(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -215,7 +217,7 @@ func (h *Handler) handleCancelFindingsReport(c *echo.Context) error { ReportID string `json:"reportId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -237,7 +239,7 @@ func (h *Handler) handleGetFindingsReportStatus(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -286,7 +288,7 @@ func (h *Handler) handleListFindingAggregations(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -316,7 +318,7 @@ func (h *Handler) handleSearchVulnerabilities(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -404,7 +406,7 @@ func (h *Handler) handleBatchGetCodeSnippet(c *echo.Context) error { FindingArns []string `json:"findingArns"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -430,7 +432,7 @@ func (h *Handler) handleBatchGetFindingDetails(c *echo.Context) error { FindingArns []string `json:"findingArns"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_findings_core_test.go b/services/inspector2/handler_findings_core_test.go index a0cbb60c19..76c62c5ec7 100644 --- a/services/inspector2/handler_findings_core_test.go +++ b/services/inspector2/handler_findings_core_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -27,7 +28,7 @@ func TestListFindingsBasic(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, ok := resp["findings"] require.True(t, ok) require.NotNil(t, findings) @@ -379,7 +380,7 @@ func TestListFindingsResponseStructure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "findings", "response must have findings key") } diff --git a/services/inspector2/handler_findings_reports_test.go b/services/inspector2/handler_findings_reports_test.go index 62438032ad..eb3d1b275e 100644 --- a/services/inspector2/handler_findings_reports_test.go +++ b/services/inspector2/handler_findings_reports_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -28,7 +29,7 @@ func TestFindingsReportLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) reportID, _ := createResp["reportId"].(string) require.NotEmpty(t, reportID) @@ -39,7 +40,7 @@ func TestFindingsReportLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var statusResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &statusResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &statusResp, jsonx.V1())) assert.Equal(t, reportID, statusResp["reportId"]) assert.Equal(t, "SUCCEEDED", statusResp["status"]) @@ -74,7 +75,7 @@ func TestFindingAggregations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["responses"] assert.True(t, ok) } @@ -106,7 +107,7 @@ func TestSearchVulnerabilities(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vulns, ok := resp["vulnerabilities"].([]any) require.True(t, ok) @@ -134,7 +135,7 @@ func TestSearchVulnerabilitiesNoMatches(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vulns, ok := resp["vulnerabilities"].([]any) require.True(t, ok) @@ -161,7 +162,7 @@ func TestBatchGetCodeSnippet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["codeSnippetResults"].([]any) require.True(t, ok) @@ -207,7 +208,7 @@ func TestBatchGetFindingDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) details, ok := resp["findingDetails"].([]any) require.True(t, ok) diff --git a/services/inspector2/handler_members.go b/services/inspector2/handler_members.go index aa25e8fd2c..2eaf30b7bc 100644 --- a/services/inspector2/handler_members.go +++ b/services/inspector2/handler_members.go @@ -1,13 +1,14 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -32,7 +33,7 @@ func (h *Handler) handleAssociateMember(c *echo.Context) error { AccountID string `json:"accountId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -56,7 +57,7 @@ func (h *Handler) handleDisassociateMember(c *echo.Context) error { AccountID string `json:"accountId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -77,7 +78,7 @@ func (h *Handler) handleGetMember(c *echo.Context) error { AccountID string `json:"accountId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -116,7 +117,7 @@ func (h *Handler) handleListMembers(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_members_test.go b/services/inspector2/handler_members_test.go index ba92c735ed..9e52bb1dd1 100644 --- a/services/inspector2/handler_members_test.go +++ b/services/inspector2/handler_members_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestMembersLifecycle(t *testing.T) { @@ -36,7 +38,7 @@ func TestMembersLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "111111111111", resp["accountId"]) }, }, @@ -64,7 +66,7 @@ func TestMembersLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) m, _ := resp["member"].(map[string]any) assert.Equal(t, "222222222222", m["accountId"]) }, @@ -78,7 +80,7 @@ func TestMembersLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) members, _ := resp["members"].([]any) assert.Len(t, members, 1) }, diff --git a/services/inspector2/handler_organization.go b/services/inspector2/handler_organization.go index a6dbb3d0cb..23f1d0a9f7 100644 --- a/services/inspector2/handler_organization.go +++ b/services/inspector2/handler_organization.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -40,7 +41,7 @@ func (h *Handler) handleEnableDelegatedAdminAccount(c *echo.Context) error { ClientToken string `json:"clientToken"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -73,7 +74,7 @@ func (h *Handler) handleDisableDelegatedAdminAccount(c *echo.Context) error { DelegatedAdminAccountID string `json:"delegatedAdminAccountId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -148,7 +149,7 @@ func (h *Handler) handleUpdateOrganizationConfiguration(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), diff --git a/services/inspector2/handler_organization_test.go b/services/inspector2/handler_organization_test.go index 7c36eed66c..a386c90c6d 100644 --- a/services/inspector2/handler_organization_test.go +++ b/services/inspector2/handler_organization_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDelegatedAdminAccountLifecycle(t *testing.T) { @@ -36,7 +38,7 @@ func TestDelegatedAdminAccountLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "333333333333", resp["delegatedAdminAccountId"]) assert.Equal(t, "ENABLED", resp["status"]) }, @@ -50,7 +52,7 @@ func TestDelegatedAdminAccountLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) d, _ := resp["delegatedAdmin"].(map[string]any) assert.Equal(t, "333333333333", d["accountId"]) }, @@ -64,7 +66,7 @@ func TestDelegatedAdminAccountLifecycle(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) admins, _ := resp["delegatedAdminAccounts"].([]any) assert.Len(t, admins, 1) }, @@ -150,7 +152,7 @@ func TestOrganizationConfiguration(t *testing.T) { t.Helper() assert.Equal(t, http.StatusOK, code) var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) _, ok := resp["autoEnable"] assert.True(t, ok) }, diff --git a/services/inspector2/handler_sbom.go b/services/inspector2/handler_sbom.go index a40b3346cb..1ec38d2ee4 100644 --- a/services/inspector2/handler_sbom.go +++ b/services/inspector2/handler_sbom.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -19,6 +20,7 @@ const ( pathSbomExportGet = "/sbomexport/get" ) +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleCreateSbomExport(c *echo.Context) error { body, err := httputils.ReadBody(c.Request()) if err != nil { @@ -36,7 +38,7 @@ func (h *Handler) handleCreateSbomExport(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -62,7 +64,7 @@ func (h *Handler) handleCancelSbomExport(c *echo.Context) error { ReportID string `json:"reportId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } @@ -83,7 +85,7 @@ func (h *Handler) handleGetSbomExport(c *echo.Context) error { ReportID string `json:"reportId"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_sbom_test.go b/services/inspector2/handler_sbom_test.go index 3989d0f6f7..b0a4e0e53e 100644 --- a/services/inspector2/handler_sbom_test.go +++ b/services/inspector2/handler_sbom_test.go @@ -1,12 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSbomExportLifecycle(t *testing.T) { @@ -28,7 +30,7 @@ func TestSbomExportLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) reportID, _ := createResp["reportId"].(string) require.NotEmpty(t, reportID) @@ -39,7 +41,7 @@ func TestSbomExportLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["status"]) assert.Equal(t, "CYCLONEDX_1_4", getResp["format"]) diff --git a/services/inspector2/handler_tags.go b/services/inspector2/handler_tags.go index d97c85c755..3a75aa2584 100644 --- a/services/inspector2/handler_tags.go +++ b/services/inspector2/handler_tags.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleListTagsForResource handles GET /tags/{resourceArn}. @@ -50,7 +51,7 @@ func (h *Handler) handleTagResource(c *echo.Context) error { Tags map[string]string `json:"tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_tags_test.go b/services/inspector2/handler_tags_test.go index 155a0ee66f..455e81a6a2 100644 --- a/services/inspector2/handler_tags_test.go +++ b/services/inspector2/handler_tags_test.go @@ -1,7 +1,7 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -40,7 +41,7 @@ func TestTagsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "security", tags["team"]) @@ -72,7 +73,7 @@ func TestTagsLifecycle(t *testing.T) { listRec := auditDo(t, h, http.MethodGet, "/tags/"+filterARN, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].(map[string]any) assert.Equal(t, "yes", tags["keep"]) _, hasRemove := tags["remove"] @@ -122,7 +123,7 @@ func TestTagsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code, listRec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].(map[string]any) assert.Empty(t, tags) }, diff --git a/services/inspector2/handler_test.go b/services/inspector2/handler_test.go index eed8c2e36a..956e160b76 100644 --- a/services/inspector2/handler_test.go +++ b/services/inspector2/handler_test.go @@ -8,7 +8,7 @@ package inspector2_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -56,7 +57,7 @@ func auditDo(t *testing.T, h *inspector2.Handler, method, path string, body any) if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -82,7 +83,7 @@ func auditCreateFilter(t *testing.T, h *inspector2.Handler, name string) string require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["arn"].(string) require.True(t, ok) require.NotEmpty(t, arn) @@ -95,7 +96,7 @@ func auditCreateFilter(t *testing.T, h *inspector2.Handler, name string) string func inspector2UnmarshalError(t *testing.T, data []byte, v any) error { t.Helper() - return json.Unmarshal(data, v) + return json.Unmarshal(data, v, jsonx.V1()) } // auditCreateConnector creates an Azure connector named name, associated @@ -116,7 +117,7 @@ func auditCreateConnector(t *testing.T, h *inspector2.Handler, name, awsConfigCo require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, ok := resp["connectorArn"].(string) require.True(t, ok) require.NotEmpty(t, arn) @@ -149,7 +150,7 @@ func parityDo(t *testing.T, h *inspector2.Handler, method, path string, body any if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -185,7 +186,7 @@ func parityListFindings(t *testing.T, h *inspector2.Handler, body map[string]any require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, ok := resp["findings"].([]any) if !ok { @@ -205,7 +206,7 @@ func parityListFindingsWithToken( require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["findings"].([]any) nextToken, _ := resp["nextToken"].(string) diff --git a/services/inspector2/handler_usage.go b/services/inspector2/handler_usage.go index 4fea58a369..c520724591 100644 --- a/services/inspector2/handler_usage.go +++ b/services/inspector2/handler_usage.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -32,7 +33,7 @@ func (h *Handler) handleListUsageTotals(c *echo.Context) error { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON( http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON"), @@ -60,7 +61,7 @@ func (h *Handler) handleBatchGetFreeTrialInfo(c *echo.Context) error { AccountIDs []string `json:"accountIds"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return c.JSON(http.StatusBadRequest, errorResponse("ValidationException", "invalid JSON")) } diff --git a/services/inspector2/handler_usage_test.go b/services/inspector2/handler_usage_test.go index 3677fda903..798e80f8cd 100644 --- a/services/inspector2/handler_usage_test.go +++ b/services/inspector2/handler_usage_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -21,7 +22,7 @@ func TestUsageTotals(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) totals, ok := resp["totals"].([]any) require.True(t, ok) require.Len(t, totals, 1) @@ -56,7 +57,7 @@ func TestUsageTotalsReflectsEnabledCoverage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) totals, ok := resp["totals"].([]any) require.True(t, ok) require.Len(t, totals, 1) @@ -84,7 +85,7 @@ func TestBatchGetFreeTrialInfo(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accounts, _ := resp["accounts"].([]any) assert.Len(t, accounts, 1) } diff --git a/services/inspector2/paging.go b/services/inspector2/paging.go index 297a3f6741..0683290e8b 100644 --- a/services/inspector2/paging.go +++ b/services/inspector2/paging.go @@ -1,12 +1,13 @@ package inspector2 import ( - "encoding/json" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const defaultPageSize = 100 @@ -30,7 +31,7 @@ func readPageRequest(c *echo.Context) (pageRequest, error) { } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return req, fmt.Errorf("%w: invalid JSON", ErrValidation) } } diff --git a/services/inspector2/persistence.go b/services/inspector2/persistence.go index c091672cfe..67fbdc2dbd 100644 --- a/services/inspector2/persistence.go +++ b/services/inspector2/persistence.go @@ -2,10 +2,12 @@ package inspector2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -79,7 +81,7 @@ const findingsTableName = "findings" // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` EnabledTypes map[string]bool `json:"enabledTypes"` MemberEnabled map[string]map[string]bool `json:"memberEnabled,omitempty"` @@ -121,7 +123,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { dtos[i] = toFindingSnapshot(f) } - findingsData, err := json.Marshal(dtos) + findingsData, err := json.Marshal(dtos, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "inspector2: snapshot findings table marshal failed", "error", err) @@ -194,7 +196,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { if hasFindings { var dtos []*findingSnapshot - if err := json.Unmarshal(findingsRaw, &dtos); err != nil { + if err := json.Unmarshal(findingsRaw, &dtos, jsonx.V1()); err != nil { return fmt.Errorf("inspector2: restore findings table: %w", err) } diff --git a/services/inspector2/required_output_members_test.go b/services/inspector2/required_output_members_test.go index 122e316d00..43a23682fd 100644 --- a/services/inspector2/required_output_members_test.go +++ b/services/inspector2/required_output_members_test.go @@ -1,7 +1,7 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/inspector2/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestUpdateOrganizationConfiguration_AutoEnableAlwaysPresent proves @@ -53,6 +55,6 @@ func TestUpdateOrganizationConfiguration_MissingAutoEnableRejected(t *testing.T) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } diff --git a/services/inspector2/wire_field_fixes_y1zn_test.go b/services/inspector2/wire_field_fixes_y1zn_test.go index b9ccda981b..94cde9bb57 100644 --- a/services/inspector2/wire_field_fixes_y1zn_test.go +++ b/services/inspector2/wire_field_fixes_y1zn_test.go @@ -1,13 +1,14 @@ package inspector2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/inspector2" ) @@ -76,7 +77,7 @@ func TestListFindingAggregations_NoLowKey_RealClient(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) responses, ok := out["responses"].([]any) require.True(t, ok) require.NotEmpty(t, responses) diff --git a/services/iot/authorizer_invoke.go b/services/iot/authorizer_invoke.go index 8300b8a44a..3ebfda334f 100644 --- a/services/iot/authorizer_invoke.go +++ b/services/iot/authorizer_invoke.go @@ -7,7 +7,8 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "errors" "fmt" @@ -15,6 +16,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -99,11 +101,11 @@ type authorizerMQTTData struct { } type authorizerResponse struct { - DisconnectAfterInSeconds *int64 `json:"disconnectAfterInSeconds"` - RefreshAfterInSeconds *int64 `json:"refreshAfterInSeconds"` - PrincipalID string `json:"principalId"` - PolicyDocuments []json.RawMessage `json:"policyDocuments"` - IsAuthenticated bool `json:"isAuthenticated"` + DisconnectAfterInSeconds *int64 `json:"disconnectAfterInSeconds"` + RefreshAfterInSeconds *int64 `json:"refreshAfterInSeconds"` + PrincipalID string `json:"principalId"` + PolicyDocuments []jsontext.Value `json:"policyDocuments"` + IsAuthenticated bool `json:"isAuthenticated"` } // TestInvokeAuthorizer invokes the authorizer's Lambda function with the request. @@ -166,7 +168,7 @@ func (b *InMemoryBackend) InvokeAuthorizer( return nil, fmt.Errorf("%w: %w", errRuleActionDenied, err) } - payload, err := json.Marshal(buildAuthorizerEvent(req, verified)) + payload, err := json.Marshal(buildAuthorizerEvent(req, verified), jsonx.V1()) if err != nil { return nil, fmt.Errorf("authorizer event: %w", err) } @@ -247,7 +249,7 @@ func verifyTokenSignature(keys map[string]string, req *AuthorizerRequest) bool { func parseAuthorizerResponse(raw []byte) (*TestInvokeAuthorizerOutput, error) { var resp authorizerResponse - if err := json.Unmarshal(raw, &resp); err != nil { + if err := json.Unmarshal(raw, &resp, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: not a JSON object", ErrInvalidAuthorizerResponse) } @@ -269,7 +271,7 @@ func parseAuthorizerResponse(raw []byte) (*TestInvokeAuthorizerOutput, error) { doc := string(d) var asString string - if json.Unmarshal(d, &asString) == nil { + if json.Unmarshal(d, &asString, jsonx.V1()) == nil { doc = asString } diff --git a/services/iot/broker_auth.go b/services/iot/broker_auth.go index 08c8e28526..0b991fecbe 100644 --- a/services/iot/broker_auth.go +++ b/services/iot/broker_auth.go @@ -9,7 +9,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -23,6 +23,7 @@ import ( "github.com/mochi-mqtt/server/v2/packets" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -325,7 +326,7 @@ func (b *InMemoryBackend) thingGroupARNsLocked(thingName string) []string { } func jsonEscape(v string) string { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil || len(raw) < 2 { return "" } diff --git a/services/iot/broker_commands.go b/services/iot/broker_commands.go index d5927f45b1..30a90515ba 100644 --- a/services/iot/broker_commands.go +++ b/services/iot/broker_commands.go @@ -1,9 +1,11 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "errors" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -77,7 +79,7 @@ func (b *Broker) handleCommandResponse(r commandResponse, payload []byte) { } reply := func(outcome string, body map[string]any) { - out, err := json.Marshal(body) + out, err := json.Marshal(body, jsonx.V1()) if err != nil { return } @@ -101,7 +103,7 @@ func (b *Broker) handleCommandResponse(r commandResponse, payload []byte) { } var body commandResponseBody - if err := json.Unmarshal(payload, &body); err != nil { + if err := json.Unmarshal(payload, &body, jsonx.V1()); err != nil { reject(jobsCodeInvalidJSON, "payload is not valid JSON", "") return diff --git a/services/iot/broker_custom_auth_test.go b/services/iot/broker_custom_auth_test.go index e0dad7d18a..ac8326faef 100644 --- a/services/iot/broker_custom_auth_test.go +++ b/services/iot/broker_custom_auth_test.go @@ -9,7 +9,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "net/url" @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -40,7 +41,7 @@ type fakeAuthLambda struct { func (f *fakeAuthLambda) RequestLambda(_ context.Context, _, _ string, payload []byte) ([]byte, error) { var ev map[string]any - if err := json.Unmarshal(payload, &ev); err != nil { + if err := json.Unmarshal(payload, &ev, jsonx.V1()); err != nil { return nil, err } @@ -49,7 +50,7 @@ func (f *fakeAuthLambda) RequestLambda(_ context.Context, _, _ string, payload [ respond := f.respond f.mu.Unlock() - return json.Marshal(respond(ev)) + return json.Marshal(respond(ev), jsonx.V1()) } func (f *fakeAuthLambda) calls() []map[string]any { @@ -68,7 +69,7 @@ func (f *fakeAuthLambda) setRespond(r func(map[string]any) any) { func authzResponse(principal string, refresh, disconnect int, statements ...string) map[string]any { var doc any - _ = json.Unmarshal([]byte(authPolicy(statements...)), &doc) + _ = json.Unmarshal([]byte(authPolicy(statements...)), &doc, jsonx.V1()) out := map[string]any{"isAuthenticated": true, "principalId": principal, "policyDocuments": []any{doc}} if refresh > 0 { diff --git a/services/iot/broker_events.go b/services/iot/broker_events.go index 71f20dab72..2b9e614e16 100644 --- a/services/iot/broker_events.go +++ b/services/iot/broker_events.go @@ -1,13 +1,15 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "strings" "sync/atomic" "github.com/google/uuid" mqtt "github.com/mochi-mqtt/server/v2" "github.com/mochi-mqtt/server/v2/packets" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -41,7 +43,7 @@ func lifecycleEligible(clientID string) bool { } func (b *Broker) publishLifecycle(topic string, payload map[string]any) { - raw, err := json.Marshal(payload) + raw, err := json.Marshal(payload, jsonx.V1()) if err != nil { return } diff --git a/services/iot/broker_events_test.go b/services/iot/broker_events_test.go index 5df3d3e3cc..6172551ccc 100644 --- a/services/iot/broker_events_test.go +++ b/services/iot/broker_events_test.go @@ -1,7 +1,7 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -79,7 +80,7 @@ func (w *eventWatcher) next(t *testing.T, topic string) map[string]any { } var out map[string]any - require.NoError(t, json.Unmarshal(m.Payload(), &out)) + require.NoError(t, json.Unmarshal(m.Payload(), &out, jsonx.V1())) return out case <-timeout: @@ -174,7 +175,7 @@ func TestBrokerLifecycleEvents(t *testing.T) { select { case m := <-w.msgs: var p map[string]any - require.NoError(t, json.Unmarshal(m.Payload(), &p)) + require.NoError(t, json.Unmarshal(m.Payload(), &p, jsonx.V1())) switch { case p["clientId"] != "dev-dup": @@ -645,7 +646,7 @@ func TestBrokerConnectFailedEvent(t *testing.T) { assert.Equal(t, "$aws/events/presence/connect_failed/intruder", m.Topic()) var p map[string]any - require.NoError(t, json.Unmarshal(m.Payload(), &p)) + require.NoError(t, json.Unmarshal(m.Payload(), &p, jsonx.V1())) assert.Equal(t, "connect_failed", p["eventType"]) assert.Equal(t, "AUTHORIZATION_FAILED", p["connectFailureReason"]) assert.Equal(t, "intruder", p["clientId"]) diff --git a/services/iot/broker_jobs.go b/services/iot/broker_jobs.go index 8e8f9b55e9..7c52d227ba 100644 --- a/services/iot/broker_jobs.go +++ b/services/iot/broker_jobs.go @@ -1,11 +1,14 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -85,7 +88,7 @@ type jobsExecutionWire struct { JobID string `json:"jobId"` ThingName string `json:"thingName,omitempty"` Status JobExecutionStatus `json:"status,omitempty"` - JobDocument json.RawMessage `json:"jobDocument,omitempty"` + JobDocument jsontext.Value `json:"jobDocument,omitempty"` ApproxSecondsBeforeTimedOut int64 `json:"approximateSecondsBeforeTimedOut,omitempty"` QueuedAt int64 `json:"queuedAt"` StartedAt int64 `json:"startedAt,omitempty"` @@ -111,16 +114,16 @@ func executionWire(e *DeviceJobExecution) *jobsExecutionWire { } // documentRaw embeds a JSON job document as an object; MQTT carries it unquoted, unlike the HTTP API. -func documentRaw(doc string) json.RawMessage { +func documentRaw(doc string) jsontext.Value { if doc == "" { return nil } - if json.Valid([]byte(doc)) { - return json.RawMessage(doc) + if jsontext.Value([]byte(doc)).IsValid(jsonx.V1()) { + return jsontext.Value(doc) } - quoted, _ := json.Marshal(doc) + quoted, _ := json.Marshal(doc, jsonx.V1()) return quoted } @@ -140,7 +143,7 @@ type jobsRequestBody struct { func (b *Broker) handleJobsRequest(topic string, req jobsRequest, payload []byte) { var body jobsRequestBody if len(payload) > 0 { - if err := json.Unmarshal(payload, &body); err != nil { + if err := json.Unmarshal(payload, &body, jsonx.V1()); err != nil { b.publishJobs(topic+jobsSuffixReject, map[string]any{ "code": jobsCodeInvalidJSON, jobsKeyMessage: "payload is not valid JSON", @@ -343,7 +346,7 @@ func (b *Broker) rejectJobs(topic, clientToken, code, message string, state map[ } func (b *Broker) publishJobs(topic string, body map[string]any) { - payload, err := json.Marshal(body) + payload, err := json.Marshal(body, jsonx.V1()) if err != nil { return } diff --git a/services/iot/broker_jobs_test.go b/services/iot/broker_jobs_test.go index fa049cee80..3096075281 100644 --- a/services/iot/broker_jobs_test.go +++ b/services/iot/broker_jobs_test.go @@ -2,7 +2,7 @@ package iot_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -113,7 +114,7 @@ func (e *jobsMQTTEnv) expect(t *testing.T, suffix string) map[string]any { } var out map[string]any - require.NoError(t, json.Unmarshal(m.Payload(), &out)) + require.NoError(t, json.Unmarshal(m.Payload(), &out, jsonx.V1())) return out case <-timer.C: diff --git a/services/iot/command_executions_test.go b/services/iot/command_executions_test.go index 5b6f143eb8..6c621e1669 100644 --- a/services/iot/command_executions_test.go +++ b/services/iot/command_executions_test.go @@ -2,7 +2,7 @@ package iot_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http/httptest" @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -401,7 +402,7 @@ func TestCommandExecutionDeviceLifecycle(t *testing.T) { e.respond(t, id, `{"status":"IN_PROGRESS","clientToken":"b"}`) var rejected map[string]string - require.NoError(t, json.Unmarshal(e.expect(t, base+"rejected/json"), &rejected)) + require.NoError(t, json.Unmarshal(e.expect(t, base+"rejected/json"), &rejected, jsonx.V1())) assert.Equal(t, "TerminalStateReached", rejected["error"]) assert.Equal(t, "b", rejected["clientToken"]) @@ -457,7 +458,7 @@ func TestCommandExecutionDeviceRejections(t *testing.T) { t, json.Unmarshal( e.expect(t, "$aws/commands/things/"+cmdThing+"/executions/"+id+"/response/rejected/json"), - &got, + &got, jsonx.V1(), ), ) assert.Equal(t, tt.wantErr, got["error"]) @@ -475,7 +476,7 @@ func TestCommandExecutionDeviceRejections(t *testing.T) { t, json.Unmarshal( e.expect(t, "$aws/commands/things/"+cmdThing+"/executions/ghost/response/rejected/json"), - &got, + &got, jsonx.V1(), ), ) assert.Equal(t, "ResourceNotFound", got["error"]) diff --git a/services/iot/events.go b/services/iot/events.go index ab6b70ee94..5e2c7c63c0 100644 --- a/services/iot/events.go +++ b/services/iot/events.go @@ -1,13 +1,15 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "maps" "slices" "strings" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // UpdateEventConfigurations keys that gate registry events. @@ -92,7 +94,7 @@ func (b *InMemoryBackend) flushEvents() { } for _, ev := range events { - raw, err := json.Marshal(ev.payload) + raw, err := json.Marshal(ev.payload, jsonx.V1()) if err != nil { continue } diff --git a/services/iot/handler.go b/services/iot/handler.go index 975a598aef..4665c3b240 100644 --- a/services/iot/handler.go +++ b/services/iot/handler.go @@ -2,7 +2,8 @@ package iot import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -322,7 +324,7 @@ func (h *Handler) handleCreateThing(c *echo.Context) error { BillingGroupName string `json:"billingGroupName"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -549,7 +551,7 @@ func (h *Handler) handleUpdateThing(c *echo.Context) error { var body UpdateThingInput - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_audit_suppressions_list_test.go b/services/iot/handler_audit_suppressions_list_test.go index 47718618e0..3d026cae91 100644 --- a/services/iot/handler_audit_suppressions_list_test.go +++ b/services/iot/handler_audit_suppressions_list_test.go @@ -1,12 +1,14 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListAuditSuppressions_FilterOrderAndPagination proves @@ -38,7 +40,7 @@ func TestListAuditSuppressions_FilterOrderAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) suppressions, _ := out["suppressions"].([]any) require.Len(t, suppressions, 1) }) @@ -51,7 +53,7 @@ func TestListAuditSuppressions_FilterOrderAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) suppressions, _ := out["suppressions"].([]any) require.Len(t, suppressions, 1) }) @@ -63,7 +65,7 @@ func TestListAuditSuppressions_FilterOrderAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) suppressions, _ := out["suppressions"].([]any) require.Len(t, suppressions, 3) @@ -79,7 +81,7 @@ func TestListAuditSuppressions_FilterOrderAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) suppressions, _ := out["suppressions"].([]any) require.Len(t, suppressions, 3) @@ -95,7 +97,7 @@ func TestListAuditSuppressions_FilterOrderAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) suppressions, _ := out["suppressions"].([]any) require.Len(t, suppressions, 2) assert.NotEmpty(t, out["nextToken"]) diff --git a/services/iot/handler_billing_groups.go b/services/iot/handler_billing_groups.go index b39aa2e97a..babc149f45 100644 --- a/services/iot/handler_billing_groups.go +++ b/services/iot/handler_billing_groups.go @@ -1,19 +1,22 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddThingToBillingGroup(c *echo.Context) error { var body AddThingToBillingGroupInput - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_billing_groups_test.go b/services/iot/handler_billing_groups_test.go index 7546d6289c..b8cac09850 100644 --- a/services/iot/handler_billing_groups_test.go +++ b/services/iot/handler_billing_groups_test.go @@ -1,12 +1,14 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestNewOps_BillingGroup tests BillingGroup CRUD. @@ -75,7 +77,7 @@ func TestBillingGroup_UpdateVersionConflict_Rejected(t *testing.T) { var errBody struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "VersionConflictException", errBody.Type) out := iotOK(t, h, http.MethodGet, "/billing-groups/ver-group", nil) diff --git a/services/iot/handler_certificates.go b/services/iot/handler_certificates.go index e9ab84ba71..0016760a5a 100644 --- a/services/iot/handler_certificates.go +++ b/services/iot/handler_certificates.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -141,7 +143,7 @@ func (h *Handler) handleCreateCertificateFromCsr(c *echo.Context) error { var body struct { CertificateSigningRequest string `json:"certificateSigningRequest"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -174,7 +176,7 @@ func (h *Handler) handleRegisterCertificate(c *echo.Context) error { CACertificatePem string `json:"caCertificatePem"` Status string `json:"status"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -199,7 +201,7 @@ func (h *Handler) handleRegisterCertificateWithoutCA(c *echo.Context) error { CertificatePem string `json:"certificatePem"` Status string `json:"status"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -372,7 +374,7 @@ func (h *Handler) handleCreateCertificateProvider(c *echo.Context) error { Tags []tags.KV `json:"tags,omitempty"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -450,7 +452,7 @@ func (h *Handler) handleUpdateCertificateProvider(c *echo.Context) error { AccountDefaultForOperations []string `json:"accountDefaultForOperations"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_certificates_test.go b/services/iot/handler_certificates_test.go index 5ac7896aad..78d92457b7 100644 --- a/services/iot/handler_certificates_test.go +++ b/services/iot/handler_certificates_test.go @@ -1,7 +1,7 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -121,7 +122,7 @@ func TestListCertificatesByCA(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) certs, _ := out["certificates"].([]any) require.Len(t, certs, 1) @@ -632,7 +633,7 @@ func TestDescribeCertificate_ReturnsPem(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) certID, _ := cr["certificateId"].(string) require.NotEmpty(t, certID) @@ -640,7 +641,7 @@ func TestDescribeCertificate_ReturnsPem(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &desc, jsonx.V1())) certDesc, _ := desc["certificateDescription"].(map[string]any) pem, _ := certDesc["certificatePem"].(string) assert.NotEmpty(t, pem, "certificatePem must be present in DescribeCertificate response") @@ -737,7 +738,7 @@ func TestTransferCertificate_ReturnsArn(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) certID, _ := cr["certificateId"].(string) certARN, _ := cr["certificateArn"].(string) require.NotEmpty(t, certID) @@ -748,7 +749,7 @@ func TestTransferCertificate_ReturnsArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) transferredARN, _ := resp["transferredCertificateArn"].(string) assert.Equal(t, certARN, transferredARN, "transferredCertificateArn must be the cert ARN, not ID") } @@ -795,7 +796,7 @@ func TestListOutgoingCertificates_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) certs, _ := out["outgoingCertificates"].([]any) assert.LessOrEqual(t, len(certs), 1) assert.NotEmpty(t, out["nextMarker"]) diff --git a/services/iot/handler_helpers.go b/services/iot/handler_helpers.go index fb29116175..ee5d7ce73a 100644 --- a/services/iot/handler_helpers.go +++ b/services/iot/handler_helpers.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -9,6 +10,8 @@ import ( "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // errTypeInvalidRequest is the AWS IoT restjson1 __type for a malformed or @@ -20,7 +23,12 @@ const errTypeInvalidRequest = "InvalidRequestException" const maxTopicRuleBodyBytes int64 = 1 << 20 func readBody(c *echo.Context, dst any) error { - if err := json.NewDecoder(c.Request().Body).Decode(dst); err != nil && !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + dst, + jsonx.V1(), + ); err != nil && + !errors.Is(err, io.EOF) { if jsonErr := c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}); jsonErr != nil { return jsonErr } diff --git a/services/iot/handler_helpers_test.go b/services/iot/handler_helpers_test.go index fe9e4c9ff5..39c0f689ff 100644 --- a/services/iot/handler_helpers_test.go +++ b/services/iot/handler_helpers_test.go @@ -2,7 +2,8 @@ package iot_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -48,15 +50,15 @@ func TestReadBodyMalformedWritesExactlyOneDocument(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) - dec := json.NewDecoder(rec.Body) + dec := jsontext.NewDecoder(rec.Body) var first map[string]any - require.NoError(t, dec.Decode(&first)) + require.NoError(t, json.UnmarshalDecode(dec, &first, jsonx.V1())) require.Contains(t, first, "__type") require.Contains(t, first, "message") var second map[string]any - err := dec.Decode(&second) + err := json.UnmarshalDecode(dec, &second, jsonx.V1()) require.ErrorIs( t, err, @@ -95,7 +97,7 @@ func TestReadBodyMalformed_RealSDKClient_DecodesInvalidRequestException(t *testi require.Equal(t, http.StatusBadRequest, rec.Code) - errorType, message, err := restjson.GetErrorInfo(json.NewDecoder(rec.Body)) + errorType, message, err := restjson.GetErrorInfo(jsonx.SDKDecoder(rec.Body)) require.NoError(t, err) assert.Equal(t, "InvalidRequestException", errorType, "a real client must decode a modeled error code, not fall through to UnknownError") diff --git a/services/iot/handler_indexing.go b/services/iot/handler_indexing.go index 6ab042b8dc..60e89ad97b 100644 --- a/services/iot/handler_indexing.go +++ b/services/iot/handler_indexing.go @@ -1,13 +1,16 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resolveIndexOps maps fleet-indexing REST paths to their operation names. @@ -96,7 +99,12 @@ func (h *Handler) dispatchIndexOps(c *echo.Context, op string) (bool, error) { const keyCount = "count" func decodeJSONBody(c *echo.Context, v any) error { - if err := json.NewDecoder(c.Request().Body).Decode(v); err != nil && !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + v, + jsonx.V1(), + ); err != nil && + !errors.Is(err, io.EOF) { return err } diff --git a/services/iot/handler_jobs.go b/services/iot/handler_jobs.go index c783d29b74..b9eb838756 100644 --- a/services/iot/handler_jobs.go +++ b/services/iot/handler_jobs.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -9,6 +10,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAssociateTargetsWithJob(c *echo.Context) error { @@ -22,7 +25,7 @@ func (h *Handler) handleAssociateTargetsWithJob(c *echo.Context) error { Targets []string `json:"targets"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -379,7 +382,12 @@ func (h *Handler) handleCancelJobExecution(c *echo.Context) error { ExpectedVersion int64 `json:"expectedVersion"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &body, + jsonx.V1(), + ); err != nil && + !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_jobs_test.go b/services/iot/handler_jobs_test.go index 12cb7a44e6..12ea1231b0 100644 --- a/services/iot/handler_jobs_test.go +++ b/services/iot/handler_jobs_test.go @@ -2,7 +2,7 @@ package iot_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -233,7 +234,7 @@ func TestJobExecution_RoutingAndStateGuards(t *testing.T) { assert.Equal(t, http.StatusConflict, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidStateTransitionException", body["__type"]) }, }, @@ -278,7 +279,7 @@ func TestJobExecution_RoutingAndStateGuards(t *testing.T) { assert.Equal(t, http.StatusConflict, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "VersionConflictException", body["__type"]) }, }, @@ -888,7 +889,7 @@ func TestHandler_ListJobsNamespaceFilter(t *testing.T) { JobID string `json:"jobId"` } `json:"jobs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) got := make([]string, 0, len(out.Jobs)) for _, j := range out.Jobs { got = append(got, j.JobID) diff --git a/services/iot/handler_packages.go b/services/iot/handler_packages.go index e8c94ce54b..be0ee8cb82 100644 --- a/services/iot/handler_packages.go +++ b/services/iot/handler_packages.go @@ -1,13 +1,16 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxPackagePathSegments is the number of segments in /packages/{pkg}/versions/{ver}/sbom. @@ -35,7 +38,7 @@ func (h *Handler) handleAssociateSbomWithPackageVersion(c *echo.Context) error { Sbom *SbomDocument `json:"sbom"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_policies.go b/services/iot/handler_policies.go index d0459553fe..494f147463 100644 --- a/services/iot/handler_policies.go +++ b/services/iot/handler_policies.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -189,7 +191,7 @@ func (h *Handler) handleCreatePolicy(c *echo.Context) error { Tags []tags.KV `json:"tags,omitempty"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -218,7 +220,7 @@ func (h *Handler) handleAttachPolicy(c *echo.Context) error { Target string `json:"target"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -307,7 +309,7 @@ func (h *Handler) handleDetachPolicy(c *echo.Context) error { Target string `json:"target"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -343,7 +345,7 @@ func (h *Handler) handleListAttachedPolicies(c *echo.Context) error { Target string `json:"target"` Recursive bool `json:"recursive"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -408,7 +410,7 @@ func (h *Handler) handleCreatePolicyVersion(c *echo.Context) error { PolicyDocument string `json:"policyDocument"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_policies_test.go b/services/iot/handler_policies_test.go index 2291fe87cb..9e3c611522 100644 --- a/services/iot/handler_policies_test.go +++ b/services/iot/handler_policies_test.go @@ -1,7 +1,8 @@ package iot_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -105,7 +107,7 @@ func TestListPrincipalThingsV2_WireShapeAndFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) objs, _ := out["principalThingObjects"].([]any) require.Len(t, objs, 1) entry, ok := objs[0].(map[string]any) @@ -186,7 +188,7 @@ func TestPolicyPrincipalListing_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policies, _ := out["policies"].([]any) assert.LessOrEqual(t, len(policies), 1) assert.NotEmpty(t, out["nextMarker"]) @@ -209,7 +211,7 @@ func TestPolicyPrincipalListing_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) princs, _ := out["principals"].([]any) assert.LessOrEqual(t, len(princs), 1) assert.NotEmpty(t, out["nextMarker"]) @@ -229,7 +231,7 @@ func TestPolicyPrincipalListing_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) targets, _ := out["targets"].([]any) assert.LessOrEqual(t, len(targets), 1) assert.NotEmpty(t, out["nextMarker"]) @@ -252,7 +254,7 @@ func TestPolicyPrincipalListing_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) things, _ := out["things"].([]any) assert.LessOrEqual(t, len(things), 1) assert.NotEmpty(t, out["nextToken"]) @@ -456,7 +458,7 @@ func TestHandler_ListPolicies_Response(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) policies, ok := out["policies"] require.True(t, ok, "response must have 'policies' key") @@ -480,7 +482,7 @@ func TestHandler_GetPolicy_HTTP(t *testing.T) { // creationDate/lastModifiedDate are JSON numbers (epoch seconds), not // RFC3339 strings, so this must decode into map[string]any. var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, "http-policy", out["policyName"]) assert.NotEmpty(t, out["policyArn"]) @@ -1011,7 +1013,7 @@ func TestGetPolicyVersion_ReturnsPolicyArnAndName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "parb-pol2", resp["policyName"], "policyName must be in GetPolicyVersion response") policyARN, _ := resp["policyArn"].(string) assert.NotEmpty(t, policyARN, "policyArn must be in GetPolicyVersion response") diff --git a/services/iot/handler_routing_test.go b/services/iot/handler_routing_test.go index 4e5604d5f7..d0df4bf156 100644 --- a/services/iot/handler_routing_test.go +++ b/services/iot/handler_routing_test.go @@ -2,7 +2,7 @@ package iot_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -40,7 +41,7 @@ func TestHandler_Operations(t *testing.T) { validateOutput: func(t *testing.T, body []byte) { t.Helper() var out map[string]string - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.Equal(t, "my-thing", out["thingName"]) }, }, @@ -101,7 +102,7 @@ func TestHandler_Operations(t *testing.T) { var reqBody []byte if tt.body != nil { var err error - reqBody, err = json.Marshal(tt.body) + reqBody, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } @@ -372,7 +373,7 @@ func TestHandler_NewOperations(t *testing.T) { var reqBody []byte if tt.body != nil { var err error - reqBody, err = json.Marshal(tt.body) + reqBody, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/iot/handler_tags_wire_test.go b/services/iot/handler_tags_wire_test.go index dfde622508..2732824dad 100644 --- a/services/iot/handler_tags_wire_test.go +++ b/services/iot/handler_tags_wire_test.go @@ -1,7 +1,7 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -38,7 +39,7 @@ func decodeTagList(t *testing.T, body []byte) map[string]string { Value string `json:"Value"` } `json:"tags"` } - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) out := make(map[string]string, len(resp.Tags)) for _, item := range resp.Tags { out[item.Key] = item.Value @@ -435,7 +436,7 @@ func TestCreateThenListTagsForResourceRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, "body: %s", createRec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) resourceARN, ok := created[tt.arnField].(string) require.Truef(t, ok && resourceARN != "", "response missing %q: %s", tt.arnField, createRec.Body.String()) @@ -463,7 +464,7 @@ func TestTagUntagResourceAfterCreationTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, "body: %s", createRec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) resourceARN, _ := created["thingGroupArn"].(string) require.NotEmpty(t, resourceARN) diff --git a/services/iot/handler_test.go b/services/iot/handler_test.go index 5b6dd7c9d8..c31c73dcc6 100644 --- a/services/iot/handler_test.go +++ b/services/iot/handler_test.go @@ -2,7 +2,8 @@ package iot_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -27,7 +29,7 @@ func doIoTRequest(t *testing.T, h *iot.Handler, method, path string, body any) * var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -66,7 +68,7 @@ func iotRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) if err != nil { t.Fatalf("marshal request body: %v", err) } @@ -93,7 +95,7 @@ func iotOK(t *testing.T, h *iot.Handler, method, path string, body any) map[stri return map[string]any{} } var out map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { + if err := json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()); err != nil { t.Fatalf("decode response: %v", err) } @@ -134,7 +136,7 @@ func doRefRequest( if body != nil { var err error - raw, err = json.Marshal(body) + raw, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -166,7 +168,7 @@ func doRequest(t *testing.T, h *iot.Handler, method, path string, body any) *htt if body != nil { var err error - reqBody, err = json.Marshal(body) + reqBody, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -204,7 +206,7 @@ func r3JSON(t *testing.T, h *iot.Handler, method, path string, body any, out any t.Helper() code, raw := r3Req(t, h, method, path, body) if out != nil { - require.NoError(t, json.Unmarshal(raw, out)) + require.NoError(t, json.Unmarshal(raw, out, jsonx.V1())) } return code @@ -541,7 +543,7 @@ func TestHandler_ListThings_ResponseFormat(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) things, ok := out["things"] require.True(t, ok, "response must have 'things' key") @@ -587,7 +589,7 @@ func TestHandler_ListThingPrincipals_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) principals, ok := out["principals"].([]any) require.True(t, ok) require.Len(t, principals, 1) @@ -641,7 +643,7 @@ func TestDescribeThing_DefaultClientId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clientID, _ := resp["defaultClientId"].(string) assert.Equal(t, tt.thingName, clientID, "defaultClientId must equal thingName") }) @@ -680,7 +682,7 @@ func TestListThings_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.Things), 2, "page exceeds maxResults") for _, th := range resp.Things { @@ -733,7 +735,7 @@ func TestListThingGroupsForThing_WireShapeAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) groups, ok := out["thingGroups"].([]any) require.True(t, ok) require.Len(t, groups, total) @@ -751,7 +753,7 @@ func TestListThingGroupsForThing_WireShapeAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) groups, _ := out["thingGroups"].([]any) assert.LessOrEqual(t, len(groups), 2) assert.NotEmpty(t, out["nextToken"]) @@ -780,7 +782,7 @@ func TestListThingPrincipals_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) principals, _ := out["principals"].([]any) assert.LessOrEqual(t, len(principals), 2) assert.NotEmpty(t, out["nextToken"]) @@ -815,7 +817,7 @@ func TestListThingPrincipalsV2_ThingPrincipalTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) objs, _ := out["thingPrincipalObjects"].([]any) require.Len(t, objs, 1, "EXCLUSIVE_THING filter must return only the EXCLUSIVE_THING attachment") entry, _ := objs[0].(map[string]any) @@ -827,7 +829,7 @@ func TestListThingPrincipalsV2_ThingPrincipalTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) objs2, _ := out2["thingPrincipalObjects"].([]any) require.Len(t, objs2, 1) entry2, _ := objs2[0].(map[string]any) diff --git a/services/iot/handler_thing_groups.go b/services/iot/handler_thing_groups.go index 4ab50661d4..5a9434550d 100644 --- a/services/iot/handler_thing_groups.go +++ b/services/iot/handler_thing_groups.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -90,7 +92,7 @@ func (h *Handler) dispatchThingGroupOps(c *echo.Context, op string) (bool, error func (h *Handler) handleAddThingToThingGroup(c *echo.Context) error { var body AddThingToThingGroupInput - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -115,7 +117,7 @@ func (h *Handler) handleCreateThingGroup(c *echo.Context) error { Tags []tags.KV `json:"tags,omitempty"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -222,7 +224,7 @@ func (h *Handler) handleUpdateThingGroup(c *echo.Context) error { ExpectedVersion *int64 `json:"expectedVersion"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -266,7 +268,7 @@ func (h *Handler) handleDeleteThingGroup(c *echo.Context) error { func (h *Handler) handleRemoveThingFromThingGroup(c *echo.Context) error { var body RemoveThingFromThingGroupInput - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_thing_types.go b/services/iot/handler_thing_types.go index f10681ff2d..2eabbddabe 100644 --- a/services/iot/handler_thing_types.go +++ b/services/iot/handler_thing_types.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -72,7 +74,7 @@ func (h *Handler) handleCreateThingType(c *echo.Context) error { Tags []tags.KV `json:"tags,omitempty"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -170,7 +172,7 @@ func (h *Handler) handleDeprecateThingType(c *echo.Context) error { UndoDeprecate bool `json:"undoDeprecate"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_thing_types_test.go b/services/iot/handler_thing_types_test.go index 75843409e7..f330c9a2ce 100644 --- a/services/iot/handler_thing_types_test.go +++ b/services/iot/handler_thing_types_test.go @@ -1,7 +1,8 @@ package iot_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -149,7 +151,7 @@ func TestHandler_DescribeThing_IncludesThingTypeName(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, "SensorType", out["thingTypeName"]) } diff --git a/services/iot/handler_thingtype_searchable_test.go b/services/iot/handler_thingtype_searchable_test.go index 318c84eff6..a0a3b0fb2d 100644 --- a/services/iot/handler_thingtype_searchable_test.go +++ b/services/iot/handler_thingtype_searchable_test.go @@ -1,12 +1,14 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestIoT_ThingTypes_SearchableAttributes validates that searchableAttributes @@ -65,7 +67,7 @@ func TestIoT_ThingTypes_SearchableAttributes(t *testing.T) { } `json:"thingTypeProperties"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantSearchable, resp.ThingTypeProperties.SearchableAttributes) }) } diff --git a/services/iot/handler_topic_rules.go b/services/iot/handler_topic_rules.go index b520dd13a0..6ce0ca07c9 100644 --- a/services/iot/handler_topic_rules.go +++ b/services/iot/handler_topic_rules.go @@ -1,7 +1,8 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resolveTopicRuleDestinationOps resolves the topic-rule-destination op @@ -148,14 +150,14 @@ func (h *Handler) handleCreateTopicRule(c *echo.Context) error { var wrapped struct { TopicRulePayload *TopicRulePayload `json:"topicRulePayload"` } - if jsonErr := json.Unmarshal(rawBody, &wrapped); jsonErr != nil && !errors.Is(jsonErr, io.EOF) { + if jsonErr := json.Unmarshal(rawBody, &wrapped, jsonx.V1()); jsonErr != nil && !errors.Is(jsonErr, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, jsonErr.Error()}) } payload := wrapped.TopicRulePayload if payload == nil { var flat TopicRulePayload - if jsonErr := json.Unmarshal(rawBody, &flat); jsonErr == nil && flat.SQL != "" { + if jsonErr := json.Unmarshal(rawBody, &flat, jsonx.V1()); jsonErr == nil && flat.SQL != "" { payload = &flat } } @@ -329,14 +331,14 @@ func (h *Handler) handleReplaceTopicRule(c *echo.Context) error { var wrapped struct { TopicRulePayload *TopicRulePayload `json:"topicRulePayload"` } - if jsonErr := json.Unmarshal(rawBody, &wrapped); jsonErr != nil && !errors.Is(jsonErr, io.EOF) { + if jsonErr := json.Unmarshal(rawBody, &wrapped, jsonx.V1()); jsonErr != nil && !errors.Is(jsonErr, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, jsonErr.Error()}) } payload := wrapped.TopicRulePayload if payload == nil { var flat TopicRulePayload - if jsonErr := json.Unmarshal(rawBody, &flat); jsonErr == nil && flat.SQL != "" { + if jsonErr := json.Unmarshal(rawBody, &flat, jsonx.V1()); jsonErr == nil && flat.SQL != "" { payload = &flat } } @@ -416,7 +418,7 @@ func (h *Handler) handleCreateTopicRuleDestination(c *echo.Context) error { var body struct { DestinationConfiguration *TopicRuleDestinationConfiguration `json:"destinationConfiguration"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } @@ -472,7 +474,7 @@ func (h *Handler) handleUpdateTopicRuleDestination(c *echo.Context) error { ARN string `json:"arn"` Status string `json:"status"` } - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && !errors.Is(err, io.EOF) { return c.JSON(http.StatusBadRequest, awsErrBody{errTypeInvalidRequest, err.Error()}) } diff --git a/services/iot/handler_topic_rules_test.go b/services/iot/handler_topic_rules_test.go index d1480c0ed0..f860e33841 100644 --- a/services/iot/handler_topic_rules_test.go +++ b/services/iot/handler_topic_rules_test.go @@ -1,7 +1,8 @@ package iot_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -102,7 +104,7 @@ func TestGetTopicRule_RuleWrapper(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ruleArn") assert.Contains(t, resp, "rule") }) @@ -169,7 +171,7 @@ func TestCreateAndGetTopicRule_Handler(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ruleArn") assert.Contains(t, resp, "rule") }) @@ -356,7 +358,7 @@ func TestHandler_ListTopicRules_ResponseFormat(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) rules, ok := out["rules"] require.True(t, ok, "response must have 'rules' key") @@ -411,7 +413,7 @@ func TestHandler_GetTopicRule_IncludesAWSSQLVersion(t *testing.T) { require.Equal(t, http.StatusOK, resp.Code) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) rule, ok := out["rule"].(map[string]any) require.True(t, ok) diff --git a/services/iot/jobs_data_plane_test.go b/services/iot/jobs_data_plane_test.go index a97badcc56..30de4839d5 100644 --- a/services/iot/jobs_data_plane_test.go +++ b/services/iot/jobs_data_plane_test.go @@ -2,7 +2,8 @@ package iot_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/iot" @@ -55,7 +57,7 @@ func newJobsDataEnv(t *testing.T) jobsDataEnv { func (e jobsDataEnv) device(t *testing.T, method, path string, body any) (int, map[string]any) { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), method, e.url+path, bytes.NewReader(raw)) @@ -69,7 +71,7 @@ func (e jobsDataEnv) device(t *testing.T, method, path string, body any) (int, m defer resp.Body.Close() out := map[string]any{} - _ = json.NewDecoder(resp.Body).Decode(&out) + _ = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1()) return resp.StatusCode, out } diff --git a/services/iot/multi_region_test.go b/services/iot/multi_region_test.go index 9eb2ae4ba1..bf3891c4b3 100644 --- a/services/iot/multi_region_test.go +++ b/services/iot/multi_region_test.go @@ -2,7 +2,7 @@ package iot_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -41,7 +42,7 @@ func regionCall(t *testing.T, h *iot.Handler, region, method, path, body string) out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out diff --git a/services/iot/names.go b/services/iot/names.go index 4cc7a69bb4..ae7987385c 100644 --- a/services/iot/names.go +++ b/services/iot/names.go @@ -1,10 +1,13 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "regexp" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxEntityNameLen = 128 @@ -41,13 +44,13 @@ func malformedPolicy(format string, args ...any) error { // validatePolicyDocument checks the document is JSON with a Version and well-formed Statement entries. func validatePolicyDocument(doc string) error { - var v map[string]json.RawMessage - if err := json.Unmarshal([]byte(doc), &v); err != nil { + var v map[string]jsontext.Value + if err := json.Unmarshal([]byte(doc), &v, jsonx.V1()); err != nil { return malformedPolicy("policy document is not valid JSON") } var version string - if raw, ok := v["Version"]; !ok || json.Unmarshal(raw, &version) != nil || version == "" { + if raw, ok := v["Version"]; !ok || json.Unmarshal(raw, &version, jsonx.V1()) != nil || version == "" { return malformedPolicy("policy document must have a string Version") } @@ -56,15 +59,15 @@ func validatePolicyDocument(doc string) error { return malformedPolicy("policy document must have a Statement") } - var statements []map[string]json.RawMessage + var statements []map[string]jsontext.Value - if err := json.Unmarshal(raw, &statements); err != nil { - var single map[string]json.RawMessage - if json.Unmarshal(raw, &single) != nil { + if err := json.Unmarshal(raw, &statements, jsonx.V1()); err != nil { + var single map[string]jsontext.Value + if json.Unmarshal(raw, &single, jsonx.V1()) != nil { return malformedPolicy("Statement must be an object or an array of objects") } - statements = []map[string]json.RawMessage{single} + statements = []map[string]jsontext.Value{single} } for _, st := range statements { @@ -76,9 +79,10 @@ func validatePolicyDocument(doc string) error { return nil } -func validatePolicyStatement(st map[string]json.RawMessage) error { +func validatePolicyStatement(st map[string]jsontext.Value) error { var effect string - if raw, ok := st["Effect"]; !ok || json.Unmarshal(raw, &effect) != nil || (effect != "Allow" && effect != "Deny") { + if raw, ok := st["Effect"]; !ok || json.Unmarshal(raw, &effect, jsonx.V1()) != nil || + (effect != "Allow" && effect != "Deny") { return malformedPolicy("statement Effect must be Allow or Deny") } @@ -91,17 +95,17 @@ func validatePolicyStatement(st map[string]json.RawMessage) error { return nil } -func stringOrList(raw json.RawMessage) bool { +func stringOrList(raw jsontext.Value) bool { if len(raw) == 0 { return false } var one string - if json.Unmarshal(raw, &one) == nil { + if json.Unmarshal(raw, &one, jsonx.V1()) == nil { return one != "" } var many []string - return json.Unmarshal(raw, &many) == nil && len(many) > 0 + return json.Unmarshal(raw, &many, jsonx.V1()) == nil && len(many) > 0 } diff --git a/services/iot/ota_options.go b/services/iot/ota_options.go index 62227d1f10..d20d7d2478 100644 --- a/services/iot/ota_options.go +++ b/services/iot/ota_options.go @@ -1,9 +1,11 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -63,10 +65,10 @@ func otaJobInput(opts OTAUpdateOptions, roleARN string) *CreateJobInput { // remapJSON copies the keys src shares with dst's JSON tags; an unmarshal failure leaves dst as is. func remapJSON(src map[string]any, dst any) { - raw, err := json.Marshal(src) + raw, err := json.Marshal(src, jsonx.V1()) if err != nil { return } - _ = json.Unmarshal(raw, dst) + _ = json.Unmarshal(raw, dst, jsonx.V1()) } diff --git a/services/iot/ota_updates.go b/services/iot/ota_updates.go index 9ce95dafd1..7f28e482fe 100644 --- a/services/iot/ota_updates.go +++ b/services/iot/ota_updates.go @@ -1,12 +1,13 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // OTAUpdate represents an AWS IoT OTA update. @@ -74,7 +75,7 @@ func (b *InMemoryBackend) CreateOTAUpdate( now := float64(time.Now().Unix()) jobID := "AFR_OTA-" + id - doc, docErr := json.Marshal(map[string]any{"afr_ota": map[string]any{"files": files}}) + doc, docErr := json.Marshal(map[string]any{"afr_ota": map[string]any{"files": files}}, jsonx.V1()) if docErr != nil { return nil, fmt.Errorf("building OTA job document: %w", docErr) } diff --git a/services/iot/persistence.go b/services/iot/persistence.go index e2dc6a04f6..6e05fe0a12 100644 --- a/services/iot/persistence.go +++ b/services/iot/persistence.go @@ -2,11 +2,13 @@ package iot import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -35,7 +37,7 @@ import ( const iotSnapshotVersion = 3 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` AuditTasks map[string]string `json:"auditTasks"` ClientRequestTokens map[string]string `json:"clientRequestTokens,omitempty"` OpReplay []string `json:"opReplay,omitempty"` @@ -175,7 +177,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { DefaultAuthorizer: cfgSnap.DefaultAuthorizer, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "iot: failed to snapshot backend state", "error", err) @@ -456,7 +458,7 @@ func fromTopicRuleDestSnap(s *topicRuleDestSnap) *TopicRuleDestination { // out-of-band), so registry.SnapshotAll's generic encoding would drop it; // the topicRuleDestSnap DTO carries it through instead. Must be called with // b.mu held (read or write). -func (b *InMemoryBackend) snapshotTopicRuleDestinationsTable() (map[string]json.RawMessage, error) { +func (b *InMemoryBackend) snapshotTopicRuleDestinationsTable() (map[string]jsontext.Value, error) { destDTOReg := store.NewRegistry() destDTOs := store.Register(destDTOReg, topicRuleDestinationsTableName, store.New(topicRuleDestSnapKey)) @@ -476,7 +478,7 @@ func (b *InMemoryBackend) snapshotTopicRuleDestinationsTable() (map[string]json. // DTO entry in tables (the inverse of snapshotTopicRuleDestinationsTable). // Extracted from Restore to keep it within the repo's funlen limit. Must be // called with b.mu held (write). -func (b *InMemoryBackend) restoreTopicRuleDestinationsTable(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreTopicRuleDestinationsTable(tables map[string]jsontext.Value) error { destDTOReg := store.NewRegistry() destDTOs := store.Register(destDTOReg, topicRuleDestinationsTableName, store.New(topicRuleDestSnapKey)) diff --git a/services/iot/policy_evaluation.go b/services/iot/policy_evaluation.go index 2ee476f19c..a5c404bf27 100644 --- a/services/iot/policy_evaluation.go +++ b/services/iot/policy_evaluation.go @@ -1,11 +1,13 @@ package iot import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AuthInfo describes one action/resources pair to evaluate authorization for. @@ -237,14 +239,14 @@ type flexStringList []string func (f *flexStringList) UnmarshalJSON(data []byte) error { var single string - if err := json.Unmarshal(data, &single); err == nil { + if err := json.Unmarshal(data, &single, jsonx.V1()); err == nil { *f = []string{single} return nil } var list []string - if err := json.Unmarshal(data, &list); err != nil { + if err := json.Unmarshal(data, &list, jsonx.V1()); err != nil { return err } @@ -264,7 +266,7 @@ func parseIoTPolicyDocument(doc string) ([]iamStatement, error) { } `json:"Statement"` } - if err := json.Unmarshal([]byte(doc), &raw); err != nil { + if err := json.Unmarshal([]byte(doc), &raw, jsonx.V1()); err != nil { return nil, err } diff --git a/services/iot/realism_test.go b/services/iot/realism_test.go index bb61a6470b..719c263798 100644 --- a/services/iot/realism_test.go +++ b/services/iot/realism_test.go @@ -1,13 +1,15 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func iotErr(t *testing.T, body []byte) (string, string) { @@ -17,7 +19,7 @@ func iotErr(t *testing.T, body []byte) (string, string) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out.Type, out.Message } diff --git a/services/iot/rule_actions.go b/services/iot/rule_actions.go index 67dd71abd6..32d645b2e9 100644 --- a/services/iot/rule_actions.go +++ b/services/iot/rule_actions.go @@ -3,7 +3,8 @@ package iot import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" @@ -14,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -30,7 +32,7 @@ const ( opDelete = "DELETE" ) -type actionRunner func(h *ruleHook, rule *TopicRule, msg *ruleMessage, raw json.RawMessage) error +type actionRunner func(h *ruleHook, rule *TopicRule, msg *ruleMessage, raw jsontext.Value) error type otherRunner struct { run actionRunner @@ -83,8 +85,8 @@ func (h *ruleHook) allow(roleARN, resource string, actions ...string) error { return nil } -func decodeAction(raw json.RawMessage, dst any) error { - if err := json.Unmarshal(raw, dst); err != nil { +func decodeAction(raw jsontext.Value, dst any) error { + if err := json.Unmarshal(raw, dst, jsonx.V1()); err != nil { return fmt.Errorf("decode action: %w", err) } @@ -132,7 +134,7 @@ type kinesisWire struct { PartitionKey string `json:"partitionKey"` } -func (h *ruleHook) runKinesis(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runKinesis(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w kinesisWire if err := decodeAction(raw, &w); err != nil { return err @@ -166,7 +168,7 @@ type firehoseWire struct { BatchMode bool `json:"batchMode"` } -func (h *ruleHook) runFirehose(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runFirehose(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w firehoseWire if err := decodeAction(raw, &w); err != nil { return err @@ -214,8 +216,8 @@ func (m *ruleMessage) batchElements(batch bool) ([][]byte, error) { return [][]byte{m.payload}, nil } - var elems []json.RawMessage - if err := json.Unmarshal(m.payload, &elems); err != nil { + var elems []jsontext.Value + if err := json.Unmarshal(m.payload, &elems, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: payload is not a valid JSON array", errTemplate) } @@ -226,7 +228,7 @@ func (m *ruleMessage) batchElements(batch bool) ([][]byte, error) { out := make([][]byte, len(elems)) for i, e := range elems { var s string - if json.Unmarshal(e, &s) == nil { + if json.Unmarshal(e, &s, jsonx.V1()) == nil { out[i] = []byte(s) continue @@ -251,7 +253,7 @@ type dynamoDBWire struct { Operation string `json:"operation"` } -func (h *ruleHook) runDynamoDB(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runDynamoDB(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w dynamoDBWire if err := decodeAction(raw, &w); err != nil { return err @@ -324,10 +326,9 @@ func keyAttribute(keyType, value string) map[string]any { func payloadAttribute(payload []byte) map[string]any { var obj map[string]any - dec := json.NewDecoder(bytes.NewReader(payload)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(payload)) - if err := dec.Decode(&obj); err == nil && obj != nil { + if err := json.UnmarshalDecode(dec, &obj, jsonx.V1(), jsonx.UseNumber()); err == nil && obj != nil { return jsonToAttribute(obj) } @@ -350,7 +351,7 @@ func jsonToAttribute(v any) map[string]any { } return map[string]any{"L": l} - case json.Number: + case jsonx.Number: return map[string]any{"N": t.String()} case string: return map[string]any{"S": t} @@ -368,7 +369,7 @@ type dynamoDBv2Wire struct { } `json:"putItem"` } -func (h *ruleHook) runDynamoDBv2(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runDynamoDBv2(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w dynamoDBv2Wire if err := decodeAction(raw, &w); err != nil { return err @@ -405,7 +406,7 @@ type s3Wire struct { CannedACL string `json:"cannedAcl"` } -func (h *ruleHook) runS3(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runS3(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w s3Wire if err := decodeAction(raw, &w); err != nil { return err @@ -436,7 +437,7 @@ type cloudwatchMetricWire struct { MetricTimestamp string `json:"metricTimestamp"` } -func (h *ruleHook) runCloudwatchMetric(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runCloudwatchMetric(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w cloudwatchMetricWire if err := decodeAction(raw, &w); err != nil { return err @@ -481,7 +482,7 @@ type cloudwatchAlarmWire struct { StateValue string `json:"stateValue"` } -func (h *ruleHook) runCloudwatchAlarm(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runCloudwatchAlarm(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w cloudwatchAlarmWire if err := decodeAction(raw, &w); err != nil { return err @@ -515,7 +516,7 @@ type batchedLogEvent struct { Message *string `json:"message"` } -func (h *ruleHook) runCloudwatchLogs(rule *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runCloudwatchLogs(rule *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w cloudwatchLogsWire if err := decodeAction(raw, &w); err != nil { return err @@ -552,7 +553,7 @@ func (m *ruleMessage) logEvents(batch bool) ([]LogEvent, error) { } var elems []batchedLogEvent - if err := json.Unmarshal(m.payload, &elems); err != nil { + if err := json.Unmarshal(m.payload, &elems, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: batchMode needs an array of {timestamp,message}", errTemplate) } @@ -579,7 +580,7 @@ type stepFunctionsWire struct { ExecutionNamePrefix string `json:"executionNamePrefix"` } -func (h *ruleHook) runStepFunctions(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runStepFunctions(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w stepFunctionsWire if err := decodeAction(raw, &w); err != nil { return err @@ -611,7 +612,7 @@ type iotAnalyticsWire struct { BatchMode bool `json:"batchMode"` } -func (h *ruleHook) runIoTAnalytics(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runIoTAnalytics(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w iotAnalyticsWire if err := decodeAction(raw, &w); err != nil { return err diff --git a/services/iot/rule_authz.go b/services/iot/rule_authz.go index 09d99a536f..f782868565 100644 --- a/services/iot/rule_authz.go +++ b/services/iot/rule_authz.go @@ -2,12 +2,13 @@ package iot import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -122,7 +123,7 @@ func (h *ruleHook) runErrorAction( "base64OriginalPayload": base64.StdEncoding.EncodeToString(msg.original), "failedAction": failedAction, "failedActionReason": "Failed to run action. Message: " + cause.Error(), - }) + }, jsonx.V1()) if err != nil { return } diff --git a/services/iot/rule_http.go b/services/iot/rule_http.go index cbb06ced9b..da5975123c 100644 --- a/services/iot/rule_http.go +++ b/services/iot/rule_http.go @@ -5,7 +5,7 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "io" @@ -18,6 +18,7 @@ import ( v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -60,7 +61,7 @@ type httpWire struct { } // runHTTP posts the projected payload to a URL covered by an ENABLED destination (iot https-rule-action). -func (h *ruleHook) runHTTP(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runHTTP(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w httpWire if err := decodeAction(raw, &w); err != nil { return err @@ -141,7 +142,7 @@ func (b *InMemoryBackend) requireHTTPDestination(rawURL string) error { } func contentTypeFor(payload []byte) string { - if json.Valid(payload) { + if jsontext.Value(payload).IsValid(jsonx.V1()) { return "application/json" } diff --git a/services/iot/rule_http_destinations.go b/services/iot/rule_http_destinations.go index f1c9ce0c6a..10dc0ad5bf 100644 --- a/services/iot/rule_http_destinations.go +++ b/services/iot/rule_http_destinations.go @@ -3,7 +3,7 @@ package iot import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -93,7 +94,7 @@ func sendConfirmation(c confirmation) error { body, err := json.Marshal(map[string]string{ keyArn: c.destARN, "confirmationToken": c.token, "enableUrl": c.enableURL, "messageType": msgTypeConfirmation, - }) + }, jsonx.V1()) if err != nil { return err } @@ -148,7 +149,7 @@ func httpActionsOf(actions []RuleAction, errAction *RuleAction) ([]httpWire, err } var w httpWire - if err := json.Unmarshal(raw, &w); err != nil { + if err := json.Unmarshal(raw, &w, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: http action: %w", ErrValidation, err) } diff --git a/services/iot/rule_lookups.go b/services/iot/rule_lookups.go index 8ef4ef9d18..3687a7cd7a 100644 --- a/services/iot/rule_lookups.go +++ b/services/iot/rule_lookups.go @@ -2,12 +2,13 @@ package iot import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -175,7 +176,7 @@ func describeThingJSON(bk *InMemoryBackend, thing string) ([]byte, error) { return nil, err } - return json.Marshal(thingDescription(t)) + return json.Marshal(thingDescription(t), jsonx.V1()) } func thingGroupsJSON(bk *InMemoryBackend, thing string) ([]byte, error) { @@ -190,5 +191,5 @@ func thingGroupsJSON(bk *InMemoryBackend, thing string) ([]byte, error) { groups = append(groups, entry) } - return json.Marshal(map[string]any{keyThingGroups: groups}) + return json.Marshal(map[string]any{keyThingGroups: groups}, jsonx.V1()) } diff --git a/services/iot/rule_republish.go b/services/iot/rule_republish.go index fcf26ef1c7..7cb9fb3fbd 100644 --- a/services/iot/rule_republish.go +++ b/services/iot/rule_republish.go @@ -2,7 +2,7 @@ package iot import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" "strconv" @@ -38,7 +38,7 @@ type republishWire struct { Qos int32 `json:"qos"` } -func (h *ruleHook) runRepublish(_ *TopicRule, msg *ruleMessage, raw json.RawMessage) error { +func (h *ruleHook) runRepublish(_ *TopicRule, msg *ruleMessage, raw jsontext.Value) error { var w republishWire if err := decodeAction(raw, &w); err != nil { return err diff --git a/services/iot/sql_value.go b/services/iot/sql_value.go index 8b04e773ce..d954c35441 100644 --- a/services/iot/sql_value.go +++ b/services/iot/sql_value.go @@ -2,12 +2,15 @@ package iot import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sqlUndefined is the SQL Undefined value: omitted from JSON output. @@ -77,52 +80,55 @@ var errBadJSON = errors.New("invalid json") // decodeJSONValue parses a complete JSON document. func decodeJSONValue(data []byte, v2016 bool) (any, error) { - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(data), jsonx.V1()) v, err := decodeToken(dec, v2016) if err != nil { return nil, err } - if _, err = dec.Token(); !errors.Is(err, io.EOF) { + if _, err = dec.ReadToken(); !errors.Is(err, io.EOF) { return nil, errBadJSON } return v, nil } -func decodeToken(dec *json.Decoder, v2016 bool) (any, error) { - tok, err := dec.Token() +func decodeToken(dec *jsontext.Decoder, v2016 bool) (any, error) { + tok, err := dec.ReadToken() if err != nil { return nil, errBadJSON } - switch t := tok.(type) { - case json.Delim: - if t == '{' { - return decodeObject(dec, v2016) - } - + switch tok.Kind() { + case '{': + return decodeObject(dec, v2016) + case '[': return decodeArray(dec, v2016) - case json.Number: - return sqlNumber(t.String(), v2016), nil + case '0': + return sqlNumber(tok.String(), v2016), nil + case '"': + return tok.String(), nil + case 't', 'f': + return tok.Bool(), nil default: - return t, nil + var null any + + return null, nil } } -func decodeObject(dec *json.Decoder, v2016 bool) (any, error) { +func decodeObject(dec *jsontext.Decoder, v2016 bool) (any, error) { obj := newSQLObject() - for dec.More() { - kt, err := dec.Token() - - key, ok := kt.(string) - if err != nil || !ok { + for dec.PeekKind() != '}' { + kt, err := dec.ReadToken() + if err != nil || kt.Kind() != '"' { return nil, errBadJSON } + key := kt.String() + v, verr := decodeToken(dec, v2016) if verr != nil { return nil, verr @@ -131,17 +137,17 @@ func decodeObject(dec *json.Decoder, v2016 bool) (any, error) { obj.set(key, v) } - if _, err := dec.Token(); err != nil { + if _, err := dec.ReadToken(); err != nil { return nil, errBadJSON } return obj, nil } -func decodeArray(dec *json.Decoder, v2016 bool) (any, error) { +func decodeArray(dec *jsontext.Decoder, v2016 bool) (any, error) { arr := []any{} - for dec.More() { + for dec.PeekKind() != ']' { v, err := decodeToken(dec, v2016) if err != nil { return nil, err @@ -150,7 +156,7 @@ func decodeArray(dec *json.Decoder, v2016 bool) (any, error) { arr = append(arr, v) } - if _, err := dec.Token(); err != nil { + if _, err := dec.ReadToken(); err != nil { return nil, errBadJSON } @@ -163,7 +169,7 @@ func writeJSONValue(buf *bytes.Buffer, v any) { case nil: buf.WriteString(jsonNull) case bool, string: - b, _ := json.Marshal(t) + b, _ := json.Marshal(t, jsonx.V1()) buf.Write(b) case int64: buf.WriteString(strconv.FormatInt(t, 10)) @@ -174,7 +180,7 @@ func writeJSONValue(buf *bytes.Buffer, v any) { case *sqlObject: writeJSONObject(buf, t) case sqlRaw: - b, _ := json.Marshal(string(t)) + b, _ := json.Marshal(string(t), jsonx.V1()) buf.Write(b) default: buf.WriteString(jsonNull) @@ -212,7 +218,7 @@ func writeJSONObject(buf *bytes.Buffer, o *sqlObject) { first = false - kb, _ := json.Marshal(k) + kb, _ := json.Marshal(k, jsonx.V1()) buf.Write(kb) buf.WriteByte(':') writeJSONValue(buf, v) diff --git a/services/iot/things_test.go b/services/iot/things_test.go index b2fa7b2de8..3ec6255e3c 100644 --- a/services/iot/things_test.go +++ b/services/iot/things_test.go @@ -1,7 +1,7 @@ package iot_test import ( - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iot" ) @@ -705,8 +706,8 @@ func TestUpdateThing_MergeFalse_ReplacesAttributes(t *testing.T) { require.NoError(t, err) var bodyAttr iot.AttributePayload - raw, _ := json.Marshal(tt.updateBody["attributePayload"]) - require.NoError(t, json.Unmarshal(raw, &bodyAttr)) + raw, _ := json.Marshal(tt.updateBody["attributePayload"], jsonx.V1()) + require.NoError(t, json.Unmarshal(raw, &bodyAttr, jsonx.V1())) err = b.UpdateThing(&iot.UpdateThingInput{ ThingName: "merge-test", diff --git a/services/iot/topic_rules.go b/services/iot/topic_rules.go index af4c0ca34d..b6563239e9 100644 --- a/services/iot/topic_rules.go +++ b/services/iot/topic_rules.go @@ -1,7 +1,7 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -26,9 +26,9 @@ func cloneRuleAction(a RuleAction) RuleAction { out.SNS = &cp } if len(a.Other) > 0 { - out.Other = make(map[string]json.RawMessage, len(a.Other)) + out.Other = make(map[string]jsontext.Value, len(a.Other)) for k, v := range a.Other { - out.Other[k] = append(json.RawMessage(nil), v...) + out.Other[k] = append(jsontext.Value(nil), v...) } } diff --git a/services/iot/types.go b/services/iot/types.go index dfbec94c99..7ac37530b2 100644 --- a/services/iot/types.go +++ b/services/iot/types.go @@ -3,9 +3,12 @@ package iot import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Thing represents an AWS IoT Thing. @@ -54,15 +57,15 @@ type RuleAction struct { Lambda *LambdaAction `json:"lambda,omitempty"` SNS *SNSAction `json:"sns,omitempty"` // Other holds action types with no typed model (s3, dynamoDB, ...), kept verbatim. - Other map[string]json.RawMessage `json:"-"` + Other map[string]jsontext.Value `json:"-"` } const typedRuleActionKeys = 3 // UnmarshalJSON decodes the typed actions and keeps every other key verbatim. func (a *RuleAction) UnmarshalJSON(data []byte) error { - var raw map[string]json.RawMessage - if err := json.Unmarshal(data, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return err } *a = RuleAction{} @@ -73,14 +76,14 @@ func (a *RuleAction) UnmarshalJSON(data []byte) error { var err error switch k { case "sqs": - err = json.Unmarshal(v, &a.SQS) + err = json.Unmarshal(v, &a.SQS, jsonx.V1()) case "lambda": - err = json.Unmarshal(v, &a.Lambda) + err = json.Unmarshal(v, &a.Lambda, jsonx.V1()) case "sns": - err = json.Unmarshal(v, &a.SNS) + err = json.Unmarshal(v, &a.SNS, jsonx.V1()) default: if a.Other == nil { - a.Other = map[string]json.RawMessage{} + a.Other = map[string]jsontext.Value{} } a.Other[k] = v } @@ -106,10 +109,10 @@ func (a RuleAction) MarshalJSON() ([]byte, error) { out["sns"] = a.SNS } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } -func castRaw(in map[string]json.RawMessage) map[string]any { +func castRaw(in map[string]jsontext.Value) map[string]any { out := make(map[string]any, len(in)) for k, v := range in { out[k] = v diff --git a/services/iotanalytics/handler_channels.go b/services/iotanalytics/handler_channels.go index 256e18006c..2ac6b4881a 100644 --- a/services/iotanalytics/handler_channels.go +++ b/services/iotanalytics/handler_channels.go @@ -1,16 +1,18 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateChannel(c *echo.Context, body []byte) error { var req createChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -114,7 +116,7 @@ func (h *Handler) handleUpdateChannel(c *echo.Context, name string, body []byte) var req updateChannelRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/iotanalytics/handler_channels_test.go b/services/iotanalytics/handler_channels_test.go index 09fb7bd4b0..147fece4f6 100644 --- a/services/iotanalytics/handler_channels_test.go +++ b/services/iotanalytics/handler_channels_test.go @@ -1,13 +1,15 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAndDescribeChannel(t *testing.T) { @@ -85,7 +87,7 @@ func TestHandler_ListChannels(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - err := json.Unmarshal(rec.Body.Bytes(), &resp) + err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) require.NoError(t, err) summaries, ok := resp["channelSummaries"].([]any) @@ -270,7 +272,7 @@ func TestHandler_SampleChannelData_WithMessages(t *testing.T) { require.Equal(t, http.StatusOK, sampleRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sampleRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sampleRec.Body.Bytes(), &resp, jsonx.V1())) payloads, ok := resp["payloads"].([]any) require.True(t, ok) assert.Len(t, payloads, 2) @@ -394,7 +396,7 @@ func TestHandler_DescribeChannel_NewFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "describe response must contain field %q", field) } @@ -435,7 +437,7 @@ func TestHandler_CreateChannel_RetentionPeriodInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasRP := resp["retentionPeriod"] assert.Equal(t, tt.wantRetention, hasRP) }) @@ -484,7 +486,7 @@ func TestHandler_DescribeChannel_IncludeStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasStats := resp["statistics"] assert.Equal(t, tt.wantStatistics, hasStats) }) @@ -508,7 +510,7 @@ func TestHandler_ListChannels_ChannelStorage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["channelSummaries"].([]any) require.Len(t, summaries, 1) summary, _ := summaries[0].(map[string]any) diff --git a/services/iotanalytics/handler_datasets.go b/services/iotanalytics/handler_datasets.go index a2a53967d1..7376eeb4dc 100644 --- a/services/iotanalytics/handler_datasets.go +++ b/services/iotanalytics/handler_datasets.go @@ -1,12 +1,14 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseQueryDateTime parses a scheduledBefore/scheduledOnOrAfter query parameter into epoch @@ -61,7 +63,7 @@ func filterDatasetContentsBySchedule(contents []*DatasetContent, c *echo.Context func (h *Handler) handleCreateDataset(c *echo.Context, body []byte) error { var req createDatasetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -190,7 +192,7 @@ func (h *Handler) handleUpdateDataset(c *echo.Context, name string, body []byte) var req updateDatasetRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -222,7 +224,7 @@ func (h *Handler) handleCreateDatasetContent(c *echo.Context, datasetName string var req createDatasetContentRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/iotanalytics/handler_datasets_test.go b/services/iotanalytics/handler_datasets_test.go index 44fe78cca5..ade737561a 100644 --- a/services/iotanalytics/handler_datasets_test.go +++ b/services/iotanalytics/handler_datasets_test.go @@ -1,7 +1,7 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAndDescribeDataset(t *testing.T) { @@ -101,7 +103,7 @@ func TestHandler_DatasetContentLifecycle(t *testing.T) { if tt.wantList == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["datasetContentSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -140,7 +142,7 @@ func TestHandler_CreateDatasetContent_ExplicitVersionID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-custom-version", resp["versionId"]) getRec := doRequest(t, h, http.MethodGet, "/datasets/explicit_ver_ds/content?versionId=my-custom-version", nil) @@ -189,7 +191,7 @@ func TestHandler_GetDatasetContent_MagicVersionStrings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) status, _ := resp["status"].(map[string]any) assert.Equal(t, "SUCCEEDED", status["state"]) @@ -214,7 +216,7 @@ func TestHandler_DeleteDatasetContent_OmittedVersionDeletesOnlyLatest(t *testing if i == 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) firstVersion, _ = resp["versionId"].(string) require.NotEmpty(t, firstVersion) } @@ -227,7 +229,7 @@ func TestHandler_DeleteDatasetContent_OmittedVersionDeletesOnlyLatest(t *testing require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, _ := listResp["datasetContentSummaries"].([]any) require.Len(t, summaries, 2, "omitted versionId must delete exactly one version") @@ -259,7 +261,7 @@ func TestHandler_ListDatasetContents_PaginationAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) vid, _ := resp["versionId"].(string) require.NotEmpty(t, vid) wantVersions[vid] = true @@ -273,7 +275,7 @@ func TestHandler_ListDatasetContents_PaginationAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["datasetContentSummaries"].([]any) for _, s := range summaries { @@ -315,7 +317,7 @@ func TestHandler_ListDatasetContents_ScheduleTime(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["datasetContentSummaries"].([]any) require.Len(t, summaries, 1) @@ -360,7 +362,7 @@ func TestHandler_ListDatasetContents_ScheduledFilters(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["datasetContentSummaries"].([]any) assert.Len(t, summaries, tt.wantLen) @@ -422,7 +424,7 @@ func TestHandler_Datasets(t *testing.T) { listRec := doRequest(t, h, http.MethodGet, "/datasets", nil) assert.Equal(t, tt.wantStatus, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["datasetSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -478,7 +480,7 @@ func TestHandler_DatasetContent_GetVersionSpecific(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) versionID, ok := createResp["versionId"].(string) require.True(t, ok) require.NotEmpty(t, versionID) @@ -542,7 +544,7 @@ func TestHandler_DescribeDataset_NewFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "describe response must contain field %q", field) } @@ -566,7 +568,7 @@ func TestHandler_GetDatasetContent_VersionAndEntries(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasVersionID := resp["versionId"] assert.False(t, hasVersionID, "GetDatasetContent must not return versionId -- AWS has no such member") _, hasStatus := resp["status"] @@ -608,7 +610,7 @@ func TestHandler_DeleteDatasetContent_RequiresVersionId(t *testing.T) { deletePath := tt.path if deletePath == "" { var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) vid, _ := cr["versionId"].(string) require.NotEmpty(t, vid) deletePath = "/datasets/delcontds/content?versionId=" + vid @@ -635,7 +637,7 @@ func TestHandler_ListDatasetContents_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["nextToken"], "nextToken must be set when results are truncated") summaries, _ := resp["datasetContentSummaries"].([]any) assert.Len(t, summaries, 2) diff --git a/services/iotanalytics/handler_datastores.go b/services/iotanalytics/handler_datastores.go index bdc65b22ae..c8712dee56 100644 --- a/services/iotanalytics/handler_datastores.go +++ b/services/iotanalytics/handler_datastores.go @@ -1,15 +1,17 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateDatastore(c *echo.Context, body []byte) error { var req createDatastoreRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -135,7 +137,7 @@ func (h *Handler) handleUpdateDatastore(c *echo.Context, name string, body []byt var req updateDatastoreRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/iotanalytics/handler_datastores_test.go b/services/iotanalytics/handler_datastores_test.go index 4a9c9e205f..f0f66f40be 100644 --- a/services/iotanalytics/handler_datastores_test.go +++ b/services/iotanalytics/handler_datastores_test.go @@ -1,12 +1,14 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAndDescribeDatastore(t *testing.T) { @@ -125,7 +127,7 @@ func TestHandler_Datastores(t *testing.T) { listRec := doRequest(t, h, http.MethodGet, "/datastores", nil) assert.Equal(t, tt.wantStatus, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["datastoreSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -207,7 +209,7 @@ func TestHandler_DescribeDatastore_NewFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "describe response must contain field %q", field) } @@ -248,7 +250,7 @@ func TestHandler_CreateDatastore_RetentionPeriodInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasRP := resp["retentionPeriod"] assert.Equal(t, tt.wantRetention, hasRP) }) @@ -292,7 +294,7 @@ func TestHandler_DescribeDatastore_IncludeStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasStats := resp["statistics"] assert.Equal(t, tt.wantStatistics, hasStats) }) @@ -316,7 +318,7 @@ func TestHandler_ListDatastores_DatastoreStorage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["datastoreSummaries"].([]any) require.Len(t, summaries, 1) summary, _ := summaries[0].(map[string]any) diff --git a/services/iotanalytics/handler_logging.go b/services/iotanalytics/handler_logging.go index c10d9bd8d6..376cf5801b 100644 --- a/services/iotanalytics/handler_logging.go +++ b/services/iotanalytics/handler_logging.go @@ -1,10 +1,12 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeLoggingOptions(c *echo.Context) error { @@ -24,7 +26,7 @@ func (h *Handler) handleDescribeLoggingOptions(c *echo.Context) error { func (h *Handler) handlePutLoggingOptions(c *echo.Context, body []byte) error { var req putLoggingOptionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/iotanalytics/handler_logging_test.go b/services/iotanalytics/handler_logging_test.go index 46508535ff..402b97ec90 100644 --- a/services/iotanalytics/handler_logging_test.go +++ b/services/iotanalytics/handler_logging_test.go @@ -1,12 +1,14 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_LoggingOptions(t *testing.T) { @@ -64,7 +66,7 @@ func TestHandler_LoggingOptions(t *testing.T) { if tt.wantDescribe == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) opts, ok := resp["loggingOptions"].(map[string]any) require.True(t, ok) assert.Equal(t, "ERROR", opts["level"]) diff --git a/services/iotanalytics/handler_messages.go b/services/iotanalytics/handler_messages.go index e9da65197c..5be56842c3 100644 --- a/services/iotanalytics/handler_messages.go +++ b/services/iotanalytics/handler_messages.go @@ -1,16 +1,18 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleBatchPutMessage(c *echo.Context, body []byte) error { var req batchPutMessageRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/iotanalytics/handler_messages_test.go b/services/iotanalytics/handler_messages_test.go index 2b532ff521..9b5ada8821 100644 --- a/services/iotanalytics/handler_messages_test.go +++ b/services/iotanalytics/handler_messages_test.go @@ -1,13 +1,15 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_BatchPutMessage(t *testing.T) { @@ -67,7 +69,7 @@ func TestHandler_BatchPutMessage(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) entries, _ := resp["batchPutMessageErrorEntries"].([]any) assert.Len(t, entries, tt.wantErrors) } @@ -149,7 +151,7 @@ func TestHandler_BatchPutMessage_NoChannelNameInErrorEntry(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) entries, _ := resp["batchPutMessageErrorEntries"].([]any) require.Len(t, entries, 1) entry, _ := entries[0].(map[string]any) diff --git a/services/iotanalytics/handler_pipelines.go b/services/iotanalytics/handler_pipelines.go index 5897b777a0..ddb13bf253 100644 --- a/services/iotanalytics/handler_pipelines.go +++ b/services/iotanalytics/handler_pipelines.go @@ -1,16 +1,18 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreatePipeline(c *echo.Context, body []byte) error { var req createPipelineRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -97,7 +99,7 @@ func (h *Handler) handleUpdatePipeline(c *echo.Context, name string, body []byte var req updatePipelineRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -126,7 +128,7 @@ func (h *Handler) handleStartPipelineReprocessing(c *echo.Context, pipelineName var req startPipelineReprocessingRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -162,7 +164,7 @@ func (h *Handler) handleCancelPipelineReprocessing(c *echo.Context, resource str func (h *Handler) handleRunPipelineActivity(c *echo.Context, body []byte) error { var req runPipelineActivityRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/iotanalytics/handler_pipelines_test.go b/services/iotanalytics/handler_pipelines_test.go index 4a99b42814..0e98f62387 100644 --- a/services/iotanalytics/handler_pipelines_test.go +++ b/services/iotanalytics/handler_pipelines_test.go @@ -1,12 +1,14 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAndDescribePipeline(t *testing.T) { @@ -105,7 +107,7 @@ func TestHandler_StartAndCancelPipelineReprocessing(t *testing.T) { if tt.wantStart == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &resp, jsonx.V1())) reprocessingID, ok := resp["reprocessingId"].(string) require.True(t, ok) assert.NotEmpty(t, reprocessingID) @@ -169,7 +171,7 @@ func TestHandler_RunPipelineActivity(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) payloads, _ := resp["payloads"].([]any) assert.Len(t, payloads, tt.wantLen) } @@ -233,7 +235,7 @@ func TestHandler_Pipelines(t *testing.T) { listRec := doRequest(t, h, http.MethodGet, "/pipelines", nil) assert.Equal(t, tt.wantStatus, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["pipelineSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -350,7 +352,7 @@ func TestHandler_DescribePipeline_NewFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field, "describe response must contain field %q", field) } @@ -452,7 +454,7 @@ func TestHandler_ReprocessingSummaries_Sorted(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &resp, jsonx.V1())) ids[i], _ = resp["reprocessingId"].(string) require.NotEmpty(t, ids[i]) } @@ -462,7 +464,7 @@ func TestHandler_ReprocessingSummaries_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) pipeline, ok := resp["pipeline"].(map[string]any) require.True(t, ok) summaries, ok := pipeline["reprocessingSummaries"].([]any) @@ -510,7 +512,7 @@ func TestHandler_PipelineReprocessingSummary_NoStartEndTimeLeak(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) pl, _ := resp["pipeline"].(map[string]any) summaries, _ := pl["reprocessingSummaries"].([]any) require.Len(t, summaries, 1) diff --git a/services/iotanalytics/handler_sdk_route_table_test.go b/services/iotanalytics/handler_sdk_route_table_test.go index ca841ecf3e..944ad8cf44 100644 --- a/services/iotanalytics/handler_sdk_route_table_test.go +++ b/services/iotanalytics/handler_sdk_route_table_test.go @@ -1,7 +1,7 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sdkRouteCases is the authoritative method+path for every real IoT @@ -113,7 +115,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var resp struct { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEqual(t, "not found", resp.Message, "method=%s path=%s op=%s: dispatched to the unmatched-route default", tc.method, tc.path, tc.op) }) diff --git a/services/iotanalytics/handler_tags.go b/services/iotanalytics/handler_tags.go index 4009a59beb..5db478f222 100644 --- a/services/iotanalytics/handler_tags.go +++ b/services/iotanalytics/handler_tags.go @@ -1,10 +1,12 @@ package iotanalytics import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTagsForResource(c *echo.Context) error { @@ -38,7 +40,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { } var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/iotanalytics/handler_tags_test.go b/services/iotanalytics/handler_tags_test.go index cc2fedb9b6..a4366d8936 100644 --- a/services/iotanalytics/handler_tags_test.go +++ b/services/iotanalytics/handler_tags_test.go @@ -1,13 +1,15 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_TagOperations covers ListTagsForResource/TagResource/UntagResource. @@ -63,7 +65,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn, _ := createResp["channelArn"].(string) require.NotEmpty(t, arn) @@ -152,7 +154,7 @@ func TestHandler_CreateResource_InvalidTagsRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errResp["__type"]) okRec := doRequest(t, h, http.MethodPost, tt.path, tt.body(validTags)) @@ -191,7 +193,7 @@ func TestHandler_TagResource_Returns204(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["channelArn"].(string) require.NotEmpty(t, arn) @@ -252,7 +254,7 @@ func TestHandler_TagValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["channelArn"].(string) require.NotEmpty(t, arn) @@ -306,7 +308,7 @@ func TestHandler_ListTagsForResource_EmptyNotError(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn, _ := createResp[tt.arnKey].(string) require.NotEmpty(t, arn) @@ -314,7 +316,7 @@ func TestHandler_ListTagsForResource_EmptyNotError(t *testing.T) { assert.Equal(t, http.StatusOK, tagRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp, jsonx.V1())) tags, ok := tagResp["tags"].([]any) require.True(t, ok, "tags field must be array") assert.Empty(t, tags, "newly created resource must have empty tag array") diff --git a/services/iotanalytics/handler_test.go b/services/iotanalytics/handler_test.go index 0f0b1dbc52..5ab9ab69ab 100644 --- a/services/iotanalytics/handler_test.go +++ b/services/iotanalytics/handler_test.go @@ -2,7 +2,7 @@ package iotanalytics_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotanalytics" ) @@ -47,7 +48,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -321,7 +322,7 @@ func TestHandler_AlreadyExistsErrorType(t *testing.T) { require.Equal(t, http.StatusConflict, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceAlreadyExistsException", resp["__type"]) }) } @@ -387,7 +388,7 @@ func TestHandler_ErrorResponseFormat(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"], "error response must have __type field") assert.NotEmpty(t, resp["message"], "error response must have message field") }) @@ -626,7 +627,7 @@ func TestHandler_Pagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp[summaryKey].([]any) require.True(t, ok) assert.Len(t, summaries, tt.wantLen) @@ -658,7 +659,7 @@ func TestHandler_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) summaries1, ok := resp1["channelSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries1, 2) @@ -671,7 +672,7 @@ func TestHandler_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) summaries2, ok := resp2["channelSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries2, 1, "page 2 should have the remaining 1 channel") diff --git a/services/iotanalytics/persistence.go b/services/iotanalytics/persistence.go index 8f03b63400..e6919698f2 100644 --- a/services/iotanalytics/persistence.go +++ b/services/iotanalytics/persistence.go @@ -2,7 +2,7 @@ package iotanalytics import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -42,7 +42,7 @@ const iotanalyticsSnapshotVersion = 2 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` ChannelMessages map[string][]ChannelMessage `json:"channelMessages"` DatasetContents map[string][]*DatasetContent `json:"datasetContents"` diff --git a/services/iotanalytics/pipeline_expr_test.go b/services/iotanalytics/pipeline_expr_test.go index 795cf6ad4a..89022c7885 100644 --- a/services/iotanalytics/pipeline_expr_test.go +++ b/services/iotanalytics/pipeline_expr_test.go @@ -1,12 +1,13 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotanalytics" ) @@ -246,7 +247,7 @@ func TestInMemoryBackend_RunPipelineActivity_Math(t *testing.T) { } var decoded map[string]any - require.NoError(t, json.Unmarshal(out[0], &decoded)) + require.NoError(t, json.Unmarshal(out[0], &decoded, jsonx.V1())) assert.InDelta(t, tt.wantValue, decoded[tt.attribute], 1e-9) }) } diff --git a/services/iotanalytics/pipeline_lambda_iot_test.go b/services/iotanalytics/pipeline_lambda_iot_test.go index 63d4a9d439..9037db1c84 100644 --- a/services/iotanalytics/pipeline_lambda_iot_test.go +++ b/services/iotanalytics/pipeline_lambda_iot_test.go @@ -2,13 +2,14 @@ package iotanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotanalytics" ) @@ -43,7 +44,7 @@ func echoAddFieldLambda(calls *[]string) *fakeLambdaInvoker { calls: calls, invoke: func(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { var msgs []map[string]any - if err := json.Unmarshal(payload, &msgs); err != nil { + if err := json.Unmarshal(payload, &msgs, jsonx.V1()); err != nil { return nil, 0, err } @@ -51,7 +52,7 @@ func echoAddFieldLambda(calls *[]string) *fakeLambdaInvoker { m["seen"] = true } - out, err := json.Marshal(msgs) + out, err := json.Marshal(msgs, jsonx.V1()) return out, 200, err }, @@ -105,7 +106,7 @@ func TestInMemoryBackend_RunPipelineActivity_Lambda(t *testing.T) { require.Len(t, out, 1) var decoded map[string]any - require.NoError(t, json.Unmarshal(out[0], &decoded)) + require.NoError(t, json.Unmarshal(out[0], &decoded, jsonx.V1())) assert.Equal(t, true, decoded["seen"]) assert.InDelta(t, 1.0, decoded["x"], 1e-9) }) @@ -130,11 +131,11 @@ func TestInMemoryBackend_RunPipelineActivity_Lambda(t *testing.T) { require.Len(t, calls, 2, "3 payloads at batchSize=2 must invoke twice (2 then 1)") var firstBatch []map[string]any - require.NoError(t, json.Unmarshal([]byte(calls[0]), &firstBatch)) + require.NoError(t, json.Unmarshal([]byte(calls[0]), &firstBatch, jsonx.V1())) assert.Len(t, firstBatch, 2) var secondBatch []map[string]any - require.NoError(t, json.Unmarshal([]byte(calls[1]), &secondBatch)) + require.NoError(t, json.Unmarshal([]byte(calls[1]), &secondBatch, jsonx.V1())) assert.Len(t, secondBatch, 1) }) @@ -218,7 +219,7 @@ func TestInMemoryBackend_RunPipelineActivity_DeviceRegistryEnrich(t *testing.T) require.Len(t, out, 1) var decoded map[string]any - require.NoError(t, json.Unmarshal(out[0], &decoded)) + require.NoError(t, json.Unmarshal(out[0], &decoded, jsonx.V1())) registry, ok := decoded["registry"].(map[string]any) require.True(t, ok) @@ -270,7 +271,7 @@ func TestInMemoryBackend_RunPipelineActivity_DeviceShadowEnrich(t *testing.T) { require.Len(t, out, 1) var decoded map[string]any - require.NoError(t, json.Unmarshal(out[0], &decoded)) + require.NoError(t, json.Unmarshal(out[0], &decoded, jsonx.V1())) shadow, ok := decoded["shadow"].(map[string]any) require.True(t, ok) diff --git a/services/iotanalytics/pipelines.go b/services/iotanalytics/pipelines.go index e4fbc43467..dccf1d6074 100644 --- a/services/iotanalytics/pipelines.go +++ b/services/iotanalytics/pipelines.go @@ -3,13 +3,15 @@ package iotanalytics import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // clonePipelineActivities deep-copies a slice of PipelineActivity. @@ -371,7 +373,7 @@ func (b *InMemoryBackend) invokeLambdaBatch(ctx context.Context, lambdaName stri return nil } - reqBody, err := json.Marshal(msgs) + reqBody, err := json.Marshal(msgs, jsonx.V1()) if err != nil { return fmt.Errorf("%w: marshal lambda batch for %q: %w", ErrPipelineActivityFailed, lambdaName, err) } @@ -382,7 +384,7 @@ func (b *InMemoryBackend) invokeLambdaBatch(ctx context.Context, lambdaName stri } var results []map[string]any - if jsonErr := json.Unmarshal(respBody, &results); jsonErr != nil || len(results) != len(msgs) { + if jsonErr := json.Unmarshal(respBody, &results, jsonx.V1()); jsonErr != nil || len(results) != len(msgs) { return fmt.Errorf( "%w: lambda activity %q did not return a JSON object array of the same length", ErrPipelineActivityFailed, lambdaName, @@ -464,7 +466,7 @@ func enrichPayloads(attribute string, data map[string]any, payloads [][]byte) [] // decodeMessage attempts to unmarshal a message payload as a JSON object. func decodeMessage(payload []byte) (map[string]any, bool) { var m map[string]any - if err := json.Unmarshal(payload, &m); err != nil { + if err := json.Unmarshal(payload, &m, jsonx.V1()); err != nil { return nil, false } @@ -475,7 +477,7 @@ func decodeMessage(payload []byte) (map[string]any, bool) { // marshaling somehow fails (m came from a successful Unmarshal, so this is unreachable in // practice, but encodeMessage must never panic or drop data on error). func encodeMessage(m map[string]any, fallback []byte) []byte { - b, err := json.Marshal(m) + b, err := json.Marshal(m, jsonx.V1()) if err != nil { return fallback } diff --git a/services/iotanalytics/pipelines_test.go b/services/iotanalytics/pipelines_test.go index 05cd8cd9b4..78e24be74d 100644 --- a/services/iotanalytics/pipelines_test.go +++ b/services/iotanalytics/pipelines_test.go @@ -2,12 +2,13 @@ package iotanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotanalytics" ) @@ -230,7 +231,7 @@ func TestInMemoryBackend_RunPipelineActivity_AttributeActivities(t *testing.T) { } var decoded map[string]any - require.NoError(t, json.Unmarshal(out[0], &decoded)) + require.NoError(t, json.Unmarshal(out[0], &decoded, jsonx.V1())) assert.Len(t, decoded, len(tt.wantKeys)) for k, v := range tt.wantKeys { diff --git a/services/iotanalytics/wire_shape_sdk_roundtrip_test.go b/services/iotanalytics/wire_shape_sdk_roundtrip_test.go index b0ff62716e..462c973a08 100644 --- a/services/iotanalytics/wire_shape_sdk_roundtrip_test.go +++ b/services/iotanalytics/wire_shape_sdk_roundtrip_test.go @@ -1,7 +1,7 @@ package iotanalytics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotanalytics" ) @@ -226,7 +227,7 @@ func TestListSummaries_NoFabricatedARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp[tt.summariesK].([]any) require.True(t, ok) @@ -273,7 +274,7 @@ func TestDatastore_IotSiteWiseCustomerManagedS3Storage_NoRoleArn(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) datastore, ok := resp["datastore"].(map[string]any) require.True(t, ok) diff --git a/services/iotdataplane/connections_test.go b/services/iotdataplane/connections_test.go index 1b0b74ba1f..0aee54e023 100644 --- a/services/iotdataplane/connections_test.go +++ b/services/iotdataplane/connections_test.go @@ -2,7 +2,7 @@ package iotdataplane_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -209,7 +210,7 @@ func Test_ListConnections_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) conns := resp["connections"].([]any) assert.Empty(t, conns) } @@ -257,7 +258,7 @@ func Test_ListConnections_ShowsRegistered(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) conns := resp["connections"].([]any) assert.Len(t, conns, 2) } @@ -323,7 +324,7 @@ func Test_AdminConnectionsPath_BasicFlow(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/_admin/connections", nil) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["connections"].([]any), 1) // Delete. @@ -393,7 +394,7 @@ func Test_Connection_Register_DuplicateShape(t *testing.T) { require.Equal(t, http.StatusConflict, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ConflictException", resp["error"]) } func Test_Connection_EmptyClientID_Rejected(t *testing.T) { @@ -414,7 +415,7 @@ func Test_Connection_DollarPrefix_Rejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["error"]) } func Test_Connection_DeleteUnknown_NotFound_DollarStillRejected(t *testing.T) { @@ -457,7 +458,7 @@ func Test_DeleteConnection_UnknownClient_HTTPShape(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["error"]) }) } @@ -568,7 +569,7 @@ func TestHandler_GetConnection_WireShape(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.clientID, resp["clientId"]) assert.Equal(t, true, resp["connected"]) assert.NotZero(t, resp["connectedSince"]) @@ -661,7 +662,7 @@ func TestHandler_ListSubscriptions_WireShape(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) subs, ok := resp["subscriptions"].([]any) require.True(t, ok) assert.Len(t, subs, len(tt.brokerSubs)) @@ -753,7 +754,7 @@ func TestHandler_ListSubscriptions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) subs, ok := resp["subscriptions"].([]any) require.True(t, ok) @@ -985,7 +986,7 @@ func TestHandler_SendDirectMessage_WireShape(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "OK", resp["message"]) assert.NotEmpty(t, resp["traceId"]) @@ -1081,7 +1082,7 @@ func Test_GetConnection_AfterDelete(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, false, resp["connected"]) assert.Equal(t, "SERVER_INITIATED_DISCONNECT", resp["disconnectReason"]) assert.NotZero(t, resp["disconnectedSince"]) diff --git a/services/iotdataplane/export_test.go b/services/iotdataplane/export_test.go index 0dab9f5829..ddbca34063 100644 --- a/services/iotdataplane/export_test.go +++ b/services/iotdataplane/export_test.go @@ -1,9 +1,11 @@ package iotdataplane -import "encoding/json" +import ( + "encoding/json/jsontext" +) // MergeStateFields exposes mergeStateFields for white-box testing. -func MergeStateFields(base, patch map[string]json.RawMessage) map[string]json.RawMessage { +func MergeStateFields(base, patch map[string]jsontext.Value) map[string]jsontext.Value { return mergeStateFields(base, patch) } @@ -80,7 +82,7 @@ func ForceSetShadowVersion(b *InMemoryBackend, thingName, shadowName string, ver } // GetShadowDesired returns the desired state map for white-box testing. -func GetShadowDesired(b *InMemoryBackend, thingName, shadowName string) map[string]json.RawMessage { +func GetShadowDesired(b *InMemoryBackend, thingName, shadowName string) map[string]jsontext.Value { b.mu.RLock("GetShadowDesired") defer b.mu.RUnlock() @@ -93,7 +95,7 @@ func GetShadowDesired(b *InMemoryBackend, thingName, shadowName string) map[stri } // GetShadowReported returns the reported state map for white-box testing. -func GetShadowReported(b *InMemoryBackend, thingName, shadowName string) map[string]json.RawMessage { +func GetShadowReported(b *InMemoryBackend, thingName, shadowName string) map[string]jsontext.Value { b.mu.RLock("GetShadowReported") defer b.mu.RUnlock() diff --git a/services/iotdataplane/handler_publish.go b/services/iotdataplane/handler_publish.go index 9d6747495b..d4dee28cf0 100644 --- a/services/iotdataplane/handler_publish.go +++ b/services/iotdataplane/handler_publish.go @@ -1,7 +1,8 @@ package iotdataplane import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -39,12 +41,12 @@ func unwrapPublishPayload(body []byte, contentType string) []byte { } var wrapper struct { - Payload *json.RawMessage `json:"payload"` + Payload *jsontext.Value `json:"payload"` } - if jsonErr := json.Unmarshal(body, &wrapper); jsonErr == nil && wrapper.Payload != nil { + if jsonErr := json.Unmarshal(body, &wrapper, jsonx.V1()); jsonErr == nil && wrapper.Payload != nil { var payloadStr string - if unmarshalErr := json.Unmarshal(*wrapper.Payload, &payloadStr); unmarshalErr == nil { + if unmarshalErr := json.Unmarshal(*wrapper.Payload, &payloadStr, jsonx.V1()); unmarshalErr == nil { return []byte(payloadStr) } } diff --git a/services/iotdataplane/handler_sdk_route_table_test.go b/services/iotdataplane/handler_sdk_route_table_test.go index 7f8385d37f..3a695d2f08 100644 --- a/services/iotdataplane/handler_sdk_route_table_test.go +++ b/services/iotdataplane/handler_sdk_route_table_test.go @@ -1,7 +1,7 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sdkRouteCases is the authoritative method+path for every real IoT Data @@ -96,7 +98,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var decoded struct { Error string `json:"error"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &decoded, jsonx.V1())) assert.NotEqual(t, "not found", decoded.Error, "method=%s path=%s op=%s: dispatched to the unmatched-route default", tc.method, tc.path, tc.op) }) diff --git a/services/iotdataplane/handler_test.go b/services/iotdataplane/handler_test.go index 77639ec0aa..270972c7c7 100644 --- a/services/iotdataplane/handler_test.go +++ b/services/iotdataplane/handler_test.go @@ -2,7 +2,7 @@ package iotdataplane_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -609,7 +610,7 @@ func Test_ErrorShapes_AllTypes(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code, "unexpected status for %s %s", tt.method, tt.path) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantError, resp["error"], "unexpected error type for %s %s", tt.method, tt.path) _, hasMsg := resp["message"] @@ -629,7 +630,7 @@ func Test_VersionConflict_ErrorShape(t *testing.T) { require.Equal(t, http.StatusConflict, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ConflictException", resp["error"]) assert.NotContains(t, resp, "code", "a numeric code breaks the SDK's string-typed body decode") } diff --git a/services/iotdataplane/models.go b/services/iotdataplane/models.go index e90c08b904..d598d57729 100644 --- a/services/iotdataplane/models.go +++ b/services/iotdataplane/models.go @@ -1,7 +1,7 @@ package iotdataplane import ( - "encoding/json" + "encoding/json/jsontext" "regexp" "time" ) @@ -57,10 +57,10 @@ var _ StorageBackend = (*InMemoryBackend)(nil) // never JSON-marshaled directly; shadowEntrySnap (persistence.go) is its serialisable form. type shadowEntry struct { updatedAt time.Time - desired map[string]json.RawMessage // nil = not set - reported map[string]json.RawMessage // nil = not set - metaDesired map[string]int64 // field → epoch seconds of last update - metaReported map[string]int64 // field → epoch seconds of last update + desired map[string]jsontext.Value // nil = not set + reported map[string]jsontext.Value // nil = not set + metaDesired map[string]int64 // field → epoch seconds of last update + metaReported map[string]int64 // field → epoch seconds of last update thingName string shadowName string version int diff --git a/services/iotdataplane/persistence.go b/services/iotdataplane/persistence.go index c8e70f0571..6bcba90ca0 100644 --- a/services/iotdataplane/persistence.go +++ b/services/iotdataplane/persistence.go @@ -2,7 +2,7 @@ package iotdataplane import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "maps" @@ -32,14 +32,14 @@ const iotdataplaneSnapshotVersion = 1 // unexported identity fields (see store_setup.go) -- shadowEntry itself // carries no exported fields. type shadowEntrySnap struct { - UpdatedAt time.Time `json:"updatedAt"` - Desired map[string]json.RawMessage `json:"desired,omitempty"` - Reported map[string]json.RawMessage `json:"reported,omitempty"` - MetaDesired map[string]int64 `json:"metaDesired,omitempty"` - MetaReported map[string]int64 `json:"metaReported,omitempty"` - ThingName string `json:"thingName"` - ShadowName string `json:"shadowName"` - Version int `json:"version"` + UpdatedAt time.Time `json:"updatedAt"` + Desired map[string]jsontext.Value `json:"desired,omitempty"` + Reported map[string]jsontext.Value `json:"reported,omitempty"` + MetaDesired map[string]int64 `json:"metaDesired,omitempty"` + MetaReported map[string]int64 `json:"metaReported,omitempty"` + ThingName string `json:"thingName"` + ShadowName string `json:"shadowName"` + Version int `json:"version"` // Deleted round-trips the tombstone flag (see shadowEntry.deleted) so a // restored snapshot preserves delete-then-recreate version continuity. // Omitted (defaults to false) for pre-existing snapshots, which is the @@ -80,14 +80,14 @@ func newDirtyDTORegistry() ( } // copyRawMessages returns a deep copy of a map[string]json.RawMessage. -func copyRawMessages(src map[string]json.RawMessage) map[string]json.RawMessage { +func copyRawMessages(src map[string]jsontext.Value) map[string]jsontext.Value { if src == nil { return nil } - dst := make(map[string]json.RawMessage, len(src)) + dst := make(map[string]jsontext.Value, len(src)) for k, v := range src { - cp := make(json.RawMessage, len(v)) + cp := make(jsontext.Value, len(v)) copy(cp, v) dst[k] = cp } @@ -147,8 +147,8 @@ func snapToEntry(es *shadowEntrySnap) *shadowEntry { // guards against decoding a snapshot from an incompatible (older or newer) // build of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises backend state to JSON. @@ -236,7 +236,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreDirtyTablesLocked restores the "dirty" tables (shadows, // connections) from tables via the ephemeral DTO registry, converting each // DTO back to its live type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, shadowDTOs, connDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/iotdataplane/persistence_test.go b/services/iotdataplane/persistence_test.go index ccf80ea9b2..5e83db3757 100644 --- a/services/iotdataplane/persistence_test.go +++ b/services/iotdataplane/persistence_test.go @@ -1,13 +1,14 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -377,7 +378,7 @@ func Test_Persistence_ShadowWithMetadata(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) desired := state["desired"].(map[string]any) @@ -412,7 +413,7 @@ func Test_Persistence_NullWipeSurvivesRoundTrip(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] diff --git a/services/iotdataplane/publish.go b/services/iotdataplane/publish.go index 6eba6a8703..f19ddf593e 100644 --- a/services/iotdataplane/publish.go +++ b/services/iotdataplane/publish.go @@ -2,11 +2,13 @@ package iotdataplane import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // payloadFormatIndicatorUnspecifiedBytes and payloadFormatIndicatorUTF8Data @@ -108,7 +110,7 @@ func parseUserProperties(raw []byte) ([]MQTT5UserProperty, error) { } var entries []map[string]string - if err := json.Unmarshal(raw, &entries); err != nil { + if err := json.Unmarshal(raw, &entries, jsonx.V1()); err != nil { return nil, fmt.Errorf( "%w: userProperties must be a JSON array of single-key objects", ErrValidation, diff --git a/services/iotdataplane/retained_messages_test.go b/services/iotdataplane/retained_messages_test.go index 5208022651..927627ca26 100644 --- a/services/iotdataplane/retained_messages_test.go +++ b/services/iotdataplane/retained_messages_test.go @@ -2,7 +2,7 @@ package iotdataplane_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -212,7 +213,7 @@ func Test_ListRetainedMessages_Pagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) topics1 := page1["retainedTopics"].([]any) assert.Len(t, topics1, 2) @@ -225,7 +226,7 @@ func Test_ListRetainedMessages_Pagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) topics2 := page2["retainedTopics"].([]any) assert.Len(t, topics2, 2) } @@ -237,7 +238,7 @@ func Test_ListRetainedMessages_NonNilWhenEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) topics, ok := resp["retainedTopics"] require.True(t, ok) @@ -318,7 +319,7 @@ func Test_ListRetainedMessages_PageSizeParam(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) topics := resp["retainedTopics"].([]any) assert.Len(t, topics, tt.wantPageCount, "query: %s", tt.query) }) @@ -339,7 +340,7 @@ func Test_ListRetainedMessages_PaginationOffByOneFixed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) topics1 := page1["retainedTopics"].([]any) require.Len(t, topics1, 2) @@ -354,7 +355,7 @@ func Test_ListRetainedMessages_PaginationOffByOneFixed(t *testing.T) { // Page 2: cursor=c/3 → page starts at c/3 (nextToken IS the first item of the page). rec = doRequest(t, h, http.MethodGet, "/retainedMessage?pageSize=2&nextToken="+nextToken, nil) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) topics2 := page2["retainedTopics"].([]any) require.Len(t, topics2, 2) @@ -373,7 +374,7 @@ func Test_ListRetainedMessages_SummaryIncludesQos(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) topics := resp["retainedTopics"].([]any) require.Len(t, topics, 1) @@ -401,7 +402,7 @@ func Test_GetRetainedMessage_StillHasQos(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "qos", "GetRetainedMessage full response must include qos") } @@ -433,7 +434,7 @@ func Test_GetRetainedMessage_UserPropertiesField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Contains(t, resp, "userProperties") if tt.wantNull { @@ -489,7 +490,7 @@ func Test_RetainedMessage_GetNonexistent_404Shape(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["error"]) } func Test_RetainedMessage_Lifecycle(t *testing.T) { @@ -550,7 +551,7 @@ func Test_ListRetainedMessages_MaxResultsAlias(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) topics := resp["retainedTopics"].([]any) assert.Len(t, topics, 3) diff --git a/services/iotdataplane/shadows.go b/services/iotdataplane/shadows.go index c17825ed6c..49773de1bf 100644 --- a/services/iotdataplane/shadows.go +++ b/services/iotdataplane/shadows.go @@ -1,7 +1,8 @@ package iotdataplane import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -9,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validateThingName checks that a thing name meets AWS IoT naming rules. @@ -83,8 +85,8 @@ func validateShadowDocument(doc []byte) error { return fmt.Errorf("%w: The payload exceeds the maximum size allowed", ErrRequestTooLarge) } - var obj map[string]json.RawMessage - if err := json.Unmarshal(doc, &obj); err != nil || obj == nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(doc, &obj, jsonx.V1()); err != nil || obj == nil { return fmt.Errorf("%w: Invalid JSON", ErrValidation) } @@ -92,16 +94,16 @@ func validateShadowDocument(doc []byte) error { } // isJSONNull reports whether v is the JSON null literal. -func isJSONNull(v json.RawMessage) bool { +func isJSONNull(v jsontext.Value) bool { var x any - return json.Unmarshal(v, &x) == nil && x == nil + return json.Unmarshal(v, &x, jsonx.V1()) == nil && x == nil } // mergeStateFields deep-merges patch into base: null deletes a key, nested // objects merge recursively, other values replace. base and patch are unchanged. -func mergeStateFields(base, patch map[string]json.RawMessage) map[string]json.RawMessage { - result := make(map[string]json.RawMessage, len(base)) +func mergeStateFields(base, patch map[string]jsontext.Value) map[string]jsontext.Value { + result := make(map[string]jsontext.Value, len(base)) maps.Copy(result, base) for k, v := range patch { @@ -116,7 +118,7 @@ func mergeStateFields(base, patch map[string]json.RawMessage) map[string]json.Ra return result } -func mergeJSONValue(existing, patch json.RawMessage) json.RawMessage { +func mergeJSONValue(existing, patch jsontext.Value) jsontext.Value { patchObj, ok := decodeJSONObject(patch) if !ok { return patch @@ -127,7 +129,7 @@ func mergeJSONValue(existing, patch json.RawMessage) json.RawMessage { existingObj = nil } - merged, err := json.Marshal(mergeStateFields(existingObj, patchObj)) + merged, err := json.Marshal(mergeStateFields(existingObj, patchObj), jsonx.V1()) if err != nil { return patch } @@ -135,9 +137,9 @@ func mergeJSONValue(existing, patch json.RawMessage) json.RawMessage { return merged } -func decodeJSONObject(raw json.RawMessage) (map[string]json.RawMessage, bool) { - var obj map[string]json.RawMessage - if len(raw) == 0 || json.Unmarshal(raw, &obj) != nil || obj == nil { +func decodeJSONObject(raw jsontext.Value) (map[string]jsontext.Value, bool) { + var obj map[string]jsontext.Value + if len(raw) == 0 || json.Unmarshal(raw, &obj, jsonx.V1()) != nil || obj == nil { return nil, false } @@ -146,7 +148,7 @@ func decodeJSONObject(raw json.RawMessage) (map[string]json.RawMessage, bool) { // updateMetaFields returns a copy of meta with timestamps updated for keys present in patch. // Null-deleted keys are removed from the returned meta map. -func updateMetaFields(meta map[string]int64, patch map[string]json.RawMessage, ts int64) map[string]int64 { +func updateMetaFields(meta map[string]int64, patch map[string]jsontext.Value, ts int64) map[string]int64 { result := make(map[string]int64, len(meta)+len(patch)) maps.Copy(result, meta) @@ -163,8 +165,8 @@ func updateMetaFields(meta map[string]int64, patch map[string]json.RawMessage, t // computeDelta returns the fields where desired differs from reported, recursing // into nested objects so only the differing members appear. nil means no delta. -func computeDelta(desired, reported map[string]json.RawMessage) map[string]json.RawMessage { - delta := make(map[string]json.RawMessage) +func computeDelta(desired, reported map[string]jsontext.Value) map[string]jsontext.Value { + delta := make(map[string]jsontext.Value) for k, dv := range desired { rv, ok := reported[k] @@ -179,7 +181,7 @@ func computeDelta(desired, reported map[string]json.RawMessage) map[string]json. if dIsObj && rIsObj { if sub := computeDelta(dObj, rObj); sub != nil { - if raw, err := json.Marshal(sub); err == nil { + if raw, err := json.Marshal(sub, jsonx.V1()); err == nil { delta[k] = raw } } @@ -199,14 +201,14 @@ func computeDelta(desired, reported map[string]json.RawMessage) map[string]json. return delta } -func jsonRawEqual(a, b json.RawMessage) bool { +func jsonRawEqual(a, b jsontext.Value) bool { var av, bv any - if json.Unmarshal(a, &av) != nil || json.Unmarshal(b, &bv) != nil { + if json.Unmarshal(a, &av, jsonx.V1()) != nil || json.Unmarshal(b, &bv, jsonx.V1()) != nil { return string(a) == string(b) } - ab, errA := json.Marshal(av) - bb, errB := json.Marshal(bv) + ab, errA := json.Marshal(av, jsonx.V1()) + bb, errB := json.Marshal(bv, jsonx.V1()) return errA == nil && errB == nil && string(ab) == string(bb) } @@ -272,7 +274,7 @@ func buildShadowResponse(entry *shadowEntry, clientToken string) ([]byte, error) resp["clientToken"] = clientToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // sortedKeys returns a sorted copy of the keys of a map[string]V. @@ -303,8 +305,8 @@ func (b *InMemoryBackend) GetThingShadow(thingName, shadowName string) ([]byte, type shadowUpdateInput struct { Version *int ClientToken string - StateDesired json.RawMessage - StateReported json.RawMessage + StateDesired jsontext.Value + StateReported jsontext.Value } // parseShadowUpdateDoc validates and parses an UpdateThingShadow request body. @@ -312,12 +314,12 @@ type shadowUpdateInput struct { func parseShadowUpdateDoc(document []byte) (*shadowUpdateInput, error) { // Outer document uses RawMessage for State so we can detect absent vs null. var outer struct { - Version json.RawMessage `json:"version,omitempty"` - ClientToken string `json:"clientToken,omitempty"` - State json.RawMessage `json:"state"` + Version jsontext.Value `json:"version,omitempty"` + ClientToken string `json:"clientToken,omitempty"` + State jsontext.Value `json:"state"` } - if err := json.Unmarshal(document, &outer); err != nil { + if err := json.Unmarshal(document, &outer, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: Invalid JSON", ErrValidation) } @@ -325,7 +327,7 @@ func parseShadowUpdateDoc(document []byte) (*shadowUpdateInput, error) { if len(outer.Version) > 0 && !isJSONNull(outer.Version) { var v int - if err := json.Unmarshal(outer.Version, &v); err != nil { + if err := json.Unmarshal(outer.Version, &v, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: Invalid version", ErrValidation) } @@ -345,11 +347,11 @@ func parseShadowUpdateDoc(document []byte) (*shadowUpdateInput, error) { } var stateDoc struct { - Desired json.RawMessage `json:"desired"` - Reported json.RawMessage `json:"reported"` + Desired jsontext.Value `json:"desired"` + Reported jsontext.Value `json:"reported"` } - if err := json.Unmarshal(outer.State, &stateDoc); err != nil { + if err := json.Unmarshal(outer.State, &stateDoc, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: State node must be an object", ErrValidation) } @@ -389,9 +391,9 @@ func jsonValueDepth(v any) int { // validateShadowStateDepth checks that a state.desired/state.reported section // does not exceed AWS IoT's documented maximum JSON nesting depth (see // maxShadowStateDepth). raw must already be known non-empty and non-null. -func validateShadowStateDepth(raw json.RawMessage, sectionName string) error { +func validateShadowStateDepth(raw jsontext.Value, sectionName string) error { var v any - if err := json.Unmarshal(raw, &v); err != nil { + if err := json.Unmarshal(raw, &v, jsonx.V1()); err != nil { return fmt.Errorf("%w: %s node must be an object", ErrValidation, sectionLabel(sectionName)) } @@ -406,12 +408,12 @@ func validateShadowStateDepth(raw json.RawMessage, sectionName string) error { // applyShadowStateSection merges a raw state section into existing state. // raw absent (nil) → keep existing; raw null → clear; raw object → merge patch. func applyShadowStateSection( - existing map[string]json.RawMessage, + existing map[string]jsontext.Value, existingMeta map[string]int64, - raw json.RawMessage, + raw jsontext.Value, sectionName string, ts int64, -) (map[string]json.RawMessage, map[string]int64, error) { +) (map[string]jsontext.Value, map[string]int64, error) { if len(raw) == 0 { return existing, existingMeta, nil } @@ -424,8 +426,8 @@ func applyShadowStateSection( return nil, nil, err } - var patch map[string]json.RawMessage - if err := json.Unmarshal(raw, &patch); err != nil { + var patch map[string]jsontext.Value + if err := json.Unmarshal(raw, &patch, jsonx.V1()); err != nil { return nil, nil, fmt.Errorf("%w: %s node must be an object", ErrValidation, sectionLabel(sectionName)) } @@ -464,7 +466,7 @@ func (b *InMemoryBackend) UpdateThingShadow(thingName, shadowName string, docume now := time.Now() ts := now.Unix() - var existingDesired, existingReported map[string]json.RawMessage + var existingDesired, existingReported map[string]jsontext.Value var existingMetaDesired, existingMetaReported map[string]int64 if current != nil { @@ -561,7 +563,7 @@ func (b *InMemoryBackend) DeleteThingShadow(thingName, shadowName string) ([]byt payload, err := json.Marshal(map[string]any{ "version": entry.version, keyTimestamp: entry.updatedAt.Unix(), - }) + }, jsonx.V1()) if err != nil { return nil, fmt.Errorf("iotdataplane: marshal delete response: %w", err) } @@ -635,12 +637,12 @@ func (b *InMemoryBackend) AddShadowInternal(thingName, shadowName string, docume // Best-effort parse of state.desired / state.reported from the seeded document. var doc struct { State struct { - Desired map[string]json.RawMessage `json:"desired"` - Reported map[string]json.RawMessage `json:"reported"` + Desired map[string]jsontext.Value `json:"desired"` + Reported map[string]jsontext.Value `json:"reported"` } `json:"state"` } - if json.Unmarshal(document, &doc) == nil { + if json.Unmarshal(document, &doc, jsonx.V1()) == nil { if doc.State.Desired != nil { entry.desired = doc.State.Desired } diff --git a/services/iotdataplane/shadows_merge_test.go b/services/iotdataplane/shadows_merge_test.go index c838557587..4e83e86bf1 100644 --- a/services/iotdataplane/shadows_merge_test.go +++ b/services/iotdataplane/shadows_merge_test.go @@ -1,13 +1,15 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -83,7 +85,7 @@ func TestBackend_ShadowVersioning(t *testing.T) { require.NoError(t, err) var result map[string]any - require.NoError(t, json.Unmarshal(resp, &result)) + require.NoError(t, json.Unmarshal(resp, &result, jsonx.V1())) gotVersion, ok := result["version"].(float64) require.True(t, ok, "response should contain numeric version field") @@ -247,7 +249,7 @@ func Test_ShadowVersion_OverflowProtection(t *testing.T) { require.NoError(t, err) var result map[string]any - require.NoError(t, json.Unmarshal(resp, &result)) + require.NoError(t, json.Unmarshal(resp, &result, jsonx.V1())) versionFloat, ok := result["version"].(float64) require.True(t, ok, "version must be a number") @@ -270,7 +272,7 @@ func Test_ShadowMerge_DesiredPartialUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) desired := state["desired"].(map[string]any) @@ -292,7 +294,7 @@ func Test_ShadowMerge_NullDeletion(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev2/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) desired := state["desired"].(map[string]any) @@ -315,7 +317,7 @@ func Test_ShadowMerge_ReportedIndependent(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev3/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) desired := state["desired"].(map[string]any) @@ -331,12 +333,12 @@ func Test_ShadowMerge_VersionIncrements(t *testing.T) { rec1 := doRequest(t, h, http.MethodPost, "/things/dev-ver/shadow", []byte(`{"state":{"desired":{"k":"v1"}}}`)) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) rec2 := doRequest(t, h, http.MethodPost, "/things/dev-ver/shadow", []byte(`{"state":{"desired":{"k":"v2"}}}`)) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) v1 := r1["version"].(float64) v2 := r2["version"].(float64) @@ -355,7 +357,7 @@ func Test_ShadowDelta_ComputedOnGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) delta, hasDelta := state["delta"] @@ -377,7 +379,7 @@ func Test_ShadowDelta_AbsentWhenEqual(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev-nodelta/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) _, hasDelta := state["delta"] @@ -400,7 +402,7 @@ func Test_ShadowDelta_UpdatedAfterReported(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev-delta2/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) _, hasDelta := state["delta"] @@ -417,7 +419,7 @@ func Test_ShadowMetadata_PresentAfterUpdate(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev-meta/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta, hasMeta := resp["metadata"] require.True(t, hasMeta, "metadata must be present after update") @@ -440,7 +442,7 @@ func Test_ShadowMetadata_UnchangedFieldTimestampPreserved(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/things/dev-meta2/shadow", nil) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1, jsonx.V1())) ts1 := resp1["metadata"].(map[string]any)["desired"].(map[string]any)["a"].(map[string]any)["timestamp"].(float64) // Update only b. @@ -450,7 +452,7 @@ func Test_ShadowMetadata_UnchangedFieldTimestampPreserved(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/things/dev-meta2/shadow", nil) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2, jsonx.V1())) ts2 := resp2["metadata"].(map[string]any)["desired"].(map[string]any)["a"].(map[string]any)["timestamp"].(float64) assert.InDelta(t, ts1, ts2, 0, "timestamp for unmodified field must not change") @@ -486,7 +488,7 @@ func Test_ClientToken_Echoed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) token, hasToken := resp["clientToken"] if tt.wantPresent { @@ -545,9 +547,9 @@ func Test_MergeStateFields(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - var base, patch map[string]json.RawMessage - require.NoError(t, json.Unmarshal([]byte(tt.base), &base)) - require.NoError(t, json.Unmarshal([]byte(tt.patch), &patch)) + var base, patch map[string]jsontext.Value + require.NoError(t, json.Unmarshal([]byte(tt.base), &base, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(tt.patch), &patch, jsonx.V1())) result := iotdataplane.MergeStateFields(base, patch) diff --git a/services/iotdataplane/shadows_pagination_test.go b/services/iotdataplane/shadows_pagination_test.go index 2191c43f4b..5105d40ed8 100644 --- a/services/iotdataplane/shadows_pagination_test.go +++ b/services/iotdataplane/shadows_pagination_test.go @@ -1,7 +1,7 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -31,7 +32,7 @@ func Test_ListNamedShadows_ExcludesClassicShadow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["results"].([]any) require.True(t, ok, "results must be an array") @@ -85,7 +86,7 @@ func Test_ListNamedShadows_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { _, ok := resp[field] @@ -119,7 +120,7 @@ func Test_ListNamedShadows_SortedAlphabetically(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["results"].([]any) require.True(t, ok) @@ -144,7 +145,7 @@ func Test_ListNamedShadows_IncludesTimestamp(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "results") assert.Contains(t, resp, "timestamp") } @@ -171,7 +172,7 @@ func Test_ListThingsWithShadows_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) things := resp["things"].([]any) assert.Empty(t, things) } @@ -188,7 +189,7 @@ func Test_ListThingsWithShadows_Multiple(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) things := resp["things"].([]any) assert.Len(t, things, 3) // Must be sorted. @@ -231,7 +232,7 @@ func Test_ListThingsWithShadows_PageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) things := resp["things"].([]any) assert.Len(t, things, 10) assert.Contains(t, resp, "nextToken") @@ -251,7 +252,7 @@ func Test_ListNamedShadows_PageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["results"].([]any) assert.Len(t, results, 10) assert.Contains(t, resp, "nextToken") @@ -270,7 +271,7 @@ func Test_ListThingsWithShadows_PaginationOffByOneFixed(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/api/things/shadow/ListThingsWithShadows?pageSize=2", nil) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) things1 := page1["things"].([]any) require.Len(t, things1, 2) @@ -287,7 +288,7 @@ func Test_ListThingsWithShadows_PaginationOffByOneFixed(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/api/things/shadow/ListThingsWithShadows?pageSize=2&nextToken="+nextToken, nil) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) things2 := page2["things"].([]any) require.Len(t, things2, 2) @@ -305,7 +306,7 @@ func Test_ListThingsWithShadows_IncludesTimestamp(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "things") assert.Contains(t, resp, "timestamp") @@ -329,7 +330,7 @@ func Test_ListThingsWithShadows_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) things1 := page1["things"].([]any) assert.Len(t, things1, 2) nextToken := page1["nextToken"].(string) @@ -339,7 +340,7 @@ func Test_ListThingsWithShadows_Pagination(t *testing.T) { paginatedURL := "/api/things/shadow/ListThingsWithShadows?pageSize=2&nextToken=" + nextToken rec2 := doRequest(t, h, http.MethodGet, paginatedURL, nil) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) things2 := page2["things"].([]any) assert.Len(t, things2, 2) diff --git a/services/iotdataplane/shadows_state_test.go b/services/iotdataplane/shadows_state_test.go index 14c980e0ec..497038f2c7 100644 --- a/services/iotdataplane/shadows_state_test.go +++ b/services/iotdataplane/shadows_state_test.go @@ -1,13 +1,14 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -29,7 +30,7 @@ func Test_ShadowDesiredNull_WipesDesired(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] @@ -51,7 +52,7 @@ func Test_ShadowDesiredNull_LeavesReportedIntact(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] @@ -78,7 +79,7 @@ func Test_ShadowReportedNull_WipesReported(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasReported := state["reported"] @@ -105,7 +106,7 @@ func Test_ShadowBothNull_WipesBoth(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] @@ -133,7 +134,7 @@ func Test_ShadowDesiredNull_ThenResetDesired(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) desired := state["desired"].(map[string]any) @@ -150,12 +151,12 @@ func Test_ShadowDesiredNull_VersionStillIncrements(t *testing.T) { resp1, err := b.UpdateThingShadow("dev", "", []byte(`{"state":{"desired":{"k":"v"}}}`)) require.NoError(t, err) var r1 map[string]any - require.NoError(t, json.Unmarshal(resp1, &r1)) + require.NoError(t, json.Unmarshal(resp1, &r1, jsonx.V1())) resp2, err := b.UpdateThingShadow("dev", "", []byte(`{"state":{"desired":null}}`)) require.NoError(t, err) var r2 map[string]any - require.NoError(t, json.Unmarshal(resp2, &r2)) + require.NoError(t, json.Unmarshal(resp2, &r2, jsonx.V1())) v1 := int(r1["version"].(float64)) v2 := int(r2["version"].(float64)) @@ -175,7 +176,7 @@ func Test_ShadowDesiredNull_ViaHTTP(t *testing.T) { // Confirm desired absent in GET. rec = doRequest(t, h, http.MethodGet, "/things/dev/shadow", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) state := resp["state"].(map[string]any) _, hasDesired := state["desired"] @@ -193,7 +194,7 @@ func Test_ShadowResponse_OnlyDesiredSet_ReportedOmitted(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] @@ -213,7 +214,7 @@ func Test_ShadowResponse_OnlyReportedSet_DesiredOmitted(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasReported := state["reported"] @@ -233,7 +234,7 @@ func Test_ShadowResponse_BothSet_BothPresent(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDesired := state["desired"] @@ -256,7 +257,7 @@ func Test_ShadowResponse_EmptyStateSection_StateObjectPresent(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) // "state" key must still exist (as empty object {}). _, hasState := doc["state"] @@ -285,7 +286,7 @@ func Test_ShadowMetadata_ClearedOnDesiredNull(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) meta, hasMeta := doc["metadata"].(map[string]any) if hasMeta { @@ -313,7 +314,7 @@ func Test_ShadowDelta_AfterDesiredWipe_NoDelta(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) _, hasDelta := state["delta"] @@ -332,7 +333,7 @@ func Test_ShadowUpdate_EmptyDesiredPatch_NoOpForKeys(t *testing.T) { require.NoError(t, err) var r2 map[string]any - require.NoError(t, json.Unmarshal(resp2, &r2)) + require.NoError(t, json.Unmarshal(resp2, &r2, jsonx.V1())) assert.InDelta(t, float64(2), r2["version"], 0) state := r2["state"].(map[string]any) @@ -361,7 +362,7 @@ func Test_ShadowUpdate_MultiplePatchesAccumulate(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) desired := state["desired"].(map[string]any) diff --git a/services/iotdataplane/shadows_test.go b/services/iotdataplane/shadows_test.go index d0449acecc..08df3c6220 100644 --- a/services/iotdataplane/shadows_test.go +++ b/services/iotdataplane/shadows_test.go @@ -1,7 +1,7 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -22,7 +23,7 @@ func TestHandler_ShadowCRUD(t *testing.T) { "desired": map[string]any{"color": "red"}, }, } - docBytes, err := json.Marshal(shadowDoc) + docBytes, err := json.Marshal(shadowDoc, jsonx.V1()) require.NoError(t, err) // Step 1: shadow does not exist yet. @@ -133,7 +134,7 @@ func TestHandler_ShadowResponseContainsVersionAndTimestamp(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "version") assert.Contains(t, resp, "timestamp") @@ -142,7 +143,7 @@ func TestHandler_ShadowResponseContainsVersionAndTimestamp(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Contains(t, getResp, "version") assert.Contains(t, getResp, "timestamp") } @@ -174,7 +175,7 @@ func updateShadow(t *testing.T, h *iotdataplane.Handler, thingName, shadowName s require.Equal(t, http.StatusOK, rec.Code, "UpdateThingShadow: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -194,7 +195,7 @@ func getShadow(t *testing.T, h *iotdataplane.Handler, thingName, shadowName stri var resp map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) } return resp, rec.Code @@ -315,7 +316,7 @@ func Test_DeleteThingShadow_ResponseOmitsState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasState := resp["state"] assert.False(t, hasState, "delete response must NOT include state (empty response state document)") @@ -428,7 +429,7 @@ func Test_DeleteThingShadow_ReturnsPayload(t *testing.T) { // Response must be a JSON object with version and timestamp (AWS contract). var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "version") assert.Contains(t, resp, "timestamp") } @@ -608,7 +609,7 @@ func Test_CrossThing_ShadowIsolation(t *testing.T) { require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) desired := state["desired"].(map[string]any) assert.Equal(t, "blue", desired["color"]) @@ -631,7 +632,7 @@ func Test_ClassicVsNamedShadow_Isolation(t *testing.T) { resp, err := b.GetThingShadow("dev", "named-one") require.NoError(t, err) var doc map[string]any - require.NoError(t, json.Unmarshal(resp, &doc)) + require.NoError(t, json.Unmarshal(resp, &doc, jsonx.V1())) state := doc["state"].(map[string]any) desired := state["desired"].(map[string]any) assert.Equal(t, "yes", desired["named"]) @@ -659,8 +660,8 @@ func Test_NamedShadow_IndependentVersions(t *testing.T) { require.NoError(t, err) var alpha, beta map[string]any - require.NoError(t, json.Unmarshal(alphaResp, &alpha)) - require.NoError(t, json.Unmarshal(betaResp, &beta)) + require.NoError(t, json.Unmarshal(alphaResp, &alpha, jsonx.V1())) + require.NoError(t, json.Unmarshal(betaResp, &beta, jsonx.V1())) assert.InDelta(t, float64(3), alpha["version"], 0, "alpha must be at version 3") assert.InDelta(t, float64(1), beta["version"], 0, "beta must be at version 1") diff --git a/services/iotdataplane/shadows_validation_test.go b/services/iotdataplane/shadows_validation_test.go index 7cf29ccc31..3793a41185 100644 --- a/services/iotdataplane/shadows_validation_test.go +++ b/services/iotdataplane/shadows_validation_test.go @@ -1,7 +1,7 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotdataplane" ) @@ -50,7 +51,7 @@ func TestBackend_ShadowUpdate_ObjectDocumentSucceeds(t *testing.T) { require.NoError(t, err) var result map[string]any - require.NoError(t, json.Unmarshal(resp, &result), "response must be valid JSON") + require.NoError(t, json.Unmarshal(resp, &result, jsonx.V1()), "response must be valid JSON") assert.Contains(t, result, "version", "response must contain version") assert.Contains(t, result, "timestamp", "response must contain timestamp") } @@ -102,7 +103,7 @@ func Test_UpdateThingShadow_StateRequired(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["error"], "error type must be InvalidRequestException") } @@ -157,7 +158,7 @@ func Test_VersionConflict_RejectedWithCorrectError(t *testing.T) { if tt.wantErrorType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrorType, resp["error"], "error type must be ConflictException") } @@ -421,7 +422,7 @@ func Test_ThingName_ValidationOnDelete(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "error") } func Test_ThingName_ValidationOnListNamedShadows(t *testing.T) { @@ -493,7 +494,7 @@ func Test_ClientToken_TooLong_ViaHTTP(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["error"]) } func Test_ClientToken_Exactly64Chars_Accepted(t *testing.T) { @@ -507,7 +508,7 @@ func Test_ClientToken_Exactly64Chars_Accepted(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, exact, resp["clientToken"]) } func Test_ShadowUpdate_StateRequired(t *testing.T) { @@ -584,7 +585,7 @@ func Test_ShadowUpdate_StateRequired_ErrorShape(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["error"]) assert.Contains(t, resp["message"], "state") } diff --git a/services/iotdataplane/value_semantics_test.go b/services/iotdataplane/value_semantics_test.go index 4fb1e694a8..4cec508776 100644 --- a/services/iotdataplane/value_semantics_test.go +++ b/services/iotdataplane/value_semantics_test.go @@ -1,13 +1,15 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" iotdataplanesdk "github.com/aws/aws-sdk-go-v2/service/iotdataplane" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestShadow_NestedUpdatesMergeAndDeltaIsMinimal(t *testing.T) { @@ -66,7 +68,7 @@ func TestShadow_NestedUpdatesMergeAndDeltaIsMinimal(t *testing.T) { var doc struct { State map[string]any `json:"state"` } - require.NoError(t, json.Unmarshal(got.Payload, &doc)) + require.NoError(t, json.Unmarshal(got.Payload, &doc, jsonx.V1())) if tt.wantDesired != "" { assert.JSONEq(t, tt.wantDesired, mustJSON(t, doc.State["desired"])) @@ -86,7 +88,7 @@ func TestShadow_NestedUpdatesMergeAndDeltaIsMinimal(t *testing.T) { func mustJSON(t *testing.T, v any) string { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/iotdataplane/whitebox_test.go b/services/iotdataplane/whitebox_test.go index 2b517ea9d5..6ae6042f7a 100644 --- a/services/iotdataplane/whitebox_test.go +++ b/services/iotdataplane/whitebox_test.go @@ -2,7 +2,7 @@ package iotdataplane import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body []byte) *httptest.ResponseRecorder { @@ -91,6 +93,6 @@ func Test_ShadowStateDepth_ViaHTTP(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["error"]) } diff --git a/services/iotdataplane/wire_sdk_roundtrip_test.go b/services/iotdataplane/wire_sdk_roundtrip_test.go index 4eba4a5657..f143d70d93 100644 --- a/services/iotdataplane/wire_sdk_roundtrip_test.go +++ b/services/iotdataplane/wire_sdk_roundtrip_test.go @@ -1,7 +1,7 @@ package iotdataplane_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/iotdataplane" @@ -112,7 +113,7 @@ func TestShadowLifecycle_SDKRoundTrip(t *testing.T) { Version int64 `json:"version"` Timestamp int64 `json:"timestamp"` } - require.NoError(t, json.Unmarshal(deleted.Payload, &deleteBody)) + require.NoError(t, json.Unmarshal(deleted.Payload, &deleteBody, jsonx.V1())) _, err = client.GetThingShadow(t.Context(), &iotdataplanesdk.GetThingShadowInput{ ThingName: aws.String(thingName), diff --git a/services/iotwireless/handler.go b/services/iotwireless/handler.go index 4e3fa6a71b..df1af1d7c7 100644 --- a/services/iotwireless/handler.go +++ b/services/iotwireless/handler.go @@ -1,7 +1,8 @@ package iotwireless import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -975,7 +977,10 @@ func writeError(c *echo.Context, status int, message string) error { c.Response().Header().Set("X-Amzn-Errortype", errType) c.Response().WriteHeader(status) - _ = json.NewEncoder(c.Response()).Encode(errorResponse{Type: errType, Message: message}) + _ = json.MarshalEncode( + jsontext.NewEncoder(c.Response(), jsonx.V1()), + errorResponse{Type: errType, Message: message}, + ) return nil } @@ -985,7 +990,7 @@ func writeJSON(c *echo.Context, status int, v any) error { c.Response().Header().Set("Content-Type", "application/json") c.Response().WriteHeader(status) - _ = json.NewEncoder(c.Response()).Encode(v) + _ = json.MarshalEncode(jsontext.NewEncoder(c.Response(), jsonx.V1()), v) return nil } diff --git a/services/iotwireless/handler_certificates.go b/services/iotwireless/handler_certificates.go index c87b08617c..32ddf21a8b 100644 --- a/services/iotwireless/handler_certificates.go +++ b/services/iotwireless/handler_certificates.go @@ -1,12 +1,13 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -124,7 +125,7 @@ type startSingleWirelessDeviceImportTaskResponse struct { func (h *Handler) associateWirelessGatewayWithCertificate(c *echo.Context, gatewayID string, body []byte) error { var req associateWirelessGatewayWithCertificateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -168,7 +169,7 @@ func (h *Handler) startWirelessDeviceImportTask(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) task, err := h.Backend.StartWirelessDeviceImportTask( h.AccountID, h.DefaultRegion, req.DestinationName, req.Positioning, req.Sidewalk.toModel(), @@ -202,7 +203,7 @@ func (h *Handler) startSingleWirelessDeviceImportTask(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) details := SingleImportDetails{DeviceName: req.DeviceName, Positioning: req.Positioning} if req.Sidewalk != nil { @@ -293,7 +294,7 @@ func (h *Handler) updateWirelessDeviceImportTask(c *echo.Context, id string) err } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateWirelessDeviceImportTask(id, req.Sidewalk.toModel()); err != nil { return handleError(c, err) diff --git a/services/iotwireless/handler_certificates_test.go b/services/iotwireless/handler_certificates_test.go index 52f472d9bb..17fb29d617 100644 --- a/services/iotwireless/handler_certificates_test.go +++ b/services/iotwireless/handler_certificates_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AssociateWirelessGatewayWithCertificate(t *testing.T) { @@ -46,7 +48,7 @@ func TestHandler_AssociateWirelessGatewayWithCertificate(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) gwID = createResp["Id"].(string) } @@ -56,7 +58,7 @@ func TestHandler_AssociateWirelessGatewayWithCertificate(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.iotCertificateID, resp["IotCertificateId"]) } }) @@ -73,7 +75,7 @@ func TestHandler_GatewayCertificateLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) gwID := createResp["Id"].(string) // Associate certificate @@ -86,7 +88,7 @@ func TestHandler_GatewayCertificateLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var certResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &certResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &certResp, jsonx.V1())) assert.Equal(t, "cert-123", certResp["IotCertificateId"]) // Disassociate certificate @@ -107,7 +109,7 @@ func TestHandler_ListDevicesForWirelessDeviceImportTask_ByID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -115,7 +117,7 @@ func TestHandler_ListDevicesForWirelessDeviceImportTask_ByID(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Equal(t, "dest-import", listResp["DestinationName"]) rec = doIoTWRequest(t, h, http.MethodGet, "/wireless_device_import_task?id=does-not-exist", "") @@ -159,7 +161,7 @@ func TestHandler_StartWirelessDeviceImportTask(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["Arn"].(string) id, _ := resp["Id"].(string) @@ -207,7 +209,7 @@ func TestHandler_GetWirelessDeviceImportTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskID = createResp["Id"].(string) } @@ -216,7 +218,7 @@ func TestHandler_GetWirelessDeviceImportTask(t *testing.T) { if tt.wantFound { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.setupDest, resp["DestinationName"]) assert.Equal(t, "Initialized", resp["Status"]) } @@ -256,7 +258,7 @@ func TestHandler_DeleteWirelessDeviceImportTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskID = createResp["Id"].(string) } @@ -310,7 +312,7 @@ func TestHandler_UpdateWirelessDeviceImportTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskID := createResp["Id"].(string) rec = doIoTWRequest(t, h, http.MethodPatch, "/wireless_device_import_task/"+taskID, tt.updateBody) @@ -321,7 +323,7 @@ func TestHandler_UpdateWirelessDeviceImportTask(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "dest-old", getResp["DestinationName"]) if tt.wantFileList == nil { @@ -363,7 +365,7 @@ func TestHandler_ListWirelessDeviceImportTasks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["WirelessDeviceImportTaskList"].([]any) require.True(t, ok, "WirelessDeviceImportTaskList should be array") @@ -401,7 +403,7 @@ func TestHandler_StartSingleWirelessDeviceImportTask(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["Arn"].(string) devID, _ := resp["WirelessDeviceId"].(string) @@ -420,7 +422,7 @@ func TestHandler_ListDevicesForWirelessDeviceImportTask_NoIDReturnsEmpty(t *test require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["ImportedWirelessDeviceList"].([]any) require.True(t, ok, "ImportedWirelessDeviceList should be array") diff --git a/services/iotwireless/handler_destinations.go b/services/iotwireless/handler_destinations.go index 582fd9afd2..04886b3dee 100644 --- a/services/iotwireless/handler_destinations.go +++ b/services/iotwireless/handler_destinations.go @@ -1,11 +1,12 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -36,7 +37,7 @@ type listDestinationsResponse struct { func (h *Handler) createDestination(c *echo.Context, body []byte) error { var req createDestinationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -114,7 +115,7 @@ func (h *Handler) updateDestination(c *echo.Context, name string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateDestination( h.AccountID, h.DefaultRegion, name, diff --git a/services/iotwireless/handler_destinations_test.go b/services/iotwireless/handler_destinations_test.go index 3194fba3ef..f4eb1ec4b0 100644 --- a/services/iotwireless/handler_destinations_test.go +++ b/services/iotwireless/handler_destinations_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateGetListDeleteDestination(t *testing.T) { @@ -40,7 +42,7 @@ func TestHandler_CreateGetListDeleteDestination(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, tt.destName, createResp["Name"]) // Get @@ -48,7 +50,7 @@ func TestHandler_CreateGetListDeleteDestination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.destName, getResp["Name"]) assert.Equal(t, tt.expression, getResp["Expression"]) @@ -57,7 +59,7 @@ func TestHandler_CreateGetListDeleteDestination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) dests, ok := listResp["DestinationList"].([]any) require.True(t, ok) assert.Len(t, dests, 1) @@ -103,6 +105,6 @@ func TestHandler_UpdateDestination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "rule/updated", getResp["Expression"]) } diff --git a/services/iotwireless/handler_event_configurations.go b/services/iotwireless/handler_event_configurations.go index 24308f0d35..46a3a4669d 100644 --- a/services/iotwireless/handler_event_configurations.go +++ b/services/iotwireless/handler_event_configurations.go @@ -1,10 +1,12 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type eventConfigDocResponse struct { @@ -56,7 +58,7 @@ type listEventConfigurationsResponse struct { func eventConfigDocFromBody(body []byte) *EventConfigDoc { var doc EventConfigDoc - _ = json.Unmarshal(body, &doc) + _ = json.Unmarshal(body, &doc, jsonx.V1()) return &doc } diff --git a/services/iotwireless/handler_event_configurations_test.go b/services/iotwireless/handler_event_configurations_test.go index 92d0e22e2d..8eec2f19dd 100644 --- a/services/iotwireless/handler_event_configurations_test.go +++ b/services/iotwireless/handler_event_configurations_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_EventConfigurations(t *testing.T) { @@ -19,7 +21,7 @@ func TestHandler_EventConfigurations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var defaultResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defaultResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defaultResp, jsonx.V1())) assert.NotContains(t, defaultResp, "DeviceRegistrationState") // Update event config by resource types (PATCH, not POST -- iotwireless@v1.59.4 @@ -32,7 +34,7 @@ func TestHandler_EventConfigurations(t *testing.T) { // fabricated always-empty response. rec = doIoTWRequest(t, h, http.MethodGet, "/event-configurations-resource-types", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defaultResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defaultResp, jsonx.V1())) drs, ok := defaultResp["DeviceRegistrationState"].(map[string]any) require.True(t, ok) sidewalk, ok := drs["Sidewalk"].(map[string]any) @@ -44,7 +46,7 @@ func TestHandler_EventConfigurations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok := listResp["EventConfigurationsList"].([]any) require.True(t, ok) assert.Empty(t, configs) @@ -54,7 +56,7 @@ func TestHandler_EventConfigurations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, getResp, "Join") // Update resource event configuration @@ -66,7 +68,7 @@ func TestHandler_EventConfigurations(t *testing.T) { // Get resource event configuration must now reflect the stored config. rec = doIoTWRequest(t, h, http.MethodGet, "/event-configurations/some-resource", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) join, ok := getResp["Join"].(map[string]any) require.True(t, ok) loRaWAN, ok := join["LoRaWAN"].(map[string]any) @@ -82,7 +84,7 @@ func TestHandler_EventConfigurations(t *testing.T) { "", ) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok = listResp["EventConfigurationsList"].([]any) require.True(t, ok) require.Len(t, configs, 1) @@ -124,7 +126,7 @@ func TestHandler_EventConfigurationByResourceTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["DeviceRegistrationState"] assert.True(t, ok, "persisted event config must be returned") } @@ -172,7 +174,7 @@ func TestHandler_ResourceEventConfiguration(t *testing.T) { // field (it echoes only the event-type configuration); confirm the // stored per-resource event config round-trips instead. var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp[tt.wantField] assert.True(t, ok, "persisted event config field %q must be returned", tt.wantField) }) @@ -189,7 +191,7 @@ func TestHandler_ListEventConfigurations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var pre map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pre)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pre, jsonx.V1())) assert.Empty(t, pre["EventConfigurationsList"]) // Update config for two resources. @@ -204,7 +206,7 @@ func TestHandler_ListEventConfigurations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var post map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &post)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &post, jsonx.V1())) list, ok := post["EventConfigurationsList"].([]any) require.True(t, ok) assert.Len(t, list, 2) @@ -265,7 +267,7 @@ func TestHandler_ListEventConfigurations_FilterByResourceType(t *testing.T) { Identifier string `json:"Identifier"` } `json:"EventConfigurationsList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids := make([]string, 0, len(resp.EventConfigurationsList)) for _, e := range resp.EventConfigurationsList { diff --git a/services/iotwireless/handler_fuota_tasks.go b/services/iotwireless/handler_fuota_tasks.go index 83ac62874c..67c8bfc896 100644 --- a/services/iotwireless/handler_fuota_tasks.go +++ b/services/iotwireless/handler_fuota_tasks.go @@ -1,13 +1,14 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -84,7 +85,7 @@ type listMulticastGroupsByFuotaTaskResponse struct { func (h *Handler) createFuotaTask(c *echo.Context, body []byte) error { var req createFuotaTaskRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -166,7 +167,7 @@ func (h *Handler) deleteFuotaTask(c *echo.Context, id string) error { func (h *Handler) associateMulticastGroupWithFuotaTask(c *echo.Context, fuotaTaskID string, body []byte) error { var req associateMulticastGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -181,7 +182,7 @@ func (h *Handler) associateMulticastGroupWithFuotaTask(c *echo.Context, fuotaTas func (h *Handler) associateWirelessDeviceWithFuotaTask(c *echo.Context, fuotaTaskID string, body []byte) error { var req associateWirelessDeviceWithFuotaRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -208,7 +209,7 @@ func (h *Handler) updateFuotaTask(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateFuotaTask( h.AccountID, h.DefaultRegion, id, req.Name, req.Description, @@ -230,7 +231,7 @@ func (h *Handler) startFuotaTask(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) var startTime *time.Time diff --git a/services/iotwireless/handler_fuota_tasks_test.go b/services/iotwireless/handler_fuota_tasks_test.go index 9b76d35e47..942ae0605a 100644 --- a/services/iotwireless/handler_fuota_tasks_test.go +++ b/services/iotwireless/handler_fuota_tasks_test.go @@ -1,13 +1,16 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_FuotaTask_LoRaWANRoundTrip verifies the LoRaWANFuotaTask @@ -27,7 +30,7 @@ func TestHandler_FuotaTask_LoRaWANRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -35,7 +38,7 @@ func TestHandler_FuotaTask_LoRaWANRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok := getResp["LoRaWAN"].(map[string]any) require.True(t, ok) assert.Equal(t, "US915", loRaWAN["RfRegion"]) @@ -48,7 +51,7 @@ func TestHandler_FuotaTask_LoRaWANRoundTrip(t *testing.T) { rec = doIoTWRequest(t, h, http.MethodGet, "/fuota-tasks/"+id, "") require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok = getResp["LoRaWAN"].(map[string]any) require.True(t, ok) assert.Equal(t, "EU868", loRaWAN["RfRegion"]) @@ -114,7 +117,7 @@ func TestHandler_UpdateFuotaTask_Fields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -125,7 +128,7 @@ func TestHandler_UpdateFuotaTask_Fields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.wantValue, getResp[tt.wantField]) }) } @@ -163,7 +166,7 @@ func TestHandler_StartFuotaTask_StartTime(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -174,7 +177,7 @@ func TestHandler_StartFuotaTask_StartTime(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, hasLoRaWAN := getResp["LoRaWAN"].(map[string]any) if tt.wantStartTime == "" { @@ -225,7 +228,7 @@ func TestHandler_CreateFuotaTask(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.NotEmpty(t, resp["Arn"]) }) @@ -310,7 +313,7 @@ func TestHandler_ListMulticastGroupsByFuotaTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ftResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ftResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ftResp, jsonx.V1())) ftID := ftResp["Id"].(string) // Create a multicast group @@ -318,7 +321,7 @@ func TestHandler_ListMulticastGroupsByFuotaTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var mgResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mgResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mgResp, jsonx.V1())) mgID := mgResp["Id"].(string) // Associate multicast group with FUOTA task; AssociateMulticastGroupWithFuotaTask @@ -332,7 +335,7 @@ func TestHandler_ListMulticastGroupsByFuotaTask(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) groups, ok := listResp["MulticastGroupList"].([]any) require.True(t, ok) assert.Len(t, groups, 1) @@ -343,7 +346,7 @@ func TestHandler_ListMulticastGroupsByFuotaTask(t *testing.T) { // List again should return empty rec = doIoTWRequest(t, h, http.MethodGet, "/fuota-tasks/"+ftID+"/multicast-groups", "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) groups = listResp["MulticastGroupList"].([]any) assert.Empty(t, groups) } @@ -364,7 +367,7 @@ func TestHandler_FuotaTaskUpdate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Update @@ -377,7 +380,7 @@ func TestHandler_FuotaTaskUpdate(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "ft1-updated", getResp["Name"]) } @@ -397,7 +400,7 @@ func TestHandler_StartFuotaTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Start the task @@ -421,7 +424,7 @@ func TestHandler_DisassociateWirelessDeviceFromFuotaTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) ftID := createResp["Id"].(string) // Create a device @@ -435,7 +438,7 @@ func TestHandler_DisassociateWirelessDeviceFromFuotaTask(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var devResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp, jsonx.V1())) devID := devResp["Id"].(string) // Associate device with FUOTA task; AssociateWirelessDeviceWithFuotaTask @@ -482,7 +485,7 @@ func TestHandler_FuotaTask_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) require.NotEmpty(t, id) @@ -491,7 +494,7 @@ func TestHandler_FuotaTask_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.taskName, getResp["Name"]) assert.Equal(t, id, getResp["Id"]) @@ -500,7 +503,7 @@ func TestHandler_FuotaTask_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list, ok := listResp["FuotaTaskList"].([]any) require.True(t, ok) assert.Len(t, list, 1) @@ -543,7 +546,7 @@ func TestHandler_FuotaTasks_FullLifecycle(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) fuotaID, _ := createResp["Id"].(string) require.NotEmpty(t, fuotaID, "CreateFuotaTask must return an Id") diff --git a/services/iotwireless/handler_log_levels.go b/services/iotwireless/handler_log_levels.go index 91ea8602da..4e64bc0f5d 100644 --- a/services/iotwireless/handler_log_levels.go +++ b/services/iotwireless/handler_log_levels.go @@ -1,10 +1,12 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // getResourceLogLevelResponse mirrors GetResourceLogLevelOutput, which @@ -52,7 +54,7 @@ func (h *Handler) updateLogLevelsByResourceTypes(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) cfg := LogLevelsConfig{ DefaultLogLevel: req.DefaultLogLevel, @@ -95,7 +97,7 @@ func (h *Handler) putResourceLogLevel(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.PutResourceLogLevel(id, req.LogLevel); err != nil { return handleError(c, err) diff --git a/services/iotwireless/handler_log_levels_test.go b/services/iotwireless/handler_log_levels_test.go index 839d6fdd8f..8522edb3ed 100644 --- a/services/iotwireless/handler_log_levels_test.go +++ b/services/iotwireless/handler_log_levels_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_LogLevels(t *testing.T) { @@ -19,7 +21,7 @@ func TestHandler_LogLevels(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "INFO", getResp["DefaultLogLevel"]) // Update log levels @@ -28,7 +30,7 @@ func TestHandler_LogLevels(t *testing.T) { // Verify updated rec = doIoTWRequest(t, h, http.MethodGet, "/log-levels", "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "DEBUG", getResp["DefaultLogLevel"]) // Put resource log level @@ -45,7 +47,7 @@ func TestHandler_LogLevels(t *testing.T) { rec = doIoTWRequest(t, h, http.MethodGet, "/log-levels/my-device", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "ERROR", getResp["LogLevel"]) // Reset resource log level @@ -54,7 +56,7 @@ func TestHandler_LogLevels(t *testing.T) { // After reset, should return INFO default rec = doIoTWRequest(t, h, http.MethodGet, "/log-levels/my-device", "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "INFO", getResp["LogLevel"]) // Reset all resource log levels @@ -91,7 +93,7 @@ func TestHandler_LogLevels_OptionListsRoundTrip(t *testing.T) { WirelessDeviceLogOptions []map[string]any `json:"WirelessDeviceLogOptions"` WirelessGatewayLogOptions []map[string]any `json:"WirelessGatewayLogOptions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ERROR", resp.DefaultLogLevel) require.Len(t, resp.WirelessDeviceLogOptions, 1) diff --git a/services/iotwireless/handler_metrics.go b/services/iotwireless/handler_metrics.go index bd2fe2b0ab..c6cf13a281 100644 --- a/services/iotwireless/handler_metrics.go +++ b/services/iotwireless/handler_metrics.go @@ -1,10 +1,12 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const stubServiceType = "CUPS" @@ -65,7 +67,7 @@ func (h *Handler) updateMetricConfiguration(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateMetricConfigurationStatus(req.SummaryMetric.Status); err != nil { return handleError(c, err) @@ -117,7 +119,7 @@ func (h *Handler) getMetrics(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) results := make([]summaryMetricQueryResultResponse, 0, len(req.SummaryMetricQueries)) for _, q := range req.SummaryMetricQueries { diff --git a/services/iotwireless/handler_metrics_test.go b/services/iotwireless/handler_metrics_test.go index 8473561201..38e0d776ba 100644 --- a/services/iotwireless/handler_metrics_test.go +++ b/services/iotwireless/handler_metrics_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetMetricConfiguration(t *testing.T) { @@ -19,7 +21,7 @@ func TestHandler_GetMetricConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) summaryMetric, ok := getResp["SummaryMetric"].(map[string]any) require.True(t, ok) assert.Equal(t, "Enabled", summaryMetric["Status"]) @@ -31,7 +33,7 @@ func TestHandler_GetMetricConfiguration(t *testing.T) { rec = doIoTWRequest(t, h, http.MethodGet, "/metric-configuration", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) summaryMetric, ok = getResp["SummaryMetric"].(map[string]any) require.True(t, ok) assert.Equal(t, "Disabled", summaryMetric["Status"]) @@ -52,7 +54,7 @@ func TestHandler_GetMetrics_MapsQueriesToResults(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["SummaryMetricQueryResults"].([]any) require.True(t, ok) require.Len(t, results, 2) @@ -71,7 +73,7 @@ func TestHandler_GetServiceEndpoint_ExactURLsAndValidation(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CUPS", resp["ServiceType"]) assert.Equal(t, "https://cups.lorawan.us-east-1.amazonaws.com", resp["ServiceEndpoint"]) @@ -80,7 +82,7 @@ func TestHandler_GetServiceEndpoint_ExactURLsAndValidation(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var lnsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lnsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lnsResp, jsonx.V1())) assert.Equal(t, "LNS", lnsResp["ServiceType"]) assert.Equal(t, "https://lns.lorawan.us-east-1.amazonaws.com", lnsResp["ServiceEndpoint"]) @@ -126,7 +128,7 @@ func TestHandler_GetMetrics_ReturnsEmptyResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["SummaryMetricQueryResults"].([]any) require.True(t, ok) @@ -168,7 +170,7 @@ func TestHandler_MetricConfiguration_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp[tt.wantKV] assert.True(t, ok, "expected key %q in response", tt.wantKV) }) @@ -214,7 +216,7 @@ func TestHandler_GetServiceEndpoint_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantServiceType, resp["ServiceType"]) endpoint, _ := resp["ServiceEndpoint"].(string) assert.Contains(t, endpoint, tt.wantSubstr) diff --git a/services/iotwireless/handler_multicast_groups.go b/services/iotwireless/handler_multicast_groups.go index e025bdb73e..c156629de7 100644 --- a/services/iotwireless/handler_multicast_groups.go +++ b/services/iotwireless/handler_multicast_groups.go @@ -1,7 +1,8 @@ package iotwireless import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "time" @@ -9,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -59,7 +61,7 @@ func (h *Handler) createMulticastGroup(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) mg, err := h.Backend.CreateMulticastGroup( h.AccountID, @@ -141,7 +143,7 @@ func (h *Handler) updateMulticastGroup(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateMulticastGroup( h.AccountID, h.DefaultRegion, id, req.Name, req.Description, req.LoRaWAN, @@ -214,11 +216,11 @@ func (h *Handler) startMulticastGroupSession(c *echo.Context, id string) error { // would, not answered with a fabricated success. func (h *Handler) sendDataToMulticastGroup(c *echo.Context, _ string) error { var req struct { - PayloadData string `json:"PayloadData"` - WirelessMetadata json.RawMessage `json:"WirelessMetadata"` + PayloadData string `json:"PayloadData"` + WirelessMetadata jsontext.Value `json:"WirelessMetadata"` } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.PayloadData == "" { return writeError(c, http.StatusBadRequest, "ValidationException: PayloadData is required") @@ -236,7 +238,7 @@ func (h *Handler) associateWirelessDeviceWithMulticastGroup( c *echo.Context, multicastGroupID string, body []byte, ) error { var req associateWirelessDeviceWithMulticastRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/iotwireless/handler_multicast_groups_test.go b/services/iotwireless/handler_multicast_groups_test.go index 49e0b43419..6914de8b6a 100644 --- a/services/iotwireless/handler_multicast_groups_test.go +++ b/services/iotwireless/handler_multicast_groups_test.go @@ -1,13 +1,16 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AssociateWirelessDeviceWithMulticastGroup(t *testing.T) { @@ -62,7 +65,7 @@ func TestHandler_MulticastGroup_LoRaWANRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -70,7 +73,7 @@ func TestHandler_MulticastGroup_LoRaWANRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok := getResp["LoRaWAN"].(map[string]any) require.True(t, ok) assert.Equal(t, "US915", loRaWAN["RfRegion"]) @@ -92,7 +95,7 @@ func TestHandler_MulticastGroup_LoRaWANRoundTrip(t *testing.T) { rec = doIoTWRequest(t, h, http.MethodGet, "/multicast-groups/"+id, "") require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok = getResp["LoRaWAN"].(map[string]any) require.True(t, ok) assert.InDelta(t, float64(1), loRaWAN["NumberOfDevicesInGroup"], 0.001) @@ -104,7 +107,7 @@ func TestHandler_MulticastGroup_LoRaWANRoundTrip(t *testing.T) { rec = doIoTWRequest(t, h, http.MethodGet, "/multicast-groups/"+id, "") require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok = getResp["LoRaWAN"].(map[string]any) require.True(t, ok) assert.Equal(t, "EU868", loRaWAN["RfRegion"]) @@ -132,7 +135,7 @@ func TestHandler_CancelMulticastGroupSession(t *testing.T) { if tt.create { rec := doIoTWRequest(t, h, http.MethodPost, "/multicast-groups", `{"Name":"mg"}`) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ = resp["Id"].(string) } @@ -153,7 +156,7 @@ func TestHandler_MulticastGroupCRUD(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, ok := createResp["Id"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -166,7 +169,7 @@ func TestHandler_MulticastGroupCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "mg1", getResp["Name"]) assert.Equal(t, id, getResp["Id"]) @@ -175,7 +178,7 @@ func TestHandler_MulticastGroupCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) groups, ok := listResp["MulticastGroupList"].([]any) require.True(t, ok) assert.Len(t, groups, 1) @@ -186,7 +189,7 @@ func TestHandler_MulticastGroupCRUD(t *testing.T) { // Verify update rec = doIoTWRequest(t, h, http.MethodGet, "/multicast-groups/"+id, "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "mg1-updated", getResp["Name"]) // Delete @@ -208,7 +211,7 @@ func TestHandler_MulticastGroupSession(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Getting the session before one has been started must 404, matching real @@ -225,7 +228,7 @@ func TestHandler_MulticastGroupSession(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var sessionResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sessionResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sessionResp, jsonx.V1())) _, hasLoRaWAN := sessionResp["LoRaWAN"] assert.True(t, hasLoRaWAN) } @@ -240,7 +243,7 @@ func TestHandler_DisassociateWirelessDeviceFromMulticastGroup(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var mgResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mgResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mgResp, jsonx.V1())) mgID := mgResp["Id"].(string) // Create wireless device @@ -254,7 +257,7 @@ func TestHandler_DisassociateWirelessDeviceFromMulticastGroup(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var devResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp, jsonx.V1())) devID := devResp["Id"].(string) // Associate device with multicast group; AssociateWirelessDeviceWithMulticastGroup @@ -284,7 +287,7 @@ func TestHandler_SendDataToMulticastGroup_UniqueMessageID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) mgID := createResp["Id"].(string) seen := make(map[string]bool) @@ -296,7 +299,7 @@ func TestHandler_SendDataToMulticastGroup_UniqueMessageID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) msgID, _ := resp["MessageId"].(string) assert.NotEmpty(t, msgID) @@ -334,7 +337,7 @@ func TestHandler_SendDataToMulticastGroup_RequiresBody(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) mgID, _ := createResp["Id"].(string) require.NotEmpty(t, mgID) @@ -375,7 +378,7 @@ func TestHandler_MulticastGroup_BulkOps(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) mgID := createResp["Id"].(string) path := "/multicast-groups/" + mgID + "/bulk" @@ -397,7 +400,7 @@ func TestHandler_MulticastGroups_FullLifecycle(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) mgID, _ := createResp["Id"].(string) require.NotEmpty(t, mgID) diff --git a/services/iotwireless/handler_network_analyzer.go b/services/iotwireless/handler_network_analyzer.go index 7dab9b41f5..ab7e043e87 100644 --- a/services/iotwireless/handler_network_analyzer.go +++ b/services/iotwireless/handler_network_analyzer.go @@ -1,11 +1,12 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -46,7 +47,7 @@ func (h *Handler) createNetworkAnalyzerConfiguration(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) nc, err := h.Backend.CreateNetworkAnalyzerConfig( h.AccountID, h.DefaultRegion, @@ -125,7 +126,7 @@ func (h *Handler) updateNetworkAnalyzerConfiguration(c *echo.Context, name strin } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateNetworkAnalyzerConfig( h.AccountID, h.DefaultRegion, name, diff --git a/services/iotwireless/handler_network_analyzer_test.go b/services/iotwireless/handler_network_analyzer_test.go index 689e29244a..47d8fe5c65 100644 --- a/services/iotwireless/handler_network_analyzer_test.go +++ b/services/iotwireless/handler_network_analyzer_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_NetworkAnalyzerCRUD(t *testing.T) { @@ -20,7 +22,7 @@ func TestHandler_NetworkAnalyzerCRUD(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) name, _ := createResp["Name"].(string) assert.Equal(t, "na1", name) @@ -29,7 +31,7 @@ func TestHandler_NetworkAnalyzerCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "na1", getResp["Name"]) assert.Equal(t, "test config", getResp["Description"]) @@ -38,7 +40,7 @@ func TestHandler_NetworkAnalyzerCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok := listResp["NetworkAnalyzerConfigurationList"].([]any) require.True(t, ok) assert.Len(t, configs, 1) @@ -50,7 +52,7 @@ func TestHandler_NetworkAnalyzerCRUD(t *testing.T) { // Verify update rec = doIoTWRequest(t, h, http.MethodGet, "/network-analyzer-configurations/na1", "") - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "updated", getResp["Description"]) // Delete diff --git a/services/iotwireless/handler_partner_accounts.go b/services/iotwireless/handler_partner_accounts.go index 57371546c7..2ac6d00b98 100644 --- a/services/iotwireless/handler_partner_accounts.go +++ b/services/iotwireless/handler_partner_accounts.go @@ -2,12 +2,13 @@ package iotwireless import ( "cmp" - "encoding/json" + "encoding/json/v2" "net/http" "slices" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -47,7 +48,7 @@ type listPartnerAccountsResponse struct { func (h *Handler) associateAwsAccountWithPartnerAccount(c *echo.Context, body []byte) error { var req associatePartnerAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/iotwireless/handler_partner_accounts_test.go b/services/iotwireless/handler_partner_accounts_test.go index c1e21d25cb..e372eaa804 100644 --- a/services/iotwireless/handler_partner_accounts_test.go +++ b/services/iotwireless/handler_partner_accounts_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AssociateAwsAccountWithPartnerAccount(t *testing.T) { @@ -42,7 +44,7 @@ func TestHandler_AssociateAwsAccountWithPartnerAccount(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) sidewalk, ok := resp["Sidewalk"].(map[string]any) require.True(t, ok) @@ -63,7 +65,7 @@ func TestHandler_PartnerAccounts(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &assocResp, jsonx.V1())) assert.NotEmpty(t, assocResp["Arn"]) // Get partner account @@ -71,7 +73,7 @@ func TestHandler_PartnerAccounts(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, true, getResp["AccountLinked"]) sidewalk, ok := getResp["Sidewalk"].(map[string]any) require.True(t, ok, "AccountLinked accounts must include Sidewalk info") @@ -84,7 +86,7 @@ func TestHandler_PartnerAccounts(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var unlinkedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &unlinkedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &unlinkedResp, jsonx.V1())) assert.Equal(t, false, unlinkedResp["AccountLinked"]) // List partner accounts @@ -92,7 +94,7 @@ func TestHandler_PartnerAccounts(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sidewalkList, ok := listResp["Sidewalk"].([]any) require.True(t, ok) require.Len( diff --git a/services/iotwireless/handler_positioning.go b/services/iotwireless/handler_positioning.go index fddad4332e..857837ada2 100644 --- a/services/iotwireless/handler_positioning.go +++ b/services/iotwireless/handler_positioning.go @@ -3,13 +3,15 @@ package iotwireless import ( "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net" "net/http" "net/netip" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // accuracyResponse mirrors types.Accuracy: a struct of two float fields, not @@ -99,7 +101,7 @@ func (h *Handler) updatePosition(c *echo.Context, id string) error { var req map[string]any body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) _ = h.Backend.UpdatePosition(id, req) return stubNoContent(c) @@ -126,7 +128,7 @@ func (h *Handler) putPositionConfiguration(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.PutPositionConfiguration(id, resourceType, req.Destination, req.Solvers); err != nil { return handleError(c, err) @@ -172,7 +174,7 @@ type getPositionEstimateRequest struct { func (h *Handler) getPositionEstimate(c *echo.Context) error { var req getPositionEstimateRequest - _ = json.Unmarshal(readStubBody(c), &req) + _ = json.Unmarshal(readStubBody(c), &req, jsonx.V1()) if err := validatePositionEstimate(&req); err != nil { return handleError(c, err) diff --git a/services/iotwireless/handler_positioning_test.go b/services/iotwireless/handler_positioning_test.go index 1e33ae91cb..5334b18ca5 100644 --- a/services/iotwireless/handler_positioning_test.go +++ b/services/iotwireless/handler_positioning_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Position(t *testing.T) { @@ -19,7 +21,7 @@ func TestHandler_Position(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) pos, ok := getResp["Position"].([]any) require.True(t, ok) assert.Empty(t, pos) @@ -39,7 +41,7 @@ func TestHandler_Position(t *testing.T) { // coordinates, not a fabricated empty response. rec = doIoTWRequest(t, h, http.MethodGet, "/positions/resource-123", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) pos, ok = getResp["Position"].([]any) require.True(t, ok) assert.Equal(t, []any{47.6, -122.3, 100.0}, pos) @@ -57,7 +59,7 @@ func TestHandler_Position(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getCfgResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfgResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfgResp, jsonx.V1())) assert.NotContains(t, getCfgResp, "Destination") // Put position configuration with a real ResourceType and Destination. @@ -69,7 +71,7 @@ func TestHandler_Position(t *testing.T) { // Get position configuration must now reflect the stored destination. rec = doIoTWRequest(t, h, http.MethodGet, "/position-configurations/resource-123", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfgResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfgResp, jsonx.V1())) assert.Equal(t, "my-position-dest", getCfgResp["Destination"]) solvers, ok := getCfgResp["Solvers"].(map[string]any) require.True(t, ok) @@ -91,7 +93,7 @@ func TestHandler_Position(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok := listResp["PositionConfigurationList"].([]any) require.True(t, ok) require.Len(t, configs, 1) @@ -106,7 +108,7 @@ func TestHandler_Position(t *testing.T) { "", ) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok = listResp["PositionConfigurationList"].([]any) require.True(t, ok) assert.Empty(t, configs) @@ -228,7 +230,7 @@ func TestHandler_PositionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp[tt.wantKey] assert.True(t, ok, "expected key %q in position config response", tt.wantKey) @@ -237,7 +239,7 @@ func TestHandler_PositionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) cfgs, ok := listResp["PositionConfigurationList"].([]any) require.True(t, ok) require.Len(t, cfgs, 1) diff --git a/services/iotwireless/handler_profiles.go b/services/iotwireless/handler_profiles.go index b8248e35a3..0438b521a3 100644 --- a/services/iotwireless/handler_profiles.go +++ b/services/iotwireless/handler_profiles.go @@ -7,11 +7,12 @@ package iotwireless // grouped in one cohesive file instead. import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -54,7 +55,7 @@ type listDeviceProfilesResponse struct { func (h *Handler) createDeviceProfile(c *echo.Context, body []byte) error { var req createDeviceProfileRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -147,7 +148,7 @@ type listServiceProfilesResponse struct { func (h *Handler) createServiceProfile(c *echo.Context, body []byte) error { var req createServiceProfileRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/iotwireless/handler_profiles_test.go b/services/iotwireless/handler_profiles_test.go index c1889a8749..39451d1063 100644 --- a/services/iotwireless/handler_profiles_test.go +++ b/services/iotwireless/handler_profiles_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateDeviceProfile(t *testing.T) { @@ -39,7 +41,7 @@ func TestHandler_CreateDeviceProfile(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.NotEmpty(t, resp["Arn"]) }) @@ -71,7 +73,7 @@ func TestHandler_DeviceProfile_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) require.NotEmpty(t, id) @@ -80,7 +82,7 @@ func TestHandler_DeviceProfile_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.profileName, getResp["Name"]) assert.Equal(t, id, getResp["Id"]) @@ -89,7 +91,7 @@ func TestHandler_DeviceProfile_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) list, ok := listResp["DeviceProfileList"].([]any) require.True(t, ok) assert.Len(t, list, 1) @@ -121,7 +123,7 @@ func TestHandler_ServiceProfile_LoRaWANGetShape(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -129,7 +131,7 @@ func TestHandler_ServiceProfile_LoRaWANGetShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) loRaWAN, ok := getResp["LoRaWAN"].(map[string]any) require.True(t, ok, "LoRaWAN must be present on Get") assert.InDelta(t, float64(15), loRaWAN["DrMax"], 0.001) @@ -153,7 +155,7 @@ func TestHandler_DeviceProfile_SidewalkCreateShape(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) @@ -161,7 +163,7 @@ func TestHandler_DeviceProfile_SidewalkCreateShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) sidewalk, ok := getResp["Sidewalk"].(map[string]any) require.True(t, ok, "an empty Sidewalk request object must still mark the profile as Sidewalk on Get") assert.Empty(t, sidewalk, "AWS-assigned Sidewalk fields must not be fabricated") @@ -205,7 +207,7 @@ func TestHandler_ListDeviceProfiles_FilterByType(t *testing.T) { Name string `json:"Name"` } `json:"DeviceProfileList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := make([]string, 0, len(resp.DeviceProfileList)) for _, dp := range resp.DeviceProfileList { @@ -253,7 +255,7 @@ func TestHandler_CreateGetListDeleteServiceProfile(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, ok := createResp["Id"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -263,7 +265,7 @@ func TestHandler_CreateGetListDeleteServiceProfile(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.profileName, getResp["Name"]) // List @@ -271,7 +273,7 @@ func TestHandler_CreateGetListDeleteServiceProfile(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) profiles, ok := listResp["ServiceProfileList"].([]any) require.True(t, ok) assert.Len(t, profiles, 1) diff --git a/services/iotwireless/handler_tags.go b/services/iotwireless/handler_tags.go index 901f81a2c7..66abb03e09 100644 --- a/services/iotwireless/handler_tags.go +++ b/services/iotwireless/handler_tags.go @@ -1,12 +1,13 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -46,7 +47,7 @@ func (h *Handler) tagResource(c *echo.Context, resourceArn string, body []byte) } var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } diff --git a/services/iotwireless/handler_tags_test.go b/services/iotwireless/handler_tags_test.go index d86c12658d..1f3bb61064 100644 --- a/services/iotwireless/handler_tags_test.go +++ b/services/iotwireless/handler_tags_test.go @@ -1,7 +1,7 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListTagsTagUntagResource(t *testing.T) { @@ -51,7 +53,7 @@ func TestHandler_ListTagsTagUntagResource(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["Arn"].(string) require.NotEmpty(t, arn) @@ -85,7 +87,7 @@ func TestHandler_ListTagsTagUntagResource(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tagList, ok := tagsResp["Tags"].([]any) require.True(t, ok) diff --git a/services/iotwireless/handler_test.go b/services/iotwireless/handler_test.go index 2d8b687e8d..d95a6cdfa6 100644 --- a/services/iotwireless/handler_test.go +++ b/services/iotwireless/handler_test.go @@ -1,7 +1,7 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -325,7 +326,7 @@ func TestHandler_Reset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["WirelessDeviceList"].([]any) require.True(t, ok) @@ -411,7 +412,7 @@ func TestHandler_ListEmpty_NonNil(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp[tt.listKey].([]any) require.True(t, ok, "key %q should be a JSON array, got %T", tt.listKey, resp[tt.listKey]) @@ -482,7 +483,7 @@ func Test_RouteMatcher_TagOperations(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) resourceArn := created["Arn"].(string) require.NotEmpty(t, resourceArn) @@ -503,7 +504,7 @@ func Test_RouteMatcher_TagOperations(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listed, jsonx.V1())) require.Len(t, listed.Tags, 1) assert.Equal(t, "env", listed.Tags[0].Key) assert.Equal(t, "prod", listed.Tags[0].Value) @@ -531,7 +532,7 @@ func Test_RouteMatcher_AssociateAwsAccountWithPartnerAccount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) sidewalk, ok := resp["Sidewalk"].(map[string]any) @@ -545,7 +546,7 @@ func Test_RouteMatcher_AssociateAwsAccountWithPartnerAccount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, true, getResp["AccountLinked"]) } @@ -594,7 +595,7 @@ func Test_ErrorResponse_SetsAmznErrortypeHeader(t *testing.T) { assert.Equal(t, tt.wantErrType, rec.Header().Get("X-Amzn-Errortype")) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantErrType, body["__type"]) assert.NotEmpty(t, body["Message"]) }) diff --git a/services/iotwireless/handler_wireless_devices.go b/services/iotwireless/handler_wireless_devices.go index 99677f6bdd..bf982a4242 100644 --- a/services/iotwireless/handler_wireless_devices.go +++ b/services/iotwireless/handler_wireless_devices.go @@ -1,13 +1,14 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -100,7 +101,7 @@ type testWirelessDeviceResponse struct { func (h *Handler) createWirelessDevice(c *echo.Context, body []byte) error { var req createWirelessDeviceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -194,7 +195,7 @@ func (h *Handler) deleteWirelessDevice(c *echo.Context, id string) error { func (h *Handler) associateWirelessDeviceWithThing(c *echo.Context, wirelessDeviceID string, body []byte) error { var req associateWirelessDeviceWithThingRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -219,7 +220,7 @@ func (h *Handler) updateWirelessDevice(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateWirelessDevice( h.AccountID, h.DefaultRegion, id, @@ -283,7 +284,7 @@ func (h *Handler) sendDataToWirelessDevice(c *echo.Context, wirelessDeviceID str } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) messageID := uuid.NewString() diff --git a/services/iotwireless/handler_wireless_devices_filter_test.go b/services/iotwireless/handler_wireless_devices_filter_test.go index e0777de97d..69e2611d48 100644 --- a/services/iotwireless/handler_wireless_devices_filter_test.go +++ b/services/iotwireless/handler_wireless_devices_filter_test.go @@ -1,13 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -25,7 +26,7 @@ func listWirelessDevicesNames(t *testing.T, h *iotwireless.Handler, rawQuery str Name string `json:"Name"` } `json:"WirelessDeviceList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := make([]string, 0, len(resp.WirelessDeviceList)) for _, d := range resp.WirelessDeviceList { diff --git a/services/iotwireless/handler_wireless_devices_test.go b/services/iotwireless/handler_wireless_devices_test.go index 9216384928..27a826e1a7 100644 --- a/services/iotwireless/handler_wireless_devices_test.go +++ b/services/iotwireless/handler_wireless_devices_test.go @@ -1,13 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -41,7 +42,7 @@ func TestHandler_CreateGetListDeleteWirelessDevice(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, ok := createResp["Id"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -51,7 +52,7 @@ func TestHandler_CreateGetListDeleteWirelessDevice(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.deviceName, getResp["Name"]) // List @@ -59,7 +60,7 @@ func TestHandler_CreateGetListDeleteWirelessDevice(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) devices, ok := listResp["WirelessDeviceList"].([]any) require.True(t, ok) assert.Len(t, devices, 1) @@ -133,7 +134,7 @@ func TestHandler_AssociateWirelessDeviceWithThing(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) devID = createResp["Id"].(string) } @@ -155,7 +156,7 @@ func TestHandler_UpdateWirelessDevice(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Update @@ -168,7 +169,7 @@ func TestHandler_UpdateWirelessDevice(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "dev1-updated", getResp["Name"]) } @@ -188,7 +189,7 @@ func TestHandler_DeregisterWirelessDevice(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Deregister (should delete the device) @@ -216,7 +217,7 @@ func TestHandler_DisassociateWirelessDeviceFromThing(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) devID := createResp["Id"].(string) // Associate with thing @@ -239,14 +240,14 @@ func TestHandler_GetWirelessDeviceStatistics(t *testing.T) { require.Equal(t, http.StatusCreated, createResp.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) devID := created["Id"].(string) rec := doIoTWRequest(t, h, http.MethodGet, "/wireless-devices/"+devID+"/statistics", "") assert.Equal(t, http.StatusOK, rec.Code) var stats map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stats)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stats, jsonx.V1())) assert.Equal(t, devID, stats["WirelessDeviceId"]) } @@ -269,7 +270,7 @@ func TestHandler_SendDataToWirelessDevice(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["MessageId"]) } @@ -283,14 +284,14 @@ func TestHandler_TestWirelessDevice(t *testing.T) { require.Equal(t, http.StatusCreated, createResp.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) devID := created["Id"].(string) rec := doIoTWRequest(t, h, http.MethodPost, "/wireless-devices/"+devID+"/test", `{}`) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PASS", resp["Result"]) } @@ -319,7 +320,7 @@ func TestHandler_QueuedMessages_SendListDelete(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var devResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &devResp, jsonx.V1())) devID := devResp["Id"].(string) // List queued messages (empty) @@ -327,7 +328,7 @@ func TestHandler_QueuedMessages_SendListDelete(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) msgs, ok := listResp["DownlinkQueueMessagesList"].([]any) require.True(t, ok) assert.Empty(t, msgs) @@ -339,14 +340,14 @@ func TestHandler_QueuedMessages_SendListDelete(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sendResp, jsonx.V1())) sentMessageID, _ := sendResp["MessageId"].(string) require.NotEmpty(t, sentMessageID) // List queued messages must now reflect the sent message. rec = doIoTWRequest(t, h, http.MethodGet, "/wireless-devices/"+devID+"/data", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) msgs, ok = listResp["DownlinkQueueMessagesList"].([]any) require.True(t, ok) require.Len(t, msgs, 1) @@ -359,7 +360,7 @@ func TestHandler_QueuedMessages_SendListDelete(t *testing.T) { // After deletion, the queue must be empty again. rec = doIoTWRequest(t, h, http.MethodGet, "/wireless-devices/"+devID+"/data", "") assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) msgs, ok = listResp["DownlinkQueueMessagesList"].([]any) require.True(t, ok) assert.Empty(t, msgs) @@ -381,7 +382,7 @@ func TestHandler_SendDataToWirelessDevice_UniqueMessageID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) msgID, _ := resp["MessageId"].(string) assert.NotEmpty(t, msgID, "MessageId should not be empty") @@ -399,7 +400,7 @@ func setupWirelessDevice(t *testing.T, h *iotwireless.Handler, name, devType, de require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ := resp["Id"].(string) require.NotEmpty(t, id) @@ -418,7 +419,7 @@ func TestHandler_QueuedMessages_MultipleSends(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) devID, ok := createResp["Id"].(string) require.True(t, ok) @@ -427,7 +428,7 @@ func TestHandler_QueuedMessages_MultipleSends(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var pre map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pre)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pre, jsonx.V1())) assert.Empty(t, pre["DownlinkQueueMessagesList"]) // Send two messages (POST /wireless-devices/{id}/data). @@ -438,7 +439,7 @@ func TestHandler_QueuedMessages_MultipleSends(t *testing.T) { require.Equal(t, http.StatusCreated, r.Code) var sr map[string]any - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &sr)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &sr, jsonx.V1())) assert.NotEmpty(t, sr["MessageId"], "SendData must return a MessageId") } @@ -447,7 +448,7 @@ func TestHandler_QueuedMessages_MultipleSends(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var post map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &post)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &post, jsonx.V1())) msgs, ok := post["DownlinkQueueMessagesList"].([]any) require.True(t, ok) assert.Len(t, msgs, 2, "two enqueued messages must appear in list") @@ -472,7 +473,7 @@ func Test_GetWirelessDevice_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) id := created["Id"].(string) // Before association, ThingArn/ThingName must be absent. @@ -480,7 +481,7 @@ func Test_GetWirelessDevice_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var before map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &before, jsonx.V1())) assert.Empty(t, before["ThingArn"]) assert.Empty(t, before["ThingName"]) @@ -493,7 +494,7 @@ func Test_GetWirelessDevice_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var after map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &after, jsonx.V1())) assert.Equal(t, thingArn, after["ThingArn"]) assert.Equal(t, "my-thing", after["ThingName"]) @@ -504,7 +505,7 @@ func Test_GetWirelessDevice_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var afterDisassoc map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &afterDisassoc)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &afterDisassoc, jsonx.V1())) assert.Empty(t, afterDisassoc["ThingArn"]) assert.Empty(t, afterDisassoc["ThingName"]) } diff --git a/services/iotwireless/handler_wireless_devices_wireshape_test.go b/services/iotwireless/handler_wireless_devices_wireshape_test.go index b872399a17..6519dd787f 100644 --- a/services/iotwireless/handler_wireless_devices_wireshape_test.go +++ b/services/iotwireless/handler_wireless_devices_wireshape_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_CreateGetWirelessDevice_LoRaWANSidewalkWireShape drives a real @@ -35,7 +37,7 @@ func TestHandler_CreateGetWirelessDevice_LoRaWANSidewalkWireShape(t *testing.T) var createResp struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec = doIoTWRequest(t, h, http.MethodGet, "/wireless-devices/"+createResp.ID, "") require.Equal(t, http.StatusOK, rec.Code) @@ -57,7 +59,7 @@ func TestHandler_CreateGetWirelessDevice_LoRaWANSidewalkWireShape(t *testing.T) } `json:"AbpV1_0_x"` } `json:"LoRaWAN"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "dp-1", got.LoRaWAN.DeviceProfileID) assert.Equal(t, "addr-1", got.LoRaWAN.AbpV1_0X.DevAddr) @@ -94,7 +96,7 @@ func TestHandler_NetworkAnalyzerConfig_TraceContentWireShape(t *testing.T) { var got struct { TraceContent traceContent `json:"TraceContent"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "INFO", got.TraceContent.LogLevel) assert.Equal(t, "ENABLED", got.TraceContent.MulticastFrameInfo) assert.Empty(t, got.TraceContent.WirelessDeviceFrameInfo) @@ -109,7 +111,7 @@ func TestHandler_NetworkAnalyzerConfig_TraceContentWireShape(t *testing.T) { var got2 struct { TraceContent traceContent `json:"TraceContent"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got2, jsonx.V1())) assert.Empty(t, got2.TraceContent.LogLevel, "update replaces TraceContent wholesale") assert.Equal(t, "ENABLED", got2.TraceContent.WirelessDeviceFrameInfo) } @@ -137,7 +139,7 @@ func TestHandler_ListWirelessDevices_EntryShape(t *testing.T) { var got struct { WirelessDeviceList []map[string]any `json:"WirelessDeviceList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) require.Len(t, got.WirelessDeviceList, 1) entry := got.WirelessDeviceList[0] diff --git a/services/iotwireless/handler_wireless_gateway_tasks.go b/services/iotwireless/handler_wireless_gateway_tasks.go index 7d5e5e1802..b7674f2c7e 100644 --- a/services/iotwireless/handler_wireless_gateway_tasks.go +++ b/services/iotwireless/handler_wireless_gateway_tasks.go @@ -1,11 +1,13 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createWirelessGatewayTaskResponse struct { @@ -57,7 +59,7 @@ func (h *Handler) createWirelessGatewayTask(c *echo.Context, gatewayID string) e } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) task, err := h.Backend.CreateWirelessGatewayTask(gatewayID, req.WirelessGatewayTaskDefinitionID) if err != nil { @@ -103,7 +105,7 @@ func (h *Handler) createWirelessGatewayTaskDefinition(c *echo.Context) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) def, err := h.Backend.CreateWirelessGatewayTaskDefinition( h.AccountID, diff --git a/services/iotwireless/handler_wireless_gateway_tasks_test.go b/services/iotwireless/handler_wireless_gateway_tasks_test.go index 1238329c32..b3eec2db46 100644 --- a/services/iotwireless/handler_wireless_gateway_tasks_test.go +++ b/services/iotwireless/handler_wireless_gateway_tasks_test.go @@ -1,13 +1,15 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GatewayTaskLifecycle(t *testing.T) { @@ -20,7 +22,7 @@ func TestHandler_GatewayTaskLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var gwResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gwResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &gwResp, jsonx.V1())) gwID := gwResp["Id"].(string) // Create task definition @@ -29,7 +31,7 @@ func TestHandler_GatewayTaskLifecycle(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var defResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &defResp, jsonx.V1())) defID := defResp["Id"].(string) assert.NotEmpty(t, defID) @@ -39,7 +41,7 @@ func TestHandler_GatewayTaskLifecycle(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var taskResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &taskResp, jsonx.V1())) assert.Equal(t, "PENDING", taskResp["Status"]) // Get task @@ -47,7 +49,7 @@ func TestHandler_GatewayTaskLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getTaskResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getTaskResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getTaskResp, jsonx.V1())) assert.Equal(t, "PENDING", getTaskResp["Status"]) // List task definitions @@ -68,7 +70,7 @@ func TestHandler_GatewayTaskLifecycle(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var notFoundResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ¬FoundResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ¬FoundResp, jsonx.V1())) msg, _ := notFoundResp["Message"].(string) assert.NotContains(t, msg, "ResourceNotFoundException") assert.Contains(t, msg, "task") @@ -130,7 +132,7 @@ func TestHandler_ListWirelessGatewayTaskDefinitions_ReturnsRealData(t *testing.T require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, _ := createResp["Id"].(string) require.NotEmpty(t, id) createdIDs = append(createdIDs, id) @@ -140,7 +142,7 @@ func TestHandler_ListWirelessGatewayTaskDefinitions_ReturnsRealData(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) defs, ok := resp["TaskDefinitions"].([]any) require.True(t, ok, "TaskDefinitions should be array") @@ -199,7 +201,7 @@ func TestHandler_GatewayTaskDefinitionCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) arn := createResp["Arn"].(string) require.NotEmpty(t, id) @@ -210,7 +212,7 @@ func TestHandler_GatewayTaskDefinitionCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.defName, getResp["Name"]) // Delete. @@ -222,7 +224,7 @@ func TestHandler_GatewayTaskDefinitionCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) defs := listResp["TaskDefinitions"].([]any) assert.Empty(t, defs) }) @@ -252,7 +254,7 @@ func TestGetWirelessGatewayTaskDefinition_NotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) msg, _ := resp["Message"].(string) assert.NotContains(t, msg, "ResourceNotFoundException") @@ -276,7 +278,7 @@ func TestGetWirelessGatewayTaskDefinition_Found(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id, _ := created["Id"].(string) require.NotEmpty(t, id) @@ -284,7 +286,7 @@ func TestGetWirelessGatewayTaskDefinition_Found(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "real-taskdef", got["Name"]) assert.Equal(t, true, got["AutoCreateTasks"]) assert.NotEmpty(t, got["Arn"]) diff --git a/services/iotwireless/handler_wireless_gateways.go b/services/iotwireless/handler_wireless_gateways.go index 3fae05c554..eab16d533c 100644 --- a/services/iotwireless/handler_wireless_gateways.go +++ b/services/iotwireless/handler_wireless_gateways.go @@ -1,12 +1,13 @@ package iotwireless import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -51,7 +52,7 @@ type getWirelessGatewayStatisticsResponse struct { func (h *Handler) createWirelessGateway(c *echo.Context, body []byte) error { var req createWirelessGatewayRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -117,7 +118,7 @@ func (h *Handler) deleteWirelessGateway(c *echo.Context, id string) error { func (h *Handler) associateWirelessGatewayWithThing(c *echo.Context, gatewayID string, body []byte) error { var req associateWirelessGatewayWithThingRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "invalid request body") } @@ -141,7 +142,7 @@ func (h *Handler) updateWirelessGateway(c *echo.Context, id string) error { } body := readStubBody(c) - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if err := h.Backend.UpdateWirelessGateway( h.AccountID, h.DefaultRegion, id, req.Name, req.Description, diff --git a/services/iotwireless/handler_wireless_gateways_test.go b/services/iotwireless/handler_wireless_gateways_test.go index 4d7d5e41ea..ddfe6ef589 100644 --- a/services/iotwireless/handler_wireless_gateways_test.go +++ b/services/iotwireless/handler_wireless_gateways_test.go @@ -1,12 +1,15 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateGetListDeleteWirelessGateway(t *testing.T) { @@ -45,7 +48,7 @@ func TestHandler_CreateGetListDeleteWirelessGateway(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id, ok := createResp["Id"].(string) require.True(t, ok) assert.NotEmpty(t, id) @@ -55,7 +58,7 @@ func TestHandler_CreateGetListDeleteWirelessGateway(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.gatewayName, getResp["Name"]) // List @@ -63,7 +66,7 @@ func TestHandler_CreateGetListDeleteWirelessGateway(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) gateways, ok := listResp["WirelessGatewayList"].([]any) require.True(t, ok) assert.Len(t, gateways, 1) @@ -116,7 +119,7 @@ func TestHandler_AssociateWirelessGatewayWithThing(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) gwID = createResp["Id"].(string) } @@ -143,7 +146,7 @@ func TestHandler_UpdateWirelessGateway(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) // Update @@ -161,7 +164,7 @@ func TestHandler_UpdateWirelessGateway(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "gw1-updated", getResp["Name"]) } @@ -175,7 +178,7 @@ func TestHandler_DisassociateWirelessGatewayFromThing(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) gwID := createResp["Id"].(string) // Associate with thing @@ -198,7 +201,7 @@ func TestHandler_GetWirelessGatewayStatistics(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) gwID := createResp["Id"].(string) // Get statistics @@ -206,7 +209,7 @@ func TestHandler_GetWirelessGatewayStatistics(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var statsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &statsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &statsResp, jsonx.V1())) assert.Equal(t, "Connected", statsResp["ConnectionStatus"]) assert.Equal(t, gwID, statsResp["WirelessGatewayId"]) } @@ -229,14 +232,14 @@ func TestHandler_GetWirelessGatewayFirmwareInformation(t *testing.T) { require.Equal(t, http.StatusCreated, createResp.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) gwID := created["Id"].(string) rec := doIoTWRequest(t, h, http.MethodGet, "/wireless-gateways/"+gwID+"/firmware-information", "") assert.Equal(t, http.StatusOK, rec.Code) var info map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &info)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &info, jsonx.V1())) loRaWAN, ok := info["LoRaWAN"].(map[string]any) require.True(t, ok) currentVersion, ok := loRaWAN["CurrentVersion"].(map[string]any) @@ -264,7 +267,7 @@ func TestHandler_GatewayFirmwareInformation(t *testing.T) { require.Equal(t, http.StatusCreated, gwRec.Code) var gwResp map[string]any - require.NoError(t, json.Unmarshal(gwRec.Body.Bytes(), &gwResp)) + require.NoError(t, json.Unmarshal(gwRec.Body.Bytes(), &gwResp, jsonx.V1())) gwID, ok := gwResp["Id"].(string) require.True(t, ok) @@ -274,7 +277,7 @@ func TestHandler_GatewayFirmwareInformation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) loRaWAN, ok := resp["LoRaWAN"].(map[string]any) require.True(t, ok, "response must have LoRaWAN field") _, ok = loRaWAN["CurrentVersion"] @@ -292,7 +295,7 @@ func Test_GetWirelessGateway_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) id := created["Id"].(string) thingArn := "arn:aws:iot:us-east-1:000000000000:thing/gw-thing" @@ -304,7 +307,7 @@ func Test_GetWirelessGateway_ReflectsThingAssociation(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var after map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &after, jsonx.V1())) assert.Equal(t, thingArn, after["ThingArn"]) assert.Equal(t, "gw-thing", after["ThingName"]) } @@ -321,7 +324,7 @@ func TestHandler_WirelessGatewayAndDestinationUpdates(t *testing.T) { require.True(t, rec.Code >= 200 && rec.Code < 300) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) gwID, _ := createResp["Id"].(string) require.NotEmpty(t, gwID, "CreateWirelessGateway must return an Id") diff --git a/services/iotwireless/idempotency.go b/services/iotwireless/idempotency.go index 9d6d70a2fc..1f5d063aa0 100644 --- a/services/iotwireless/idempotency.go +++ b/services/iotwireless/idempotency.go @@ -3,13 +3,15 @@ package iotwireless import ( "bytes" "crypto/sha256" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/url" "sync" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxIdempotencyEntries = 1024 @@ -98,7 +100,7 @@ func (w *captureWriter) WriteHeader(status int) { // other body member. func requestTokenAndParams(body []byte) (string, [sha256.Size]byte, bool) { var members map[string]any - if err := json.Unmarshal(body, &members); err != nil { + if err := json.Unmarshal(body, &members, jsonx.V1()); err != nil { return "", [sha256.Size]byte{}, false } @@ -108,7 +110,7 @@ func requestTokenAndParams(body []byte) (string, [sha256.Size]byte, bool) { } delete(members, "ClientRequestToken") - canonical, err := json.Marshal(members) + canonical, err := json.Marshal(members, jsonx.V1()) if err != nil { return "", [sha256.Size]byte{}, false } diff --git a/services/iotwireless/idempotency_persistence_test.go b/services/iotwireless/idempotency_persistence_test.go index 2b38476260..7c70c7f27e 100644 --- a/services/iotwireless/idempotency_persistence_test.go +++ b/services/iotwireless/idempotency_persistence_test.go @@ -1,13 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -42,7 +43,7 @@ func TestClientRequestTokenSurvivesRestore(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var first map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first, jsonx.V1())) snap := h.Backend.(*iotwireless.InMemoryBackend).Snapshot(t.Context()) require.NotEmpty(t, snap) @@ -55,7 +56,7 @@ func TestClientRequestTokenSurvivesRestore(t *testing.T) { if tt.wantSameID { var again map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &again)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &again, jsonx.V1())) assert.Equal(t, first["Id"], again["Id"]) } }) diff --git a/services/iotwireless/import_task_single_test.go b/services/iotwireless/import_task_single_test.go index e1e006d21e..7e94f03c0b 100644 --- a/services/iotwireless/import_task_single_test.go +++ b/services/iotwireless/import_task_single_test.go @@ -1,13 +1,15 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSingleImportTaskRoundTrip(t *testing.T) { @@ -44,7 +46,7 @@ func TestSingleImportTaskRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var start map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) id, _ := start["Id"].(string) arn, _ := start["Arn"].(string) @@ -53,7 +55,7 @@ func TestSingleImportTaskRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "d1", got["DestinationName"]) assert.Equal(t, tt.wantPositioning, orEmpty(got["Positioning"])) @@ -71,7 +73,7 @@ func TestSingleImportTaskRoundTrip(t *testing.T) { } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp, jsonx.V1())) if tt.wantTagValue == "" { assert.Empty(t, tagResp.Tags) diff --git a/services/iotwireless/multi_region_test.go b/services/iotwireless/multi_region_test.go index 0976e756e6..9a2bfda197 100644 --- a/services/iotwireless/multi_region_test.go +++ b/services/iotwireless/multi_region_test.go @@ -2,7 +2,8 @@ package iotwireless_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -180,8 +182,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/iotwireless/multicast_bulk_test.go b/services/iotwireless/multicast_bulk_test.go index 32cb47192a..e690e00082 100644 --- a/services/iotwireless/multicast_bulk_test.go +++ b/services/iotwireless/multicast_bulk_test.go @@ -1,12 +1,13 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -86,7 +87,7 @@ func TestHandler_StartBulkAssociate_MutatesState(t *testing.T) { var created struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec = doIoTWRequest(t, h, "POST", "/wireless-devices", `{"Name":"d1","Type":"LoRaWAN"}`) require.Equal(t, 201, rec.Code) diff --git a/services/iotwireless/pagination_test.go b/services/iotwireless/pagination_test.go index db1b13b0fa..bbd3eb97af 100644 --- a/services/iotwireless/pagination_test.go +++ b/services/iotwireless/pagination_test.go @@ -1,12 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListWirelessDevices_Pagination locks in real cursor @@ -47,7 +49,7 @@ func TestHandler_ListWirelessDevices_Pagination(t *testing.T) { require.Equal(t, 200, rec.Code) var resp listResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.LessOrEqual(t, len(resp.WirelessDeviceList), 2, "page size must respect maxResults") for _, d := range resp.WirelessDeviceList { diff --git a/services/iotwireless/persistence.go b/services/iotwireless/persistence.go index 037e563fec..f79d017b71 100644 --- a/services/iotwireless/persistence.go +++ b/services/iotwireless/persistence.go @@ -2,11 +2,13 @@ package iotwireless import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -229,7 +231,7 @@ func newDirtyDTORegistry() ( // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourceTags map[string]map[string]string `json:"resourceTags,omitempty"` PartnerAccounts map[string]string `json:"partnerAccounts,omitempty"` FuotaTaskMulticast map[string]map[string]bool `json:"fuotaTaskMulticast,omitempty"` @@ -296,7 +298,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Idempotency: idemEntries, } - data, err := json.Marshal(snap) //nolint:musttag // nested types lack tags + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "iotwireless: failed to marshal snapshot", "error", err) @@ -309,7 +311,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { // snapshotDirtyTablesLocked builds the ephemeral DTO registry from the 8 // account/region-scoped tables and returns its SnapshotAll() output. Must be // called with b.mu held for reading. -func (b *InMemoryBackend) snapshotDirtyTablesLocked() (map[string]json.RawMessage, error) { +func (b *InMemoryBackend) snapshotDirtyTablesLocked() (map[string]jsontext.Value, error) { dtoReg, deviceDTOs, gatewayDTOs, serviceProfileDTOs, destinationDTOs, deviceProfileDTOs, fuotaTaskDTOs, multicastGroupDTOs, networkAnalyzerConfigDTOs := newDirtyDTORegistry() @@ -473,7 +475,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // ephemeral DTO registry, converting each DTO back to its live type by // re-populating the AccountID/Region fields the live type excludes from // JSON. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, deviceDTOs, gatewayDTOs, serviceProfileDTOs, destinationDTOs, deviceProfileDTOs, fuotaTaskDTOs, multicastGroupDTOs, networkAnalyzerConfigDTOs := newDirtyDTORegistry() diff --git a/services/iotwireless/wireless_gateway_tasks_test.go b/services/iotwireless/wireless_gateway_tasks_test.go index 1025fc4bff..7aff86bd25 100644 --- a/services/iotwireless/wireless_gateway_tasks_test.go +++ b/services/iotwireless/wireless_gateway_tasks_test.go @@ -1,13 +1,14 @@ package iotwireless_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iotwireless" ) @@ -115,7 +116,7 @@ func TestHandler_ListWirelessGatewayTaskDefinitions_EntryShape(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) entries, ok := resp["TaskDefinitions"].([]any) require.True(t, ok) diff --git a/services/kafka/handler.go b/services/kafka/handler.go index 9207105890..f57fbfc811 100644 --- a/services/kafka/handler.go +++ b/services/kafka/handler.go @@ -3,7 +3,7 @@ package kafka import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -653,7 +654,7 @@ func decodeJSONBody(body []byte, v any) error { return nil } - return json.Unmarshal(body, v) + return json.Unmarshal(body, v, jsonx.V1()) } // ---------------------------------------- @@ -666,7 +667,7 @@ const defaultPageSize = 100 func encodeKafkaPageToken(offset int) string { data, _ := json.Marshal(struct { O int `json:"o"` - }{O: offset}) + }{O: offset}, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(data) } @@ -689,7 +690,7 @@ func decodeKafkaPageToken(token string) int { O int `json:"o"` } - err = json.Unmarshal(data, &t) + err = json.Unmarshal(data, &t, jsonx.V1()) if err != nil || t.O < 0 { return 0 } diff --git a/services/kafka/handler_channels.go b/services/kafka/handler_channels.go index 114bcda87d..891f34bbfb 100644 --- a/services/kafka/handler_channels.go +++ b/services/kafka/handler_channels.go @@ -2,11 +2,13 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // splitChannelResource splits the handler's internal "clusterArn|channelArn" @@ -53,7 +55,7 @@ func (h *Handler) handleCreateChannel( body []byte, ) error { var in createChannelInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "BadRequestException", "invalid request body: "+err.Error()) } @@ -219,7 +221,7 @@ func (h *Handler) handleUpdateChannel( } var in updateChannelInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "BadRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kafka/handler_channels_test.go b/services/kafka/handler_channels_test.go index d9d2e62426..767831b74b 100644 --- a/services/kafka/handler_channels_test.go +++ b/services/kafka/handler_channels_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -278,7 +279,7 @@ func TestKafka_ListChannelsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) channels1, _ := resp1["channels"].([]any) assert.Len(t, channels1, 3) @@ -292,7 +293,7 @@ func TestKafka_ListChannelsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) channels2, _ := resp2["channels"].([]any) assert.Len(t, channels2, 2) assert.Empty(t, resp2["nextToken"]) diff --git a/services/kafka/handler_cluster_operations_test.go b/services/kafka/handler_cluster_operations_test.go index c84eba36fb..b72cfc4ec9 100644 --- a/services/kafka/handler_cluster_operations_test.go +++ b/services/kafka/handler_cluster_operations_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -44,7 +45,7 @@ func TestKafka_DescribeClusterOperation(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) op := backend.AddClusterOperationInternal(clusterArn, "UPDATE_BROKER_COUNT") @@ -63,7 +64,7 @@ func TestKafka_DescribeClusterOperation(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) opInfo, ok := resp["clusterOperationInfo"].(map[string]any) require.True(t, ok) @@ -145,7 +146,7 @@ func TestClusterOperationTracking_V1(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) opList, ok := listResp["clusterOperationInfoList"].([]any) require.True(t, ok) assert.GreaterOrEqual(t, len(opList), 2, "should have at least 2 operations") @@ -167,7 +168,7 @@ func TestClusterOperationTracking_V1(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) opInfo, infoOK := descResp["clusterOperationInfo"].(map[string]any) require.True(t, infoOK) assert.Equal(t, opArn, opInfo["operationArn"]) @@ -195,7 +196,7 @@ func TestClusterOperationTracking_V2(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) opList, ok := listResp["clusterOperationInfoList"].([]any) require.True(t, ok) require.NotEmpty(t, opList) @@ -205,7 +206,7 @@ func TestClusterOperationTracking_V2(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) opInfo, ok := descResp["clusterOperationInfo"].(map[string]any) require.True(t, ok) assert.Equal(t, opArn, opInfo["operationArn"]) @@ -242,7 +243,7 @@ func TestListClusterOperationsV2_OmitsDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) opList, ok := listResp["clusterOperationInfoList"].([]any) require.True(t, ok) require.NotEmpty(t, opList) diff --git a/services/kafka/handler_cluster_policy.go b/services/kafka/handler_cluster_policy.go index 8c22076d55..253d60166c 100644 --- a/services/kafka/handler_cluster_policy.go +++ b/services/kafka/handler_cluster_policy.go @@ -2,10 +2,12 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDeleteClusterPolicy( @@ -54,7 +56,7 @@ func (h *Handler) handlePutClusterPolicy( body []byte, ) error { var in putClusterPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_cluster_policy_test.go b/services/kafka/handler_cluster_policy_test.go index c78902031f..58ddcf8898 100644 --- a/services/kafka/handler_cluster_policy_test.go +++ b/services/kafka/handler_cluster_policy_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -45,7 +46,7 @@ func TestKafka_DeleteClusterPolicy(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) var clusterArn string if tt.useRealArn { diff --git a/services/kafka/handler_cluster_updates.go b/services/kafka/handler_cluster_updates.go index 14b0951e7a..15e429de47 100644 --- a/services/kafka/handler_cluster_updates.go +++ b/services/kafka/handler_cluster_updates.go @@ -2,10 +2,12 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type rebootBrokerInput struct { @@ -23,7 +25,7 @@ func (h *Handler) handleRebootBroker( body []byte, ) error { var in rebootBrokerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -116,7 +118,7 @@ func (h *Handler) handleUpdateBrokerCount( body []byte, ) error { var in updateBrokerCountInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -147,7 +149,7 @@ func (h *Handler) handleUpdateBrokerStorage( body []byte, ) error { var in updateBrokerStorageInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -183,7 +185,7 @@ func (h *Handler) handleUpdateBrokerType( body []byte, ) error { var in updateBrokerTypeInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -214,7 +216,7 @@ func (h *Handler) handleUpdateClusterConfiguration( body []byte, ) error { var in updateClusterConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -249,7 +251,7 @@ func (h *Handler) handleUpdateClusterKafkaVersion( body []byte, ) error { var in updateClusterKafkaVersionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -361,7 +363,7 @@ func (h *Handler) handleUpdateRebalancing( body []byte, ) error { var in updateRebalancingInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_cluster_updates_test.go b/services/kafka/handler_cluster_updates_test.go index 95bf2799d1..c1d0ea8b83 100644 --- a/services/kafka/handler_cluster_updates_test.go +++ b/services/kafka/handler_cluster_updates_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -164,7 +165,7 @@ func TestUpdateOpsRequireCurrentVersion(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "BadRequestException", errResp["__type"], "wrong version must produce BadRequestException") } @@ -285,7 +286,7 @@ func TestUpdateBrokerCount_Persists(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo, ok := descResp["clusterInfo"].(map[string]any) require.True(t, ok) provisioned, ok := clusterInfo["provisioned"].(map[string]any) @@ -323,7 +324,7 @@ func TestUpdateBrokerStorage_Persists(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo, _ := descResp["clusterInfo"].(map[string]any) provisioned, _ := clusterInfo["provisioned"].(map[string]any) bng, _ := provisioned["brokerNodeGroupInfo"].(map[string]any) @@ -357,7 +358,7 @@ func TestUpdateBrokerType_Persists(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo, _ := descResp["clusterInfo"].(map[string]any) provisioned, _ := clusterInfo["provisioned"].(map[string]any) bng, _ := provisioned["brokerNodeGroupInfo"].(map[string]any) @@ -389,7 +390,7 @@ func TestUpdateClusterKafkaVersion_Persists(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo, _ := descResp["clusterInfo"].(map[string]any) provisioned, _ := clusterInfo["provisioned"].(map[string]any) assert.Equal( @@ -608,7 +609,7 @@ func TestUpdateClusterConfiguration_V2Path(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo, _ := descResp["clusterInfo"].(map[string]any) provisioned, _ := clusterInfo["provisioned"].(map[string]any) assert.NotContains(t, provisioned, "configurationInfo", diff --git a/services/kafka/handler_clusters.go b/services/kafka/handler_clusters.go index 80a9844d5a..663ed3da9e 100644 --- a/services/kafka/handler_clusters.go +++ b/services/kafka/handler_clusters.go @@ -2,13 +2,15 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createClusterInput is CreateClusterInput (api_op_CreateCluster.go, @@ -233,7 +235,7 @@ type createClusterV2Output struct { func (h *Handler) handleCreateCluster(ctx context.Context, c *echo.Context, body []byte) error { var in createClusterInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -264,7 +266,7 @@ func (h *Handler) handleCreateCluster(ctx context.Context, c *echo.Context, body func (h *Handler) handleCreateClusterV2(ctx context.Context, c *echo.Context, body []byte) error { var in createClusterV2Input - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_clusters_fields_test.go b/services/kafka/handler_clusters_fields_test.go index 4ff7c37be6..9e51d41ce5 100644 --- a/services/kafka/handler_clusters_fields_test.go +++ b/services/kafka/handler_clusters_fields_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -240,7 +241,7 @@ func TestServerlessCluster_HTTP(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["clusterType"]) } }) @@ -266,7 +267,7 @@ func TestDescribeClusterV2_ServerlessArm(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo, ok := resp["clusterInfo"].(map[string]any) require.True(t, ok) @@ -287,7 +288,7 @@ func TestDescribeClusterV2_ProvisionedArm(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo, ok := resp["clusterInfo"].(map[string]any) require.True(t, ok) @@ -392,7 +393,7 @@ func TestEncryptionInfo_InV1Response(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo := resp["clusterInfo"].(map[string]any) encInfo, ok := clInfo["encryptionInfo"].(map[string]any) @@ -428,7 +429,7 @@ func TestEncryptionInfo_InV2Response(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo := resp["clusterInfo"].(map[string]any) provisioned, ok := clInfo["provisioned"].(map[string]any) diff --git a/services/kafka/handler_clusters_shapes_test.go b/services/kafka/handler_clusters_shapes_test.go index e12fc8cdd8..1903a276fc 100644 --- a/services/kafka/handler_clusters_shapes_test.go +++ b/services/kafka/handler_clusters_shapes_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -184,7 +185,7 @@ func TestListClustersV2_ClusterType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterList := resp["clusterInfoList"].([]any) require.Len(t, clusterList, 2) @@ -271,7 +272,7 @@ func TestListClustersV2_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterList, _ := resp["clusterInfoList"].([]any) @@ -301,7 +302,7 @@ func TestListClusters_NameFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterList, _ := resp["clusterInfoList"].([]any) @@ -328,7 +329,7 @@ func TestListClustersV2_ServerlessHasNoProvisionedArm(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterList := resp["clusterInfoList"].([]any) require.Len(t, clusterList, 1) @@ -411,7 +412,7 @@ func TestOpenMonitoring_InV2Response(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo := resp["clusterInfo"].(map[string]any) provisioned := clInfo["provisioned"].(map[string]any) @@ -593,7 +594,7 @@ func TestCreateCluster_V1_HTTP_AuthModes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["clusterArn"]) }) } @@ -695,7 +696,7 @@ func TestGetBootstrapBrokers_Variants(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["bootstrapBrokerString"], "default ClientBroker is TLS: no plaintext listener") assert.NotEmpty(t, resp["bootstrapBrokerStringTls"], "TLS broker string present for the default mode") @@ -754,7 +755,7 @@ func TestGetBootstrapBrokers_ScramPublic(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["bootstrapBrokerStringSaslScram"]) assert.NotEmpty(t, resp["bootstrapBrokerStringPublicTls"]) @@ -773,7 +774,7 @@ func TestZookeeperConnectString(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo := resp["clusterInfo"].(map[string]any) zkStr, ok := clInfo["zookeeperConnectString"].(string) @@ -810,7 +811,7 @@ func TestDescribeCluster_V1_IncludesNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clInfo := resp["clusterInfo"].(map[string]any) assert.Equal(t, kafka.EnhancedMonitoringPerBroker, clInfo["enhancedMonitoring"]) diff --git a/services/kafka/handler_clusters_test.go b/services/kafka/handler_clusters_test.go index 25d03dbb46..db05354409 100644 --- a/services/kafka/handler_clusters_test.go +++ b/services/kafka/handler_clusters_test.go @@ -3,7 +3,7 @@ package kafka_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -60,7 +61,7 @@ func TestKafka_CreateCluster(t *testing.T) { var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -78,7 +79,7 @@ func TestKafka_CreateCluster(t *testing.T) { if tt.wantFields != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for k, v := range tt.wantFields { assert.Equal(t, v, resp[k]) @@ -149,7 +150,7 @@ func TestKafka_ListClusters(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["clusterInfoList"].([]any) require.True(t, ok) @@ -211,7 +212,7 @@ func TestKafka_DescribeAndDeleteCluster(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) var clusterArn string if tt.useRealArn { @@ -276,7 +277,7 @@ func TestKafka_GetBootstrapBrokers(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) var clusterArn string if tt.useRealArn { @@ -293,7 +294,7 @@ func TestKafka_GetBootstrapBrokers(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["bootstrapBrokerString"], "default ClientBroker is TLS: no plaintext listener") assert.Len(t, strings.Split(resp["bootstrapBrokerStringTls"].(string), ","), 3) } @@ -340,7 +341,7 @@ func TestKafka_CreateClusterV2(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "v2-cluster", resp["clusterName"]) assert.Equal(t, "PROVISIONED", resp["clusterType"]) assert.NotEmpty(t, resp["clusterArn"]) @@ -380,7 +381,7 @@ func TestKafka_DescribeClusterV2(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) var clusterArn string if tt.useRealArn { @@ -395,7 +396,7 @@ func TestKafka_DescribeClusterV2(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterInfo, ok := resp["clusterInfo"].(map[string]any) require.True(t, ok) @@ -456,7 +457,7 @@ func TestKafka_ListClustersV2(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, ok := resp["clusterInfoList"].([]any) require.True(t, ok) @@ -582,7 +583,7 @@ func TestListClustersPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) list1, ok := resp1["clusterInfoList"].([]any) require.True(t, ok, "clusterInfoList must be an array") @@ -601,7 +602,7 @@ func TestListClustersPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) list2, ok2 := resp2["clusterInfoList"].([]any) require.True(t, ok2) @@ -638,7 +639,7 @@ func TestListClusters_NegativeOffsetToken(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["clusterInfoList"].([]any) require.True(t, ok, "clusterInfoList must be an array") @@ -662,7 +663,7 @@ func TestListClustersV2Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) list1 := resp1["clusterInfoList"].([]any) assert.Len(t, list1, 2) @@ -675,7 +676,7 @@ func TestListClustersV2Pagination(t *testing.T) { var tok struct { O int `json:"o"` } - require.NoError(t, json.Unmarshal(decoded, &tok)) + require.NoError(t, json.Unmarshal(decoded, &tok, jsonx.V1())) assert.Equal(t, 2, tok.O) path2 := "/api/v2/clusters?maxResults=2&nextToken=" + url.QueryEscape(nextToken) @@ -683,7 +684,7 @@ func TestListClustersV2Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) list2 := resp2["clusterInfoList"].([]any) assert.Len(t, list2, 2) assert.Empty(t, resp2["nextToken"]) @@ -727,7 +728,7 @@ func TestClusterCreatingToActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn = createResp["clusterArn"].(string) } else { rec := doKafkaRequest(t, h, http.MethodPost, "/v1/clusters", map[string]any{ @@ -742,7 +743,7 @@ func TestClusterCreatingToActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn = createResp["clusterArn"].(string) assert.Equal(t, kafka.ClusterStateCreating, createResp["state"]) } @@ -755,7 +756,7 @@ func TestClusterCreatingToActive(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusterInfo := descResp["clusterInfo"].(map[string]any) assert.Equal(t, kafka.ClusterStateActive, clusterInfo["state"]) }) @@ -779,7 +780,7 @@ func TestInvalidNextTokenFallsBackToStart(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list := resp["clusterInfoList"].([]any) assert.Len(t, list, 3) } @@ -803,7 +804,7 @@ func TestCreateClusterV2HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "v2-cluster", resp["clusterName"]) assert.Equal(t, "PROVISIONED", resp["clusterType"]) } diff --git a/services/kafka/handler_configurations.go b/services/kafka/handler_configurations.go index a01c8f512b..71e4ccc8b0 100644 --- a/services/kafka/handler_configurations.go +++ b/services/kafka/handler_configurations.go @@ -2,12 +2,14 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createConfigurationInput struct { @@ -54,7 +56,7 @@ func (h *Handler) handleCreateConfiguration( body []byte, ) error { var in createConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -231,7 +233,7 @@ func (h *Handler) handleUpdateConfiguration( body []byte, ) error { var in updateConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_configurations_test.go b/services/kafka/handler_configurations_test.go index 4618d610a1..b27c39e458 100644 --- a/services/kafka/handler_configurations_test.go +++ b/services/kafka/handler_configurations_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -56,7 +57,7 @@ func TestKafka_CreateAndDescribeConfiguration(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-config", resp["name"]) assert.NotEmpty(t, resp["arn"]) } @@ -102,7 +103,7 @@ func TestKafka_ListConfigurations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs, ok := resp["configurations"].([]any) require.True(t, ok) @@ -144,7 +145,7 @@ func TestKafka_DescribeAndDeleteConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) var cfgArn string if tt.useRealArn { @@ -209,7 +210,7 @@ func TestCreateConfigurationResponseIncludesStateAndRevision(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"], "arn must be present") assert.NotEmpty(t, resp["name"], "name must be present") @@ -269,7 +270,7 @@ func TestListConfigurationsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs := resp["configurations"].([]any) assert.Len(t, configs, tt.wantPage1) @@ -301,7 +302,7 @@ func TestConfigRevision_DescribeAndList(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.InDelta(t, float64(1), descResp["revision"], 0) // ListConfigurationRevisions. @@ -310,7 +311,7 @@ func TestConfigRevision_DescribeAndList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) revisions, _ := listResp["revisions"].([]any) assert.NotEmpty(t, revisions, "should have at least one revision") } @@ -330,7 +331,7 @@ func TestConfigRevision_UpdateConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, configArn, updateResp["arn"]) assert.Equal(t, "cfg-update-test", updateResp["name"]) } diff --git a/services/kafka/handler_nodes_test.go b/services/kafka/handler_nodes_test.go index 1b599896a7..442b5d11b0 100644 --- a/services/kafka/handler_nodes_test.go +++ b/services/kafka/handler_nodes_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -110,7 +111,7 @@ func TestListKafkaVersions_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["kafkaVersions"].([]any) require.True(t, ok) diff --git a/services/kafka/handler_replicators.go b/services/kafka/handler_replicators.go index 8f1687cbfd..913c41434f 100644 --- a/services/kafka/handler_replicators.go +++ b/services/kafka/handler_replicators.go @@ -2,12 +2,14 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // amazonMskClusterDTO mirrors types.AmazonMskCluster. @@ -338,7 +340,7 @@ type createReplicatorOutput struct { func (h *Handler) handleCreateReplicator(ctx context.Context, c *echo.Context, body []byte) error { var in createReplicatorInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -599,7 +601,7 @@ func (h *Handler) handleUpdateReplicationInfo( body []byte, ) error { var in updateReplicationInfoInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_replicators_test.go b/services/kafka/handler_replicators_test.go index 828b4ada3c..6be668964f 100644 --- a/services/kafka/handler_replicators_test.go +++ b/services/kafka/handler_replicators_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -48,7 +49,7 @@ func TestKafka_CreateReplicator(t *testing.T) { var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -66,7 +67,7 @@ func TestKafka_CreateReplicator(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-replicator", resp["replicatorName"]) assert.NotEmpty(t, resp["replicatorArn"]) assert.Equal(t, kafka.ReplicatorStateRunning, resp["replicatorState"]) @@ -97,7 +98,7 @@ func TestKafka_DeleteReplicator(t *testing.T) { bodyBytes, _ := json.Marshal(map[string]any{ "replicatorName": "my-replicator", "serviceExecutionRoleArn": "arn:aws:iam::000000000000:role/my-role", - }) + }, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/replication/v1/replicators", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() @@ -107,7 +108,7 @@ func TestKafka_DeleteReplicator(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) var replicatorArn string if tt.useRealArn { @@ -172,7 +173,7 @@ func TestListReplicatorsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) reps := resp["replicators"].([]any) assert.Len(t, reps, 2) @@ -186,7 +187,7 @@ func TestListReplicatorsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) reps2 := resp2["replicators"].([]any) assert.Len(t, reps2, 1) assert.Empty(t, resp2["nextToken"]) @@ -341,7 +342,7 @@ func TestReplicator_UpdateReplicationInfo(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code, updateRec.Body.String()) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, replicatorArn, updateResp["replicatorArn"]) assert.NotEmpty(t, updateResp["replicatorState"]) } diff --git a/services/kafka/handler_scram_secrets.go b/services/kafka/handler_scram_secrets.go index 94118f7c09..b60a996776 100644 --- a/services/kafka/handler_scram_secrets.go +++ b/services/kafka/handler_scram_secrets.go @@ -2,10 +2,12 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type scramSecretInput struct { @@ -24,7 +26,7 @@ func (h *Handler) handleBatchAssociateScramSecret( body []byte, ) error { var in scramSecretInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -48,7 +50,7 @@ func (h *Handler) handleBatchDisassociateScramSecret( body []byte, ) error { var in scramSecretInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_scram_secrets_test.go b/services/kafka/handler_scram_secrets_test.go index 611898c9b5..5c04a56063 100644 --- a/services/kafka/handler_scram_secrets_test.go +++ b/services/kafka/handler_scram_secrets_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestKafka_BatchAssociateScramSecret(t *testing.T) { @@ -53,14 +55,14 @@ func TestKafka_BatchAssociateScramSecret(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) encodedArn := url.PathEscape(clusterArn) var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -82,7 +84,7 @@ func TestKafka_BatchAssociateScramSecret(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "unprocessedScramSecrets") } }) @@ -117,14 +119,14 @@ func TestKafka_BatchDisassociateScramSecret(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) encodedArn := url.PathEscape(clusterArn) secretArn := "arn:aws:secretsmanager:us-east-1:000000000000:secret/my-secret" // Associate first - bodyBytes, _ := json.Marshal(map[string]any{"secretArnList": []string{secretArn}}) + bodyBytes, _ := json.Marshal(map[string]any{"secretArnList": []string{secretArn}}, jsonx.V1()) e := echo.New() req := httptest.NewRequest( http.MethodPost, @@ -139,7 +141,7 @@ func TestKafka_BatchDisassociateScramSecret(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) // Disassociate - bodyBytes2, _ := json.Marshal(map[string]any{"secretArnList": []string{secretArn}}) + bodyBytes2, _ := json.Marshal(map[string]any{"secretArnList": []string{secretArn}}, jsonx.V1()) req2 := httptest.NewRequest( http.MethodPatch, "/v1/clusters/"+encodedArn+"/scram-secrets", @@ -188,7 +190,7 @@ func TestScramSecret_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) unprocessed, _ := assocResp["unprocessedScramSecrets"].([]any) assert.Empty(t, unprocessed, "no secrets should be unprocessed") @@ -198,7 +200,7 @@ func TestScramSecret_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) secretList, _ := listResp["secretArnList"].([]any) require.Len(t, secretList, 1) assert.Equal(t, secretArn, secretList[0]) @@ -210,7 +212,7 @@ func TestScramSecret_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, disassocRec.Code) var disassocResp map[string]any - require.NoError(t, json.Unmarshal(disassocRec.Body.Bytes(), &disassocResp)) + require.NoError(t, json.Unmarshal(disassocRec.Body.Bytes(), &disassocResp, jsonx.V1())) unprocessed2, _ := disassocResp["unprocessedScramSecrets"].([]any) assert.Empty(t, unprocessed2, "no secrets should be unprocessed after disassociate") @@ -220,7 +222,7 @@ func TestScramSecret_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2, jsonx.V1())) secretList2, _ := listResp2["secretArnList"].([]any) assert.Empty(t, secretList2, "list should be empty after disassociate") } @@ -248,7 +250,7 @@ func TestScramSecret_AssociateMultiple(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) secretList, _ := listResp["secretArnList"].([]any) assert.Len(t, secretList, 3) } @@ -292,7 +294,7 @@ func TestBatchAssociateThenDisassociate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) clusterArn := cr["clusterArn"].(string) encoded := url.PathEscape(clusterArn) diff --git a/services/kafka/handler_tags.go b/services/kafka/handler_tags.go index f562abdbf4..acfd4d29ef 100644 --- a/services/kafka/handler_tags.go +++ b/services/kafka/handler_tags.go @@ -2,11 +2,13 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listTagsOutput struct { @@ -37,7 +39,7 @@ func (h *Handler) handleTagResource( body []byte, ) error { var in tagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_tags_test.go b/services/kafka/handler_tags_test.go index 50cabf4b2f..fdf6d0f9f4 100644 --- a/services/kafka/handler_tags_test.go +++ b/services/kafka/handler_tags_test.go @@ -1,7 +1,7 @@ package kafka_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestKafka_TagOperations(t *testing.T) { @@ -43,7 +45,7 @@ func TestKafka_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) encodedArn := url.PathEscape(clusterArn) tagPath := "/v1/tags/" + encodedArn @@ -98,7 +100,7 @@ func TestTag_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) assert.Equal(t, "infra", tags["team"]) @@ -119,7 +121,7 @@ func TestTag_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp2, jsonx.V1())) tags2, _ := listResp2["tags"].(map[string]any) assert.Equal(t, "test", tags2["env"]) _, hasTeam := tags2["team"] @@ -138,7 +140,7 @@ func TestTag_ListEmpty(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) assert.Empty(t, tags, "freshly created cluster should have no tags") } diff --git a/services/kafka/handler_test.go b/services/kafka/handler_test.go index 703e4b3934..1fda0d39e2 100644 --- a/services/kafka/handler_test.go +++ b/services/kafka/handler_test.go @@ -2,7 +2,7 @@ package kafka_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -39,7 +40,7 @@ func doKafkaRequest(t *testing.T, h *kafka.Handler, method, path string, body an if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -68,7 +69,7 @@ func decodeJSONResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -88,7 +89,7 @@ func doKafkaRequestJSON( var resp map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) } return resp, rec.Code diff --git a/services/kafka/handler_topics.go b/services/kafka/handler_topics.go index 3ebf1f8085..1b26b1a699 100644 --- a/services/kafka/handler_topics.go +++ b/services/kafka/handler_topics.go @@ -2,11 +2,13 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createTopicInput mirrors aws-sdk-go-v2/service/kafka's CreateTopicInput @@ -34,7 +36,7 @@ func (h *Handler) handleCreateTopic( body []byte, ) error { var in createTopicInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -275,7 +277,7 @@ func (h *Handler) handleUpdateTopic( } var in updateTopicInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_topics_test.go b/services/kafka/handler_topics_test.go index 9f8c3d9256..e26f6e14c5 100644 --- a/services/kafka/handler_topics_test.go +++ b/services/kafka/handler_topics_test.go @@ -3,7 +3,7 @@ package kafka_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestKafka_CreateTopic(t *testing.T) { @@ -57,14 +59,14 @@ func TestKafka_CreateTopic(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) encodedArn := url.PathEscape(clusterArn) var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -86,7 +88,7 @@ func TestKafka_CreateTopic(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-topic", resp["topicName"]) } }) @@ -123,14 +125,14 @@ func TestKafka_DeleteTopic(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) encodedArn := url.PathEscape(clusterArn) // Create topic e := echo.New() topicBody, _ := json.Marshal( - map[string]any{"topicName": "my-topic", "replicationFactor": 1, "partitionCount": 3}, + map[string]any{"topicName": "my-topic", "replicationFactor": 1, "partitionCount": 3}, jsonx.V1(), ) reqCreate := httptest.NewRequest( http.MethodPost, @@ -236,7 +238,7 @@ func TestListTopicsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) topics1 := resp1["topics"].([]any) assert.Len(t, topics1, 3) @@ -253,7 +255,7 @@ func TestListTopicsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) topics2 := resp2["topics"].([]any) assert.Len(t, topics2, 2) assert.Empty(t, resp2["nextToken"]) diff --git a/services/kafka/handler_vpc_connections.go b/services/kafka/handler_vpc_connections.go index 47e824e445..cb7d381f17 100644 --- a/services/kafka/handler_vpc_connections.go +++ b/services/kafka/handler_vpc_connections.go @@ -2,10 +2,12 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createVpcConnectionInput struct { @@ -37,7 +39,7 @@ func (h *Handler) handleCreateVpcConnection( body []byte, ) error { var in createVpcConnectionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, @@ -208,7 +210,7 @@ func (h *Handler) handleRejectClientVpcConnection( body []byte, ) error { var in rejectClientVpcConnectionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError( c, http.StatusBadRequest, diff --git a/services/kafka/handler_vpc_connections_test.go b/services/kafka/handler_vpc_connections_test.go index aced96d301..67add0f2c8 100644 --- a/services/kafka/handler_vpc_connections_test.go +++ b/services/kafka/handler_vpc_connections_test.go @@ -3,7 +3,7 @@ package kafka_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -58,7 +59,7 @@ func TestKafka_CreateVpcConnection(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) body := tt.body @@ -69,7 +70,7 @@ func TestKafka_CreateVpcConnection(t *testing.T) { var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -87,7 +88,7 @@ func TestKafka_CreateVpcConnection(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["vpcConnectionArn"]) assert.Equal(t, kafka.VpcConnectionStateAvailable, resp["state"]) } @@ -126,7 +127,7 @@ func TestKafka_DeleteVpcConnection(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["clusterArn"].(string) // Create VPC connection @@ -135,7 +136,7 @@ func TestKafka_DeleteVpcConnection(t *testing.T) { "targetClusterArn": clusterArn, "vpcId": "vpc-12345", "authentication": "SASL_IAM", - }) + }, jsonx.V1()) reqVpc := httptest.NewRequest(http.MethodPost, "/v1/vpc-connection", bytes.NewReader(vpcBody)) reqVpc.Header.Set("Content-Type", "application/json") recVpc := httptest.NewRecorder() @@ -144,7 +145,7 @@ func TestKafka_DeleteVpcConnection(t *testing.T) { require.Equal(t, http.StatusOK, recVpc.Code) var vpcResp map[string]any - require.NoError(t, json.Unmarshal(recVpc.Body.Bytes(), &vpcResp)) + require.NoError(t, json.Unmarshal(recVpc.Body.Bytes(), &vpcResp, jsonx.V1())) var vpcConnectionArn string if tt.useRealArn { @@ -213,7 +214,7 @@ func TestVpcConnection_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) vpcConnArn, _ := createResp["vpcConnectionArn"].(string) require.NotEmpty(t, vpcConnArn) // Real CreateVpcConnectionOutput has no targetClusterArn field (unlike @@ -230,7 +231,7 @@ func TestVpcConnection_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, vpcConnArn, descResp["vpcConnectionArn"]) assert.Equal(t, clusterArn, descResp["targetClusterArn"]) @@ -241,7 +242,7 @@ func TestVpcConnection_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) conns, _ := listResp["vpcConnections"].([]any) require.Len(t, conns, 1) @@ -275,7 +276,7 @@ func TestVpcConnection_ListClientVpcConnections(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) // Real ListClientVpcConnectionsOutput carries its array under the // distinct "clientVpcConnections" key (not "vpcConnections", which is // ListVpcConnections' key -- a different operation with a different @@ -303,7 +304,7 @@ func TestVpcConnection_RejectClientVpcConnection(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) vpcConnArn, _ := createResp["vpcConnectionArn"].(string) encodedCluster := url.PathEscape(clusterArn) diff --git a/services/kafka/iam_auth.go b/services/kafka/iam_auth.go index aa725d3bf6..f23ac86ca7 100644 --- a/services/kafka/iam_auth.go +++ b/services/kafka/iam_auth.go @@ -4,13 +4,14 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "net/url" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" "github.com/blackbirdworks/gopherstack/services/sts" ) @@ -83,7 +84,7 @@ type iamPayload struct { // parseIAMPayload decodes the JSON sent by the aws-msk-iam-auth clients (version 2020_10_22). func parseIAMPayload(raw []byte) (*iamPayload, error) { var kv map[string]string - if err := json.Unmarshal(raw, &kv); err != nil { + if err := json.Unmarshal(raw, &kv, jsonx.V1()); err != nil { return nil, errIAMMalformed } diff --git a/services/kafka/persistence.go b/services/kafka/persistence.go index 01abe28bd1..ff1e7a18d5 100644 --- a/services/kafka/persistence.go +++ b/services/kafka/persistence.go @@ -2,7 +2,7 @@ package kafka import ( "context" - "encoding/json" + "encoding/json/jsontext" "maps" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -32,13 +32,13 @@ const kafkaSnapshotVersion = 1 // ScramSecrets and ClusterPolicies persist the two fields left as plain maps // (their values are not *T, so they don't fit store.Table) alongside Tables. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - ScramSecrets map[string][]string `json:"scramSecrets"` - ClusterPolicies map[string]string `json:"clusterPolicies"` - PolicyVersions map[string]string `json:"clusterPolicyVersions,omitempty"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + ScramSecrets map[string][]string `json:"scramSecrets"` + ClusterPolicies map[string]string `json:"clusterPolicies"` + PolicyVersions map[string]string `json:"clusterPolicyVersions,omitempty"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/kafka/routes_test.go b/services/kafka/routes_test.go index 58d837a681..74a4519d5b 100644 --- a/services/kafka/routes_test.go +++ b/services/kafka/routes_test.go @@ -1,7 +1,7 @@ package kafka_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kafka" ) @@ -333,7 +334,7 @@ func TestUpdateOpsLiveUnderV1ClustersPath(t *testing.T) { tt.method, tt.suffix, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["clusterOperationArn"]) // The old (wrong) /api/v2 path must no longer resolve to the same op -- @@ -363,7 +364,7 @@ func TestRejectClientVpcConnectionRealPath(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) vpcConnArn, _ := createResp["vpcConnectionArn"].(string) require.NotEmpty(t, vpcConnArn) @@ -399,7 +400,7 @@ func TestListVpcConnectionsPluralPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) conns, _ := resp["vpcConnections"].([]any) assert.Len(t, conns, 1) @@ -425,7 +426,7 @@ func TestGetCompatibleKafkaVersionsTopLevelPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, _ := resp["compatibleKafkaVersions"].([]any) assert.NotEmpty(t, versions) @@ -477,7 +478,7 @@ func TestUpdateReplicationInfoStripsSuffix(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, replicatorArn, resp["replicatorArn"], "the resolved replicator ARN must not include the /replication-info suffix") } diff --git a/services/kafka/scram_sync.go b/services/kafka/scram_sync.go index 5f13e55f9e..c23cd2e1c7 100644 --- a/services/kafka/scram_sync.go +++ b/services/kafka/scram_sync.go @@ -5,7 +5,7 @@ import ( "crypto/pbkdf2" "crypto/rand" "crypto/sha512" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +14,7 @@ import ( "github.com/twmb/franz-go/pkg/kgo" "github.com/twmb/franz-go/pkg/kmsg" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -249,8 +250,13 @@ func ParseScramSecret(secretString string) (ScramCredential, error) { Password *string `json:"password"` } - if err := json.Unmarshal([]byte(secretString), &v); err != nil || v.Username == nil || v.Password == nil || - *v.Username == "" || *v.Password == "" { + if err := json.Unmarshal( + []byte(secretString), + &v, + jsonx.V1(), + ); err != nil || v.Username == nil || v.Password == nil || + *v.Username == "" || + *v.Password == "" { return ScramCredential{}, errScramSecretShape } diff --git a/services/kafkaconnect/handler.go b/services/kafkaconnect/handler.go index 4df86a8156..72b7305bff 100644 --- a/services/kafkaconnect/handler.go +++ b/services/kafkaconnect/handler.go @@ -1,7 +1,7 @@ package kafkaconnect import ( - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -225,7 +226,7 @@ func decodeBody[T any](body []byte, out *T) error { return nil } - return json.Unmarshal(body, out) + return json.Unmarshal(body, out, jsonx.V1()) } // writeJSON writes a 200 JSON response. diff --git a/services/kafkaconnect/multi_region_test.go b/services/kafkaconnect/multi_region_test.go index d8dd0439c2..c36be1f356 100644 --- a/services/kafkaconnect/multi_region_test.go +++ b/services/kafkaconnect/multi_region_test.go @@ -2,7 +2,8 @@ package kafkaconnect_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/kafkaconnect" ) @@ -185,8 +187,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/kafkaconnect/persistence.go b/services/kafkaconnect/persistence.go index 2554cc270a..3f05a3021c 100644 --- a/services/kafkaconnect/persistence.go +++ b/services/kafkaconnect/persistence.go @@ -2,7 +2,7 @@ package kafkaconnect import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -30,8 +30,8 @@ const kafkaConnectSnapshotVersion = 1 // workerConfigurations, connectorOperations -- see store_setup.go), produced // by b.registry.SnapshotAll(). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes backend state to JSON. diff --git a/services/kafkaconnect/worker_engine.go b/services/kafkaconnect/worker_engine.go index 860bb50c0b..21af36e50d 100644 --- a/services/kafkaconnect/worker_engine.go +++ b/services/kafkaconnect/worker_engine.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -22,6 +22,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" ) @@ -620,7 +621,7 @@ func (e *workerEngine) call(ctx context.Context, method, url string, body any) ( var rdr io.Reader if body != nil { - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) if err != nil { return nil, 0, fmt.Errorf("encode request: %w", err) } @@ -670,7 +671,7 @@ func (e *workerEngine) readStatus(ctx context.Context, base, name string) (strin } var st connectStatus - if err = json.Unmarshal(data, &st); err != nil { + if err = json.Unmarshal(data, &st, jsonx.V1()); err != nil { return "", "", fmt.Errorf("decode status: %w", err) } diff --git a/services/kinesis/account_settings_test.go b/services/kinesis/account_settings_test.go index 5c214b55ad..410186cb7c 100644 --- a/services/kinesis/account_settings_test.go +++ b/services/kinesis/account_settings_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -104,7 +105,7 @@ func TestDescribeLimits_DynamicOpenShardCount(t *testing.T) { ShardLimit int `json:"ShardLimit"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before, jsonx.V1())) assert.Equal(t, 500, before.ShardLimit) // Create a stream with 3 shards. @@ -117,7 +118,7 @@ func TestDescribeLimits_DynamicOpenShardCount(t *testing.T) { OpenShardCount int `json:"OpenShardCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after, jsonx.V1())) assert.Equal(t, before.OpenShardCount+3, after.OpenShardCount) } @@ -142,7 +143,7 @@ func TestDescribeLimits(t *testing.T) { OnDemandStreamCountLimit int `json:"OnDemandStreamCountLimit"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 500, resp.ShardLimit) assert.Equal(t, 0, resp.OpenShardCount) assert.Equal(t, 0, resp.OnDemandStreamCount) @@ -194,7 +195,7 @@ func TestDescribeAccountSettings(t *testing.T) { } `json:"MinimumThroughputBillingCommitment"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DISABLED", resp.MinimumThroughputBillingCommitment.Status) assert.Zero(t, resp.MinimumThroughputBillingCommitment.StartedAt) assert.Zero(t, resp.MinimumThroughputBillingCommitment.EndedAt) @@ -256,7 +257,7 @@ func TestHandleDescribeLimits(t *testing.T) { OpenShardCount int `json:"OpenShardCount"` ShardLimit int `json:"ShardLimit"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 0, resp.OpenShardCount) assert.Equal(t, 500, resp.ShardLimit) } diff --git a/services/kinesis/cbor_test.go b/services/kinesis/cbor_test.go index b9b879bf7a..142758295d 100644 --- a/services/kinesis/cbor_test.go +++ b/services/kinesis/cbor_test.go @@ -3,7 +3,7 @@ package kinesis_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -191,7 +192,7 @@ func TestKinesisCBOR_GetRecords(t *testing.T) { "ShardId": "shardId-000000000000", "ShardIteratorType": "TRIM_HORIZON", } - iterBytes, _ := json.Marshal(iterBody) + iterBytes, _ := json.Marshal(iterBody, jsonx.V1()) iterReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(iterBytes)) iterReq.Header.Set("Content-Type", "application/x-amz-json-1.1") iterReq.Header.Set("X-Amz-Target", kinesisPrefix+"GetShardIterator") @@ -203,7 +204,7 @@ func TestKinesisCBOR_GetRecords(t *testing.T) { require.Equal(t, http.StatusOK, iterRec.Code) var iterResp map[string]any - require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp, jsonx.V1())) shardIter, ok := iterResp["ShardIterator"].(string) require.True(t, ok, "must have ShardIterator") @@ -253,7 +254,7 @@ func TestKinesisCBOR_DataRoundTrip(t *testing.T) { "Data": b64Data, "PartitionKey": "pk1", } - b, _ := json.Marshal(jsonPut) + b, _ := json.Marshal(jsonPut, jsonx.V1()) putReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(b)) putReq.Header.Set("Content-Type", "application/x-amz-json-1.1") putReq.Header.Set("X-Amz-Target", kinesisPrefix+"PutRecord") @@ -276,7 +277,7 @@ func TestKinesisCBOR_DataRoundTrip(t *testing.T) { iterC := e.NewContext(iterReq, iterRec) require.NoError(t, h.Handler()(iterC)) var iterResp map[string]any - require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp, jsonx.V1())) shardIter := iterResp["ShardIterator"].(string) // Read via CBOR. @@ -376,7 +377,7 @@ func TestKinesisCBOR_JSONAndCBORCoexist(t *testing.T) { "Data": b64, "PartitionKey": "pk2", } - rawJSON, _ := json.Marshal(putJSON) + rawJSON, _ := json.Marshal(putJSON, jsonx.V1()) req2 := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(rawJSON)) req2.Header.Set("Content-Type", "application/x-amz-json-1.1") req2.Header.Set("X-Amz-Target", kinesisPrefix+"PutRecord") @@ -398,7 +399,7 @@ func TestKinesisCBOR_JSONAndCBORCoexist(t *testing.T) { iterC := e.NewContext(iterReq, iterRec) require.NoError(t, h.Handler()(iterC)) var iterResp map[string]any - require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp, jsonx.V1())) shardIter := iterResp["ShardIterator"].(string) getReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(mustMarshalBytes(t, map[string]any{ @@ -413,7 +414,7 @@ func TestKinesisCBOR_JSONAndCBORCoexist(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) recs := getResp["Records"].([]any) assert.GreaterOrEqual(t, len(recs), 2, "both records must be visible via JSON") }) @@ -421,7 +422,7 @@ func TestKinesisCBOR_JSONAndCBORCoexist(t *testing.T) { func mustMarshalBytes(t *testing.T, v any) []byte { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return b diff --git a/services/kinesis/channel_delivery.go b/services/kinesis/channel_delivery.go index 559a10e044..6244cc75f6 100644 --- a/services/kinesis/channel_delivery.go +++ b/services/kinesis/channel_delivery.go @@ -4,7 +4,8 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "regexp" @@ -18,6 +19,7 @@ import ( "github.com/google/uuid" "github.com/klauspost/compress/zstd" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -159,7 +161,7 @@ func validateChannelRecord(formatType string, data []byte) string { return "record is not valid UTF-8 for RecordFormatType STRING" } case recordFormatTypeJSON: - if !json.Valid(data) { + if !jsontext.Value(data).IsValid(jsonx.V1()) { return "record is not valid JSON for RecordFormatType JSON" } } @@ -399,7 +401,7 @@ func (b *InMemoryBackend) writeChannelDeadLetterQueue( ShardID string `json:"shardID"` SequenceNumber string `json:"sequenceNumber"` ErrorMessage string `json:"errorMessage"` - }{f.streamARN, f.shardID, f.sequenceNumber, f.errorMessage}) + }{f.streamARN, f.shardID, f.sequenceNumber, f.errorMessage}, jsonx.V1()) if err != nil { continue } diff --git a/services/kinesis/consumer_wire_shape_test.go b/services/kinesis/consumer_wire_shape_test.go index ee1168b29a..65edd5a578 100644 --- a/services/kinesis/consumer_wire_shape_test.go +++ b/services/kinesis/consumer_wire_shape_test.go @@ -1,12 +1,14 @@ package kinesis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestConsumerWireShape_StreamARN locks in that StreamARN only appears on @@ -36,7 +38,7 @@ func TestConsumerWireShape_StreamARN(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(setupRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(setupRec.Body.Bytes(), &descResp, jsonx.V1())) streamARN := descResp.StreamDescription.StreamARN require.NotEmpty(t, streamARN) @@ -82,7 +84,7 @@ func TestConsumerWireShape_StreamARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) consumerObj := raw[tc.wrapperKey] var obj map[string]any diff --git a/services/kinesis/consumers_test.go b/services/kinesis/consumers_test.go index 1c9f2e296c..7264950757 100644 --- a/services/kinesis/consumers_test.go +++ b/services/kinesis/consumers_test.go @@ -3,7 +3,7 @@ package kinesis_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -34,7 +35,7 @@ func subscribeAndCollect(t *testing.T, h *kinesis.Handler, consumerARN, shardID "StartingPosition": map[string]any{ "Type": "TRIM_HORIZON", }, - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) @@ -62,7 +63,7 @@ func getFirstShardID(t *testing.T, h *kinesis.Handler, streamName string) string } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.StreamDescription.Shards) return resp.StreamDescription.Shards[0].ShardID @@ -82,7 +83,7 @@ func registerConsumerAndGetARN(t *testing.T, h *kinesis.Handler, streamARN, cons ConsumerARN string `json:"ConsumerARN"` } `json:"Consumer"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp.Consumer.ConsumerARN } @@ -220,7 +221,7 @@ func TestRegisterStreamConsumer_InvalidName(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) }) } @@ -349,7 +350,7 @@ func TestListStreamConsumers_MaxResultsPagination(t *testing.T) { ConsumerName string `json:"ConsumerName"` } `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Consumers, 2) assert.NotEmpty(t, page1.NextToken) @@ -367,7 +368,7 @@ func TestListStreamConsumers_MaxResultsPagination(t *testing.T) { ConsumerName string `json:"ConsumerName"` } `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Consumers, 2) assert.NotEmpty(t, page2.NextToken) @@ -385,7 +386,7 @@ func TestListStreamConsumers_MaxResultsPagination(t *testing.T) { ConsumerName string `json:"ConsumerName"` } `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page3)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page3, jsonx.V1())) assert.Len(t, page3.Consumers, 1) assert.Empty(t, page3.NextToken) @@ -430,7 +431,7 @@ func TestListStreamConsumers_NoMaxResults_ReturnsAll(t *testing.T) { ConsumerName string `json:"ConsumerName"` } `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Consumers, 3) assert.Empty(t, resp.NextToken) } @@ -741,7 +742,7 @@ func createStreamAndGetARN(t *testing.T, h *kinesis.Handler, streamName string) StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.NotEmpty(t, descResp.StreamDescription.StreamARN) return descResp.StreamDescription.StreamARN @@ -785,7 +786,7 @@ func TestConsumerLifecycle(t *testing.T) { // only types.ConsumerDescription (returned by DescribeStreamConsumer) // does. A raw map catches a StreamARN key resurfacing on this response. var regRespRaw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®RespRaw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®RespRaw, jsonx.V1())) consumerRaw, ok := regRespRaw["Consumer"].(map[string]any) require.True(t, ok) _, hasStreamARN := consumerRaw["StreamARN"] @@ -799,7 +800,7 @@ func TestConsumerLifecycle(t *testing.T) { ConsumerStatus string `json:"ConsumerStatus"` } `json:"Consumer"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) assert.Equal(t, tt.consumerName, regResp.Consumer.ConsumerName) assert.Equal(t, tt.expectedStatus, regResp.Consumer.ConsumerStatus) assert.NotEmpty(t, regResp.Consumer.ConsumerARN) @@ -819,7 +820,7 @@ func TestConsumerLifecycle(t *testing.T) { ConsumerARN string `json:"ConsumerARN"` } `json:"ConsumerDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.consumerName, descResp.ConsumerDescription.ConsumerName) assert.Equal(t, consumerARN, descResp.ConsumerDescription.ConsumerARN) @@ -841,7 +842,7 @@ func TestConsumerLifecycle(t *testing.T) { ConsumerName string `json:"ConsumerName"` } `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Consumers, 1) assert.Equal(t, tt.consumerName, listResp.Consumers[0].ConsumerName) @@ -860,7 +861,7 @@ func TestConsumerLifecycle(t *testing.T) { var listResp2 struct { Consumers []any `json:"Consumers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp2, jsonx.V1())) assert.Empty(t, listResp2.Consumers) }) } @@ -921,7 +922,7 @@ func TestConsumerErrors(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) @@ -951,7 +952,7 @@ func TestRegisterConsumerDuplicate(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceInUseException", errResp.Type) } @@ -975,7 +976,7 @@ func TestSubscribeToShard(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -998,7 +999,7 @@ func TestSubscribeToShard(t *testing.T) { ConsumerARN string `json:"ConsumerARN"` } `json:"Consumer"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) consumerARN := regResp.Consumer.ConsumerARN // SubscribeToShard. @@ -1009,7 +1010,7 @@ func TestSubscribeToShard(t *testing.T) { "StartingPosition": map[string]any{ "Type": "TRIM_HORIZON", }, - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) diff --git a/services/kinesis/errors_test.go b/services/kinesis/errors_test.go index 4e597f0649..8eb75acd3c 100644 --- a/services/kinesis/errors_test.go +++ b/services/kinesis/errors_test.go @@ -1,12 +1,14 @@ package kinesis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestErrValidation verifies ErrValidation is returned for invalid stream name. @@ -39,7 +41,7 @@ func TestErrValidation(t *testing.T) { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp.Type) }) } diff --git a/services/kinesis/handler.go b/services/kinesis/handler.go index 573b7493ef..459b8de3f2 100644 --- a/services/kinesis/handler.go +++ b/services/kinesis/handler.go @@ -2,7 +2,7 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -269,7 +270,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var req extractStreamNameInput - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "" } @@ -378,7 +379,7 @@ func (h *Handler) kinesisRoute(ctx context.Context, r *http.Request, action stri return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // handleError writes a Kinesis error response using the standard error details mapping. diff --git a/services/kinesis/handler_account_settings.go b/services/kinesis/handler_account_settings.go index 0e40ec97bd..96960de77a 100644 --- a/services/kinesis/handler_account_settings.go +++ b/services/kinesis/handler_account_settings.go @@ -2,10 +2,11 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // describeLimitsOutput mirrors kinesis@v1.46.4 DescribeLimitsOutput @@ -106,7 +107,7 @@ func (h *Handler) handleUpdateAccountSettings( body []byte, ) (any, error) { var req jsonUpdateAccountSettingsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -137,7 +138,7 @@ func (h *Handler) handleUpdateMaxRecordSize( body []byte, ) (any, error) { var req jsonUpdateMaxRecordSizeReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_channels.go b/services/kinesis/handler_channels.go index 2640be03ad..65d8f9f085 100644 --- a/services/kinesis/handler_channels.go +++ b/services/kinesis/handler_channels.go @@ -2,10 +2,11 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- wire types (field names verified against kinesis@v1.53.0 serializers.go/deserializers.go) --- @@ -419,7 +420,7 @@ func channelSummaryToJSON(c *Channel) jsonChannelSummary { func (h *Handler) handleCreateChannel(ctx context.Context, _ *http.Request, body []byte) (any, error) { var req jsonCreateChannelReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -447,7 +448,7 @@ func (h *Handler) handleCreateChannel(ctx context.Context, _ *http.Request, body func (h *Handler) handleDeleteChannel(ctx context.Context, _ *http.Request, body []byte) (any, error) { var req jsonChannelARNReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -460,7 +461,7 @@ func (h *Handler) handleDeleteChannel(ctx context.Context, _ *http.Request, body func (h *Handler) handleDescribeChannel(ctx context.Context, _ *http.Request, body []byte) (any, error) { var req jsonChannelARNReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -483,7 +484,7 @@ func channelStreamFiltersFromJSON(filters []jsonStreamFilter) []ChannelStreamFil func (h *Handler) handleListChannels(ctx context.Context, _ *http.Request, body []byte) (any, error) { var req jsonListChannelsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -511,7 +512,7 @@ func (h *Handler) handleListChannels(ctx context.Context, _ *http.Request, body func (h *Handler) handleUpdateChannel(ctx context.Context, _ *http.Request, body []byte) (any, error) { var req jsonUpdateChannelReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_consumers.go b/services/kinesis/handler_consumers.go index 302c04e6f3..0976f694bd 100644 --- a/services/kinesis/handler_consumers.go +++ b/services/kinesis/handler_consumers.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "hash/crc32" "math" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -124,7 +125,7 @@ func (h *Handler) handleRegisterStreamConsumer( body []byte, ) (any, error) { var req jsonRegisterStreamConsumerReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -146,7 +147,7 @@ func (h *Handler) handleDescribeStreamConsumer( body []byte, ) (any, error) { var req jsonDescribeStreamConsumerReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -168,7 +169,7 @@ func (h *Handler) handleListStreamConsumers( body []byte, ) (any, error) { var req jsonListStreamConsumersReq - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) out, err := h.Backend.ListStreamConsumers(ctx, &ListStreamConsumersInput{ StreamARN: req.StreamARN, @@ -193,7 +194,7 @@ func (h *Handler) handleDeregisterStreamConsumer( body []byte, ) (any, error) { var req jsonDeregisterStreamConsumerReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -351,7 +352,7 @@ func (h *Handler) parseSubscribeToShardRequest( return req, StartingPosition{}, false, h.handleError(ctx, c, "SubscribeToShard", readErr) } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return req, StartingPosition{}, false, h.handleError(ctx, c, "SubscribeToShard", ErrInvalidArgument) } @@ -552,7 +553,7 @@ func (h *Handler) pollSubscribeToShardTick( Records: records, ContinuationSequenceNumber: out.Event.ContinuationSequenceNumber, MillisBehindLatest: out.Event.MillisBehindLatest, - }) + }, jsonx.V1()) if marshalErr != nil { return nil, false, marshalErr } diff --git a/services/kinesis/handler_monitoring.go b/services/kinesis/handler_monitoring.go index 73159c0dad..7f9c7a705b 100644 --- a/services/kinesis/handler_monitoring.go +++ b/services/kinesis/handler_monitoring.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonEnhancedMonitoringReq struct { @@ -25,7 +27,7 @@ func (h *Handler) handleEnableEnhancedMonitoring( body []byte, ) (any, error) { var req jsonEnhancedMonitoringReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -53,7 +55,7 @@ func (h *Handler) handleDisableEnhancedMonitoring( body []byte, ) (any, error) { var req jsonEnhancedMonitoringReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_records.go b/services/kinesis/handler_records.go index 6da6d760c0..fd408d7121 100644 --- a/services/kinesis/handler_records.go +++ b/services/kinesis/handler_records.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonPutRecordReq struct { @@ -91,7 +93,7 @@ func (h *Handler) handlePutRecord( body []byte, ) (any, error) { var req jsonPutRecordReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -128,7 +130,7 @@ func (h *Handler) handlePutRecords( body []byte, ) (any, error) { var req jsonPutRecordsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -168,7 +170,7 @@ func (h *Handler) handleGetRecords( body []byte, ) (any, error) { var req jsonGetRecordsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_resharding.go b/services/kinesis/handler_resharding.go index f7a22fe398..dec2a2f878 100644 --- a/services/kinesis/handler_resharding.go +++ b/services/kinesis/handler_resharding.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonMergeShardsReq struct { @@ -40,7 +42,7 @@ func (h *Handler) handleMergeShards( body []byte, ) (any, error) { var req jsonMergeShardsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -62,7 +64,7 @@ func (h *Handler) handleSplitShard( body []byte, ) (any, error) { var req jsonSplitShardReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -84,7 +86,7 @@ func (h *Handler) handleUpdateShardCount( body []byte, ) (any, error) { var req jsonUpdateShardCountReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_resource_policy.go b/services/kinesis/handler_resource_policy.go index 378f0cfdf6..85e7aa011f 100644 --- a/services/kinesis/handler_resource_policy.go +++ b/services/kinesis/handler_resource_policy.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonResourceARNReq struct { @@ -25,7 +27,7 @@ func (h *Handler) handlePutResourcePolicy( body []byte, ) (any, error) { var req jsonPutResourcePolicyReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -45,7 +47,7 @@ func (h *Handler) handleGetResourcePolicy( body []byte, ) (any, error) { var req jsonResourceARNReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -65,7 +67,7 @@ func (h *Handler) handleDeleteResourcePolicy( body []byte, ) (any, error) { var req jsonResourceARNReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_shard_iterators.go b/services/kinesis/handler_shard_iterators.go index 7199843a64..0790a77d3b 100644 --- a/services/kinesis/handler_shard_iterators.go +++ b/services/kinesis/handler_shard_iterators.go @@ -2,9 +2,11 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonGetShardIteratorReq struct { @@ -27,7 +29,7 @@ func (h *Handler) handleGetShardIterator( body []byte, ) (any, error) { var req jsonGetShardIteratorReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_shards.go b/services/kinesis/handler_shards.go index 8d589ee07e..abbef6a97b 100644 --- a/services/kinesis/handler_shards.go +++ b/services/kinesis/handler_shards.go @@ -2,9 +2,11 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonShardFilter struct { @@ -67,7 +69,7 @@ func (h *Handler) handleListShards( body []byte, ) (any, error) { var req jsonListShardsReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_stream_encryption.go b/services/kinesis/handler_stream_encryption.go index a2fb0977f1..86445110f3 100644 --- a/services/kinesis/handler_stream_encryption.go +++ b/services/kinesis/handler_stream_encryption.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonEncryptionReq struct { @@ -19,7 +21,7 @@ func (h *Handler) handleStartStreamEncryption( body []byte, ) (any, error) { var req jsonEncryptionReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -41,7 +43,7 @@ func (h *Handler) handleStopStreamEncryption( body []byte, ) (any, error) { var req jsonEncryptionReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_stream_modes.go b/services/kinesis/handler_stream_modes.go index a84e903a35..4e46606446 100644 --- a/services/kinesis/handler_stream_modes.go +++ b/services/kinesis/handler_stream_modes.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // jsonUpdateStreamWarmThroughputReq mirrors UpdateStreamWarmThroughputInput @@ -35,7 +37,7 @@ func (h *Handler) handleUpdateStreamMode(ctx context.Context, _ *http.Request, b WarmThroughputMiBps *int `json:"WarmThroughputMiBps"` StreamARN string `json:"StreamARN"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } if req.StreamModeDetails == nil { @@ -55,7 +57,7 @@ func (h *Handler) handleUpdateStreamWarmThroughput( body []byte, ) (any, error) { var req jsonUpdateStreamWarmThroughputReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_stream_retention.go b/services/kinesis/handler_stream_retention.go index fb358338a9..a897b65d22 100644 --- a/services/kinesis/handler_stream_retention.go +++ b/services/kinesis/handler_stream_retention.go @@ -2,8 +2,10 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonRetentionPeriodReq struct { @@ -18,7 +20,7 @@ func (h *Handler) handleIncreaseStreamRetentionPeriod( body []byte, ) (any, error) { var req jsonRetentionPeriodReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -40,7 +42,7 @@ func (h *Handler) handleDecreaseStreamRetentionPeriod( body []byte, ) (any, error) { var req jsonRetentionPeriodReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_streams.go b/services/kinesis/handler_streams.go index 903e7f3552..4f41b2814b 100644 --- a/services/kinesis/handler_streams.go +++ b/services/kinesis/handler_streams.go @@ -2,10 +2,11 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -109,7 +110,7 @@ func (h *Handler) handleCreateStream( body []byte, ) (any, error) { var req jsonCreateStreamReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -181,7 +182,7 @@ func (h *Handler) handleDeleteStream( body []byte, ) (any, error) { var req jsonDeleteStreamReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -224,7 +225,7 @@ func (h *Handler) handleDescribeStream( body []byte, ) (any, error) { var req jsonDescribeStreamReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -282,7 +283,7 @@ func (h *Handler) handleDescribeStreamSummary( body []byte, ) (any, error) { var req jsonDescribeStreamReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -343,7 +344,7 @@ func (h *Handler) handleListStreams( body []byte, ) (any, error) { var req jsonListStreamsReq - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) out, err := h.Backend.ListStreams(ctx, &ListStreamsInput{ Limit: req.Limit, diff --git a/services/kinesis/handler_tags.go b/services/kinesis/handler_tags.go index 009b54534e..e2b38c0c62 100644 --- a/services/kinesis/handler_tags.go +++ b/services/kinesis/handler_tags.go @@ -3,12 +3,13 @@ package kinesis import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "slices" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -77,7 +78,7 @@ func (h *Handler) handleAddTagsToStream( body []byte, ) (any, error) { var req handleAddTagsToStreamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -124,7 +125,7 @@ func (h *Handler) handleRemoveTagsFromStream( body []byte, ) (any, error) { var req handleRemoveTagsFromStreamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -151,7 +152,7 @@ func (h *Handler) handleListTagsForStream( body []byte, ) (any, error) { var req listTagsForStreamReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -205,7 +206,7 @@ func (h *Handler) handleTagResource( body []byte, ) (any, error) { var req jsonTagResourceReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -243,7 +244,7 @@ func (h *Handler) handleUntagResource( body []byte, ) (any, error) { var req jsonUntagResourceReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } @@ -263,7 +264,7 @@ func (h *Handler) handleListTagsForResource( body []byte, ) (any, error) { var req jsonResourceARNReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrInvalidArgument } diff --git a/services/kinesis/handler_test.go b/services/kinesis/handler_test.go index 5c1334b8ce..639c6bd532 100644 --- a/services/kinesis/handler_test.go +++ b/services/kinesis/handler_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/md5" - "encoding/json" + "encoding/json/v2" "math/big" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -57,7 +58,7 @@ func doRequest(t *testing.T, h *kinesis.Handler, action string, body any) *httpt var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -159,7 +160,7 @@ func doParityRequest(t *testing.T, h *kinesis.Handler, action string, body any) var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -255,7 +256,7 @@ func TestKinesisHandler_ErrorResponses(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) @@ -312,7 +313,7 @@ func TestExtractResource(t *testing.T) { e := echo.New() // Valid body - body, _ := json.Marshal(map[string]string{"StreamName": "my-stream"}) + body, _ := json.Marshal(map[string]string{"StreamName": "my-stream"}, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") c := e.NewContext(req, httptest.NewRecorder()) diff --git a/services/kinesis/inspect_test.go b/services/kinesis/inspect_test.go index 7af71a8125..88eff6fbe3 100644 --- a/services/kinesis/inspect_test.go +++ b/services/kinesis/inspect_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -79,7 +80,7 @@ func (e *inspectEnv) get(t *testing.T, path string, out any) int { require.NoError(t, err) if out != nil && resp.StatusCode == http.StatusOK { - require.NoError(t, json.Unmarshal(body, out)) + require.NoError(t, json.Unmarshal(body, out, jsonx.V1())) } return resp.StatusCode diff --git a/services/kinesis/monitoring_test.go b/services/kinesis/monitoring_test.go index 3526c21a7e..527209fb73 100644 --- a/services/kinesis/monitoring_test.go +++ b/services/kinesis/monitoring_test.go @@ -1,12 +1,14 @@ package kinesis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEnhancedMonitoring(t *testing.T) { @@ -33,7 +35,7 @@ func TestEnhancedMonitoring(t *testing.T) { CurrentShardLevelMetrics []string `json:"CurrentShardLevelMetrics"` DesiredShardLevelMetrics []string `json:"DesiredShardLevelMetrics"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &enableResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &enableResp, jsonx.V1())) assert.Equal(t, streamName, enableResp.StreamName) assert.Empty(t, enableResp.CurrentShardLevelMetrics) assert.ElementsMatch(t, []string{"IncomingBytes", "OutgoingRecords"}, enableResp.DesiredShardLevelMetrics) @@ -50,7 +52,7 @@ func TestEnhancedMonitoring(t *testing.T) { CurrentShardLevelMetrics []string `json:"CurrentShardLevelMetrics"` DesiredShardLevelMetrics []string `json:"DesiredShardLevelMetrics"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &disableResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &disableResp, jsonx.V1())) assert.ElementsMatch(t, []string{"IncomingBytes", "OutgoingRecords"}, disableResp.CurrentShardLevelMetrics) assert.Equal(t, []string{"OutgoingRecords"}, disableResp.DesiredShardLevelMetrics) } @@ -91,7 +93,7 @@ func TestEnhancedMonitoring_StreamARN(t *testing.T) { var resp struct { StreamARN string `json:"StreamARN"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.StreamARN) assert.Contains(t, resp.StreamARN, streamName) }) diff --git a/services/kinesis/page_token.go b/services/kinesis/page_token.go index 3c38066e01..ef18ce197b 100644 --- a/services/kinesis/page_token.go +++ b/services/kinesis/page_token.go @@ -2,8 +2,10 @@ package kinesis import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // pageTokenLifetime is the documented lifetime of ListShards and @@ -34,7 +36,7 @@ func encodePageToken(kind string, stream *Stream, cursor string, now time.Time) Cursor: cursor, IssuedAt: now.UnixNano(), Created: stream.CreatedAt.UnixNano(), - }) + }, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(raw) } @@ -48,7 +50,7 @@ func decodePageToken(kind, token string, now time.Time) (pageToken, error) { } var pt pageToken - if err = json.Unmarshal(raw, &pt); err != nil || pt.Kind != kind || pt.Stream == "" { + if err = json.Unmarshal(raw, &pt, jsonx.V1()); err != nil || pt.Kind != kind || pt.Stream == "" { return pageToken{}, ErrInvalidArgument } diff --git a/services/kinesis/persistence.go b/services/kinesis/persistence.go index d1466392c5..11ef9137b7 100644 --- a/services/kinesis/persistence.go +++ b/services/kinesis/persistence.go @@ -2,11 +2,13 @@ package kinesis import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -38,7 +40,7 @@ const kinesisSnapshotVersion = 1 // persisted the same way it always was. type backendSnapshot struct { MinimumThroughputBillingCommitment MinimumThroughputBillingCommitmentOutput `json:"minimumThroughputBillingCommitment"` - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourcePolicies map[string]map[string]string `json:"resourcePolicies,omitempty"` ChannelBuffers map[string]channelBufferSnapshot `json:"channelBuffers,omitempty"` AccountID string `json:"accountID"` @@ -122,7 +124,7 @@ func (stream *Stream) MarshalJSON() ([]byte, error) { stream.mu.RLock("Snapshot") defer stream.mu.RUnlock() - return json.Marshal((*streamAlias)(stream)) + return json.Marshal((*streamAlias)(stream), jsonx.V1()) } // Snapshot serialises the backend state to JSON. @@ -150,7 +152,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { MinimumThroughputBillingCommitment: b.minimumThroughputBillingCommitment, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "kinesis: snapshot serialization failed", "error", err) diff --git a/services/kinesis/persistence_roundtrip_test.go b/services/kinesis/persistence_roundtrip_test.go index 42ba51fd65..9a3315c401 100644 --- a/services/kinesis/persistence_roundtrip_test.go +++ b/services/kinesis/persistence_roundtrip_test.go @@ -2,13 +2,16 @@ package kinesis //nolint:testpackage // reuses ctxRegion (isolation_test.go) for import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "testing/synctest" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestInMemoryBackend_FullStateSnapshotRestoreRoundTrip exercises every kind @@ -185,12 +188,12 @@ func TestInMemoryBackend_Restore_IncompatibleVersion_ResetsEmpty(t *testing.T) { // Corrupt just the version field to simulate a snapshot from an // incompatible (older/newer) build. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &raw)) - bumped, err := json.Marshal(kinesisSnapshotVersion + 1) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) + bumped, err := json.Marshal(kinesisSnapshotVersion+1, jsonx.V1()) require.NoError(t, err) raw["version"] = bumped - mutated, err := json.Marshal(raw) + mutated, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) fresh := NewInMemoryBackendWithConfig("000000000000", "us-east-1") diff --git a/services/kinesis/persistence_test.go b/services/kinesis/persistence_test.go index 138f736761..7ae48b5f0f 100644 --- a/services/kinesis/persistence_test.go +++ b/services/kinesis/persistence_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -318,7 +319,7 @@ func TestTags_SurvivePersistenceRestore(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) found := false for _, kv := range resp.Tags { diff --git a/services/kinesis/records_get_test.go b/services/kinesis/records_get_test.go index 596fb9a82c..e0e93e0352 100644 --- a/services/kinesis/records_get_test.go +++ b/services/kinesis/records_get_test.go @@ -3,7 +3,7 @@ package kinesis_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -48,7 +49,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.NotEmpty(t, descResp.StreamDescription.Shards) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -75,7 +76,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { var putResp struct { FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assert.Equal(t, 0, putResp.FailedRecordCount) rec = doRequest(t, h, "GetShardIterator", map[string]any{ @@ -88,7 +89,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp, jsonx.V1())) require.NotEmpty(t, iterResp.ShardIterator) rec = doRequest(t, h, "GetRecords", map[string]any{ @@ -101,7 +102,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { PartitionKey string `json:"PartitionKey"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Len( t, getResp.Records, @@ -134,7 +135,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.NotEmpty(t, descResp.StreamDescription.Shards) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -166,7 +167,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp, jsonx.V1())) require.NotEmpty(t, iterResp.ShardIterator) rec = doRequest(t, h, "GetRecords", map[string]any{ @@ -179,7 +180,7 @@ func TestGetRecords_SizeCap_ExcludesPartitionKey(t *testing.T) { Data []byte `json:"Data"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.LessOrEqual(t, len(getResp.Records), 2, "data cap (10 MiB) should stop GetRecords before the 3rd 4-MiB record") }, @@ -367,7 +368,7 @@ func TestPutAndGetRecords(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.NotEmpty(t, descResp.StreamDescription.Shards) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -383,7 +384,7 @@ func TestPutAndGetRecords(t *testing.T) { ShardID string `json:"ShardId"` SequenceNumber string `json:"SequenceNumber"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assert.NotEmpty(t, putResp.ShardID) assert.NotEmpty(t, putResp.SequenceNumber) firstSeq := putResp.SequenceNumber @@ -405,7 +406,7 @@ func TestPutAndGetRecords(t *testing.T) { } `json:"Records"` FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp, jsonx.V1())) assert.Equal(t, 0, batchResp.FailedRecordCount) assert.Len(t, batchResp.Records, 2) @@ -420,7 +421,7 @@ func TestPutAndGetRecords(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp, jsonx.V1())) assert.NotEmpty(t, iterResp.ShardIterator) // GetRecords @@ -438,7 +439,7 @@ func TestPutAndGetRecords(t *testing.T) { Data []byte `json:"Data"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Len(t, getResp.Records, 3) // 1 + 2 batch assert.NotEmpty(t, getResp.NextShardIterator) @@ -454,7 +455,7 @@ func TestPutAndGetRecords(t *testing.T) { var atSeqIterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &atSeqIterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &atSeqIterResp, jsonx.V1())) rec = doRequest(t, h, "GetRecords", map[string]any{ "ShardIterator": atSeqIterResp.ShardIterator, @@ -467,7 +468,7 @@ func TestPutAndGetRecords(t *testing.T) { SequenceNumber string `json:"SequenceNumber"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &atSeqResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &atSeqResp, jsonx.V1())) // AT_SEQUENCE_NUMBER starts at the given record (inclusive) require.NotEmpty(t, atSeqResp.Records) assert.Equal(t, firstSeq, atSeqResp.Records[0].SequenceNumber) @@ -484,7 +485,7 @@ func TestPutAndGetRecords(t *testing.T) { var afterIterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterIterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterIterResp, jsonx.V1())) rec = doRequest(t, h, "GetRecords", map[string]any{ "ShardIterator": afterIterResp.ShardIterator, @@ -497,7 +498,7 @@ func TestPutAndGetRecords(t *testing.T) { SequenceNumber string `json:"SequenceNumber"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterSeqResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterSeqResp, jsonx.V1())) // AFTER_SEQUENCE_NUMBER skips the given record assert.Len(t, afterSeqResp.Records, 2) @@ -512,7 +513,7 @@ func TestPutAndGetRecords(t *testing.T) { var latestIterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &latestIterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &latestIterResp, jsonx.V1())) rec = doRequest(t, h, "GetRecords", map[string]any{ "ShardIterator": latestIterResp.ShardIterator, @@ -522,7 +523,7 @@ func TestPutAndGetRecords(t *testing.T) { var latestResp struct { Records []any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &latestResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &latestResp, jsonx.V1())) assert.Empty(t, latestResp.Records) // No new records since iterator was created }) } @@ -554,7 +555,7 @@ func TestSequenceNumberOrdering(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) shardID := descResp.StreamDescription.Shards[0].ShardID // Put 5 records @@ -570,7 +571,7 @@ func TestSequenceNumberOrdering(t *testing.T) { var putResp struct { SequenceNumber string `json:"SequenceNumber"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) seqNums[i] = putResp.SequenceNumber } @@ -591,7 +592,7 @@ func TestSequenceNumberOrdering(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp, jsonx.V1())) rec = doRequest(t, h, "GetRecords", map[string]any{ "ShardIterator": iterResp.ShardIterator, @@ -604,7 +605,7 @@ func TestSequenceNumberOrdering(t *testing.T) { SequenceNumber string `json:"SequenceNumber"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) require.Len(t, getResp.Records, 5) for i, r := range getResp.Records { @@ -1153,7 +1154,7 @@ func TestGetRecords_MillisBehindLatest_ViaHandler(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(iterRec.Body.Bytes(), &iterResp, jsonx.V1())) // Fetch 1 record (leaving 2 behind). rec = doRequest(t, h, "GetRecords", map[string]any{ @@ -1166,7 +1167,7 @@ func TestGetRecords_MillisBehindLatest_ViaHandler(t *testing.T) { Records []any `json:"Records"` MillisBehindLatest int64 `json:"MillisBehindLatest"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Len(t, getResp.Records, 1) // Should be behind the last record, not just the next one. assert.GreaterOrEqual(t, getResp.MillisBehindLatest, int64(0)) diff --git a/services/kinesis/records_test.go b/services/kinesis/records_test.go index 62092defba..9cde186913 100644 --- a/services/kinesis/records_test.go +++ b/services/kinesis/records_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "math/big" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -73,7 +74,7 @@ func TestPutRecord_ByARN(t *testing.T) { ShardID string `json:"ShardId"` SequenceNumber string `json:"SequenceNumber"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.ShardID) assert.NotEmpty(t, resp.SequenceNumber) }) @@ -133,7 +134,7 @@ func TestPutRecords_ByARN(t *testing.T) { } `json:"Records"` FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 0, resp.FailedRecordCount) assert.Len(t, resp.Records, 2) }) @@ -257,7 +258,7 @@ func TestPutRecordsNotFound(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp.Type) } @@ -288,7 +289,7 @@ func TestMultipleShardRouting(t *testing.T) { var putResp struct { ShardID string `json:"ShardId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) shardIDs[putResp.ShardID] = true } @@ -343,7 +344,7 @@ func TestPutRecords_OversizeRecordReturnsValidationException(t *testing.T) { } `json:"Records"` FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 1, resp.FailedRecordCount) require.Len(t, resp.Records, 2) @@ -386,7 +387,7 @@ func TestPutRecords_ThrottledRecordReturnsProvisionedThroughputException(t *test } `json:"Records"` FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 1, resp.FailedRecordCount) assert.Equal(t, "ProvisionedThroughputExceededException", resp.Records[0].ErrorCode) @@ -422,7 +423,7 @@ func TestPutRecords_AllValidRecordsSucceed(t *testing.T) { } `json:"Records"` FailedRecordCount int `json:"FailedRecordCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 0, resp.FailedRecordCount) for _, r := range resp.Records { assert.Empty(t, r.ErrorCode) diff --git a/services/kinesis/resharding_shard_count_test.go b/services/kinesis/resharding_shard_count_test.go index 90c205befb..af8b9c98ac 100644 --- a/services/kinesis/resharding_shard_count_test.go +++ b/services/kinesis/resharding_shard_count_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -70,7 +71,7 @@ func TestUpdateShardCount(t *testing.T) { CurrentShardCount int `json:"CurrentShardCount"` TargetShardCount int `json:"TargetShardCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, streamName, resp.StreamName) assert.Equal(t, tt.wantCurrentCount, resp.CurrentShardCount) assert.Equal(t, tt.wantTargetCount, resp.TargetShardCount) @@ -82,7 +83,7 @@ func TestUpdateShardCount(t *testing.T) { var shardsResp struct { Shards []any `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shardsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shardsResp, jsonx.V1())) assert.Len(t, shardsResp.Shards, tt.targetShards) }) }) @@ -296,7 +297,7 @@ func TestUpdateShardCount_ViaHandler_OpenShardsOnly(t *testing.T) { CurrentShardCount int `json:"CurrentShardCount"` TargetShardCount int `json:"TargetShardCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, 2, updateResp.CurrentShardCount) assert.Equal(t, 4, updateResp.TargetShardCount) @@ -307,7 +308,7 @@ func TestUpdateShardCount_ViaHandler_OpenShardsOnly(t *testing.T) { var listResp struct { Shards []any `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Shards, 4) }) } diff --git a/services/kinesis/resharding_test.go b/services/kinesis/resharding_test.go index 05e132e016..4668eef711 100644 --- a/services/kinesis/resharding_test.go +++ b/services/kinesis/resharding_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "math/big" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -193,7 +194,7 @@ func TestScalingCap_HandlerValidationException(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp.Type) assert.NotEmpty(t, resp.Message) } @@ -494,7 +495,7 @@ func TestMergeShards_ARNSupport(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 2) streamARN := descResp.StreamDescription.StreamARN @@ -539,7 +540,7 @@ func TestSplitShard_ARNSupport(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) rec = doRequest(t, h, "SplitShard", map[string]any{ @@ -574,7 +575,7 @@ func TestMergeShards_OnDemandRejected(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) }) } @@ -602,7 +603,7 @@ func TestSplitShard_OnDemandRejected(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) }) } @@ -659,7 +660,7 @@ func TestMergeShards(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 2) shard0 := descResp.StreamDescription.Shards[0].ShardID @@ -690,7 +691,7 @@ func TestMergeShards(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp2, jsonx.V1())) assert.Len(t, descResp2.StreamDescription.Shards, 3) }) } @@ -725,7 +726,7 @@ func TestMergeAndSplitShardIDs(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &d)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &d, jsonx.V1())) return d.StreamDescription.Shards } @@ -739,7 +740,7 @@ func TestMergeAndSplitShardIDs(t *testing.T) { Shards []shardEntry `json:"Shards"` } - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &d)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &d, jsonx.V1())) return d.Shards } @@ -850,7 +851,7 @@ func TestMergeShards_Errors(t *testing.T) { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) @@ -884,7 +885,7 @@ func TestSplitShard(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -903,7 +904,7 @@ func TestSplitShard(t *testing.T) { // After splitting 1 → 2, expect 3 total: 1 closed parent + 2 open children. rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "split-stream"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Len(t, descResp.StreamDescription.Shards, 3) }) } @@ -961,7 +962,7 @@ func TestSplitShard_Errors(t *testing.T) { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.errType, errResp.Type) } }) diff --git a/services/kinesis/resource_policy_test.go b/services/kinesis/resource_policy_test.go index 9de4ed1341..aa5bed2fe8 100644 --- a/services/kinesis/resource_policy_test.go +++ b/services/kinesis/resource_policy_test.go @@ -1,13 +1,14 @@ package kinesis_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -78,7 +79,7 @@ func TestResourcePolicyLifecycle(t *testing.T) { Policy string `json:"Policy"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp, jsonx.V1())) assert.Equal(t, tt.policy, policyResp.Policy) // DeleteResourcePolicy. @@ -110,6 +111,6 @@ func TestDeleteResourcePolicy_NotFound(t *testing.T) { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type) } diff --git a/services/kinesis/ring_buffer.go b/services/kinesis/ring_buffer.go index 3457ae6d7e..0b290e299d 100644 --- a/services/kinesis/ring_buffer.go +++ b/services/kinesis/ring_buffer.go @@ -1,9 +1,11 @@ package kinesis import ( - "encoding/json" + "encoding/json/v2" "sort" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // shardRecords is a bounded ring buffer for Kinesis shard records. @@ -82,7 +84,7 @@ func (r *shardRecords) MarshalJSON() ([]byte, error) { out[i] = r.at(i) } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } // UnmarshalJSON restores the ring buffer from a JSON array. @@ -90,7 +92,7 @@ func (r *shardRecords) MarshalJSON() ([]byte, error) { // Any previously held pointers are cleared to allow GC. func (r *shardRecords) UnmarshalJSON(data []byte) error { var records []*Record - if err := json.Unmarshal(data, &records); err != nil { + if err := json.Unmarshal(data, &records, jsonx.V1()); err != nil { return err } diff --git a/services/kinesis/shard_iterators.go b/services/kinesis/shard_iterators.go index 9530fd71a5..a23b4c5408 100644 --- a/services/kinesis/shard_iterators.go +++ b/services/kinesis/shard_iterators.go @@ -3,13 +3,15 @@ package kinesis import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // iteratorToken encodes a shard iterator as a base64 JSON token. func encodeIterator(it *ShardIterator) (string, error) { - data, err := json.Marshal(it) + data, err := json.Marshal(it, jsonx.V1()) if err != nil { return "", err } @@ -25,7 +27,7 @@ func decodeIterator(token string, now time.Time) (*ShardIterator, error) { } var it ShardIterator - if err = json.Unmarshal(data, &it); err != nil { + if err = json.Unmarshal(data, &it, jsonx.V1()); err != nil { return nil, ErrShardIteratorExpired } diff --git a/services/kinesis/shard_iterators_test.go b/services/kinesis/shard_iterators_test.go index 4c6cc159a2..1ad01efb86 100644 --- a/services/kinesis/shard_iterators_test.go +++ b/services/kinesis/shard_iterators_test.go @@ -3,7 +3,7 @@ package kinesis_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -60,7 +61,7 @@ func TestGetShardIterator_ByARN(t *testing.T) { var resp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.ShardIterator) }) } @@ -203,7 +204,7 @@ func TestGetShardIteratorBadIteratorType(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) shardID := descResp.StreamDescription.Shards[0].ShardID rec = doRequest(t, h, "GetShardIterator", map[string]any{ @@ -264,7 +265,7 @@ func testGetShardIteratorAtTimestamp(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -288,7 +289,7 @@ func testGetShardIteratorAtTimestamp(t *testing.T) { var iterResp struct { ShardIterator string `json:"ShardIterator"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &iterResp, jsonx.V1())) assert.NotEmpty(t, iterResp.ShardIterator) // GetRecords should return the record. @@ -302,7 +303,7 @@ func testGetShardIteratorAtTimestamp(t *testing.T) { Data []byte `json:"Data"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Len(t, getResp.Records, 1) assert.Equal(t, []byte("hello"), getResp.Records[0].Data) } @@ -328,7 +329,7 @@ func TestGetShardIterator_AtTimestampRequiresTimestamp(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) shardID := descResp.StreamDescription.Shards[0].ShardID rec = doRequest(t, h, "GetShardIterator", map[string]any{ @@ -342,7 +343,7 @@ func TestGetShardIterator_AtTimestampRequiresTimestamp(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", errResp.Type) } @@ -423,7 +424,7 @@ func testGetRecordsNextShardIteratorHasExpiry(t *testing.T) { require.NoError(t, err) var next kinesis.ShardIterator - require.NoError(t, json.Unmarshal(raw, &next)) + require.NoError(t, json.Unmarshal(raw, &next, jsonx.V1())) assert.False(t, next.CreatedAt.IsZero(), "NextShardIterator must carry a CreatedAt so ExpiredIteratorException can ever fire on a chained token") diff --git a/services/kinesis/shards_test.go b/services/kinesis/shards_test.go index e201c65f92..a0fdfe8a23 100644 --- a/services/kinesis/shards_test.go +++ b/services/kinesis/shards_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -31,7 +32,7 @@ func TestListShards_NextTokenStreamNameMutuallyExclusive(t *testing.T) { var page1 struct { NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) require.NotEmpty(t, page1.NextToken, "expected NextToken for pagination") tests := []struct { @@ -65,7 +66,7 @@ func TestListShards_NextTokenStreamNameMutuallyExclusive(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", errResp.Type) }) } @@ -90,7 +91,7 @@ func TestListShards_NextTokenAlone_Succeeds(t *testing.T) { ShardID string `json:"ShardId"` } `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) require.NotEmpty(t, page1.NextToken) // Use NextToken alone (no StreamName) — should succeed because the token embeds stream context. @@ -105,7 +106,7 @@ func TestListShards_NextTokenAlone_Succeeds(t *testing.T) { ShardID string `json:"ShardId"` } `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) assert.NotEmpty(t, page2.Shards) } @@ -538,7 +539,7 @@ func TestListShards(t *testing.T) { ShardID string `json:"ShardId"` } `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listShardsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listShardsResp, jsonx.V1())) assert.Len(t, listShardsResp.Shards, 3) } @@ -639,7 +640,7 @@ func TestListShards_MaxResults_ViaHandler(t *testing.T) { NextToken string `json:"NextToken"` Shards []any `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Shards, 2) assert.NotEmpty(t, resp.NextToken) @@ -654,7 +655,7 @@ func TestListShards_MaxResults_ViaHandler(t *testing.T) { NextToken string `json:"NextToken"` Shards []any `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2.Shards, 2) assert.Empty(t, resp2.NextToken, "no more pages") } @@ -927,7 +928,7 @@ func TestListShards_ShardFilterType_TimestampRequired(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", errResp.Type) }) } diff --git a/services/kinesis/stream_encryption_test.go b/services/kinesis/stream_encryption_test.go index 1b0078cc38..064c3b7c78 100644 --- a/services/kinesis/stream_encryption_test.go +++ b/services/kinesis/stream_encryption_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -44,7 +45,7 @@ func testStartEncryptionARNSupport(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) rec = doRequest(t, h, "StartStreamEncryption", map[string]any{ "StreamARN": descResp.StreamDescription.StreamARN, @@ -85,7 +86,7 @@ func TestStopEncryption_ResetsFields(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "NONE", resp.StreamDescription.EncryptionType) } @@ -208,7 +209,7 @@ func TestStreamEncryption_Errors(t *testing.T) { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(got.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) } }) diff --git a/services/kinesis/stream_modes_test.go b/services/kinesis/stream_modes_test.go index 480835ddef..6ec18ac82d 100644 --- a/services/kinesis/stream_modes_test.go +++ b/services/kinesis/stream_modes_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -155,7 +156,7 @@ func TestUpdateStreamMode_Valid(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) rec3 := doRequest(t, h, "UpdateStreamMode", map[string]any{ "StreamARN": descResp.StreamDescription.StreamARN, @@ -174,7 +175,7 @@ func TestUpdateStreamMode_Valid(t *testing.T) { } `json:"StreamModeDetails"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &verifyResp)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &verifyResp, jsonx.V1())) require.NotNil(t, verifyResp.StreamDescription.StreamModeDetails) assert.Equal(t, tt.wantMode, verifyResp.StreamDescription.StreamModeDetails.StreamMode) }) diff --git a/services/kinesis/stream_retention_test.go b/services/kinesis/stream_retention_test.go index 2bf83bc98b..b782d71602 100644 --- a/services/kinesis/stream_retention_test.go +++ b/services/kinesis/stream_retention_test.go @@ -2,13 +2,14 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -199,7 +200,7 @@ func TestHandleIncreaseStreamRetentionPeriod(t *testing.T) { RetentionPeriodHours int `json:"RetentionPeriodHours"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, 48, descResp.StreamDescription.RetentionPeriodHours) } @@ -231,7 +232,7 @@ func TestHandleDecreaseStreamRetentionPeriod(t *testing.T) { RetentionPeriodHours int `json:"RetentionPeriodHours"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, 24, descResp.StreamDescription.RetentionPeriodHours) } diff --git a/services/kinesis/streams_describe_test.go b/services/kinesis/streams_describe_test.go index f589540cf9..ce54fe9c13 100644 --- a/services/kinesis/streams_describe_test.go +++ b/services/kinesis/streams_describe_test.go @@ -2,7 +2,8 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -55,7 +57,7 @@ func TestDescribeStream_ByARN(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "arn-describe-stream", resp.StreamDescription.StreamName) assert.Equal(t, desc.StreamARN, resp.StreamDescription.StreamARN) }) @@ -100,7 +102,7 @@ func TestDescribeStreamSummary_ByARN(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescriptionSummary"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "arn-summary-stream", resp.StreamDescriptionSummary.StreamName) assert.Equal(t, desc.StreamARN, resp.StreamDescriptionSummary.StreamARN) }) @@ -142,7 +144,7 @@ func TestDescribeStream_EncryptionFields(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "KMS", resp.StreamDescription.EncryptionType) assert.Equal(t, "alias/my-key-id", resp.StreamDescription.KeyID) }) @@ -181,7 +183,7 @@ func TestDescribeStreamSummary_EncryptionFields(t *testing.T) { } `json:"StreamDescriptionSummary"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "KMS", resp.StreamDescriptionSummary.EncryptionType) assert.Equal(t, "alias/summary-key-id", resp.StreamDescriptionSummary.KeyID) }) @@ -208,7 +210,10 @@ func TestDescribeStreamSummary_OpenShardCountAndConsumerCount(t *testing.T) { } `json:"StreamDescription"` } - require.NoError(t, json.NewDecoder(strings.NewReader(descRec.Body.String())).Decode(&descResp)) + require.NoError( + t, + json.UnmarshalDecode(jsontext.NewDecoder(strings.NewReader(descRec.Body.String())), &descResp, jsonx.V1()), + ) regRec := doParityRequest(t, h, "RegisterStreamConsumer", map[string]any{ "StreamARN": descResp.StreamDescription.StreamARN, @@ -228,7 +233,10 @@ func TestDescribeStreamSummary_OpenShardCountAndConsumerCount(t *testing.T) { } `json:"StreamDescriptionSummary"` } - require.NoError(t, json.NewDecoder(strings.NewReader(sumRec.Body.String())).Decode(&sumResp)) + require.NoError( + t, + json.UnmarshalDecode(jsontext.NewDecoder(strings.NewReader(sumRec.Body.String())), &sumResp, jsonx.V1()), + ) assert.Equal(t, 3, sumResp.StreamDescriptionSummary.OpenShardCount) assert.Equal(t, 1, sumResp.StreamDescriptionSummary.ConsumerCount) @@ -269,7 +277,7 @@ func TestDescribeStream_StreamCreationTimestamp(t *testing.T) { StreamCreationTimestamp float64 `json:"StreamCreationTimestamp"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.GreaterOrEqual(t, int64(resp.StreamDescription.StreamCreationTimestamp), before) }) } @@ -309,7 +317,7 @@ func TestDescribeStream_StreamModeDetails(t *testing.T) { } `json:"StreamModeDetails"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.StreamDescription.StreamModeDetails) assert.Equal(t, tt.wantMode, resp.StreamDescription.StreamModeDetails.StreamMode) }) @@ -366,7 +374,7 @@ func TestDescribeStreamSummary_OpenShardCount_AfterMerge(t *testing.T) { ShardID string `json:"ShardId"` } `json:"Shards"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &shardsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &shardsResp, jsonx.V1())) require.Len(t, shardsResp.Shards, 2) rec3 := doRequest(t, h, "MergeShards", map[string]any{ @@ -387,7 +395,7 @@ func TestDescribeStreamSummary_OpenShardCount_AfterMerge(t *testing.T) { OpenShardCount int `json:"OpenShardCount"` } `json:"StreamDescriptionSummary"` } - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &summaryResp)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &summaryResp, jsonx.V1())) assert.Equal(t, tt.wantOpenShards, summaryResp.StreamDescriptionSummary.OpenShardCount) }) }) @@ -426,7 +434,7 @@ func TestDescribeStream_EncryptionTypeDefault(t *testing.T) { EncryptionType string `json:"EncryptionType"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantEncryption, resp.StreamDescription.EncryptionType) }) } @@ -454,7 +462,7 @@ func TestDescribeStream_IncludesClosedShardsAfterMerge(t *testing.T) { } rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-closed-merge"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) s0 := descResp.StreamDescription.Shards[0].ShardID s1 := descResp.StreamDescription.Shards[1].ShardID @@ -468,7 +476,7 @@ func TestDescribeStream_IncludesClosedShardsAfterMerge(t *testing.T) { // After merge: 2 closed parents + 1 open merged = 3 total. rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-closed-merge"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Len(t, descResp.StreamDescription.Shards, 3) }) } @@ -495,7 +503,7 @@ func TestDescribeStream_IncludesClosedShardsAfterSplit(t *testing.T) { } rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-closed-split"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) shardID := descResp.StreamDescription.Shards[0].ShardID const splitKey = "170141183460469231731687303715884105728" @@ -509,7 +517,7 @@ func TestDescribeStream_IncludesClosedShardsAfterSplit(t *testing.T) { // After split: 1 closed parent + 2 open children = 3 total. rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-closed-split"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Len(t, descResp.StreamDescription.Shards, 3) }) } @@ -537,7 +545,7 @@ func TestDescribeStream_OpenShardNoEndingSequenceNumber(t *testing.T) { } rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-open-shard"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) // Open shards have an empty EndingSequenceNumber. assert.Empty(t, descResp.StreamDescription.Shards[0].SequenceNumberRange.EndingSequenceNumber) @@ -585,7 +593,7 @@ func TestDescribeStream_OpenShardWithRecordsNoEndingSeq(t *testing.T) { } rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "open-shard-with-records"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) shard := descResp.StreamDescription.Shards[0] @@ -625,7 +633,7 @@ func TestDescribeStream_UpdateShardCount_IncludesOldClosedShards(t *testing.T) { } rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "describe-usc-stream"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Len(t, descResp.StreamDescription.Shards, 5, "3 closed + 2 open = 5") }) } @@ -734,7 +742,7 @@ func TestDescribeStream_ClosedShardHasEndingSequenceNumber(t *testing.T) { rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "closing-seqnum-stream"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 1) shardID := descResp.StreamDescription.Shards[0].ShardID @@ -750,7 +758,7 @@ func TestDescribeStream_ClosedShardHasEndingSequenceNumber(t *testing.T) { // Re-describe: closed shard should have a non-empty EndingSequenceNumber. rec = doRequest(t, h, "DescribeStream", map[string]any{"StreamName": "closing-seqnum-stream"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.StreamDescription.Shards, 3) closedCount := 0 diff --git a/services/kinesis/streams_test.go b/services/kinesis/streams_test.go index 23f7df41ed..683e8aaca6 100644 --- a/services/kinesis/streams_test.go +++ b/services/kinesis/streams_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -72,7 +73,7 @@ func TestCreateStream_ShardCount_Validation(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp.Type) } }) @@ -160,7 +161,7 @@ func TestCreateStream_OnDemand_ShardCountZero(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.StreamDescription.StreamModeDetails) assert.Equal(t, tt.wantMode, resp.StreamDescription.StreamModeDetails.StreamMode) @@ -423,7 +424,7 @@ func TestCreateStream_DefaultsToProvisioned(t *testing.T) { } `json:"StreamModeDetails"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.StreamDescription.StreamModeDetails) assert.Equal(t, tt.wantMode, resp.StreamDescription.StreamModeDetails.StreamMode) }) @@ -476,7 +477,7 @@ func TestCreateStream_WithOnDemand(t *testing.T) { } `json:"StreamModeDetails"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.StreamDescription.StreamModeDetails) assert.Equal(t, tt.wantMode, resp.StreamDescription.StreamModeDetails.StreamMode) }) @@ -524,7 +525,7 @@ func TestCreateStream_WithTags(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) rec3 := doRequest(t, h, "ListTagsForResource", map[string]any{ "ResourceARN": descResp.StreamDescription.StreamARN, @@ -537,7 +538,7 @@ func TestCreateStream_WithTags(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsResp, jsonx.V1())) found := false for _, kv := range tagsResp.Tags { @@ -572,7 +573,7 @@ func TestStreamLifecycle(t *testing.T) { var listResp struct { StreamNames []string `json:"StreamNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Contains(t, listResp.StreamNames, "my-stream") // DescribeStream @@ -590,7 +591,7 @@ func TestStreamLifecycle(t *testing.T) { } `json:"Shards"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-stream", descResp.StreamDescription.StreamName) assert.Equal(t, "ACTIVE", descResp.StreamDescription.StreamStatus) assert.Len(t, descResp.StreamDescription.Shards, 2) @@ -607,7 +608,7 @@ func TestStreamLifecycle(t *testing.T) { OpenShardCount int `json:"OpenShardCount"` } `json:"StreamDescriptionSummary"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sumResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sumResp, jsonx.V1())) assert.Equal(t, "my-stream", sumResp.StreamDescriptionSummary.StreamName) assert.Equal(t, 2, sumResp.StreamDescriptionSummary.OpenShardCount) @@ -645,7 +646,7 @@ func TestCreateStreamAlreadyExists(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceInUseException", errResp.Type) } @@ -673,7 +674,7 @@ func TestHandleListStreamsEmpty(t *testing.T) { var resp struct { StreamNames []string `json:"StreamNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp.StreamNames) assert.Empty(t, resp.StreamNames) } @@ -730,7 +731,7 @@ func TestCreateStream_OnDemandLimit_ViaHandler(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "LimitExceededException", errResp.Type) } diff --git a/services/kinesis/tags_test.go b/services/kinesis/tags_test.go index 2afce221b4..7895d5d9c5 100644 --- a/services/kinesis/tags_test.go +++ b/services/kinesis/tags_test.go @@ -2,7 +2,7 @@ package kinesis_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesis" ) @@ -88,7 +89,7 @@ func TestListTagsForResource_SortedOutput(t *testing.T) { } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Tags, 3) assert.Equal(t, "apple", resp.Tags[0].Key) assert.Equal(t, "a", resp.Tags[0].Value) @@ -113,7 +114,7 @@ func TestAddTagsToStream_KeyTooLong(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) } @@ -133,7 +134,7 @@ func TestAddTagsToStream_EmptyKey(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) } @@ -154,7 +155,7 @@ func TestAddTagsToStream_ValueTooLong(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) } @@ -188,7 +189,7 @@ func TestAddTagsToStream_StreamNotFound(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp.Type) } @@ -213,7 +214,7 @@ func TestTagResource_KeyTooLong(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) } @@ -238,7 +239,7 @@ func TestTagResource_ValueTooLong(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", resp.Type) } @@ -321,7 +322,7 @@ func TestRemoveTagsFromStream_StreamNotFound(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp.Type) } @@ -338,7 +339,7 @@ func TestListTagsForStream_StreamNotFound(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp.Type) } @@ -372,7 +373,7 @@ func TestListTagsForStream_LimitRespected(t *testing.T) { } `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Tags, 5) assert.True(t, resp.HasMoreTags) } @@ -406,7 +407,7 @@ func TestListTagsForStream_LimitDefault(t *testing.T) { } `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Tags, 10) assert.True(t, resp.HasMoreTags) } @@ -455,7 +456,7 @@ func TestListTagsForResource(t *testing.T) { } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp.Tags) } }) @@ -494,7 +495,7 @@ func TestHandleAddTagsToStream(t *testing.T) { } `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.False(t, listResp.HasMoreTags) assert.Len(t, listResp.Tags, 2) @@ -543,7 +544,7 @@ func TestHandleRemoveTagsFromStream(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Tags, 1) assert.Equal(t, "team", listResp.Tags[0].Key) } @@ -567,7 +568,7 @@ func TestHandleListTagsForStreamEmpty(t *testing.T) { } `json:"Tags"` HasMoreTags bool `json:"HasMoreTags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.Tags) assert.False(t, listResp.HasMoreTags) } @@ -616,7 +617,7 @@ func TestListTagsForResource_TagsFromCreate(t *testing.T) { StreamARN string `json:"StreamARN"` } `json:"StreamDescription"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) rec3 := doRequest(t, h, "ListTagsForResource", map[string]any{ "ResourceARN": descResp.StreamDescription.StreamARN, @@ -629,7 +630,7 @@ func TestListTagsForResource_TagsFromCreate(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsResp, jsonx.V1())) found := false for _, kv := range tagsResp.Tags { diff --git a/services/kinesisanalytics/application_cloudwatch_test.go b/services/kinesisanalytics/application_cloudwatch_test.go index 8155d308a2..b961cfe8f7 100644 --- a/services/kinesisanalytics/application_cloudwatch_test.go +++ b/services/kinesisanalytics/application_cloudwatch_test.go @@ -2,13 +2,14 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -237,7 +238,7 @@ func TestAddCWLRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) detail := resp["ApplicationDetail"].(map[string]any) cwlList := detail["CloudWatchLoggingOptionDescriptions"].([]any) assert.Len(t, cwlList, 1) diff --git a/services/kinesisanalytics/application_inputs_test.go b/services/kinesisanalytics/application_inputs_test.go index 9201033487..e8ff51a3a3 100644 --- a/services/kinesisanalytics/application_inputs_test.go +++ b/services/kinesisanalytics/application_inputs_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -541,7 +542,7 @@ func TestHandler_DiscoverInputSchema(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrCode, resp["__type"]) }) } diff --git a/services/kinesisanalytics/application_outputs_test.go b/services/kinesisanalytics/application_outputs_test.go index 7d2501c1f6..2f2eb4cec3 100644 --- a/services/kinesisanalytics/application_outputs_test.go +++ b/services/kinesisanalytics/application_outputs_test.go @@ -2,13 +2,14 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -237,7 +238,7 @@ func TestAddOutputRoundTrip(t *testing.T) { rec2 := doRequest(t, h, "DescribeApplication", map[string]any{"ApplicationName": app.ApplicationName}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) detail := resp["ApplicationDetail"].(map[string]any) outList := detail["OutputDescriptions"].([]any) assert.Len(t, outList, 1) diff --git a/services/kinesisanalytics/application_reference_data_test.go b/services/kinesisanalytics/application_reference_data_test.go index c4e56ca60e..e646325571 100644 --- a/services/kinesisanalytics/application_reference_data_test.go +++ b/services/kinesisanalytics/application_reference_data_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -256,7 +257,7 @@ func TestAddRefDataSourceRoundTrip(t *testing.T) { rec2 := doRequest(t, h, "DescribeApplication", map[string]any{"ApplicationName": app.ApplicationName}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) detail := resp["ApplicationDetail"].(map[string]any) refList := detail["ReferenceDataSourceDescriptions"].([]any) assert.Len(t, refList, 1) diff --git a/services/kinesisanalytics/application_update_test.go b/services/kinesisanalytics/application_update_test.go index 84a3ac8482..45bbe33219 100644 --- a/services/kinesisanalytics/application_update_test.go +++ b/services/kinesisanalytics/application_update_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -248,7 +249,7 @@ func TestHandler_UpdateApplication_NestedWireShapes(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp, jsonx.V1())) detail := resp["ApplicationDetail"].(map[string]any) inList := detail["InputDescriptions"].([]any) @@ -353,7 +354,7 @@ func TestUpdateApplication_ReturnsApplicationDetail(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) detail, ok := resp["ApplicationDetail"].(map[string]any) require.True(t, ok, "UpdateApplication response must contain ApplicationDetail, got: %s", rec.Body.String()) @@ -384,7 +385,7 @@ func TestUpdateApplication_CodeVisible(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) detail, ok := resp["ApplicationDetail"].(map[string]any) require.True(t, ok) @@ -424,6 +425,6 @@ func TestUpdateApplication_ConcurrentModificationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConcurrentModificationException", errResp["__type"]) } diff --git a/services/kinesisanalytics/applications_handler_test.go b/services/kinesisanalytics/applications_handler_test.go index 53eb5774fa..2f69d0f93f 100644 --- a/services/kinesisanalytics/applications_handler_test.go +++ b/services/kinesisanalytics/applications_handler_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -46,7 +47,7 @@ func TestHandler_CreateApplication(t *testing.T) { if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) } }) @@ -160,7 +161,7 @@ func TestDeleteApplication_TimestampMismatch_ConcurrentModificationException(t * assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ConcurrentModificationException", resp["__type"], "timestamp mismatch must return ConcurrentModificationException, not LimitExceededException") } @@ -225,7 +226,7 @@ func TestHandler_ListApplications(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["ApplicationSummaries"].([]any) assert.Len(t, summaries, tt.wantCount) diff --git a/services/kinesisanalytics/applications_test.go b/services/kinesisanalytics/applications_test.go index 6c0e5e768c..db175091db 100644 --- a/services/kinesisanalytics/applications_test.go +++ b/services/kinesisanalytics/applications_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -605,7 +606,7 @@ func TestResourceAlreadyExists_Returns400(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceInUseException", errResp["__type"]) } @@ -620,6 +621,6 @@ func TestErrValidationMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidArgumentException", errResp["__type"]) } diff --git a/services/kinesisanalytics/discover_schema.go b/services/kinesisanalytics/discover_schema.go index 7645c90d91..7b1a3cc33e 100644 --- a/services/kinesisanalytics/discover_schema.go +++ b/services/kinesisanalytics/discover_schema.go @@ -3,7 +3,8 @@ package kinesisanalytics import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "sort" @@ -11,6 +12,8 @@ import ( "strings" sdk_s3 "github.com/aws/aws-sdk-go-v2/service/s3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -208,7 +211,7 @@ func sampleJSONLines(body []byte, limit int) []string { for _, line := range lines { trimmed := bytes.TrimSpace(line) - if len(trimmed) == 0 || !json.Valid(trimmed) { + if len(trimmed) == 0 || !jsontext.Value(trimmed).IsValid(jsonx.V1()) { continue } @@ -266,11 +269,10 @@ func decodeSampleRecords(raw []string) []map[string]any { records := make([]map[string]any, 0, len(raw)) for _, r := range raw { - dec := json.NewDecoder(strings.NewReader(r)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(r)) var m map[string]any - if err := dec.Decode(&m); err != nil { + if err := json.UnmarshalDecode(dec, &m, jsonx.V1(), jsonx.UseNumber()); err != nil { continue } @@ -318,7 +320,7 @@ func inferColumnType(records []map[string]any, key string) string { switch val := v.(type) { case bool: sawBool = true - case json.Number: + case jsonx.Number: if _, err := strconv.ParseInt(val.String(), 10, 64); err == nil { sawInt = true } else { @@ -330,7 +332,7 @@ func inferColumnType(records []map[string]any, key string) string { default: sawOther = true - if b, err := json.Marshal(val); err == nil { + if b, err := json.Marshal(val, jsonx.V1()); err == nil { maxLen = max(maxLen, len(b)) } } @@ -357,12 +359,12 @@ func stringifyValue(v any) string { return "" case string: return val - case json.Number: + case jsonx.Number: return val.String() case bool: return strconv.FormatBool(val) default: - b, err := json.Marshal(val) + b, err := json.Marshal(val, jsonx.V1()) if err != nil { return "" } diff --git a/services/kinesisanalytics/discover_schema_test.go b/services/kinesisanalytics/discover_schema_test.go index 649f378b6d..7d4dfaef16 100644 --- a/services/kinesisanalytics/discover_schema_test.go +++ b/services/kinesisanalytics/discover_schema_test.go @@ -3,7 +3,7 @@ package kinesisanalytics_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -80,7 +81,7 @@ func discoverSchemaBody(t *testing.T, rec *httptest.ResponseRecorder) map[string t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/kinesisanalytics/handler.go b/services/kinesisanalytics/handler.go index 7ae78c3732..162971e23a 100644 --- a/services/kinesisanalytics/handler.go +++ b/services/kinesisanalytics/handler.go @@ -2,7 +2,7 @@ package kinesisanalytics import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -163,7 +164,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req applicationNameInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.ApplicationName } @@ -198,13 +199,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var code string var status int @@ -234,8 +232,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err status = http.StatusBadRequest code = errLimitExceededException case errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): status = http.StatusBadRequest code = errInvalidArgumentException case errors.Is(err, errApplicationName), diff --git a/services/kinesisanalytics/handler_test.go b/services/kinesisanalytics/handler_test.go index b56a6d26ee..638e9b8e04 100644 --- a/services/kinesisanalytics/handler_test.go +++ b/services/kinesisanalytics/handler_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -49,7 +50,7 @@ func doRequest(t *testing.T, h *kinesisanalytics.Handler, action string, body an if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -273,7 +274,7 @@ func TestErrorTypes(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) }) } diff --git a/services/kinesisanalytics/persistence.go b/services/kinesisanalytics/persistence.go index cf28ab3a45..b8784282e7 100644 --- a/services/kinesisanalytics/persistence.go +++ b/services/kinesisanalytics/persistence.go @@ -2,9 +2,11 @@ package kinesisanalytics import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" @@ -28,11 +30,11 @@ const kinesisanalyticsSnapshotVersion = 1 // Application is persisted directly (no DTO) because every field, including // the additive Region, already round-trips through encoding/json. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - NextID int64 `json:"next_id"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + NextID int64 `json:"next_id"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -56,7 +58,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NextID: b.nextID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "kinesisanalytics: failed to marshal snapshot", "error", err) diff --git a/services/kinesisanalytics/persistence_test.go b/services/kinesisanalytics/persistence_test.go index 242c92e5f7..1ddfaadd79 100644 --- a/services/kinesisanalytics/persistence_test.go +++ b/services/kinesisanalytics/persistence_test.go @@ -2,11 +2,14 @@ package kinesisanalytics //nolint:testpackage // needs kaCtxRegion and the unexp import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestInMemoryBackend_FullStateSnapshotRestoreRoundTrip exercises every kind @@ -158,12 +161,12 @@ func TestInMemoryBackend_Restore_IncompatibleVersion_ResetsEmpty(t *testing.T) { // Corrupt just the version field to simulate a snapshot from an // incompatible (older/newer) build. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &raw)) - bumped, err := json.Marshal(kinesisanalyticsSnapshotVersion + 1) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) + bumped, err := json.Marshal(kinesisanalyticsSnapshotVersion+1, jsonx.V1()) require.NoError(t, err) raw["version"] = bumped - mutated, err := json.Marshal(raw) + mutated, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) fresh := NewInMemoryBackend("us-east-1", "000000000000") diff --git a/services/kinesisanalytics/tags_test.go b/services/kinesisanalytics/tags_test.go index c3a4bc057f..beb6eff1ff 100644 --- a/services/kinesisanalytics/tags_test.go +++ b/services/kinesisanalytics/tags_test.go @@ -2,7 +2,7 @@ package kinesisanalytics_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalytics" ) @@ -434,7 +435,7 @@ func TestSortedListTagsForResource(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Tags, 3) assert.Equal(t, "a", resp.Tags[0].Key) diff --git a/services/kinesisanalyticsv2/application_update_test.go b/services/kinesisanalyticsv2/application_update_test.go index f653515151..344cca1e75 100644 --- a/services/kinesisanalyticsv2/application_update_test.go +++ b/services/kinesisanalyticsv2/application_update_test.go @@ -2,13 +2,14 @@ package kinesisanalyticsv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "math" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -520,7 +521,7 @@ func appCreateEpochSeconds(t *testing.T, b *kinesisanalyticsv2.InMemoryBackend, } `json:"ApplicationDetail"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.ApplicationDetail.CreateTimestamp } diff --git a/services/kinesisanalyticsv2/flink_engine_test.go b/services/kinesisanalyticsv2/flink_engine_test.go index 80029494aa..986f747412 100644 --- a/services/kinesisanalyticsv2/flink_engine_test.go +++ b/services/kinesisanalyticsv2/flink_engine_test.go @@ -2,7 +2,8 @@ package kinesisanalyticsv2_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "mime" "mime/multipart" @@ -16,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -127,7 +129,7 @@ func (f *fakeFlinkREST) Do(req *http.Request) (*http.Response, error) { case strings.HasSuffix(p, "/run"): var body map[string]any - _ = json.NewDecoder(req.Body).Decode(&body) + _ = json.UnmarshalDecode(jsontext.NewDecoder(req.Body), &body, jsonx.V1()) f.runs = append(f.runs, body) return jsonResp(`{"jobid":"job1"}`), nil diff --git a/services/kinesisanalyticsv2/flink_rest.go b/services/kinesisanalyticsv2/flink_rest.go index fde5d06424..322bba6ae1 100644 --- a/services/kinesisanalyticsv2/flink_rest.go +++ b/services/kinesisanalyticsv2/flink_rest.go @@ -3,7 +3,7 @@ package kinesisanalyticsv2 import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -11,6 +11,8 @@ import ( "net/http" "net/textproto" "path" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const flinkMaxBody = 1 << 20 @@ -89,7 +91,7 @@ func (c flinkREST) send(ctx context.Context, method, p, contentType string, body return nil } - if err = json.Unmarshal(raw, out); err != nil { + if err = json.Unmarshal(raw, out, jsonx.V1()); err != nil { return fmt.Errorf("decode flink response: %w", err) } @@ -100,7 +102,7 @@ func (c flinkREST) json(ctx context.Context, method, p string, in, out any) erro var body io.Reader if in != nil { - raw, err := json.Marshal(in) + raw, err := json.Marshal(in, jsonx.V1()) if err != nil { return fmt.Errorf("encode flink request: %w", err) } diff --git a/services/kinesisanalyticsv2/flink_run.go b/services/kinesisanalyticsv2/flink_run.go index c4bf340f35..c39ae593e9 100644 --- a/services/kinesisanalyticsv2/flink_run.go +++ b/services/kinesisanalyticsv2/flink_run.go @@ -2,7 +2,7 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" @@ -12,6 +12,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" ) @@ -77,7 +78,7 @@ func (e *flinkEngine) stage(in flinkLaunch) (string, error) { props = []PropertyGroup{} } - raw, err := json.Marshal(props) + raw, err := json.Marshal(props, jsonx.V1()) if err == nil { err = os.WriteFile(filepath.Join(dir, "application_properties.json"), raw, flinkFileMode) } diff --git a/services/kinesisanalyticsv2/handler.go b/services/kinesisanalyticsv2/handler.go index 579e647fd6..e09653af34 100644 --- a/services/kinesisanalyticsv2/handler.go +++ b/services/kinesisanalyticsv2/handler.go @@ -2,7 +2,7 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -131,7 +132,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ApplicationName string `json:"ApplicationName"` } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } diff --git a/services/kinesisanalyticsv2/handler_application_config.go b/services/kinesisanalyticsv2/handler_application_config.go index 1813036a99..42ddc6fd51 100644 --- a/services/kinesisanalyticsv2/handler_application_config.go +++ b/services/kinesisanalyticsv2/handler_application_config.go @@ -2,10 +2,12 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type cwlOptionInput struct { @@ -218,7 +220,7 @@ type deleteApplicationVpcConfigOutput struct { func (h *Handler) handleAddApplicationCloudWatchLoggingOption(ctx context.Context, c *echo.Context, body []byte) error { var in addApplicationCWLOptionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -258,7 +260,7 @@ func (h *Handler) handleAddApplicationInput( body []byte, ) error { var in addApplicationInputInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -288,7 +290,7 @@ func (h *Handler) handleAddApplicationInputProcessingConfiguration( ctx context.Context, c *echo.Context, body []byte, ) error { var in addInputProcessingConfigInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -337,7 +339,7 @@ func (h *Handler) handleAddApplicationOutput( body []byte, ) error { var in addApplicationOutputInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -368,7 +370,7 @@ func (h *Handler) handleAddApplicationOutput( //nolint:dupl // add input/reference-data-source handlers share structure but are semantically distinct operations func (h *Handler) handleAddApplicationReferenceDataSource(ctx context.Context, c *echo.Context, body []byte) error { var in addApplicationRefDataSourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -398,7 +400,7 @@ func (h *Handler) handleAddApplicationReferenceDataSource(ctx context.Context, c func (h *Handler) handleAddApplicationVpcConfiguration(ctx context.Context, c *echo.Context, body []byte) error { var in addApplicationVpcConfigInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -439,7 +441,7 @@ func (h *Handler) handleDeleteApplicationCloudWatchLoggingOption( ctx context.Context, c *echo.Context, body []byte, ) error { var in deleteApplicationCWLOptionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -467,7 +469,7 @@ func (h *Handler) handleDeleteApplicationInputProcessingConfiguration( ctx context.Context, c *echo.Context, body []byte, ) error { var in deleteInputProcessingConfigInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -490,7 +492,7 @@ func (h *Handler) handleDeleteApplicationInputProcessingConfiguration( func (h *Handler) handleDeleteApplicationOutput(ctx context.Context, c *echo.Context, body []byte) error { var in deleteApplicationOutputInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -513,7 +515,7 @@ func (h *Handler) handleDeleteApplicationOutput(ctx context.Context, c *echo.Con func (h *Handler) handleDeleteApplicationReferenceDataSource(ctx context.Context, c *echo.Context, body []byte) error { var in deleteApplicationRefDataSourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -536,7 +538,7 @@ func (h *Handler) handleDeleteApplicationReferenceDataSource(ctx context.Context func (h *Handler) handleDeleteApplicationVpcConfiguration(ctx context.Context, c *echo.Context, body []byte) error { var in deleteApplicationVpcConfigInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_application_config_delete_test.go b/services/kinesisanalyticsv2/handler_application_config_delete_test.go index 9dcbd09aa4..aec4dc83e6 100644 --- a/services/kinesisanalyticsv2/handler_application_config_delete_test.go +++ b/services/kinesisanalyticsv2/handler_application_config_delete_test.go @@ -1,13 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -28,7 +29,7 @@ func TestKAV2_DeleteApplicationCloudWatchLoggingOption(t *testing.T) { }) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descs := out["CloudWatchLoggingOptionDescriptions"].([]any) desc := descs[0].(map[string]any) @@ -77,7 +78,7 @@ func TestKAV2_DeleteApplicationCloudWatchLoggingOption(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descs, ok := out["CloudWatchLoggingOptionDescriptions"].([]any) require.True(t, ok) assert.Len(t, descs, tt.wantCWLCount) @@ -125,7 +126,7 @@ func TestKAV2_DeleteApplicationInputProcessingConfiguration(t *testing.T) { }) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) inputs := out["InputDescriptions"].([]any) input := inputs[0].(map[string]any) inputID := input["InputId"].(string) @@ -226,7 +227,7 @@ func TestKAV2_DeleteApplicationOutput(t *testing.T) { }) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) outputs := out["OutputDescriptions"].([]any) output := outputs[0].(map[string]any) @@ -287,7 +288,7 @@ func TestKAV2_DeleteApplicationOutput(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["ApplicationARN"]) } }) diff --git a/services/kinesisanalyticsv2/handler_application_config_test.go b/services/kinesisanalyticsv2/handler_application_config_test.go index ffc3374611..d105543cf0 100644 --- a/services/kinesisanalyticsv2/handler_application_config_test.go +++ b/services/kinesisanalyticsv2/handler_application_config_test.go @@ -1,13 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -73,7 +74,7 @@ func TestKAV2_AddApplicationCloudWatchLoggingOption(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) descriptions, ok := out["CloudWatchLoggingOptionDescriptions"].([]any) require.True(t, ok) assert.Len(t, descriptions, tt.wantCWLLen) @@ -146,7 +147,7 @@ func TestKAV2_AddApplicationInput(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) inputs, ok := out["InputDescriptions"].([]any) require.True(t, ok) assert.Len(t, inputs, tt.wantInputs) @@ -196,7 +197,7 @@ func TestKAV2_AddApplicationInputProcessingConfiguration(t *testing.T) { }) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) inputs := out["InputDescriptions"].([]any) input := inputs[0].(map[string]any) @@ -328,7 +329,7 @@ func TestKAV2_AddApplicationOutput(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) outputs, ok := out["OutputDescriptions"].([]any) require.True(t, ok) assert.Len(t, outputs, tt.wantOutputs) @@ -422,7 +423,7 @@ func TestKAV2_AddApplicationReferenceDataSource(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) refs, ok := out["ReferenceDataSourceDescriptions"].([]any) require.True(t, ok) assert.Len(t, refs, tt.wantRefs) @@ -512,7 +513,7 @@ func TestKAV2_AddApplicationVpcConfiguration(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["VpcConfigurationDescription"]) vpcDesc, ok := out["VpcConfigurationDescription"].(map[string]any) require.True(t, ok) @@ -545,7 +546,7 @@ func TestKAV2_VpcConfiguration_NonNilSlices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) vpcDesc, ok := out["VpcConfigurationDescription"].(map[string]any) require.True(t, ok) @@ -612,7 +613,7 @@ func TestKAV2_AddOps_VersionBump(t *testing.T) { respBody := tt.setupAdd(h, appName) var out map[string]any - require.NoError(t, json.Unmarshal(respBody, &out)) + require.NoError(t, json.Unmarshal(respBody, &out, jsonx.V1())) versionID, ok := out["ApplicationVersionId"].(float64) require.True(t, ok) assert.InEpsilon(t, 2.0, versionID, 1e-9) diff --git a/services/kinesisanalyticsv2/handler_application_presigned_url.go b/services/kinesisanalyticsv2/handler_application_presigned_url.go index 47c768cc48..66a4855264 100644 --- a/services/kinesisanalyticsv2/handler_application_presigned_url.go +++ b/services/kinesisanalyticsv2/handler_application_presigned_url.go @@ -2,10 +2,12 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createPresignedURLInput struct { @@ -20,7 +22,7 @@ type createPresignedURLOutput struct { func (h *Handler) handleCreateApplicationPresignedURL(ctx context.Context, c *echo.Context, body []byte) error { var in createPresignedURLInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_application_presigned_url_test.go b/services/kinesisanalyticsv2/handler_application_presigned_url_test.go index 3ca738d99f..ac003d620f 100644 --- a/services/kinesisanalyticsv2/handler_application_presigned_url_test.go +++ b/services/kinesisanalyticsv2/handler_application_presigned_url_test.go @@ -1,13 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -59,7 +60,7 @@ func TestKAV2_CreateApplicationPresignedUrl(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) url, ok := out["AuthorizedUrl"].(string) require.True(t, ok) assert.Contains(t, url, "FLINK_DASHBOARD_URL") diff --git a/services/kinesisanalyticsv2/handler_application_snapshots.go b/services/kinesisanalyticsv2/handler_application_snapshots.go index 128de02e0b..a808178339 100644 --- a/services/kinesisanalyticsv2/handler_application_snapshots.go +++ b/services/kinesisanalyticsv2/handler_application_snapshots.go @@ -2,10 +2,12 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createSnapshotInput struct { @@ -34,14 +36,14 @@ type listSnapshotsOutput struct { } type deleteSnapshotInput struct { - ApplicationName string `json:"ApplicationName"` - SnapshotName string `json:"SnapshotName"` - SnapshotCreationTimestamp json.Number `json:"SnapshotCreationTimestamp,omitempty"` + ApplicationName string `json:"ApplicationName"` + SnapshotName string `json:"SnapshotName"` + SnapshotCreationTimestamp jsonx.Number `json:"SnapshotCreationTimestamp,omitempty"` } func (h *Handler) handleCreateApplicationSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var in createSnapshotInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -57,7 +59,7 @@ func (h *Handler) handleCreateApplicationSnapshot(ctx context.Context, c *echo.C func (h *Handler) handleDescribeApplicationSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var in describeSnapshotInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -71,7 +73,7 @@ func (h *Handler) handleDescribeApplicationSnapshot(ctx context.Context, c *echo func (h *Handler) handleListApplicationSnapshots(ctx context.Context, c *echo.Context, body []byte) error { var in listSnapshotsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -90,7 +92,7 @@ func (h *Handler) handleListApplicationSnapshots(ctx context.Context, c *echo.Co func (h *Handler) handleDeleteApplicationSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var in deleteSnapshotInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_application_snapshots_test.go b/services/kinesisanalyticsv2/handler_application_snapshots_test.go index cea70f4004..bf059c3bd7 100644 --- a/services/kinesisanalyticsv2/handler_application_snapshots_test.go +++ b/services/kinesisanalyticsv2/handler_application_snapshots_test.go @@ -1,12 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestKAV2_SnapshotLifecycle(t *testing.T) { @@ -38,7 +40,7 @@ func TestKAV2_SnapshotLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) snaps, ok := listOut["SnapshotSummaries"].([]any) require.True(t, ok) assert.Len(t, snaps, 1) diff --git a/services/kinesisanalyticsv2/handler_application_update.go b/services/kinesisanalyticsv2/handler_application_update.go index 4105dab3a6..5e38724265 100644 --- a/services/kinesisanalyticsv2/handler_application_update.go +++ b/services/kinesisanalyticsv2/handler_application_update.go @@ -2,10 +2,12 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // runConfigurationInput mirrors real AWS's RunConfiguration (StartApplication) @@ -219,7 +221,7 @@ type updateApplicationOutput struct { func (h *Handler) handleUpdateApplication(ctx context.Context, c *echo.Context, body []byte) error { var in updateApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_application_update_test.go b/services/kinesisanalyticsv2/handler_application_update_test.go index 1316c51b10..f8c17a07f1 100644 --- a/services/kinesisanalyticsv2/handler_application_update_test.go +++ b/services/kinesisanalyticsv2/handler_application_update_test.go @@ -1,12 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestKAV2_CreateApplication_ExtendedInlineConfiguration verifies that @@ -57,7 +59,7 @@ func TestKAV2_CreateApplication_ExtendedInlineConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) appConfig := detail["ApplicationConfigurationDescription"].(map[string]any) @@ -110,7 +112,7 @@ func TestKAV2_UpdateApplication_ConditionalTokenWireRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) token, ok := createOut["ApplicationDetail"].(map[string]any)["ConditionalToken"].(string) require.True(t, ok, "expected ConditionalToken in CreateApplication response") require.NotEmpty(t, token) @@ -123,7 +125,7 @@ func TestKAV2_UpdateApplication_ConditionalTokenWireRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) assert.Equal(t, "FLINK-1_19", updateOut["ApplicationDetail"].(map[string]any)["RuntimeEnvironment"]) // A second call reusing the now-stale token must fail. @@ -159,14 +161,14 @@ func TestKAV2_StartApplication_RunConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut["OperationId"]) descRec := doKAV2Request(t, h, "DescribeApplication", map[string]any{"ApplicationName": "start-runcfg-wire-app"}) require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) appConfig := descOut["ApplicationDetail"].(map[string]any)["ApplicationConfigurationDescription"].(map[string]any) runConfig := appConfig["RunConfigurationDescription"].(map[string]any) restoreConfig := runConfig["ApplicationRestoreConfigurationDescription"].(map[string]any) @@ -236,7 +238,7 @@ func TestKAV2_AddDeleteVpcAndCWLOption_WireIncludesOperationID(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) detail := descOut["ApplicationDetail"].(map[string]any) cwlOptions := detail["CloudWatchLoggingOptionDescriptions"].([]any) cwlID := cwlOptions[0].(map[string]any)["CloudWatchLoggingOptionId"].(string) @@ -262,7 +264,7 @@ func assertHasOperationID(t *testing.T, body []byte) { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) opID, ok := out["OperationId"].(string) require.True(t, ok, "expected OperationId in response") assert.NotEmpty(t, opID) @@ -298,7 +300,7 @@ func TestKAV2_StartApplication_SqlRunConfigurations(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) createAppConfig := createOut["ApplicationDetail"].(map[string]any)["ApplicationConfigurationDescription"].(map[string]any) //nolint:lll // test readability createSQLConfig := createAppConfig["SqlApplicationConfigurationDescription"].(map[string]any) inputID := createSQLConfig["InputDescriptions"].([]any)[0].(map[string]any)["InputId"].(string) @@ -322,7 +324,7 @@ func TestKAV2_StartApplication_SqlRunConfigurations(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) appConfig := descOut["ApplicationDetail"].(map[string]any)["ApplicationConfigurationDescription"].(map[string]any) sqlConfig := appConfig["SqlApplicationConfigurationDescription"].(map[string]any) input := sqlConfig["InputDescriptions"].([]any)[0].(map[string]any) @@ -452,7 +454,7 @@ func TestKAV2_DiscoverInputSchema(t *testing.T) { } var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) inputSchema := out["InputSchema"].(map[string]any) assert.Len(t, inputSchema["RecordColumns"], 3) }) @@ -510,7 +512,7 @@ func TestKAV2_CreateApplication_ZeppelinConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) appConfig := detail["ApplicationConfigurationDescription"].(map[string]any) zepDesc := appConfig["ZeppelinApplicationConfigurationDescription"].(map[string]any) @@ -576,7 +578,7 @@ func TestKAV2_UpdateApplication_ZeppelinConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) appConfig := descOut["ApplicationDetail"].(map[string]any)["ApplicationConfigurationDescription"].(map[string]any) zepDesc := appConfig["ZeppelinApplicationConfigurationDescription"].(map[string]any) diff --git a/services/kinesisanalyticsv2/handler_application_versions.go b/services/kinesisanalyticsv2/handler_application_versions.go index a3a1e436c8..1a94ad0774 100644 --- a/services/kinesisanalyticsv2/handler_application_versions.go +++ b/services/kinesisanalyticsv2/handler_application_versions.go @@ -2,12 +2,13 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type describeApplicationOperationInput struct { @@ -93,7 +94,7 @@ type rollbackApplicationOutput struct { func (h *Handler) handleDescribeApplicationOperation(ctx context.Context, c *echo.Context, body []byte) error { var in describeApplicationOperationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -114,7 +115,7 @@ func (h *Handler) handleDescribeApplicationOperation(ctx context.Context, c *ech func (h *Handler) handleListApplicationOperations(ctx context.Context, c *echo.Context, body []byte) error { var in listApplicationOperationsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -144,7 +145,7 @@ func (h *Handler) handleListApplicationOperations(ctx context.Context, c *echo.C func (h *Handler) handleDescribeApplicationVersion(ctx context.Context, c *echo.Context, body []byte) error { var in describeApplicationVersionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -160,7 +161,7 @@ func (h *Handler) handleDescribeApplicationVersion(ctx context.Context, c *echo. func (h *Handler) handleListApplicationVersions(ctx context.Context, c *echo.Context, body []byte) error { var in listApplicationVersionsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -185,7 +186,7 @@ func (h *Handler) handleListApplicationVersions(ctx context.Context, c *echo.Con func (h *Handler) handleRollbackApplication(ctx context.Context, c *echo.Context, body []byte) error { var in rollbackApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_application_versions_test.go b/services/kinesisanalyticsv2/handler_application_versions_test.go index 6312718009..f9707e57ce 100644 --- a/services/kinesisanalyticsv2/handler_application_versions_test.go +++ b/services/kinesisanalyticsv2/handler_application_versions_test.go @@ -1,12 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestKAV2_ApplicationOperationsLifecycle exercises @@ -29,7 +31,7 @@ func TestKAV2_ApplicationOperationsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) opID, ok := startOut["OperationId"].(string) require.True(t, ok) require.NotEmpty(t, opID) @@ -41,7 +43,7 @@ func TestKAV2_ApplicationOperationsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) details, ok := descOut["ApplicationOperationInfoDetails"].(map[string]any) require.True(t, ok) assert.Equal(t, "StartApplication", details["Operation"]) @@ -55,7 +57,7 @@ func TestKAV2_ApplicationOperationsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) items, ok := listOut["ApplicationOperationInfoList"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -103,7 +105,7 @@ func TestKAV2_RollbackApplication(t *testing.T) { require.Equal(t, http.StatusOK, rollbackRec.Code) var rollbackOut map[string]any - require.NoError(t, json.Unmarshal(rollbackRec.Body.Bytes(), &rollbackOut)) + require.NoError(t, json.Unmarshal(rollbackRec.Body.Bytes(), &rollbackOut, jsonx.V1())) assert.NotEmpty(t, rollbackOut["OperationId"]) detail, ok := rollbackOut["ApplicationDetail"].(map[string]any) diff --git a/services/kinesisanalyticsv2/handler_applications.go b/services/kinesisanalyticsv2/handler_applications.go index 45dd23c4fc..8ef6638125 100644 --- a/services/kinesisanalyticsv2/handler_applications.go +++ b/services/kinesisanalyticsv2/handler_applications.go @@ -2,13 +2,14 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maintenanceWindowDuration = 8 * time.Hour @@ -227,8 +228,8 @@ type listApplicationsOutput struct { } type deleteApplicationInput struct { - ApplicationName string `json:"ApplicationName"` - CreateTimestamp json.Number `json:"CreateTimestamp,omitempty"` + ApplicationName string `json:"ApplicationName"` + CreateTimestamp jsonx.Number `json:"CreateTimestamp,omitempty"` } type startApplicationInput struct { @@ -313,7 +314,7 @@ type discoverInputSchemaOutput struct { func (h *Handler) handleCreateApplication(ctx context.Context, c *echo.Context, body []byte) error { var in createApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -575,7 +576,7 @@ func buildZeppelinConfigDesc(in *zeppelinApplicationConfigInput) *ZeppelinApplic func (h *Handler) handleDescribeApplication(ctx context.Context, c *echo.Context, body []byte) error { var in describeApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -591,7 +592,7 @@ func (h *Handler) handleDescribeApplication(ctx context.Context, c *echo.Context func (h *Handler) handleListApplications(ctx context.Context, c *echo.Context, body []byte) error { var in listApplicationsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -612,7 +613,7 @@ func (h *Handler) handleListApplications(ctx context.Context, c *echo.Context, b // DeleteApplication validates against. Returns nil (skip the check) when the // field is absent or unparseable, matching the pre-existing "leniency, never // causes a false accept/reject" note in PARITY.md. -func deleteTimestampSeconds(n json.Number) *float64 { +func deleteTimestampSeconds(n jsonx.Number) *float64 { if n == "" { return nil } @@ -627,7 +628,7 @@ func deleteTimestampSeconds(n json.Number) *float64 { func (h *Handler) handleDeleteApplication(ctx context.Context, c *echo.Context, body []byte) error { var in deleteApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -641,7 +642,7 @@ func (h *Handler) handleDeleteApplication(ctx context.Context, c *echo.Context, func (h *Handler) handleStartApplication(ctx context.Context, c *echo.Context, body []byte) error { var in startApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -662,7 +663,7 @@ func (h *Handler) handleStartApplication(ctx context.Context, c *echo.Context, b func (h *Handler) handleStopApplication(ctx context.Context, c *echo.Context, body []byte) error { var in startStopApplicationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -690,7 +691,7 @@ func (h *Handler) handleUpdateApplicationMaintenanceConfiguration( ctx context.Context, c *echo.Context, body []byte, ) error { var in updateMaintenanceConfigInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -711,7 +712,7 @@ func (h *Handler) handleUpdateApplicationMaintenanceConfiguration( func (h *Handler) handleDiscoverInputSchema(ctx context.Context, c *echo.Context, body []byte) error { var in discoverInputSchemaInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_applications_test.go b/services/kinesisanalyticsv2/handler_applications_test.go index 21840becdb..3542784ecb 100644 --- a/services/kinesisanalyticsv2/handler_applications_test.go +++ b/services/kinesisanalyticsv2/handler_applications_test.go @@ -1,7 +1,7 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -16,7 +17,7 @@ func assertErrorType(t *testing.T, body []byte, wantType string) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, wantType, resp["__type"]) } @@ -24,7 +25,7 @@ func assertAppStatus(t *testing.T, body []byte, wantStatus string) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) detail, ok := resp["ApplicationDetail"].(map[string]any) require.True(t, ok, "response missing ApplicationDetail") @@ -76,7 +77,7 @@ func TestKAV2_CreateApplication(t *testing.T) { if tt.wantStatus == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail, ok := out["ApplicationDetail"].(map[string]any) require.True(t, ok) @@ -184,7 +185,7 @@ func TestKAV2_CreateApplication_InlineConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) // Inline config must not bump the version past 1 -- real AWS keeps a @@ -222,7 +223,7 @@ func TestKAV2_NonNilSlices_InDetailOutput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) // Tags should be present (may be empty but not nil) @@ -292,7 +293,7 @@ func TestKAV2_ListApplications(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, ok := out["ApplicationSummaries"].([]any) require.True(t, ok) @@ -341,7 +342,7 @@ func TestKAV2_StartStopApplication(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) assert.Equal(t, "RUNNING", detail["ApplicationStatus"]) @@ -369,14 +370,14 @@ func TestKAV2_StartStopUpdate_ReturnOperationId(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) assert.NotEmpty(t, startOut["OperationId"]) stopRec := doKAV2Request(t, h, "StopApplication", map[string]any{"ApplicationName": "opid-app"}) require.Equal(t, http.StatusOK, stopRec.Code) var stopOut map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopOut)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopOut, jsonx.V1())) assert.NotEmpty(t, stopOut["OperationId"]) updateRec := doKAV2Request(t, h, "UpdateApplication", map[string]any{ @@ -387,7 +388,7 @@ func TestKAV2_StartStopUpdate_ReturnOperationId(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) assert.NotEmpty(t, updateOut["OperationId"]) } @@ -466,7 +467,7 @@ func TestKAV2_UpdateApplication(t *testing.T) { if tt.wantRole != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) detail := out["ApplicationDetail"].(map[string]any) assert.Equal(t, tt.wantRole, detail["ServiceExecutionRole"]) } diff --git a/services/kinesisanalyticsv2/handler_tags.go b/services/kinesisanalyticsv2/handler_tags.go index ac3e4eb4ae..16fca54a7f 100644 --- a/services/kinesisanalyticsv2/handler_tags.go +++ b/services/kinesisanalyticsv2/handler_tags.go @@ -2,10 +2,12 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceInput struct { @@ -28,7 +30,7 @@ type listTagsOutput struct { func (h *Handler) handleTagResource(ctx context.Context, c *echo.Context, body []byte) error { var in tagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -41,7 +43,7 @@ func (h *Handler) handleTagResource(ctx context.Context, c *echo.Context, body [ func (h *Handler) handleUntagResource(ctx context.Context, c *echo.Context, body []byte) error { var in untagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } @@ -54,7 +56,7 @@ func (h *Handler) handleUntagResource(ctx context.Context, c *echo.Context, body func (h *Handler) handleListTagsForResource(ctx context.Context, c *echo.Context, body []byte) error { var in listTagsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body: "+err.Error()) } diff --git a/services/kinesisanalyticsv2/handler_tags_test.go b/services/kinesisanalyticsv2/handler_tags_test.go index ae6c329917..a01ed56d2e 100644 --- a/services/kinesisanalyticsv2/handler_tags_test.go +++ b/services/kinesisanalyticsv2/handler_tags_test.go @@ -1,12 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestKAV2_TaggingOperations(t *testing.T) { @@ -24,7 +26,7 @@ func TestKAV2_TaggingOperations(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) detail := createOut["ApplicationDetail"].(map[string]any) appARN := detail["ApplicationARN"].(string) @@ -36,7 +38,7 @@ func TestKAV2_TaggingOperations(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tags, ok := listOut["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 1) @@ -62,7 +64,7 @@ func TestKAV2_TaggingOperations(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listOut2, jsonx.V1())) tags2, ok := listOut2["Tags"].([]any) require.True(t, ok) assert.Len(t, tags2, 1) diff --git a/services/kinesisanalyticsv2/handler_test.go b/services/kinesisanalyticsv2/handler_test.go index 65928d1933..aea291aae1 100644 --- a/services/kinesisanalyticsv2/handler_test.go +++ b/services/kinesisanalyticsv2/handler_test.go @@ -2,7 +2,7 @@ package kinesisanalyticsv2_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kinesisanalyticsv2" ) @@ -34,7 +35,7 @@ func doKAV2Request(t *testing.T, h *kinesisanalyticsv2.Handler, op string, body if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) return doRawKAV2Request(t, h, op, bodyBytes) diff --git a/services/kinesisanalyticsv2/persistence.go b/services/kinesisanalyticsv2/persistence.go index f0b5d54854..bd92e73056 100644 --- a/services/kinesisanalyticsv2/persistence.go +++ b/services/kinesisanalyticsv2/persistence.go @@ -2,7 +2,7 @@ package kinesisanalyticsv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -239,7 +239,7 @@ func fromPersistedSnap(p *persistedSnapshot) *Snapshot { // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` // FlinkStopSaves keeps each application's stop-with-savepoint location for RESTORE_FROM_LATEST_SNAPSHOT. FlinkStopSaves map[string]persistedStopSave `json:"flink_stop_saves,omitempty"` NextID int64 `json:"next_id"` diff --git a/services/kinesisanalyticsv2/wire_field_fixes_test.go b/services/kinesisanalyticsv2/wire_field_fixes_test.go index c783448136..e46dc5bdaa 100644 --- a/services/kinesisanalyticsv2/wire_field_fixes_test.go +++ b/services/kinesisanalyticsv2/wire_field_fixes_test.go @@ -1,12 +1,14 @@ package kinesisanalyticsv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestKAV2_UpdateApplication_ApplicationDescription_NotARealField proves @@ -43,7 +45,7 @@ func TestKAV2_UpdateApplication_ApplicationDescription_NotARealField(t *testing. require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) detail, ok := updateOut["ApplicationDetail"].(map[string]any) require.True(t, ok) assert.Equal(t, "original", detail["ApplicationDescription"], @@ -53,7 +55,7 @@ func TestKAV2_UpdateApplication_ApplicationDescription_NotARealField(t *testing. require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) descDetail, ok := descOut["ApplicationDetail"].(map[string]any) require.True(t, ok) assert.Equal(t, "original", descDetail["ApplicationDescription"]) diff --git a/services/kinesisvideo/handler.go b/services/kinesisvideo/handler.go index cfc39c381b..36490c3185 100644 --- a/services/kinesisvideo/handler.go +++ b/services/kinesisvideo/handler.go @@ -1,7 +1,7 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -249,7 +250,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } diff --git a/services/kinesisvideo/handler_configs.go b/services/kinesisvideo/handler_configs.go index 85011a8ac4..e03729a8d3 100644 --- a/services/kinesisvideo/handler_configs.go +++ b/services/kinesisvideo/handler_configs.go @@ -1,10 +1,12 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildConfigOps() map[string]handlerFunc { @@ -18,7 +20,7 @@ func (h *Handler) buildConfigOps() map[string]handlerFunc { func (h *Handler) handleDescribeImageGenerationConfiguration(c *echo.Context, body []byte) error { var req describeImageGenerationConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -34,7 +36,7 @@ func (h *Handler) handleDescribeImageGenerationConfiguration(c *echo.Context, bo func (h *Handler) handleUpdateImageGenerationConfiguration(c *echo.Context, body []byte) error { var req updateImageGenerationConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -49,7 +51,7 @@ func (h *Handler) handleUpdateImageGenerationConfiguration(c *echo.Context, body func (h *Handler) handleDescribeNotificationConfiguration(c *echo.Context, body []byte) error { var req describeNotificationConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -65,7 +67,7 @@ func (h *Handler) handleDescribeNotificationConfiguration(c *echo.Context, body func (h *Handler) handleUpdateNotificationConfiguration(c *echo.Context, body []byte) error { var req updateNotificationConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/handler_edge.go b/services/kinesisvideo/handler_edge.go index c902a64cec..839a155ec6 100644 --- a/services/kinesisvideo/handler_edge.go +++ b/services/kinesisvideo/handler_edge.go @@ -1,10 +1,12 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildEdgeOps() map[string]handlerFunc { @@ -18,7 +20,7 @@ func (h *Handler) buildEdgeOps() map[string]handlerFunc { func (h *Handler) handleStartEdgeConfigurationUpdate(c *echo.Context, body []byte) error { var req edgeStreamRequest - if err := json.Unmarshal(body, &req); err != nil || req.EdgeConfig == nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil || req.EdgeConfig == nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -32,7 +34,7 @@ func (h *Handler) handleStartEdgeConfigurationUpdate(c *echo.Context, body []byt func (h *Handler) handleDescribeEdgeConfiguration(c *echo.Context, body []byte) error { var req edgeStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -46,7 +48,7 @@ func (h *Handler) handleDescribeEdgeConfiguration(c *echo.Context, body []byte) func (h *Handler) handleDeleteEdgeConfiguration(c *echo.Context, body []byte) error { var req edgeStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -59,7 +61,7 @@ func (h *Handler) handleDeleteEdgeConfiguration(c *echo.Context, body []byte) er func (h *Handler) handleListEdgeAgentConfigurations(c *echo.Context, body []byte) error { var req listEdgeAgentConfigurationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/handler_signaling.go b/services/kinesisvideo/handler_signaling.go index 07b7344086..a9d5587384 100644 --- a/services/kinesisvideo/handler_signaling.go +++ b/services/kinesisvideo/handler_signaling.go @@ -1,10 +1,12 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildSignalingOps() map[string]handlerFunc { @@ -19,7 +21,7 @@ func (h *Handler) buildSignalingOps() map[string]handlerFunc { func (h *Handler) handleCreateSignalingChannel(c *echo.Context, body []byte) error { var req createSignalingChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -48,7 +50,7 @@ func (h *Handler) handleCreateSignalingChannel(c *echo.Context, body []byte) err func (h *Handler) handleDescribeSignalingChannel(c *echo.Context, body []byte) error { var req describeSignalingChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -60,9 +62,10 @@ func (h *Handler) handleDescribeSignalingChannel(c *echo.Context, body []byte) e return h.writeJSON(c, describeSignalingChannelResponse{ChannelInfo: channelInfoFromChannel(ch)}) } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleListSignalingChannels(c *echo.Context, body []byte) error { var req listSignalingChannelsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -89,7 +92,7 @@ func (h *Handler) handleListSignalingChannels(c *echo.Context, body []byte) erro func (h *Handler) handleUpdateSignalingChannel(c *echo.Context, body []byte) error { var req updateSignalingChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -117,7 +120,7 @@ func (h *Handler) handleUpdateSignalingChannel(c *echo.Context, body []byte) err func (h *Handler) handleDeleteSignalingChannel(c *echo.Context, body []byte) error { var req deleteSignalingChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/handler_storage.go b/services/kinesisvideo/handler_storage.go index c14b2eed3d..cb307e5515 100644 --- a/services/kinesisvideo/handler_storage.go +++ b/services/kinesisvideo/handler_storage.go @@ -1,10 +1,12 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildStorageOps() map[string]handlerFunc { @@ -19,7 +21,7 @@ func (h *Handler) buildStorageOps() map[string]handlerFunc { func (h *Handler) handleDescribeStreamStorageConfiguration(c *echo.Context, body []byte) error { var req streamStorageConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -37,7 +39,7 @@ func (h *Handler) handleDescribeStreamStorageConfiguration(c *echo.Context, body func (h *Handler) handleUpdateStreamStorageConfiguration(c *echo.Context, body []byte) error { var req streamStorageConfigRequest - if err := json.Unmarshal(body, &req); err != nil || req.StreamStorageConfiguration == nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil || req.StreamStorageConfiguration == nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -52,7 +54,7 @@ func (h *Handler) handleUpdateStreamStorageConfiguration(c *echo.Context, body [ func (h *Handler) handleDescribeMediaStorageConfiguration(c *echo.Context, body []byte) error { var req mediaStorageConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -71,7 +73,12 @@ func (h *Handler) handleDescribeMediaStorageConfiguration(c *echo.Context, body func (h *Handler) handleUpdateMediaStorageConfiguration(c *echo.Context, body []byte) error { var req mediaStorageConfigRequest - if err := json.Unmarshal(body, &req); err != nil || req.MediaStorageConfiguration == nil || req.ChannelARN == "" { + if err := json.Unmarshal( + body, + &req, + jsonx.V1(), + ); err != nil || req.MediaStorageConfiguration == nil || + req.ChannelARN == "" { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -89,7 +96,7 @@ func (h *Handler) handleUpdateMediaStorageConfiguration(c *echo.Context, body [] func (h *Handler) handleGetSignalingChannelEndpoint(c *echo.Context, body []byte) error { var req getSignalingChannelEndpointRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/handler_streams.go b/services/kinesisvideo/handler_streams.go index 6dfd939883..c533f93233 100644 --- a/services/kinesisvideo/handler_streams.go +++ b/services/kinesisvideo/handler_streams.go @@ -1,12 +1,13 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildStreamOps() map[string]handlerFunc { @@ -23,7 +24,7 @@ func (h *Handler) buildStreamOps() map[string]handlerFunc { func (h *Handler) handleCreateStream(c *echo.Context, body []byte) error { var req createStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -49,7 +50,7 @@ func (h *Handler) handleCreateStream(c *echo.Context, body []byte) error { func (h *Handler) handleDescribeStream(c *echo.Context, body []byte) error { var req describeStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -61,9 +62,10 @@ func (h *Handler) handleDescribeStream(c *echo.Context, body []byte) error { return h.writeJSON(c, describeStreamResponse{StreamInfo: streamInfoFromStream(s)}) } +//nolint:dupl // structurally similar JSON-protocol handlers func (h *Handler) handleListStreams(c *echo.Context, body []byte) error { var req listStreamsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -90,7 +92,7 @@ func (h *Handler) handleListStreams(c *echo.Context, body []byte) error { func (h *Handler) handleUpdateStream(c *echo.Context, body []byte) error { var req updateStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -113,7 +115,7 @@ func (h *Handler) handleUpdateStream(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteStream(c *echo.Context, body []byte) error { var req deleteStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -130,7 +132,7 @@ func (h *Handler) handleDeleteStream(c *echo.Context, body []byte) error { func (h *Handler) handleUpdateDataRetention(c *echo.Context, body []byte) error { var req updateDataRetentionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -154,7 +156,7 @@ func (h *Handler) handleUpdateDataRetention(c *echo.Context, body []byte) error func (h *Handler) handleGetDataEndpoint(c *echo.Context, body []byte) error { var req getDataEndpointRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/handler_tags.go b/services/kinesisvideo/handler_tags.go index 7ca8bf6d15..1991b15159 100644 --- a/services/kinesisvideo/handler_tags.go +++ b/services/kinesisvideo/handler_tags.go @@ -1,10 +1,12 @@ package kinesisvideo import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) buildTagOps() map[string]handlerFunc { @@ -20,7 +22,7 @@ func (h *Handler) buildTagOps() map[string]handlerFunc { func (h *Handler) handleTagStream(c *echo.Context, body []byte) error { var req tagStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -33,7 +35,7 @@ func (h *Handler) handleTagStream(c *echo.Context, body []byte) error { func (h *Handler) handleUntagStream(c *echo.Context, body []byte) error { var req untagStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -46,7 +48,7 @@ func (h *Handler) handleUntagStream(c *echo.Context, body []byte) error { func (h *Handler) handleListTagsForStream(c *echo.Context, body []byte) error { var req listTagsForStreamRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -60,7 +62,7 @@ func (h *Handler) handleListTagsForStream(c *echo.Context, body []byte) error { func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -82,7 +84,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } @@ -99,7 +101,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { func (h *Handler) handleListTagsForResource(c *echo.Context, body []byte) error { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidArgumentException", "invalid request body") } diff --git a/services/kinesisvideo/multi_region_test.go b/services/kinesisvideo/multi_region_test.go index 15609ee2b9..ba9f68a039 100644 --- a/services/kinesisvideo/multi_region_test.go +++ b/services/kinesisvideo/multi_region_test.go @@ -2,7 +2,8 @@ package kinesisvideo_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/kinesisvideo" ) @@ -175,8 +177,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/kinesisvideo/persistence.go b/services/kinesisvideo/persistence.go index a80dab1c03..b31210ac69 100644 --- a/services/kinesisvideo/persistence.go +++ b/services/kinesisvideo/persistence.go @@ -2,7 +2,7 @@ package kinesisvideo import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" @@ -29,8 +29,8 @@ const kinesisvideoSnapshotVersion = 1 // one JSON-encoded array per registered table name (streams, channels -- see // store_setup.go), produced by b.registry.SnapshotAll(). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes backend state to JSON. diff --git a/services/kms/aliases_test.go b/services/kms/aliases_test.go index c9042ddcc2..f5ed225276 100644 --- a/services/kms/aliases_test.go +++ b/services/kms/aliases_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "strings" @@ -631,7 +632,7 @@ func TestCreateAlias_InvalidName_WireErrorType(t *testing.T) { var body struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidAliasNameException", body.Type) assert.NotEqual(t, "ValidationException", body.Type) } diff --git a/services/kms/custom_key_stores_test.go b/services/kms/custom_key_stores_test.go index 3f2baa09b8..0c69147653 100644 --- a/services/kms/custom_key_stores_test.go +++ b/services/kms/custom_key_stores_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -558,7 +559,7 @@ func TestCustomKeyStore_StateGuards_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) connectBody := `{"CustomKeyStoreId":"` + createOut.CustomKeyStoreID + `"}` require.Equal(t, http.StatusOK, doKMSRequest(t, h, "ConnectCustomKeyStore", connectBody).Code) @@ -567,7 +568,7 @@ func TestCustomKeyStore_StateGuards_WireErrorType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "CustomKeyStoreInvalidStateException", errResp.Type) }) @@ -580,14 +581,14 @@ func TestCustomKeyStore_StateGuards_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) disconnectBody := `{"CustomKeyStoreId":"` + createOut.CustomKeyStoreID + `"}` rec := doKMSRequest(t, h, "DisconnectCustomKeyStore", disconnectBody) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "CustomKeyStoreInvalidStateException", errResp.Type) }) @@ -600,7 +601,7 @@ func TestCustomKeyStore_StateGuards_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) idBody := `{"CustomKeyStoreId":"` + createOut.CustomKeyStoreID + `"}` require.Equal(t, http.StatusOK, doKMSRequest(t, h, "ConnectCustomKeyStore", idBody).Code) @@ -609,7 +610,7 @@ func TestCustomKeyStore_StateGuards_WireErrorType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "CustomKeyStoreInvalidStateException", errResp.Type) }) } @@ -629,7 +630,7 @@ func TestDeleteCustomKeyStore_HasKeys_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) idBody := `{"CustomKeyStoreId":"` + createOut.CustomKeyStoreID + `"}` require.Equal(t, http.StatusOK, doKMSRequest(t, h, "ConnectCustomKeyStore", idBody).Code) @@ -643,7 +644,7 @@ func TestDeleteCustomKeyStore_HasKeys_WireErrorType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "CustomKeyStoreHasCMKsException", errResp.Type) } @@ -662,7 +663,7 @@ func TestEncrypt_DisconnectedCustomKeyStore_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) idBody := `{"CustomKeyStoreId":"` + createOut.CustomKeyStoreID + `"}` require.Equal(t, http.StatusOK, doKMSRequest(t, h, "ConnectCustomKeyStore", idBody).Code) @@ -672,7 +673,7 @@ func TestEncrypt_DisconnectedCustomKeyStore_WireErrorType(t *testing.T) { require.Equal(t, http.StatusOK, createKeyRec.Code) var createKeyOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createKeyRec.Body.Bytes(), &createKeyOut)) + require.NoError(t, json.Unmarshal(createKeyRec.Body.Bytes(), &createKeyOut, jsonx.V1())) require.Equal(t, http.StatusOK, doKMSRequest(t, h, "DisconnectCustomKeyStore", idBody).Code) @@ -681,6 +682,6 @@ func TestEncrypt_DisconnectedCustomKeyStore_WireErrorType(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) } diff --git a/services/kms/describe_key_grant_tokens_test.go b/services/kms/describe_key_grant_tokens_test.go index 42e3cf0d5d..3881e0af5a 100644 --- a/services/kms/describe_key_grant_tokens_test.go +++ b/services/kms/describe_key_grant_tokens_test.go @@ -16,12 +16,13 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -31,7 +32,7 @@ func TestDescribeKey_GrantTokens_WireField(t *testing.T) { // The field must deserialize from the AWS wire name "GrantTokens". var input kms.DescribeKeyInput require.NoError(t, json.Unmarshal( - []byte(`{"KeyId":"k-123","GrantTokens":["tok-a","tok-b"]}`), &input)) + []byte(`{"KeyId":"k-123","GrantTokens":["tok-a","tok-b"]}`), &input, jsonx.V1())) assert.Equal(t, "k-123", input.KeyID) assert.Equal(t, []string{"tok-a", "tok-b"}, input.GrantTokens) } diff --git a/services/kms/errtarget_findings_test.go b/services/kms/errtarget_findings_test.go index fb0f0d9f3d..0bc62aded8 100644 --- a/services/kms/errtarget_findings_test.go +++ b/services/kms/errtarget_findings_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -34,7 +35,7 @@ func TestCancelKeyDeletion_DisabledKey_NotDisabledException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) desc, err := h.Backend.DescribeKey(context.Background(), &kms.DescribeKeyInput{KeyID: keyID}) @@ -73,7 +74,7 @@ func TestDecrypt_DisabledKey_StillDisabledException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "DisabledException", errResp.Type) } @@ -119,7 +120,7 @@ func TestImportKeyMaterial_DisabledKey_NotDisabledException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) require.NoError(t, h.Backend.EnableKey(context.Background(), &kms.EnableKeyInput{KeyID: keyID})) @@ -169,7 +170,7 @@ func TestVerifyMac_Mismatch_IsInvalidMac_NotInvalidSignature(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidMacException", errResp.Type) } @@ -211,6 +212,6 @@ func TestDecrypt_ExpiredImportedMaterial_KMSInvalidStateException(t *testing.T) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) } diff --git a/services/kms/grant_authz.go b/services/kms/grant_authz.go index 511503e250..19a8533290 100644 --- a/services/kms/grant_authz.go +++ b/services/kms/grant_authz.go @@ -2,13 +2,15 @@ package kms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strings" gopherarn "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -162,16 +164,16 @@ func statementCoversAction(s *policyStatement, action string) bool { func keyPolicyAllows(policy string, p *awsmeta.Principal, action string) bool { var doc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if json.Unmarshal([]byte(policy), &doc) != nil { + if json.Unmarshal([]byte(policy), &doc, jsonx.V1()) != nil { return false } var stmts []policyStatement - if json.Unmarshal(doc.Statement, &stmts) != nil { + if json.Unmarshal(doc.Statement, &stmts, jsonx.V1()) != nil { var one policyStatement - if json.Unmarshal(doc.Statement, &one) != nil { + if json.Unmarshal(doc.Statement, &one, jsonx.V1()) != nil { return false } diff --git a/services/kms/handler.go b/services/kms/handler.go index 12296e1dd3..b149104cab 100644 --- a/services/kms/handler.go +++ b/services/kms/handler.go @@ -2,7 +2,7 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -215,7 +216,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -262,7 +263,7 @@ func (h *Handler) buildDispatchTable() map[string]kmsActionFn { func unmarshalAction[T any](fn func(context.Context, *T) (any, error)) kmsActionFn { return func(ctx context.Context, b []byte) (any, error) { var input T - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -300,7 +301,7 @@ func (h *Handler) dispatch(ctx context.Context, r *http.Request, action string, return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a structured error response for a KMS operation failure. @@ -389,7 +390,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin payload, _ := json.Marshal(ErrorResponse{ Type: errorType, Message: errorMessage(reqErr), - }) + }, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/kms/handler_alias_rotation.go b/services/kms/handler_alias_rotation.go index 97a503ee54..0222fa60bc 100644 --- a/services/kms/handler_alias_rotation.go +++ b/services/kms/handler_alias_rotation.go @@ -2,7 +2,9 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildAliasRotationActions returns dispatch entries for alias management and key rotation. @@ -10,7 +12,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { return map[string]kmsActionFn{ "CreateAlias": func(ctx context.Context, b []byte) (any, error) { var input CreateAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -18,7 +20,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "UpdateAlias": func(ctx context.Context, b []byte) (any, error) { var input UpdateAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -26,7 +28,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "DeleteAlias": func(ctx context.Context, b []byte) (any, error) { var input DeleteAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -34,7 +36,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "ListAliases": func(ctx context.Context, b []byte) (any, error) { var input ListAliasesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -42,7 +44,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "EnableKeyRotation": func(ctx context.Context, b []byte) (any, error) { var input EnableKeyRotationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -50,7 +52,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "DisableKeyRotation": func(ctx context.Context, b []byte) (any, error) { var input DisableKeyRotationInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -58,7 +60,7 @@ func (h *Handler) buildAliasRotationActions() map[string]kmsActionFn { }, "GetKeyRotationStatus": func(ctx context.Context, b []byte) (any, error) { var input GetKeyRotationStatusInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_alias_rotation_test.go b/services/kms/handler_alias_rotation_test.go index f3de8b48df..cc7dd8349b 100644 --- a/services/kms/handler_alias_rotation_test.go +++ b/services/kms/handler_alias_rotation_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "strings" @@ -39,7 +40,7 @@ func TestHandler_ListAliases_FilterByKeyID_ViaHTTP(t *testing.T) { AliasName string `json:"AliasName"` } `json:"Aliases"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.Aliases) for _, a := range resp.Aliases { assert.Equal(t, "alias/filter-test", a.AliasName) @@ -61,7 +62,7 @@ func TestHandler_RotateKeyOnDemand_ViaHTTP(t *testing.T) { var resp struct { KeyID string `json:"KeyId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, keyID, resp.KeyID) } @@ -86,7 +87,7 @@ func TestHandler_EnableKeyRotation_ViaHTTP(t *testing.T) { KeyRotationEnabled bool `json:"KeyRotationEnabled"` RotationPeriodInDays int32 `json:"RotationPeriodInDays"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &status)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &status, jsonx.V1())) assert.True(t, status.KeyRotationEnabled) assert.Equal(t, int32(90), status.RotationPeriodInDays) } @@ -107,14 +108,14 @@ func TestKMSHandlerAliasOperations(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq, createRec))) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut.KeyMetadata.KeyID // CreateAlias aliasBody, _ := json.Marshal(map[string]string{ "AliasName": "alias/test-alias", "TargetKeyId": keyID, - }) + }, jsonx.V1()) aliasReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(aliasBody))) aliasReq.Header.Set("X-Amz-Target", "TrentService.CreateAlias") aliasRec := httptest.NewRecorder() @@ -128,11 +129,11 @@ func TestKMSHandlerAliasOperations(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(listReq, listRec))) var listOut kms.ListAliasesOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.Aliases, 1) // DeleteAlias - deleteBody, _ := json.Marshal(map[string]string{"AliasName": "alias/test-alias"}) + deleteBody, _ := json.Marshal(map[string]string{"AliasName": "alias/test-alias"}, jsonx.V1()) deleteReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(deleteBody))) deleteReq.Header.Set("X-Amz-Target", "TrentService.DeleteAlias") deleteRec := httptest.NewRecorder() @@ -156,11 +157,11 @@ func TestKMSHandlerKeyRotation(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq, createRec))) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut.KeyMetadata.KeyID // GetKeyRotationStatus - statusBody, _ := json.Marshal(map[string]string{"KeyID": keyID}) + statusBody, _ := json.Marshal(map[string]string{"KeyID": keyID}, jsonx.V1()) statusReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(statusBody))) statusReq.Header.Set("X-Amz-Target", "TrentService.GetKeyRotationStatus") @@ -168,11 +169,11 @@ func TestKMSHandlerKeyRotation(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(statusReq, statusRec))) var statusOut kms.GetKeyRotationStatusOutput - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusOut)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusOut, jsonx.V1())) assert.False(t, statusOut.KeyRotationEnabled) // EnableKeyRotation - enableBody, _ := json.Marshal(map[string]string{"KeyID": keyID}) + enableBody, _ := json.Marshal(map[string]string{"KeyID": keyID}, jsonx.V1()) enableReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(enableBody))) enableReq.Header.Set("X-Amz-Target", "TrentService.EnableKeyRotation") enableRec := httptest.NewRecorder() @@ -180,7 +181,7 @@ func TestKMSHandlerKeyRotation(t *testing.T) { assert.Equal(t, http.StatusOK, enableRec.Code) // DisableKeyRotation - disableBody, _ := json.Marshal(map[string]string{"KeyID": keyID}) + disableBody, _ := json.Marshal(map[string]string{"KeyID": keyID}, jsonx.V1()) disableReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(disableBody))) disableReq.Header.Set("X-Amz-Target", "TrentService.DisableKeyRotation") disableRec := httptest.NewRecorder() @@ -293,7 +294,7 @@ func TestKMSHandlerErrorCases(t *testing.T) { assert.Equal(t, tt.expectedStatus, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.expectedErrTyp, errResp.Type) }) } @@ -321,7 +322,7 @@ func TestKMSListAliasesFiltered(t *testing.T) { ) // Filter by key1 - body, _ := json.Marshal(map[string]string{"KeyId": key1.KeyMetadata.KeyID}) + body, _ := json.Marshal(map[string]string{"KeyId": key1.KeyMetadata.KeyID}, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body))) req.Header.Set("X-Amz-Target", "TrentService.ListAliases") rec := httptest.NewRecorder() @@ -329,7 +330,7 @@ func TestKMSListAliasesFiltered(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out kms.ListAliasesOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Aliases, 1) assert.Equal(t, "alias/key1", out.Aliases[0].AliasName) } @@ -354,7 +355,7 @@ func TestKMSUpdateAlias_Handler(t *testing.T) { body, err := json.Marshal(kms.UpdateAliasInput{ AliasName: "alias/handler-update-test", TargetKeyID: k2.KeyMetadata.KeyID, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doKMSHTTPRequest(t, h, "UpdateAlias", string(body)) @@ -377,7 +378,7 @@ func TestHandlerCreateAliasWhitespaceRejected(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) rec := sendKMSOp(t, h, "CreateAlias", `{"AliasName":"alias/my bad alias","TargetKeyId":"`+keyID+`"}`) @@ -394,7 +395,7 @@ func TestHandlerListKeyRotationsViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) sendKMSOp(t, h, "RotateKeyOnDemand", `{"KeyId":"`+keyID+`"}`) @@ -403,7 +404,7 @@ func TestHandlerListKeyRotationsViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) rotations := out["Rotations"].([]any) require.NotEmpty(t, rotations) diff --git a/services/kms/handler_crypto.go b/services/kms/handler_crypto.go index 5229e6a679..4180a76991 100644 --- a/services/kms/handler_crypto.go +++ b/services/kms/handler_crypto.go @@ -2,7 +2,9 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildCryptoActions returns dispatch entries for encrypt, decrypt, sign, verify, and data-key operations. @@ -10,7 +12,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { return map[string]kmsActionFn{ opEncrypt: func(ctx context.Context, b []byte) (any, error) { var input EncryptInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -18,7 +20,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opDecrypt: func(ctx context.Context, b []byte) (any, error) { var input DecryptInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -26,7 +28,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opGenerateDataKey: func(ctx context.Context, b []byte) (any, error) { var input GenerateDataKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -34,7 +36,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opGenerateDataKeyWithoutPlaintext: func(ctx context.Context, b []byte) (any, error) { var input GenerateDataKeyWithoutPlaintextInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -42,7 +44,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, "ReEncrypt": func(ctx context.Context, b []byte) (any, error) { var input ReEncryptInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -50,7 +52,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opSign: func(ctx context.Context, b []byte) (any, error) { var input SignInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -58,7 +60,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opVerify: func(ctx context.Context, b []byte) (any, error) { var input VerifyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -66,7 +68,7 @@ func (h *Handler) buildCryptoActions() map[string]kmsActionFn { }, opGetPublicKey: func(ctx context.Context, b []byte) (any, error) { var input GetPublicKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_crypto_test.go b/services/kms/handler_crypto_test.go index 1cde50a4dc..e323e5b71b 100644 --- a/services/kms/handler_crypto_test.go +++ b/services/kms/handler_crypto_test.go @@ -1,7 +1,7 @@ package kms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "encoding/base64" @@ -32,7 +33,7 @@ func TestKMSHandlerEncryptDecrypt(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq, createRec))) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut.KeyMetadata.KeyID @@ -40,7 +41,7 @@ func TestKMSHandlerEncryptDecrypt(t *testing.T) { encBody, _ := json.Marshal(map[string]any{ "KeyID": keyID, "Plaintext": []byte("my-secret"), - }) + }, jsonx.V1()) encReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(encBody))) encReq.Header.Set("X-Amz-Target", "TrentService.Encrypt") encRec := httptest.NewRecorder() @@ -48,10 +49,10 @@ func TestKMSHandlerEncryptDecrypt(t *testing.T) { assert.Equal(t, http.StatusOK, encRec.Code) var encOut kms.EncryptOutput - require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut)) + require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut, jsonx.V1())) // Decrypt - decBody, _ := json.Marshal(map[string]any{"CiphertextBlob": encOut.CiphertextBlob}) + decBody, _ := json.Marshal(map[string]any{"CiphertextBlob": encOut.CiphertextBlob}, jsonx.V1()) decReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(decBody))) decReq.Header.Set("X-Amz-Target", "TrentService.Decrypt") decRec := httptest.NewRecorder() @@ -59,7 +60,7 @@ func TestKMSHandlerEncryptDecrypt(t *testing.T) { assert.Equal(t, http.StatusOK, decRec.Code) var decOut kms.DecryptOutput - require.NoError(t, json.Unmarshal(decRec.Body.Bytes(), &decOut)) + require.NoError(t, json.Unmarshal(decRec.Body.Bytes(), &decOut, jsonx.V1())) assert.Equal(t, []byte("my-secret"), decOut.Plaintext) } @@ -79,7 +80,7 @@ func TestKMSHandlerReEncrypt(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq1, createRec1))) var out1 kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(createRec1.Body.Bytes(), &out1, jsonx.V1())) createReq2 := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`)) createReq2.Header.Set("X-Amz-Target", "TrentService.CreateKey") @@ -87,26 +88,26 @@ func TestKMSHandlerReEncrypt(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq2, createRec2))) var out2 kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(createRec2.Body.Bytes(), &out2, jsonx.V1())) // Encrypt with key1 encBody, _ := json.Marshal(map[string]any{ "KeyId": out1.KeyMetadata.KeyID, "Plaintext": []byte("reencrypt-me"), - }) + }, jsonx.V1()) encReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(encBody))) encReq.Header.Set("X-Amz-Target", "TrentService.Encrypt") encRec := httptest.NewRecorder() require.NoError(t, h.Handler()(e.NewContext(encReq, encRec))) var encOut kms.EncryptOutput - require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut)) + require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut, jsonx.V1())) // ReEncrypt with key2 reEncBody, _ := json.Marshal(map[string]any{ "CiphertextBlob": encOut.CiphertextBlob, "DestinationKeyId": out2.KeyMetadata.KeyID, - }) + }, jsonx.V1()) reEncReq := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(reEncBody))) reEncReq.Header.Set("X-Amz-Target", "TrentService.ReEncrypt") reEncRec := httptest.NewRecorder() @@ -114,7 +115,7 @@ func TestKMSHandlerReEncrypt(t *testing.T) { assert.Equal(t, http.StatusOK, reEncRec.Code) var reEncOut kms.ReEncryptOutput - require.NoError(t, json.Unmarshal(reEncRec.Body.Bytes(), &reEncOut)) + require.NoError(t, json.Unmarshal(reEncRec.Body.Bytes(), &reEncOut, jsonx.V1())) assert.Equal(t, out2.KeyMetadata.Arn, reEncOut.KeyID) } @@ -140,7 +141,7 @@ func TestKMSHandlerInternalError(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidCiphertextException", errResp.Type) } @@ -154,12 +155,12 @@ func TestKMSHandlerSignVerify(t *testing.T) { createBody, _ := json.Marshal(map[string]any{ "KeyUsage": kms.KeyUsageSignVerify, "KeySpec": "RSA_2048", - }) + }, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "CreateKey", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) keyMeta, ok := createResp["KeyMetadata"].(map[string]any) require.True(t, ok) keyID, ok := keyMeta["KeyId"].(string) @@ -171,12 +172,12 @@ func TestKMSHandlerSignVerify(t *testing.T) { "Message": message, "MessageType": "RAW", "SigningAlgorithm": "RSASSA_PSS_SHA_256", - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "Sign", string(signBody)) require.Equal(t, http.StatusOK, rec.Code) var signResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &signResp, jsonx.V1())) sigRaw, ok := signResp["Signature"] require.True(t, ok) assert.NotEmpty(t, sigRaw) @@ -188,12 +189,12 @@ func TestKMSHandlerSignVerify(t *testing.T) { "MessageType": "RAW", "Signature": sigRaw, "SigningAlgorithm": "RSASSA_PSS_SHA_256", - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "Verify", string(verifyBody)) require.Equal(t, http.StatusOK, rec.Code) var verifyResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verifyResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verifyResp, jsonx.V1())) assert.True(t, verifyResp["SignatureValid"].(bool)) } @@ -206,21 +207,21 @@ func TestKMSHandlerGetPublicKey(t *testing.T) { createBody, _ := json.Marshal(map[string]any{ "KeyUsage": kms.KeyUsageSignVerify, "KeySpec": "ECC_NIST_P256", - }) + }, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "CreateKey", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) keyMeta := createResp["KeyMetadata"].(map[string]any) keyID := keyMeta["KeyId"].(string) - getKeyBody, _ := json.Marshal(map[string]any{"KeyId": keyID}) + getKeyBody, _ := json.Marshal(map[string]any{"KeyId": keyID}, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "GetPublicKey", string(getKeyBody)) require.Equal(t, http.StatusOK, rec.Code) var pubResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pubResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pubResp, jsonx.V1())) assert.NotEmpty(t, pubResp["PublicKey"]) assert.Equal(t, "ECC_NIST_P256", pubResp["KeySpec"]) } @@ -234,12 +235,12 @@ func TestKMSHandlerInvalidSignatureError(t *testing.T) { createBody, _ := json.Marshal(map[string]any{ "KeyUsage": kms.KeyUsageSignVerify, "KeySpec": "RSA_2048", - }) + }, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "CreateKey", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) keyMeta := createResp["KeyMetadata"].(map[string]any) keyID := keyMeta["KeyId"].(string) @@ -250,13 +251,13 @@ func TestKMSHandlerInvalidSignatureError(t *testing.T) { "MessageType": "RAW", "Signature": badSig, "SigningAlgorithm": "RSASSA_PSS_SHA_256", - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "Verify", string(verifyBody)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidSignatureException", errResp["__type"]) } @@ -270,7 +271,7 @@ func TestHandlerEncryptReturnsAlgorithm(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) plaintext := base64.StdEncoding.EncodeToString([]byte("test-plaintext")) @@ -278,6 +279,6 @@ func TestHandlerEncryptReturnsAlgorithm(t *testing.T) { require.Equal(t, http.StatusOK, encRec.Code) var encOut map[string]any - require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut)) + require.NoError(t, json.Unmarshal(encRec.Body.Bytes(), &encOut, jsonx.V1())) assert.Equal(t, "SYMMETRIC_DEFAULT", encOut["EncryptionAlgorithm"]) } diff --git a/services/kms/handler_custom_key_stores.go b/services/kms/handler_custom_key_stores.go index b82bb73e6f..c043e2ba79 100644 --- a/services/kms/handler_custom_key_stores.go +++ b/services/kms/handler_custom_key_stores.go @@ -2,7 +2,9 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildCustomKeyStoreActions returns dispatch entries for custom key store and ECDH operations. @@ -10,7 +12,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { return map[string]kmsActionFn{ "CreateCustomKeyStore": func(ctx context.Context, b []byte) (any, error) { var input CreateCustomKeyStoreInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -18,7 +20,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { }, "DeleteCustomKeyStore": func(ctx context.Context, b []byte) (any, error) { var input DeleteCustomKeyStoreInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -26,7 +28,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { }, "DescribeCustomKeyStores": func(ctx context.Context, b []byte) (any, error) { var input DescribeCustomKeyStoresInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -34,7 +36,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { }, "ConnectCustomKeyStore": func(ctx context.Context, b []byte) (any, error) { var input ConnectCustomKeyStoreInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -42,7 +44,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { }, "DisconnectCustomKeyStore": func(ctx context.Context, b []byte) (any, error) { var input DisconnectCustomKeyStoreInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -50,7 +52,7 @@ func (h *Handler) buildCustomKeyStoreActions() map[string]kmsActionFn { }, opDeriveSharedSecret: func(ctx context.Context, b []byte) (any, error) { var input DeriveSharedSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_custom_key_stores_test.go b/services/kms/handler_custom_key_stores_test.go index a570c0b2b0..96c22eba16 100644 --- a/services/kms/handler_custom_key_stores_test.go +++ b/services/kms/handler_custom_key_stores_test.go @@ -3,7 +3,7 @@ package kms_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -31,7 +32,7 @@ func TestHandler_XKS_CreateDescribeViaHTTP(t *testing.T) { var createResp struct { CustomKeyStoreID string `json:"CustomKeyStoreId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotEmpty(t, createResp.CustomKeyStoreID) // Describe the XKS store @@ -45,7 +46,7 @@ func TestHandler_XKS_CreateDescribeViaHTTP(t *testing.T) { ConnectionState string `json:"ConnectionState"` } `json:"CustomKeyStores"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) require.Len(t, descResp.CustomKeyStores, 1) assert.Equal(t, "EXTERNAL_KEY_STORE", descResp.CustomKeyStores[0].CustomKeyStoreType) assert.Equal(t, kms.ConnectionStateDisconnected, descResp.CustomKeyStores[0].ConnectionState) @@ -129,7 +130,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.CustomKeyStoreID) }, }, @@ -150,7 +151,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.DescribeCustomKeyStoresOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.CustomKeyStores, 1) }, }, @@ -199,7 +200,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.GenerateRandomOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Plaintext, 32) }, }, @@ -223,7 +224,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.GenerateMacOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.Mac) }, }, @@ -241,7 +242,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.GenerateDataKeyPairOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.PublicKey) assert.NotEmpty(t, out.PrivateKeyPlaintext) }, @@ -260,7 +261,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.GenerateDataKeyPairWithoutPlaintextOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.PublicKey) assert.NotEmpty(t, out.PrivateKeyCiphertextBlob) }, @@ -282,7 +283,7 @@ func TestHandlerDispatchesCustomKeyStoreAndCryptoOps(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.DeriveSharedSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.SharedSecret) }, }, @@ -349,7 +350,7 @@ func TestHandlerResetClearsCustomKeyStores(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out kms.DescribeCustomKeyStoresOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.CustomKeyStores) } @@ -365,21 +366,21 @@ func TestCustomKeyStoreHandler_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut kms.CreateCustomKeyStoreOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) storeID := createOut.CustomKeyStoreID // Connect via HTTP. - connectBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}) + connectBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}, jsonx.V1()) rec = postKMSOp(t, h, "ConnectCustomKeyStore", string(connectBody)) require.Equal(t, http.StatusOK, rec.Code) // Describe via HTTP – should be CONNECTED. - descBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}) + descBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}, jsonx.V1()) rec = postKMSOp(t, h, "DescribeCustomKeyStores", string(descBody)) require.Equal(t, http.StatusOK, rec.Code) var descOut kms.DescribeCustomKeyStoresOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descOut, jsonx.V1())) require.Len(t, descOut.CustomKeyStores, 1) assert.Equal(t, kms.ConnectionStateConnected, descOut.CustomKeyStores[0].ConnectionState) @@ -388,7 +389,7 @@ func TestCustomKeyStoreHandler_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) // Delete via HTTP. - deleteBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}) + deleteBody, _ := json.Marshal(map[string]string{"CustomKeyStoreId": storeID}, jsonx.V1()) rec = postKMSOp(t, h, "DeleteCustomKeyStore", string(deleteBody)) require.Equal(t, http.StatusOK, rec.Code) } diff --git a/services/kms/handler_generate_and_mac.go b/services/kms/handler_generate_and_mac.go index 4908a92a5f..b596e2a7e7 100644 --- a/services/kms/handler_generate_and_mac.go +++ b/services/kms/handler_generate_and_mac.go @@ -2,7 +2,9 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildGenerateAndMacActions returns dispatch entries for data key pair, MAC, and random operations. @@ -10,7 +12,7 @@ func (h *Handler) buildGenerateAndMacActions() map[string]kmsActionFn { return map[string]kmsActionFn{ opGenerateDataKeyPair: func(ctx context.Context, b []byte) (any, error) { var input GenerateDataKeyPairInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -18,7 +20,7 @@ func (h *Handler) buildGenerateAndMacActions() map[string]kmsActionFn { }, opGenerateDataKeyPairWithoutPlaintext: func(ctx context.Context, b []byte) (any, error) { var input GenerateDataKeyPairWithoutPlaintextInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -26,7 +28,7 @@ func (h *Handler) buildGenerateAndMacActions() map[string]kmsActionFn { }, opGenerateMac: func(ctx context.Context, b []byte) (any, error) { var input GenerateMacInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -34,7 +36,7 @@ func (h *Handler) buildGenerateAndMacActions() map[string]kmsActionFn { }, "GenerateRandom": func(ctx context.Context, b []byte) (any, error) { var input GenerateRandomInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -42,7 +44,7 @@ func (h *Handler) buildGenerateAndMacActions() map[string]kmsActionFn { }, opVerifyMac: func(ctx context.Context, b []byte) (any, error) { var input VerifyMacInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_generate_and_mac_test.go b/services/kms/handler_generate_and_mac_test.go index fa11021028..423ce7d907 100644 --- a/services/kms/handler_generate_and_mac_test.go +++ b/services/kms/handler_generate_and_mac_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "strings" @@ -38,12 +39,12 @@ func TestVerifyMac_Handler(t *testing.T) { "MacAlgorithm": "HMAC_SHA_256", "Message": msg, "Mac": macOut.Mac, - }) + }, jsonx.V1()) rec := sendKMSOp(t, h, "VerifyMac", string(bodyBytes)) require.Equal(t, http.StatusOK, rec.Code) var verOut kms.VerifyMacOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verOut, jsonx.V1())) assert.True(t, verOut.MacValid) } @@ -63,10 +64,10 @@ func TestKMSHandlerGenerateDataKey(t *testing.T) { require.NoError(t, h.Handler()(e.NewContext(createReq, createRec))) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut.KeyMetadata.KeyID - body, _ := json.Marshal(map[string]string{"KeyID": keyID, "KeySpec": "AES_256"}) + body, _ := json.Marshal(map[string]string{"KeyID": keyID, "KeySpec": "AES_256"}, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body))) req.Header.Set("X-Amz-Target", "TrentService.GenerateDataKey") rec := httptest.NewRecorder() @@ -74,7 +75,7 @@ func TestKMSHandlerGenerateDataKey(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out kms.GenerateDataKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Plaintext, 32) assert.NotEmpty(t, out.CiphertextBlob) } @@ -90,11 +91,11 @@ func TestKMSGenerateDataKeyWithoutPlaintext(t *testing.T) { require.NoError(t, err) keyID := keyOut.KeyMetadata.KeyID - body, _ := json.Marshal(map[string]string{"KeyId": keyID, "KeySpec": "AES_256"}) + body, _ := json.Marshal(map[string]string{"KeyId": keyID, "KeySpec": "AES_256"}, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "GenerateDataKeyWithoutPlaintext", string(body)) assert.Equal(t, http.StatusOK, rec.Code) var out kms.GenerateDataKeyWithoutPlaintextOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.CiphertextBlob) assert.Equal(t, keyOut.KeyMetadata.Arn, out.KeyID) } @@ -129,7 +130,7 @@ func TestKMS_InvalidKeySpec_Returns400UnsupportedOperationException(t *testing.T body, err := json.Marshal(map[string]string{ "KeyId": wrapKeyID, "KeyPairSpec": "BOGUS_SPEC", - }) + }, jsonx.V1()) require.NoError(t, err) return "TrentService.GenerateDataKeyPair", string(body) @@ -165,7 +166,7 @@ func TestKMS_InvalidKeySpec_Returns400UnsupportedOperationException(t *testing.T assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnsupportedOperationException", errResp.Type) }) } diff --git a/services/kms/handler_grants_policies.go b/services/kms/handler_grants_policies.go index 528f8b5531..b88bf70a7c 100644 --- a/services/kms/handler_grants_policies.go +++ b/services/kms/handler_grants_policies.go @@ -2,8 +2,10 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildGrantPolicyActions returns dispatch entries for grant and key policy operations. @@ -11,7 +13,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { return map[string]kmsActionFn{ opCreateGrant: func(ctx context.Context, b []byte) (any, error) { var input CreateGrantInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -19,7 +21,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { }, "ListGrants": func(ctx context.Context, b []byte) (any, error) { var input ListGrantsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -27,7 +29,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { }, "RevokeGrant": func(ctx context.Context, b []byte) (any, error) { var input RevokeGrantInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -35,7 +37,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { }, opRetireGrant: func(ctx context.Context, b []byte) (any, error) { var input RetireGrantInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -43,7 +45,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { }, "ListRetirableGrants": func(ctx context.Context, b []byte) (any, error) { var input ListRetirableGrantsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -59,7 +61,7 @@ func (h *Handler) buildGrantPolicyActions() map[string]kmsActionFn { // function's cognitive complexity under the gocognit limit. func (h *Handler) handlePutKeyPolicy(ctx context.Context, b []byte) (any, error) { var input PutKeyPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -85,7 +87,7 @@ func (h *Handler) handlePutKeyPolicy(ctx context.Context, b []byte) (any, error) func (h *Handler) handleGetKeyPolicy(ctx context.Context, b []byte) (any, error) { var input GetKeyPolicyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_grants_policies_test.go b/services/kms/handler_grants_policies_test.go index 3823cc5ef2..69f1f5f2dc 100644 --- a/services/kms/handler_grants_policies_test.go +++ b/services/kms/handler_grants_policies_test.go @@ -2,7 +2,8 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -34,7 +36,7 @@ func TestHandler_CreateGrant_WithRetiringPrincipal_ViaHTTP(t *testing.T) { GrantID string `json:"GrantId"` GrantToken string `json:"GrantToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.GrantID) assert.NotEmpty(t, resp.GrantToken) } @@ -57,25 +59,25 @@ func TestHandler_CreateGrant_NameTooLong_ViaHTTP(t *testing.T) { "GranteePrincipal": "arn:aws:iam::123456789012:role/grantee", "Operations": []string{"Encrypt"}, "Name": strings.Repeat("x", 257), - }) + }, jsonx.V1()) require.NoError(t, err) rec := b2postKMSOp(t, h, "CreateGrant", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "LimitExceededException", errResp.Type) assert.NotEqual(t, "ValidationException", errResp.Type) - listBody, err := json.Marshal(map[string]string{"KeyId": keyID}) + listBody, err := json.Marshal(map[string]string{"KeyId": keyID}, jsonx.V1()) require.NoError(t, err) listRec := b2postKMSOp(t, h, "ListGrants", string(listBody)) assert.Equal(t, http.StatusOK, listRec.Code) var listOut kms.ListGrantsOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut.Grants, "rejected CreateGrant must not create a grant") } @@ -102,14 +104,14 @@ func TestHandler_PutKeyPolicy_InvalidPolicyName_ViaHTTP(t *testing.T) { "KeyId": keyID, "PolicyName": "custom", "Policy": `{"Version":"2012-10-17","Statement":[]}`, - }) + }, jsonx.V1()) require.NoError(t, err) rec := b2postKMSOp(t, h, "PutKeyPolicy", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnsupportedOperationException", errResp.Type) assert.NotEqual(t, "ValidationException", errResp.Type) @@ -141,7 +143,7 @@ func TestKMSGrantOperations(t *testing.T) { rec := doKMSHTTPRequest(t, h, "CreateGrant", createBody) assert.Equal(t, http.StatusOK, rec.Code) var createOut kms.CreateGrantOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut.GrantID) assert.NotEmpty(t, createOut.GrantToken) @@ -150,7 +152,7 @@ func TestKMSGrantOperations(t *testing.T) { rec = doKMSHTTPRequest(t, h, "ListGrants", listBody) assert.Equal(t, http.StatusOK, rec.Code) var listOut kms.ListGrantsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Grants, 1) assert.Equal(t, createOut.GrantID, listOut.Grants[0].GrantID) @@ -161,7 +163,7 @@ func TestKMSGrantOperations(t *testing.T) { // ListGrants after revoke — should be empty rec = doKMSHTTPRequest(t, h, "ListGrants", listBody) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut.Grants) } @@ -181,15 +183,15 @@ func TestKMSKeyPolicy(t *testing.T) { "KeyId": keyID, "PolicyName": "default", "Policy": policy, - }) + }, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "PutKeyPolicy", string(putBody)) assert.Equal(t, http.StatusOK, rec.Code) - getBody, _ := json.Marshal(map[string]string{"KeyId": keyID, "PolicyName": "default"}) + getBody, _ := json.Marshal(map[string]string{"KeyId": keyID, "PolicyName": "default"}, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "GetKeyPolicy", string(getBody)) assert.Equal(t, http.StatusOK, rec.Code) var getOut kms.GetKeyPolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, policy, getOut.Policy) } @@ -211,30 +213,30 @@ func TestKMSRetireGrant(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createOut kms.CreateGrantOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) grantToken := createOut.GrantToken // ListRetirableGrants for the retiring principal listBody, _ := json.Marshal(map[string]string{ "RetiringPrincipal": "arn:aws:iam::000000000000:role/retire-role", - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "ListRetirableGrants", string(listBody)) assert.Equal(t, http.StatusOK, rec.Code) var listOut kms.ListGrantsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Grants, 1) assert.Equal(t, createOut.GrantID, listOut.Grants[0].GrantID) // RetireGrant using grant token - retireBody, _ := json.Marshal(map[string]string{"GrantToken": grantToken}) + retireBody, _ := json.Marshal(map[string]string{"GrantToken": grantToken}, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "RetireGrant", string(retireBody)) assert.Equal(t, http.StatusOK, rec.Code) // ListGrants should now be empty listGrantsBody := `{"KeyId":"` + keyID + `"}` rec = doKMSHTTPRequest(t, h, "ListGrants", listGrantsBody) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut.Grants) } @@ -285,9 +287,9 @@ func TestListGrants_RawBody_NoGrantTokenLeak(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw struct { - Grants []map[string]json.RawMessage `json:"Grants"` + Grants []map[string]jsontext.Value `json:"Grants"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.Grants, 1) _, hasToken := raw.Grants[0]["GrantToken"] diff --git a/services/kms/handler_keys.go b/services/kms/handler_keys.go index 93a444e868..d55b652387 100644 --- a/services/kms/handler_keys.go +++ b/services/kms/handler_keys.go @@ -2,8 +2,9 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -57,7 +58,7 @@ func (h *Handler) buildKeyLifecycleActions() map[string]kmsActionFn { // backend whenever the caller supplied a malformed tag. func (h *Handler) createKeyAction(ctx context.Context, b []byte) (any, error) { var input CreateKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_keys_test.go b/services/kms/handler_keys_test.go index 02e4071552..62ff825d9b 100644 --- a/services/kms/handler_keys_test.go +++ b/services/kms/handler_keys_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "log/slog" @@ -30,14 +31,14 @@ func TestHandler_CreateKey_DescriptionTooLong_ViaHTTP(t *testing.T) { b := h.Backend.(*kms.InMemoryBackend) longDescription := strings.Repeat("x", 8193) - body, err := json.Marshal(map[string]string{"Description": longDescription}) + body, err := json.Marshal(map[string]string{"Description": longDescription}, jsonx.V1()) require.NoError(t, err) rec := b2postKMSOp(t, h, "CreateKey", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "LimitExceededException", errResp.Type) assert.NotEqual(t, "ValidationException", errResp.Type) @@ -62,7 +63,7 @@ func TestHandler_CreateKey_KeySpecKeyUsageMismatch_ViaHTTP(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnsupportedOperationException", errResp.Type) assert.NotEqual(t, "InvalidKeyUsageException", errResp.Type) @@ -91,7 +92,7 @@ func TestHandler_CreateKey_HMACMultiRegion_ViaHTTP(t *testing.T) { MultiRegion bool `json:"MultiRegion"` } `json:"KeyMetadata"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "HMAC_256", out.KeyMetadata.KeySpec) assert.Equal(t, "GENERATE_VERIFY_MAC", out.KeyMetadata.KeyUsage) assert.True(t, out.KeyMetadata.MultiRegion) @@ -112,7 +113,7 @@ func TestHandler_ScheduleKeyDeletion_ViaHTTP(t *testing.T) { var resp struct { KeyState string `json:"KeyState"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingDeletion, resp.KeyState) } @@ -137,7 +138,7 @@ func TestHandler_CancelKeyDeletion_ViaHTTP(t *testing.T) { var resp struct { KeyID string `json:"KeyId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, keyID, resp.KeyID) desc, err := b.DescribeKey(context.Background(), &kms.DescribeKeyInput{KeyID: keyID}) @@ -180,7 +181,7 @@ func TestKMSHandler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.KeyMetadata.KeyID) }, }, @@ -192,7 +193,7 @@ func TestKMSHandler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnknownOperationException", errResp.Type) }, }, @@ -210,7 +211,7 @@ func TestKMSHandler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.Contains(t, ops, "CreateKey") }, }, @@ -222,7 +223,7 @@ func TestKMSHandler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "NotFoundException", errResp.Type) }, }, @@ -240,7 +241,7 @@ func TestKMSHandler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out kms.ListKeysOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Keys, 1) }, }, @@ -394,7 +395,7 @@ func TestKMSHandlerDisableEnableKey(t *testing.T) { rec = doKMSReqLocal(t, h, "ScheduleKeyDeletion", `{"KeyId":"`+keyID+`","PendingWindowInDays":7}`) assert.Equal(t, http.StatusOK, rec.Code) var schedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingDeletion, schedResp["KeyState"]) rec = doKMSReqLocal(t, h, "CancelKeyDeletion", body) @@ -411,7 +412,7 @@ func TestHandlerCancelKeyDeletionReturnsBody(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) sendKMSOp(t, h, "ScheduleKeyDeletion", `{"KeyId":"`+keyID+`","PendingWindowInDays":7}`) @@ -420,7 +421,7 @@ func TestHandlerCancelKeyDeletionReturnsBody(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, keyID, out["KeyId"]) assert.NotContains(t, out, "KeyState", "real CancelKeyDeletionOutput has only KeyId") } @@ -435,7 +436,7 @@ func TestHandlerUpdateKeyDescriptionMaxLengthRejected(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) longDesc := strings.Repeat("x", 8193) diff --git a/services/kms/handler_replication_maintenance.go b/services/kms/handler_replication_maintenance.go index 8f99e45d6a..fcd42a629a 100644 --- a/services/kms/handler_replication_maintenance.go +++ b/services/kms/handler_replication_maintenance.go @@ -2,8 +2,10 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // replicateKeyAction handles ReplicateKey dispatch, including tag validation and @@ -13,7 +15,7 @@ import ( // leaving a real, untagged replica key behind. func (h *Handler) replicateKeyAction(ctx context.Context, b []byte) (any, error) { var input ReplicateKeyInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -66,7 +68,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn return map[string]kmsActionFn{ "GetParametersForImport": func(ctx context.Context, b []byte) (any, error) { var input GetParametersForImportInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -74,7 +76,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn }, "ListKeyPolicies": func(ctx context.Context, b []byte) (any, error) { var input ListKeyPoliciesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -82,7 +84,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn }, "ListKeyRotations": func(ctx context.Context, b []byte) (any, error) { var input ListKeyRotationsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -91,7 +93,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn "ReplicateKey": h.replicateKeyAction, "RotateKeyOnDemand": func(ctx context.Context, b []byte) (any, error) { var input RotateKeyOnDemandInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -99,7 +101,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn }, "UpdateCustomKeyStore": func(ctx context.Context, b []byte) (any, error) { var input UpdateCustomKeyStoreInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -107,7 +109,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn }, "UpdateKeyDescription": func(ctx context.Context, b []byte) (any, error) { var input UpdateKeyDescriptionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -115,7 +117,7 @@ func (h *Handler) buildReplicationAndMaintenanceActions() map[string]kmsActionFn }, "UpdatePrimaryRegion": func(ctx context.Context, b []byte) (any, error) { var input UpdatePrimaryRegionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_replication_maintenance_test.go b/services/kms/handler_replication_maintenance_test.go index f593d5af48..1573e0d151 100644 --- a/services/kms/handler_replication_maintenance_test.go +++ b/services/kms/handler_replication_maintenance_test.go @@ -3,7 +3,7 @@ package kms_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "strings" @@ -47,7 +48,7 @@ func TestHandler_GetParametersForImport_ViaHTTP(t *testing.T) { KeyID string `json:"KeyId"` ParametersValidTo float64 `json:"ParametersValidTo"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, keyID, resp.KeyID) assert.Greater(t, resp.ParametersValidTo, float64(0)) } @@ -98,7 +99,7 @@ func TestHandler_ImportKeyMaterial_WireFieldName_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut kms.DescribeKeyOutput - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, kms.KeyStateEnabled, descOut.KeyMetadata.KeyState) } @@ -110,7 +111,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { h := kms.NewHandler(kms.NewInMemoryBackend()) // Create EXTERNAL-origin key. - createBody, err := json.Marshal(kms.CreateKeyInput{Origin: kms.KeyOriginExternal}) + createBody, err := json.Marshal(kms.CreateKeyInput{Origin: kms.KeyOriginExternal}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(createBody)) req.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -123,7 +124,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingImport, createOut.KeyMetadata.KeyState) // Import key material. @@ -135,7 +136,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { importBody, err := json.Marshal(kms.ImportKeyMaterialInput{ KeyID: createOut.KeyMetadata.KeyID, KeyMaterial: mat, - }) + }, jsonx.V1()) require.NoError(t, err) importReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(importBody)) importReq.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -147,7 +148,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { assert.Equal(t, http.StatusOK, importRec.Code) // Describe should show Enabled. - descBody, err := json.Marshal(kms.DescribeKeyInput{KeyID: createOut.KeyMetadata.KeyID}) + descBody, err := json.Marshal(kms.DescribeKeyInput{KeyID: createOut.KeyMetadata.KeyID}, jsonx.V1()) require.NoError(t, err) descReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(descBody)) descReq.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -159,11 +160,11 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut kms.DescribeKeyOutput - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, kms.KeyStateEnabled, descOut.KeyMetadata.KeyState) // DeleteImportedKeyMaterial. - delBody, err := json.Marshal(kms.DeleteImportedKeyMaterialInput{KeyID: createOut.KeyMetadata.KeyID}) + delBody, err := json.Marshal(kms.DeleteImportedKeyMaterialInput{KeyID: createOut.KeyMetadata.KeyID}, jsonx.V1()) require.NoError(t, err) delReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(delBody)) delReq.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -175,7 +176,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { assert.Equal(t, http.StatusOK, delRec.Code) // Key should be back to PendingImport. - descBody2, err := json.Marshal(kms.DescribeKeyInput{KeyID: createOut.KeyMetadata.KeyID}) + descBody2, err := json.Marshal(kms.DescribeKeyInput{KeyID: createOut.KeyMetadata.KeyID}, jsonx.V1()) require.NoError(t, err) descReq2 := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(descBody2)) descReq2.Header.Set("Content-Type", "application/x-amz-json-1.1") @@ -187,7 +188,7 @@ func TestKMSHandlerImportKeyMaterial(t *testing.T) { require.Equal(t, http.StatusOK, descRec2.Code) var descOut2 kms.DescribeKeyOutput - require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(descRec2.Body.Bytes(), &descOut2, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingImport, descOut2.KeyMetadata.KeyState) } @@ -270,7 +271,7 @@ func TestKMSHandlerNewMaintenanceOps(t *testing.T) { } var payload map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload, jsonx.V1())) _, ok := payload[tt.wantField] assert.True(t, ok) }) @@ -316,7 +317,7 @@ func TestKMS_ErrorClassification_MissingTableEntries(t *testing.T) { ValidTo: pastTS, })) - body, err := json.Marshal(kms.EncryptInput{KeyID: keyID, Plaintext: []byte("test")}) + body, err := json.Marshal(kms.EncryptInput{KeyID: keyID, Plaintext: []byte("test")}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body))) @@ -327,7 +328,7 @@ func TestKMS_ErrorClassification_MissingTableEntries(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) }) @@ -349,7 +350,7 @@ func TestKMS_ErrorClassification_MissingTableEntries(t *testing.T) { e := echo.New() h := kms.NewHandler(restored) - body, err := json.Marshal(kms.EncryptInput{KeyID: keyID, Plaintext: []byte("test")}) + body, err := json.Marshal(kms.EncryptInput{KeyID: keyID, Plaintext: []byte("test")}, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body))) @@ -360,7 +361,7 @@ func TestKMS_ErrorClassification_MissingTableEntries(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KeyUnavailableException", errResp.Type) }) } @@ -376,7 +377,7 @@ func TestHandlerReplicateKeyRequiresMultiRegion(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) rec := sendKMSOp(t, h, "ReplicateKey", `{"KeyId":"`+keyID+`","ReplicaRegion":"us-west-2"}`) @@ -393,7 +394,7 @@ func TestHandlerReplicateKeyCopiesTags(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) tagRec := sendKMSOp(t, h, "TagResource", `{"KeyId":"`+keyID+`","Tags":[{"TagKey":"env","TagValue":"prod"}]}`) @@ -403,14 +404,14 @@ func TestHandlerReplicateKeyCopiesTags(t *testing.T) { require.Equal(t, http.StatusOK, repRec.Code) var repOut map[string]any - require.NoError(t, json.Unmarshal(repRec.Body.Bytes(), &repOut)) + require.NoError(t, json.Unmarshal(repRec.Body.Bytes(), &repOut, jsonx.V1())) replicaID := repOut["ReplicaKeyMetadata"].(map[string]any)["KeyId"].(string) tagListRec := sendKMSOp(t, h, "ListResourceTags", `{"KeyId":"`+replicaID+`"}`) require.Equal(t, http.StatusOK, tagListRec.Code) var tagListOut map[string]any - require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut)) + require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut, jsonx.V1())) tags := tagListOut["Tags"].([]any) require.NotEmpty(t, tags) @@ -435,7 +436,7 @@ func TestHandlerUpdatePrimaryRegionViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) // Replicate to eu-central-1 first — UpdatePrimaryRegion requires an actual replica. @@ -456,7 +457,7 @@ func TestHandlerReplicateKeyWithInputTags(t *testing.T) { require.Equal(t, http.StatusOK, keyRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(keyRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) // Replicate with explicit tags that should overlay the source tags. @@ -465,14 +466,14 @@ func TestHandlerReplicateKeyWithInputTags(t *testing.T) { require.Equal(t, http.StatusOK, repRec.Code) var repOut map[string]any - require.NoError(t, json.Unmarshal(repRec.Body.Bytes(), &repOut)) + require.NoError(t, json.Unmarshal(repRec.Body.Bytes(), &repOut, jsonx.V1())) replicaID := repOut["ReplicaKeyMetadata"].(map[string]any)["KeyId"].(string) tagListRec := sendKMSOp(t, h, "ListResourceTags", `{"KeyId":"`+replicaID+`"}`) require.Equal(t, http.StatusOK, tagListRec.Code) var tagListOut map[string]any - require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut)) + require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut, jsonx.V1())) tags := tagListOut["Tags"].([]any) keys := make(map[string]string) diff --git a/services/kms/handler_tags.go b/services/kms/handler_tags.go index 713fd4aae2..a0e06cf602 100644 --- a/services/kms/handler_tags.go +++ b/services/kms/handler_tags.go @@ -2,12 +2,13 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -147,7 +148,7 @@ func (h *Handler) getTags(resourceID string) map[string]string { // listResourceTags handles the ListResourceTags operation. func (h *Handler) listResourceTags(ctx context.Context, b []byte) (any, error) { var input listResourceTagsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -202,7 +203,7 @@ func paginateTagList(tagList []kmsTagEntry, marker string, limit *int32) *listRe // tagResource handles the TagResource operation, validating key existence and tag count. func (h *Handler) tagResource(ctx context.Context, b []byte) (any, error) { var input tagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -291,7 +292,7 @@ func (h *Handler) validateTagCount(keyID string, newTags map[string]string) erro // untagResource handles the UntagResource operation. func (h *Handler) untagResource(ctx context.Context, b []byte) (any, error) { var input untagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/kms/handler_tags_persistence_test.go b/services/kms/handler_tags_persistence_test.go index a303e274d8..16668aa475 100644 --- a/services/kms/handler_tags_persistence_test.go +++ b/services/kms/handler_tags_persistence_test.go @@ -16,12 +16,13 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -95,7 +96,7 @@ func TestHandlerRestore_LegacyBackendOnlySnapshot(t *testing.T) { require.NotEmpty(t, legacy) var probe map[string]any - require.NoError(t, json.Unmarshal(legacy, &probe)) + require.NoError(t, json.Unmarshal(legacy, &probe, jsonx.V1())) _, hasHandlerFormat := probe["handlerFormat"] require.False(t, hasHandlerFormat, "precondition: a raw backend snapshot has no handlerFormat marker") diff --git a/services/kms/handler_tags_test.go b/services/kms/handler_tags_test.go index be7596b6c7..3585a15dd1 100644 --- a/services/kms/handler_tags_test.go +++ b/services/kms/handler_tags_test.go @@ -3,7 +3,7 @@ package kms_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "strings" @@ -29,7 +30,7 @@ func TestTagResource_PendingDeletion_Rejected(t *testing.T) { body, err := json.Marshal(map[string]any{ "KeyId": keyID, "Tags": []map[string]string{{"TagKey": "env", "TagValue": "test"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doKMSRequest(t, h, "TagResource", string(body)) @@ -38,7 +39,7 @@ func TestTagResource_PendingDeletion_Rejected(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) } @@ -69,7 +70,7 @@ func TestTagResource_EnabledAndDisabled_Accepted(t *testing.T) { body, merr := json.Marshal(map[string]any{ "KeyId": keyID, "Tags": []map[string]string{{"TagKey": "env", "TagValue": "test"}}, - }) + }, jsonx.V1()) require.NoError(t, merr) rec := doKMSRequest(t, h, "TagResource", string(body)) @@ -90,7 +91,7 @@ func TestUntagResource_PendingDeletion_Rejected(t *testing.T) { body, err := json.Marshal(map[string]any{ "KeyId": keyID, "TagKeys": []string{"env"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := doKMSRequest(t, h, "UntagResource", string(body)) @@ -99,7 +100,7 @@ func TestUntagResource_PendingDeletion_Rejected(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "KMSInvalidStateException", errResp.Type) } @@ -130,7 +131,7 @@ func TestUntagResource_EnabledAndDisabled_Accepted(t *testing.T) { body, merr := json.Marshal(map[string]any{ "KeyId": keyID, "TagKeys": []string{"nonexistent-key"}, - }) + }, jsonx.V1()) require.NoError(t, merr) rec := doKMSRequest(t, h, "UntagResource", string(body)) @@ -219,7 +220,7 @@ func TestTagResource_AndListResourceTags_Roundtrip(t *testing.T) { TagValue string `json:"TagValue"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp, jsonx.V1())) assert.Len(t, tagsResp.Tags, 2) } @@ -248,7 +249,7 @@ func TestUntagResource_RemovesSpecifiedTags(t *testing.T) { TagKey string `json:"TagKey"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp, jsonx.V1())) keys := make([]string, 0, len(tagsResp.Tags)) for _, t := range tagsResp.Tags { keys = append(keys, t.TagKey) @@ -318,7 +319,7 @@ func TestListResourceTags_Pagination(t *testing.T) { Tags []any `json:"Tags"` Truncated bool `json:"Truncated"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Tags, 3) assert.True(t, resp.Truncated) } @@ -353,12 +354,12 @@ func TestKMSTagOperations(t *testing.T) { keyID := keyOut.KeyMetadata.KeyID // ListResourceTags on a key with no tags - listBody, _ := json.Marshal(map[string]string{"KeyId": keyID}) + listBody, _ := json.Marshal(map[string]string{"KeyId": keyID}, jsonx.V1()) rec := doKMSHTTPRequest(t, h, "ListResourceTags", string(listBody)) assert.Equal(t, http.StatusOK, rec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) assert.Empty(t, listOut["Tags"]) assert.Equal(t, false, listOut["Truncated"]) @@ -369,7 +370,7 @@ func TestKMSTagOperations(t *testing.T) { {"TagKey": "env", "TagValue": "prod"}, {"TagKey": "team", "TagValue": "backend"}, }, - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "TagResource", string(tagBody)) assert.Equal(t, http.StatusOK, rec.Code) @@ -377,7 +378,7 @@ func TestKMSTagOperations(t *testing.T) { rec = doKMSHTTPRequest(t, h, "ListResourceTags", string(listBody)) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) tags, ok := listOut["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 2) @@ -386,13 +387,13 @@ func TestKMSTagOperations(t *testing.T) { untagBody, _ := json.Marshal(map[string]any{ "KeyId": keyID, "TagKeys": []string{"team"}, - }) + }, jsonx.V1()) rec = doKMSHTTPRequest(t, h, "UntagResource", string(untagBody)) assert.Equal(t, http.StatusOK, rec.Code) // ListResourceTags should now return 1 tag rec = doKMSHTTPRequest(t, h, "ListResourceTags", string(listBody)) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) tags, ok = listOut["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 1) @@ -536,7 +537,7 @@ func TestKMSHandlerListResourceTagsPagination(t *testing.T) { Tags []any `json:"Tags"` Truncated bool `json:"Truncated"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Tags, tt.wantCount) assert.Equal(t, tt.wantTruncated, out.Truncated) @@ -590,7 +591,7 @@ func TestHandlerTagResourceMaxTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) // Build a tag list with 51 unique entries. @@ -622,14 +623,14 @@ func TestHandlerCreateKeyWithTagsApplied(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut["KeyMetadata"].(map[string]any)["KeyId"].(string) tagListRec := sendKMSOp(t, h, "ListResourceTags", `{"KeyId":"`+keyID+`"}`) require.Equal(t, http.StatusOK, tagListRec.Code) var tagListOut map[string]any - require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut)) + require.NoError(t, json.Unmarshal(tagListRec.Body.Bytes(), &tagListOut, jsonx.V1())) tags := tagListOut["Tags"].([]any) require.NotEmpty(t, tags) diff --git a/services/kms/handler_test.go b/services/kms/handler_test.go index 7cbb043b55..71a13bc5a1 100644 --- a/services/kms/handler_test.go +++ b/services/kms/handler_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -68,7 +69,10 @@ func TestErrValidation_Mapping(t *testing.T) { if err != nil { return `{}` } - body, _ := json.Marshal(map[string]any{"KeyId": key.KeyMetadata.KeyID, "PendingWindowInDays": 3}) + body, _ := json.Marshal( + map[string]any{"KeyId": key.KeyMetadata.KeyID, "PendingWindowInDays": 3}, + jsonx.V1(), + ) return string(body) }, diff --git a/services/kms/helpers_test.go b/services/kms/helpers_test.go index 3deba98564..da3bd5b120 100644 --- a/services/kms/helpers_test.go +++ b/services/kms/helpers_test.go @@ -3,7 +3,7 @@ package kms_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" "log/slog" @@ -284,7 +285,7 @@ func buildBodyFromResourceID(operation, resourceID string) string { switch operation { case "ConnectCustomKeyStore", "DeleteCustomKeyStore", "DisconnectCustomKeyStore": - body, _ := json.Marshal(map[string]string{"CustomKeyStoreId": resourceID}) + body, _ := json.Marshal(map[string]string{"CustomKeyStoreId": resourceID}, jsonx.V1()) return string(body) case "GenerateMac": @@ -292,21 +293,21 @@ func buildBodyFromResourceID(operation, resourceID string) string { "KeyId": resourceID, "MacAlgorithm": "HMAC_SHA_256", "Message": []byte("test message"), - }) + }, jsonx.V1()) return string(body) case "GenerateDataKeyPair": body, _ := json.Marshal(map[string]any{ "KeyId": resourceID, "KeyPairSpec": "ECC_NIST_P256", - }) + }, jsonx.V1()) return string(body) case "GenerateDataKeyPairWithoutPlaintext": body, _ := json.Marshal(map[string]any{ "KeyId": resourceID, "KeyPairSpec": "ECC_NIST_P256", - }) + }, jsonx.V1()) return string(body) case "DeriveSharedSecret": @@ -323,7 +324,7 @@ func buildBodyFromResourceID(operation, resourceID string) string { "KeyId": resourceID, "KeyAgreementAlgorithm": "ECDH", "PublicKey": pubOut.PublicKey, - }) + }, jsonx.V1()) return string(body) } diff --git a/services/kms/import_error_wiring_test.go b/services/kms/import_error_wiring_test.go index 64e34ff480..566f48bd2c 100644 --- a/services/kms/import_error_wiring_test.go +++ b/services/kms/import_error_wiring_test.go @@ -3,13 +3,14 @@ package kms_test import ( "context" "crypto/rand" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -46,7 +47,7 @@ func TestImportKeyMaterial_RSAUnwrapFailure_IsInvalidCiphertext(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidCiphertextException", errResp.Type) assert.NotEqual(t, "InvalidKeyUsageException", errResp.Type) @@ -81,7 +82,7 @@ func TestImportKeyMaterial_AsymmetricKeySpec_IsUnsupportedOperation(t *testing.T require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnsupportedOperationException", errResp.Type) assert.NotEqual(t, "InvalidKeyUsageException", errResp.Type) @@ -111,7 +112,7 @@ func TestImportKeyMaterial_EmptyMaterial_IsIncorrectKeyMaterial(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "IncorrectKeyMaterialException", errResp.Type) assert.NotEqual(t, "InvalidKeyUsageException", errResp.Type) assert.NotEqual(t, "ValidationException", errResp.Type) @@ -139,7 +140,7 @@ func TestImportKeyMaterial_WrongLength_IsIncorrectKeyMaterial(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "IncorrectKeyMaterialException", errResp.Type) assert.NotEqual(t, "InvalidKeyUsageException", errResp.Type) @@ -175,7 +176,7 @@ func TestDecrypt_WrongKeyUsage_StillInvalidKeyUsageException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidKeyUsageException", errResp.Type) } @@ -198,6 +199,6 @@ func TestVerifyMac_WrongKeyUsage_StillInvalidKeyUsageException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidKeyUsageException", errResp.Type) } diff --git a/services/kms/incorrect_key_test.go b/services/kms/incorrect_key_test.go index 968981f0ac..bde6d5fcd6 100644 --- a/services/kms/incorrect_key_test.go +++ b/services/kms/incorrect_key_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -179,7 +180,7 @@ func TestIncorrectKeyExceptionWireType(t *testing.T) { body, err := json.Marshal(map[string]any{ "CiphertextBlob": encOut.CiphertextBlob, "KeyId": otherKey.KeyMetadata.KeyID, - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(body))) @@ -194,6 +195,6 @@ func TestIncorrectKeyExceptionWireType(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "IncorrectKeyException", resp.Type) } diff --git a/services/kms/isolation_test.go b/services/kms/isolation_test.go index 7d8fd839d5..f1385351fe 100644 --- a/services/kms/isolation_test.go +++ b/services/kms/isolation_test.go @@ -4,7 +4,7 @@ package kms_test // keys and aliases created in one region are not visible in another. import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -48,14 +49,14 @@ func TestRegionIsolation_AliasInTwoRegions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var east1Key kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1Key)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1Key, jsonx.V1())) east1KeyID := east1Key.KeyMetadata.KeyID // Create alias "alias/my-key" in us-east-1. aliasBody, _ := json.Marshal(map[string]string{ "AliasName": "alias/my-key", "TargetKeyId": east1KeyID, - }) + }, jsonx.V1()) rec = kmsCall(t, h, e, "CreateAlias", "us-east-1", string(aliasBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -64,14 +65,14 @@ func TestRegionIsolation_AliasInTwoRegions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var west2Key kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &west2Key)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &west2Key, jsonx.V1())) west2KeyID := west2Key.KeyMetadata.KeyID // Create same alias "alias/my-key" in us-west-2 — should succeed (different region). aliasBody2, _ := json.Marshal(map[string]string{ "AliasName": "alias/my-key", "TargetKeyId": west2KeyID, - }) + }, jsonx.V1()) rec = kmsCall(t, h, e, "CreateAlias", "us-west-2", string(aliasBody2)) assert.Equal(t, http.StatusOK, rec.Code, "same alias name should succeed in a different region") } @@ -102,7 +103,7 @@ func TestRegionIsolation_ListKeys_RegionScoped(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var east1List kms.ListKeysOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1List)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1List, jsonx.V1())) assert.Len(t, east1List.Keys, 3, "us-east-1 should have 3 keys") // ListKeys in us-west-2 should return only 2 keys. @@ -110,7 +111,7 @@ func TestRegionIsolation_ListKeys_RegionScoped(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var west2List kms.ListKeysOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &west2List)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &west2List, jsonx.V1())) assert.Len(t, west2List.Keys, 2, "us-west-2 should have 2 keys") } @@ -129,18 +130,18 @@ func TestRegionIsolation_DescribeKey_CrossRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var east1Key kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1Key)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &east1Key, jsonx.V1())) east1KeyARN := east1Key.KeyMetadata.Arn aliasBody, _ := json.Marshal(map[string]string{ "AliasName": "alias/region-test-key", "TargetKeyId": east1Key.KeyMetadata.KeyID, - }) + }, jsonx.V1()) rec = kmsCall(t, h, e, "CreateAlias", "us-east-1", string(aliasBody)) require.Equal(t, http.StatusOK, rec.Code) // DescribeKey via alias in us-east-1 should succeed. - descBody, _ := json.Marshal(map[string]string{"KeyId": "alias/region-test-key"}) + descBody, _ := json.Marshal(map[string]string{"KeyId": "alias/region-test-key"}, jsonx.V1()) rec = kmsCall(t, h, e, "DescribeKey", "us-east-1", string(descBody)) assert.Equal(t, http.StatusOK, rec.Code, "DescribeKey in same region via alias should succeed") @@ -149,7 +150,7 @@ func TestRegionIsolation_DescribeKey_CrossRegion(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "DescribeKey via alias from wrong region should fail") // DescribeKey via ARN from us-west-2 should succeed — ARN encodes the region. - arnBody, _ := json.Marshal(map[string]string{"KeyId": east1KeyARN}) + arnBody, _ := json.Marshal(map[string]string{"KeyId": east1KeyARN}, jsonx.V1()) rec = kmsCall(t, h, e, "DescribeKey", "us-west-2", string(arnBody)) assert.Equal(t, http.StatusOK, rec.Code, "DescribeKey via ARN should succeed from any region") } @@ -168,12 +169,12 @@ func TestRegionIsolation_AliasNotVisible_CrossRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var keyOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &keyOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &keyOut, jsonx.V1())) aliasBody, _ := json.Marshal(map[string]string{ "AliasName": "alias/east-only", "TargetKeyId": keyOut.KeyMetadata.KeyID, - }) + }, jsonx.V1()) rec = kmsCall(t, h, e, "CreateAlias", "us-east-1", string(aliasBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -182,7 +183,7 @@ func TestRegionIsolation_AliasNotVisible_CrossRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var aliasList kms.ListAliasesOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aliasList)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aliasList, jsonx.V1())) for _, a := range aliasList.Aliases { assert.NotEqual(t, "alias/east-only", a.AliasName, diff --git a/services/kms/key_policies.go b/services/kms/key_policies.go index 7fd86737d4..8630b24c58 100644 --- a/services/kms/key_policies.go +++ b/services/kms/key_policies.go @@ -2,11 +2,12 @@ package kms import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" gopherarn "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutKeyPolicy stores a key policy for a KMS key. @@ -61,7 +62,7 @@ func validKeyPolicyDoc(s string) bool { Statement any `json:"Statement"` Version string `json:"Version"` } - if err := json.Unmarshal([]byte(s), &policyDoc); err != nil { + if err := json.Unmarshal([]byte(s), &policyDoc, jsonx.V1()); err != nil { return false } diff --git a/services/kms/mac_algorithm_wiring_test.go b/services/kms/mac_algorithm_wiring_test.go index bdb0eae4bb..4a6dc75c40 100644 --- a/services/kms/mac_algorithm_wiring_test.go +++ b/services/kms/mac_algorithm_wiring_test.go @@ -2,13 +2,14 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -38,7 +39,7 @@ func TestGenerateMac_WrongAlgorithm_WireType_IsInvalidKeyUsage(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidKeyUsageException", errResp.Type) } @@ -64,6 +65,6 @@ func TestVerifyMac_WrongAlgorithm_WireType_IsInvalidKeyUsage(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp kms.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidKeyUsageException", errResp.Type) } diff --git a/services/kms/persistence.go b/services/kms/persistence.go index 18b81f94b0..84b34eb244 100644 --- a/services/kms/persistence.go +++ b/services/kms/persistence.go @@ -2,10 +2,12 @@ package kms import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -33,7 +35,7 @@ const kmsSnapshotVersion = 1 // keyMaterialsStore/keyMaterialHistoryStore in backend.go), so neither can be // expressed as a store.Table. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Policies map[string]map[string]string `json:"policies"` KeyMaterials map[string]map[string]serializedKeyMaterial `json:"key_materials,omitempty"` KeyMaterialHistory map[string]map[string][]serializedKeyMaterial `json:"key_material_history,omitempty"` @@ -262,7 +264,7 @@ func warnMissingKeyMaterials( // have no registered table for RestoreAll to find -- store.Registry.RestoreAll // only restores tables already known to the registry, silently ignoring any // snapshot entry whose name isn't registered. -func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]json.RawMessage) { +func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]jsontext.Value) { for name := range tables { kind, region, ok := strings.Cut(name, ":") if !ok { @@ -375,7 +377,7 @@ const handlerSnapshotFormat = 1 // `tags` attribute of `aws_kms_key` after a restart. type handlerSnapshot struct { Tags map[string]map[string]string `json:"tags,omitempty"` - Backend json.RawMessage `json:"backend,omitempty"` + Backend jsontext.Value `json:"backend,omitempty"` HandlerFormat int `json:"handlerFormat"` } @@ -469,7 +471,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { Tags: h.snapshotTags(), } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "kms: handler snapshot marshal failed", "error", err) @@ -489,7 +491,7 @@ func (h *Handler) Restore(ctx context.Context, data []byte) error { HandlerFormat int `json:"handlerFormat"` } - if err := json.Unmarshal(data, &peek); err != nil { + if err := json.Unmarshal(data, &peek, jsonx.V1()); err != nil { return fmt.Errorf("kms: restore handler snapshot: %w", err) } @@ -500,7 +502,7 @@ func (h *Handler) Restore(ctx context.Context, data []byte) error { } var snap handlerSnapshot - if err := json.Unmarshal(data, &snap); err != nil { + if err := json.Unmarshal(data, &snap, jsonx.V1()); err != nil { return fmt.Errorf("kms: restore handler snapshot: %w", err) } diff --git a/services/kms/realism_validation_test.go b/services/kms/realism_validation_test.go index 18807bdb38..17ed64270b 100644 --- a/services/kms/realism_validation_test.go +++ b/services/kms/realism_validation_test.go @@ -2,13 +2,15 @@ package kms_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func kmsErr(t *testing.T, body []byte) (string, string) { @@ -19,7 +21,7 @@ func kmsErr(t *testing.T, body []byte) (string, string) { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &e)) + require.NoError(t, json.Unmarshal(body, &e, jsonx.V1())) return e.Type, e.Message } @@ -98,7 +100,10 @@ func TestKMSWire_ErrorRealism(t *testing.T) { } `json:"KeyMetadata"` } - require.NoError(t, json.Unmarshal(doKMSRequest(t, h, "CreateKey", createBody).Body.Bytes(), &created)) + require.NoError( + t, + json.Unmarshal(doKMSRequest(t, h, "CreateKey", createBody).Body.Bytes(), &created, jsonx.V1()), + ) keyID := created.KeyMetadata.KeyID rec := doKMSRequest(t, h, tt.action, tt.body(keyID)) diff --git a/services/kms/replicate_key_policy_test.go b/services/kms/replicate_key_policy_test.go index 16fd765643..ba6c5e9d34 100644 --- a/services/kms/replicate_key_policy_test.go +++ b/services/kms/replicate_key_policy_test.go @@ -21,7 +21,7 @@ package kms_test // regional endpoint would. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -74,7 +75,7 @@ func Test_ReplicateKey_InlinePolicy(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) primaryID := createOut.KeyMetadata.KeyID replicateBody := `{"KeyId":"` + primaryID + `","ReplicaRegion":"us-west-2","Policy":` + @@ -89,7 +90,7 @@ func Test_ReplicateKey_InlinePolicy(t *testing.T) { require.Equal(t, http.StatusOK, replicateRec.Code, replicateRec.Body.String()) var replicateOut kms.ReplicateKeyOutput - require.NoError(t, json.Unmarshal(replicateRec.Body.Bytes(), &replicateOut)) + require.NoError(t, json.Unmarshal(replicateRec.Body.Bytes(), &replicateOut, jsonx.V1())) replicaID := replicateOut.ReplicaKeyMetadata.KeyID require.NotEmpty(t, replicaID) @@ -99,7 +100,7 @@ func Test_ReplicateKey_InlinePolicy(t *testing.T) { require.Equal(t, http.StatusOK, polRec.Code, polRec.Body.String()) var polOut kms.GetKeyPolicyOutput - require.NoError(t, json.Unmarshal(polRec.Body.Bytes(), &polOut)) + require.NoError(t, json.Unmarshal(polRec.Body.Bytes(), &polOut, jsonx.V1())) assert.Equal(t, "default", polOut.PolicyName) if tc.wantPolicy != "" { @@ -116,7 +117,7 @@ func Test_ReplicateKey_InlinePolicy(t *testing.T) { require.Equal(t, http.StatusOK, primaryPolRec.Code) var primaryPolOut kms.GetKeyPolicyOutput - require.NoError(t, json.Unmarshal(primaryPolRec.Body.Bytes(), &primaryPolOut)) + require.NoError(t, json.Unmarshal(primaryPolRec.Body.Bytes(), &primaryPolOut, jsonx.V1())) assert.NotEqual(t, customPolicy, primaryPolOut.Policy) }) } diff --git a/services/kms/terraform_lifecycle_test.go b/services/kms/terraform_lifecycle_test.go index 7875c7ca3b..bf8a68b6ac 100644 --- a/services/kms/terraform_lifecycle_test.go +++ b/services/kms/terraform_lifecycle_test.go @@ -20,7 +20,7 @@ package kms_test // regression (e.g. "KeyId" vs "KeyID") would also be caught, not just a Go-side one. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -65,7 +66,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createOut kms.CreateKeyOutput - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) keyID := createOut.KeyMetadata.KeyID require.NotEmpty(t, keyID) @@ -86,7 +87,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { "repeated DescribeKey must be byte-identical -- any diff here is perpetual terraform plan drift") var desc1 kms.DescribeKeyOutput - require.NoError(t, json.Unmarshal(descRec1.Body.Bytes(), &desc1)) + require.NoError(t, json.Unmarshal(descRec1.Body.Bytes(), &desc1, jsonx.V1())) assert.Equal(t, "terraform-managed key", desc1.KeyMetadata.Description) assert.Equal(t, "SYMMETRIC_DEFAULT", desc1.KeyMetadata.KeySpec) assert.Equal(t, "SYMMETRIC_DEFAULT", desc1.KeyMetadata.CustomerMasterKeySpec, @@ -107,7 +108,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { "that made aws_kms_key poll to a 10-minute timeout") var polOut kms.GetKeyPolicyOutput - require.NoError(t, json.Unmarshal(polRec1.Body.Bytes(), &polOut)) + require.NoError(t, json.Unmarshal(polRec1.Body.Bytes(), &polOut, jsonx.V1())) assert.JSONEq(t, policy, polOut.Policy, "GetKeyPolicy must return exactly the policy supplied to CreateKey") // --- ListResourceTags: must return exactly the tags supplied to CreateKey. --- @@ -119,7 +120,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { "repeated ListResourceTags must be byte-identical") var tagsOut tagsWireOutput - require.NoError(t, json.Unmarshal(tagsRec1.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(tagsRec1.Body.Bytes(), &tagsOut, jsonx.V1())) gotTags := make(map[string]string, len(tagsOut.Tags)) for _, tg := range tagsOut.Tags { @@ -136,7 +137,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { assert.JSONEq(t, rotRec1.Body.String(), rotRec2.Body.String()) var rotOut kms.GetKeyRotationStatusOutput - require.NoError(t, json.Unmarshal(rotRec1.Body.Bytes(), &rotOut)) + require.NoError(t, json.Unmarshal(rotRec1.Body.Bytes(), &rotOut, jsonx.V1())) assert.False(t, rotOut.KeyRotationEnabled, "key rotation must default to disabled") // --- CreateAlias -> ListAliases must show it, resolving TargetKeyId correctly. --- @@ -149,7 +150,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { require.Equal(t, http.StatusOK, listAliasesRec.Code) var aliasesOut kms.ListAliasesOutput - require.NoError(t, json.Unmarshal(listAliasesRec.Body.Bytes(), &aliasesOut)) + require.NoError(t, json.Unmarshal(listAliasesRec.Body.Bytes(), &aliasesOut, jsonx.V1())) require.Len(t, aliasesOut.Aliases, 1) assert.Equal(t, "alias/tf-lifecycle-test", aliasesOut.Aliases[0].AliasName) assert.Equal(t, keyID, aliasesOut.Aliases[0].TargetKeyID) @@ -162,7 +163,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { require.Equal(t, http.StatusOK, scheduleRec.Code, scheduleRec.Body.String()) var scheduleOut kms.ScheduleKeyDeletionOutput - require.NoError(t, json.Unmarshal(scheduleRec.Body.Bytes(), &scheduleOut)) + require.NoError(t, json.Unmarshal(scheduleRec.Body.Bytes(), &scheduleOut, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingDeletion, scheduleOut.KeyState) assert.Positive(t, scheduleOut.DeletionDate) assert.Equal(t, 7, scheduleOut.PendingWindowInDays) @@ -171,7 +172,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { require.Equal(t, http.StatusOK, finalDescRec.Code) var finalDesc kms.DescribeKeyOutput - require.NoError(t, json.Unmarshal(finalDescRec.Body.Bytes(), &finalDesc)) + require.NoError(t, json.Unmarshal(finalDescRec.Body.Bytes(), &finalDesc, jsonx.V1())) assert.Equal(t, kms.KeyStatePendingDeletion, finalDesc.KeyMetadata.KeyState) assert.Positive(t, finalDesc.KeyMetadata.DeletionDate) assert.Equal(t, 7, finalDesc.KeyMetadata.PendingDeletionWindowInDays) @@ -181,7 +182,7 @@ func TestTerraformLifecycle_KMSKey(t *testing.T) { // strconvQuote JSON-quotes s (equivalent to strconv.Quote for the ASCII/simple policy // strings this test uses, without pulling in strconv just for one call site). func strconvQuote(s string) string { - b, err := json.Marshal(s) + b, err := json.Marshal(s, jsonx.V1()) if err != nil { panic(err) } diff --git a/services/kms/undeclared_members_test.go b/services/kms/undeclared_members_test.go index bc197e250d..fcb04dbb27 100644 --- a/services/kms/undeclared_members_test.go +++ b/services/kms/undeclared_members_test.go @@ -1,7 +1,7 @@ package kms_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/kms/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_KeyMetadataOmitsUndeclaredMembers(t *testing.T) { @@ -32,14 +34,14 @@ func TestHandler_KeyMetadataOmitsUndeclaredMembers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out["KeyMetadata"], "PrimaryRegion") assert.NotContains(t, out["KeyMetadata"], "MultiRegionKeyType") id := out["KeyMetadata"]["KeyId"].(string) rec = sendKMSOp(t, h, "DescribeKey", `{"KeyId":"`+id+`"}`) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out["KeyMetadata"], "PrimaryRegion") assert.NotContains(t, out["KeyMetadata"], "MultiRegionKeyType") }) diff --git a/services/kms/validation.go b/services/kms/validation.go index fd84aaaac4..2e6f0f18e4 100644 --- a/services/kms/validation.go +++ b/services/kms/validation.go @@ -1,12 +1,14 @@ package kms import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var keyIDShapeRe = regexp.MustCompile( @@ -21,7 +23,7 @@ func validateListMarker(action string, body []byte) error { Marker string `json:"Marker"` } - if json.Unmarshal(body, &in) == nil && in.Marker != "" { + if json.Unmarshal(body, &in, jsonx.V1()) == nil && in.Marker != "" { if n, err := strconv.Atoi(in.Marker); err != nil || n < 0 { return fmt.Errorf("%w: invalid marker", ErrInvalidMarker) } diff --git a/services/kms/wire_error_types_test.go b/services/kms/wire_error_types_test.go index 2486b168f9..16f4394f30 100644 --- a/services/kms/wire_error_types_test.go +++ b/services/kms/wire_error_types_test.go @@ -2,7 +2,7 @@ package kms_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/kms" ) @@ -226,7 +227,7 @@ func TestWireErrorTypes_NotValidationException(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp.Type) assert.NotEqual(t, "ValidationException", resp.Type) }) @@ -351,7 +352,7 @@ func TestWireErrorTypes_MalformedKeyIDArn(t *testing.T) { var resp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp.Type) }) } @@ -360,7 +361,7 @@ func TestWireErrorTypes_MalformedKeyIDArn(t *testing.T) { func mustJSON(t *testing.T, v any) string { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/lakeformation/handler_credentials.go b/services/lakeformation/handler_credentials.go index 22ee1dab04..19d8a26d4f 100644 --- a/services/lakeformation/handler_credentials.go +++ b/services/lakeformation/handler_credentials.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAssumeDecoratedRoleWithSAML(_ context.Context, c *echo.Context, body []byte) error { var in assumeDecoratedRoleWithSAMLInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -40,7 +42,7 @@ func (h *Handler) handleGetDataLakePrincipal(ctx context.Context, c *echo.Contex func (h *Handler) handleGetTemporaryDataLocationCredentials(_ context.Context, c *echo.Context, body []byte) error { var in getTemporaryDataLocationCredentialsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } // GetTemporaryDataLocationCredentialsInput (lakeformation@v1.50.4 @@ -60,7 +62,7 @@ func (h *Handler) handleGetTemporaryDataLocationCredentials(_ context.Context, c func (h *Handler) handleGetTemporaryGluePartitionCredentials(_ context.Context, c *echo.Context, body []byte) error { var in getTemporaryGluePartitionCredentialsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } if strings.TrimSpace(in.TableArn) == "" { @@ -78,7 +80,7 @@ func (h *Handler) handleGetTemporaryGluePartitionCredentials(_ context.Context, func (h *Handler) handleGetTemporaryGlueTableCredentials(_ context.Context, c *echo.Context, body []byte) error { var in getTemporaryGlueTableCredentialsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } if strings.TrimSpace(in.TableArn) == "" { diff --git a/services/lakeformation/handler_credentials_test.go b/services/lakeformation/handler_credentials_test.go index d49a94c033..10c1118bdb 100644 --- a/services/lakeformation/handler_credentials_test.go +++ b/services/lakeformation/handler_credentials_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -61,7 +62,7 @@ func TestHandler_AssumeDecoratedRoleWithSAML(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, resp, field) assert.NotEmpty(t, resp[field]) @@ -85,7 +86,7 @@ func TestAssumeDecoratedRoleWithSAML_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["AccessKeyId"]) assert.NotEmpty(t, out["SecretAccessKey"]) assert.NotEmpty(t, out["SessionToken"]) diff --git a/services/lakeformation/handler_data_cells_filter.go b/services/lakeformation/handler_data_cells_filter.go index f9d0054a1e..be4fbe754e 100644 --- a/services/lakeformation/handler_data_cells_filter.go +++ b/services/lakeformation/handler_data_cells_filter.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateDataCellsFilter(_ context.Context, c *echo.Context, body []byte) error { var in createDataCellsFilterInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -32,7 +34,7 @@ func (h *Handler) handleCreateDataCellsFilter(_ context.Context, c *echo.Context func (h *Handler) handleDeleteDataCellsFilter(_ context.Context, c *echo.Context, body []byte) error { var in deleteDataCellsFilterInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -46,7 +48,7 @@ func (h *Handler) handleDeleteDataCellsFilter(_ context.Context, c *echo.Context func (h *Handler) handleListDataCellsFilter(_ context.Context, c *echo.Context, body []byte) error { var in listDataCellsFilterInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -79,7 +81,7 @@ func (h *Handler) handleListDataCellsFilter(_ context.Context, c *echo.Context, func (h *Handler) handleGetDataCellsFilter(_ context.Context, c *echo.Context, body []byte) error { var in getDataCellsFilterInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } f, err := h.Backend.GetDataCellsFilter(in.TableCatalogID, in.DatabaseName, in.TableName, in.Name) @@ -92,7 +94,7 @@ func (h *Handler) handleGetDataCellsFilter(_ context.Context, c *echo.Context, b func (h *Handler) handleUpdateDataCellsFilter(_ context.Context, c *echo.Context, body []byte) error { var in updateDataCellsFilterInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } if in.TableData == nil { diff --git a/services/lakeformation/handler_data_lake_settings.go b/services/lakeformation/handler_data_lake_settings.go index 712d6626dd..4497fd47af 100644 --- a/services/lakeformation/handler_data_lake_settings.go +++ b/services/lakeformation/handler_data_lake_settings.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetDataLakeSettings(_ context.Context, c *echo.Context, body []byte) error { var in getDataLakeSettingsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -26,7 +28,7 @@ func (h *Handler) handleGetDataLakeSettings(_ context.Context, c *echo.Context, func (h *Handler) handlePutDataLakeSettings(_ context.Context, c *echo.Context, body []byte) error { var in putDataLakeSettingsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } diff --git a/services/lakeformation/handler_data_lake_settings_test.go b/services/lakeformation/handler_data_lake_settings_test.go index 327f9d0c18..22ba6412b9 100644 --- a/services/lakeformation/handler_data_lake_settings_test.go +++ b/services/lakeformation/handler_data_lake_settings_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -41,7 +42,7 @@ func TestHandler_GetDataLakeSettings(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "DataLakeSettings") }) } diff --git a/services/lakeformation/handler_lake_formation_identity_center.go b/services/lakeformation/handler_lake_formation_identity_center.go index 6e541e9f8e..9b5d4670d2 100644 --- a/services/lakeformation/handler_lake_formation_identity_center.go +++ b/services/lakeformation/handler_lake_formation_identity_center.go @@ -2,10 +2,12 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateLakeFormationIdentityCenterConfiguration( @@ -14,7 +16,7 @@ func (h *Handler) handleCreateLakeFormationIdentityCenterConfiguration( body []byte, ) error { var in createLakeFormationIdentityCenterConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -42,7 +44,7 @@ func (h *Handler) handleDeleteLakeFormationIdentityCenterConfiguration( ) error { var in deleteLakeFormationIdentityCenterConfigurationInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -62,7 +64,7 @@ func (h *Handler) handleDescribeLakeFormationIdentityCenterConfiguration( ) error { var in describeLakeFormationIdentityCenterConfigurationInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -90,7 +92,7 @@ func (h *Handler) handleUpdateLakeFormationIdentityCenterConfiguration( ) error { var in updateLakeFormationIdentityCenterConfigurationInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } diff --git a/services/lakeformation/handler_lake_formation_identity_center_test.go b/services/lakeformation/handler_lake_formation_identity_center_test.go index d5c4948083..afffbce80f 100644 --- a/services/lakeformation/handler_lake_formation_identity_center_test.go +++ b/services/lakeformation/handler_lake_formation_identity_center_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -52,7 +53,7 @@ func TestHandler_CreateLakeFormationIdentityCenterConfiguration(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ApplicationArn") } }) @@ -72,7 +73,7 @@ func TestIdentityCenterConfiguration_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["ApplicationArn"]) assert.Contains(t, out["ApplicationArn"].(string), "arn:aws:sso") } diff --git a/services/lakeformation/handler_lf_tag_expression.go b/services/lakeformation/handler_lf_tag_expression.go index be036532c0..35e819a55d 100644 --- a/services/lakeformation/handler_lf_tag_expression.go +++ b/services/lakeformation/handler_lf_tag_expression.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateLFTagExpression(_ context.Context, c *echo.Context, body []byte) error { var in createLFTagExpressionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -32,7 +34,7 @@ func (h *Handler) handleCreateLFTagExpression(_ context.Context, c *echo.Context func (h *Handler) handleDeleteLFTagExpression(_ context.Context, c *echo.Context, body []byte) error { var in deleteLFTagExpressionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -50,7 +52,7 @@ func (h *Handler) handleDeleteLFTagExpression(_ context.Context, c *echo.Context func (h *Handler) handleListLFTagExpressions(_ context.Context, c *echo.Context, body []byte) error { var in listLFTagExpressionsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -65,7 +67,7 @@ func (h *Handler) handleListLFTagExpressions(_ context.Context, c *echo.Context, func (h *Handler) handleGetLFTagExpression(_ context.Context, c *echo.Context, body []byte) error { var in getLFTagExpressionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } expr, err := h.Backend.GetLFTagExpression(in.Name, in.CatalogID) @@ -83,7 +85,7 @@ func (h *Handler) handleGetLFTagExpression(_ context.Context, c *echo.Context, b func (h *Handler) handleUpdateLFTagExpression(_ context.Context, c *echo.Context, body []byte) error { var in updateLFTagExpressionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } if strings.TrimSpace(in.Name) == "" { diff --git a/services/lakeformation/handler_lf_tags.go b/services/lakeformation/handler_lf_tags.go index da7605c1d8..5aae9acc0a 100644 --- a/services/lakeformation/handler_lf_tags.go +++ b/services/lakeformation/handler_lf_tags.go @@ -2,15 +2,17 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateLFTag(_ context.Context, c *echo.Context, body []byte) error { var in createLFTagInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -23,7 +25,7 @@ func (h *Handler) handleCreateLFTag(_ context.Context, c *echo.Context, body []b func (h *Handler) handleDeleteLFTag(_ context.Context, c *echo.Context, body []byte) error { var in deleteLFTagInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -36,7 +38,7 @@ func (h *Handler) handleDeleteLFTag(_ context.Context, c *echo.Context, body []b func (h *Handler) handleGetLFTag(_ context.Context, c *echo.Context, body []byte) error { var in getLFTagInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -54,7 +56,7 @@ func (h *Handler) handleGetLFTag(_ context.Context, c *echo.Context, body []byte func (h *Handler) handleUpdateLFTag(_ context.Context, c *echo.Context, body []byte) error { var in updateLFTagInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -68,7 +70,7 @@ func (h *Handler) handleUpdateLFTag(_ context.Context, c *echo.Context, body []b func (h *Handler) handleListLFTags(_ context.Context, c *echo.Context, body []byte) error { var in listLFTagsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -104,7 +106,7 @@ func (h *Handler) checkAddRemoveLFTagsInput(c *echo.Context, resource *Resource, func (h *Handler) handleAddLFTagsToResource(_ context.Context, c *echo.Context, body []byte) error { var in addLFTagsToResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -122,7 +124,7 @@ func (h *Handler) handleAddLFTagsToResource(_ context.Context, c *echo.Context, func (h *Handler) handleRemoveLFTagsFromResource(_ context.Context, c *echo.Context, body []byte) error { var in removeLFTagsFromResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -140,7 +142,7 @@ func (h *Handler) handleRemoveLFTagsFromResource(_ context.Context, c *echo.Cont func (h *Handler) handleGetResourceLFTags(_ context.Context, c *echo.Context, body []byte) error { var in getResourceLFTagsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -174,7 +176,7 @@ func (h *Handler) handleGetResourceLFTags(_ context.Context, c *echo.Context, bo func (h *Handler) handleSearchDatabasesByLFTags(_ context.Context, c *echo.Context, body []byte) error { var in searchDatabasesByLFTagsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -190,7 +192,7 @@ func (h *Handler) handleSearchDatabasesByLFTags(_ context.Context, c *echo.Conte func (h *Handler) handleSearchTablesByLFTags(_ context.Context, c *echo.Context, body []byte) error { var in searchTablesByLFTagsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } diff --git a/services/lakeformation/handler_lf_tags_test.go b/services/lakeformation/handler_lf_tags_test.go index 1535d03534..56a3d89600 100644 --- a/services/lakeformation/handler_lf_tags_test.go +++ b/services/lakeformation/handler_lf_tags_test.go @@ -1,7 +1,7 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -49,7 +50,7 @@ func TestHandler_LFTagLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.tagKey, getResp["TagKey"]) // Update @@ -64,7 +65,7 @@ func TestHandler_LFTagLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["LFTags"].([]any) assert.Len(t, tags, 1) @@ -142,7 +143,7 @@ func TestHandler_AddLFTagsToResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["Failures"].([]any) assert.Len(t, failures, tt.wantFails) } @@ -234,7 +235,7 @@ func TestCreateLFTag_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "team", out["TagKey"]) } @@ -279,7 +280,7 @@ func TestListLFTags_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags := out["LFTags"].([]any) require.Len(t, tags, 3) @@ -306,7 +307,7 @@ func TestAddLFTagsToResource_PartialFailure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) failures := out["Failures"].([]any) assert.Len(t, failures, 1, "only nonexistent tag should fail") assert.Equal(t, "nonexistent", failures[0].(map[string]any)["LFTag"].(map[string]any)["TagKey"]) @@ -335,7 +336,7 @@ func TestUpdateLFTag_AddAndRemove(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) vals := out["TagValues"].([]any) strs := make([]string, len(vals)) @@ -368,7 +369,7 @@ func TestAddLFTagsToResource_TableResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["Failures"]) } diff --git a/services/lakeformation/handler_opt_ins.go b/services/lakeformation/handler_opt_ins.go index 2524c95a0a..d1e658aad2 100644 --- a/services/lakeformation/handler_opt_ins.go +++ b/services/lakeformation/handler_opt_ins.go @@ -2,15 +2,17 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateLakeFormationOptIn(_ context.Context, c *echo.Context, body []byte) error { var in createLakeFormationOptInInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -31,7 +33,7 @@ func (h *Handler) handleCreateLakeFormationOptIn(_ context.Context, c *echo.Cont func (h *Handler) handleDeleteLakeFormationOptIn(_ context.Context, c *echo.Context, body []byte) error { var in deleteLakeFormationOptInInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -53,7 +55,7 @@ func (h *Handler) handleDeleteLakeFormationOptIn(_ context.Context, c *echo.Cont func (h *Handler) handleListLakeFormationOptIns(_ context.Context, c *echo.Context, body []byte) error { var in listLakeFormationOptInsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } diff --git a/services/lakeformation/handler_permissions.go b/services/lakeformation/handler_permissions.go index bc3e691a5c..39cf7aed98 100644 --- a/services/lakeformation/handler_permissions.go +++ b/services/lakeformation/handler_permissions.go @@ -3,16 +3,18 @@ package lakeformation import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGrantPermissions(ctx context.Context, c *echo.Context, body []byte) error { var in grantPermissionsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -33,7 +35,7 @@ func (h *Handler) handleGrantPermissions(ctx context.Context, c *echo.Context, b func (h *Handler) handleRevokePermissions(ctx context.Context, c *echo.Context, body []byte) error { var in revokePermissionsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -55,7 +57,7 @@ func (h *Handler) handleRevokePermissions(ctx context.Context, c *echo.Context, func (h *Handler) handleListPermissions(_ context.Context, c *echo.Context, body []byte) error { var in listPermissionsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -85,7 +87,7 @@ func (h *Handler) handleListPermissions(_ context.Context, c *echo.Context, body func (h *Handler) handleBatchGrantPermissions(ctx context.Context, c *echo.Context, body []byte) error { var in batchGrantPermissionsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -99,7 +101,7 @@ func (h *Handler) handleBatchGrantPermissions(ctx context.Context, c *echo.Conte func (h *Handler) handleBatchRevokePermissions(ctx context.Context, c *echo.Context, body []byte) error { var in batchRevokePermissionsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -139,7 +141,7 @@ func (h *Handler) runBatchPermissions( func (h *Handler) handleGetEffectivePermissionsForPath(_ context.Context, c *echo.Context, body []byte) error { var in getEffectivePermissionsForPathInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } diff --git a/services/lakeformation/handler_permissions_test.go b/services/lakeformation/handler_permissions_test.go index d44d964d48..29b05f52c5 100644 --- a/services/lakeformation/handler_permissions_test.go +++ b/services/lakeformation/handler_permissions_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -45,7 +46,7 @@ func TestHandler_GrantRevokeListPermissions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) perms, _ := listResp["PrincipalResourcePermissions"].([]any) assert.Len(t, perms, 1) @@ -82,7 +83,7 @@ func TestHandler_BatchGrantRevokePermissions(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["Failures"].([]any) assert.Empty(t, failures) @@ -104,7 +105,7 @@ func TestBatchGrantPermissions_EmptyEntries(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["Failures"]) } @@ -126,7 +127,7 @@ func TestGrantAndListPermissions(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) perms := out["PrincipalResourcePermissions"].([]any) assert.Len(t, perms, 1) } diff --git a/services/lakeformation/handler_resources.go b/services/lakeformation/handler_resources.go index 1287564074..401ebe278a 100644 --- a/services/lakeformation/handler_resources.go +++ b/services/lakeformation/handler_resources.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleRegisterResource(_ context.Context, c *echo.Context, body []byte) error { var in registerResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -46,7 +48,7 @@ func (h *Handler) handleRegisterResource(_ context.Context, c *echo.Context, bod func (h *Handler) handleDeregisterResource(_ context.Context, c *echo.Context, body []byte) error { var in deregisterResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -59,7 +61,7 @@ func (h *Handler) handleDeregisterResource(_ context.Context, c *echo.Context, b func (h *Handler) handleDescribeResource(_ context.Context, c *echo.Context, body []byte) error { var in describeResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -74,7 +76,7 @@ func (h *Handler) handleDescribeResource(_ context.Context, c *echo.Context, bod func (h *Handler) handleListResources(_ context.Context, c *echo.Context, body []byte) error { var in listResourcesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -94,7 +96,7 @@ func (h *Handler) handleListResources(_ context.Context, c *echo.Context, body [ func (h *Handler) handleUpdateResource(_ context.Context, c *echo.Context, body []byte) error { var in updateResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } diff --git a/services/lakeformation/handler_resources_test.go b/services/lakeformation/handler_resources_test.go index 0fe4e21965..2f5e058ced 100644 --- a/services/lakeformation/handler_resources_test.go +++ b/services/lakeformation/handler_resources_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -49,7 +50,7 @@ func TestHandler_RegisterDeregisterDescribeResource(t *testing.T) { assert.Equal(t, tt.wantDescStatus, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) ri, ok := descResp["ResourceInfo"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.resourceArn, ri["ResourceArn"]) @@ -123,7 +124,7 @@ func TestHandler_ListResources(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, _ := resp["ResourceInfoList"].([]any) assert.Len(t, list, tt.wantCount) }) @@ -157,7 +158,7 @@ func TestListResources_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list := out["ResourceInfoList"].([]any) require.Len(t, list, 3) diff --git a/services/lakeformation/handler_table_storage.go b/services/lakeformation/handler_table_storage.go index b6cf74e6a8..c80c080dc9 100644 --- a/services/lakeformation/handler_table_storage.go +++ b/services/lakeformation/handler_table_storage.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetTableObjects(_ context.Context, c *echo.Context, body []byte) error { var in getTableObjectsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -25,7 +27,7 @@ func (h *Handler) handleGetTableObjects(_ context.Context, c *echo.Context, body func (h *Handler) handleListTableStorageOptimizers(_ context.Context, c *echo.Context, body []byte) error { var in listTableStorageOptimizersInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } opts, next := h.Backend.ListTableStorageOptimizers( @@ -38,7 +40,7 @@ func (h *Handler) handleListTableStorageOptimizers(_ context.Context, c *echo.Co func (h *Handler) handleUpdateTableObjects(_ context.Context, c *echo.Context, body []byte) error { var in updateTableObjectsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -53,7 +55,7 @@ func (h *Handler) handleUpdateTableObjects(_ context.Context, c *echo.Context, b func (h *Handler) handleUpdateTableStorageOptimizer(_ context.Context, c *echo.Context, body []byte) error { var in updateTableStorageOptimizerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } result := h.Backend.UpdateTableStorageOptimizer( diff --git a/services/lakeformation/handler_test.go b/services/lakeformation/handler_test.go index 7f31381a3c..0294685251 100644 --- a/services/lakeformation/handler_test.go +++ b/services/lakeformation/handler_test.go @@ -2,7 +2,8 @@ package lakeformation_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -62,7 +64,7 @@ func newTestContext(t *testing.T, path string, body any) (*echo.Context, *httpte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -91,7 +93,7 @@ func postJSON(t *testing.T, h *lakeformation.Handler, path string, body any) *ht // jsonDecode is a helper to decode a response body into a map. func jsonDecode(r io.Reader, v any) error { - return json.NewDecoder(r).Decode(v) + return json.UnmarshalDecode(jsontext.NewDecoder(r), v, jsonx.V1()) } func TestHandler_UnknownOperation(t *testing.T) { @@ -515,7 +517,7 @@ func TestErrValidationMapping(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidInputException", resp["__type"]) }) } diff --git a/services/lakeformation/handler_transactions.go b/services/lakeformation/handler_transactions.go index 31cfa87fd1..ff3a2d1f32 100644 --- a/services/lakeformation/handler_transactions.go +++ b/services/lakeformation/handler_transactions.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCancelTransaction(_ context.Context, c *echo.Context, body []byte) error { var in cancelTransactionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -28,7 +30,7 @@ func (h *Handler) handleCancelTransaction(_ context.Context, c *echo.Context, bo func (h *Handler) handleCommitTransaction(_ context.Context, c *echo.Context, body []byte) error { var in commitTransactionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -47,7 +49,7 @@ func (h *Handler) handleCommitTransaction(_ context.Context, c *echo.Context, bo func (h *Handler) handleStartTransaction(_ context.Context, c *echo.Context, body []byte) error { var in startTransactionInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -59,7 +61,7 @@ func (h *Handler) handleStartTransaction(_ context.Context, c *echo.Context, bod func (h *Handler) handleDescribeTransaction(_ context.Context, c *echo.Context, body []byte) error { var in describeTransactionInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -78,7 +80,7 @@ func (h *Handler) handleDescribeTransaction(_ context.Context, c *echo.Context, func (h *Handler) handleListTransactions(_ context.Context, c *echo.Context, body []byte) error { var in listTransactionsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } @@ -93,7 +95,7 @@ func (h *Handler) handleListTransactions(_ context.Context, c *echo.Context, bod func (h *Handler) handleDeleteObjectsOnCancel(_ context.Context, c *echo.Context, body []byte) error { var in deleteObjectsOnCancelInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } @@ -119,7 +121,7 @@ func (h *Handler) handleDeleteObjectsOnCancel(_ context.Context, c *echo.Context func (h *Handler) handleExtendTransaction(_ context.Context, c *echo.Context, body []byte) error { var in extendTransactionInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } } diff --git a/services/lakeformation/handler_transactions_test.go b/services/lakeformation/handler_transactions_test.go index 0afd7d991d..a2b2355754 100644 --- a/services/lakeformation/handler_transactions_test.go +++ b/services/lakeformation/handler_transactions_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -122,7 +123,7 @@ func TestHandler_CommitTransaction(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantStatus2 != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantStatus2, resp["TransactionStatus"]) } }) @@ -198,7 +199,7 @@ func TestCommitTransaction_ReturnsStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "COMMITTED", out["TransactionStatus"]) } diff --git a/services/lakeformation/handler_work_units.go b/services/lakeformation/handler_work_units.go index 527ff73670..b95b35c684 100644 --- a/services/lakeformation/handler_work_units.go +++ b/services/lakeformation/handler_work_units.go @@ -2,16 +2,18 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetQueryState(_ context.Context, c *echo.Context, body []byte) error { var in getQueryStateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } state, err := h.Backend.GetQueryState(in.QueryID) @@ -24,7 +26,7 @@ func (h *Handler) handleGetQueryState(_ context.Context, c *echo.Context, body [ func (h *Handler) handleGetQueryStatistics(_ context.Context, c *echo.Context, body []byte) error { var in getQueryStatisticsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } exec, plan, err := h.Backend.GetQueryStatistics(in.QueryID) @@ -37,7 +39,7 @@ func (h *Handler) handleGetQueryStatistics(_ context.Context, c *echo.Context, b func (h *Handler) handleGetWorkUnitResults(_ context.Context, c *echo.Context, body []byte) error { var in getWorkUnitResultsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } result, err := h.Backend.GetWorkUnitResults(in.QueryID, in.WorkUnitID, in.WorkUnitToken) @@ -50,7 +52,7 @@ func (h *Handler) handleGetWorkUnitResults(_ context.Context, c *echo.Context, b func (h *Handler) handleGetWorkUnits(_ context.Context, c *echo.Context, body []byte) error { var in getWorkUnitsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } ranges, nextToken, err := h.Backend.GetWorkUnits(in.QueryID) @@ -63,7 +65,7 @@ func (h *Handler) handleGetWorkUnits(_ context.Context, c *echo.Context, body [] func (h *Handler) handleStartQueryPlanning(_ context.Context, c *echo.Context, body []byte) error { var in startQueryPlanningInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidInputException", err.Error()) } if strings.TrimSpace(in.QueryString) == "" { diff --git a/services/lakeformation/handler_work_units_test.go b/services/lakeformation/handler_work_units_test.go index 310c8bc7d0..96afc053b0 100644 --- a/services/lakeformation/handler_work_units_test.go +++ b/services/lakeformation/handler_work_units_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -24,7 +25,7 @@ func TestStartQueryPlanning_RequiresDatabaseName(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out["message"].(string), "DatabaseName") } diff --git a/services/lakeformation/multi_region_test.go b/services/lakeformation/multi_region_test.go index 8957d9bcac..a0798d3308 100644 --- a/services/lakeformation/multi_region_test.go +++ b/services/lakeformation/multi_region_test.go @@ -2,7 +2,8 @@ package lakeformation_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -176,8 +178,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/lakeformation/pagination_validation.go b/services/lakeformation/pagination_validation.go index 165b9547dc..039afba684 100644 --- a/services/lakeformation/pagination_validation.go +++ b/services/lakeformation/pagination_validation.go @@ -1,10 +1,12 @@ package lakeformation import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -22,11 +24,11 @@ func validatePagination(op string, body []byte) error { NextToken string `json:"NextToken"` } - if !json.Valid(body) { + if !jsontext.Value(body).IsValid(jsonx.V1()) { return nil } - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) if in.MaxResults != nil && (*in.MaxResults < 1 || *in.MaxResults > maxListResults) { return fmt.Errorf("MaxResults must be between 1 and %d: %w", maxListResults, ErrValidation) diff --git a/services/lakeformation/persistence.go b/services/lakeformation/persistence.go index a6575a115d..bc2d6fd285 100644 --- a/services/lakeformation/persistence.go +++ b/services/lakeformation/persistence.go @@ -2,10 +2,12 @@ package lakeformation import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -94,7 +96,7 @@ func newTransactionDTORegistry() (*store.Registry, *store.Table[transactionSnaps // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourceLFTags map[string][]LFTagPair `json:"resourceLFTags"` Queries map[string]string `json:"queries,omitempty"` TableStorageOptimizers map[string][]StorageOptimizer `json:"tableStorageOptimizers,omitempty"` @@ -186,7 +188,7 @@ func (b *InMemoryBackend) Snapshot() ([]byte, error) { // Marshal failure is returned to the caller (the Persistable wrapper logs // it via the context-aware logger); do not log through slog.Default() here. - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { return nil, fmt.Errorf("lakeformation: marshal snapshot: %w", err) } @@ -278,7 +280,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreTransactionsLocked restores the "dirty" transactions table from // tables via the ephemeral DTO registry, converting each DTO back to its // live type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreTransactionsLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreTransactionsLocked(tables map[string]jsontext.Value) error { dtoReg, txDTOs := newTransactionDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/lakeformation/persistence_test.go b/services/lakeformation/persistence_test.go index 21ee862a49..288414d41e 100644 --- a/services/lakeformation/persistence_test.go +++ b/services/lakeformation/persistence_test.go @@ -1,13 +1,14 @@ package lakeformation_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/services/lakeformation" ) @@ -350,7 +351,7 @@ func TestPersistence_IncludesQueriesAndOptimizers(t *testing.T) { // Verify queries survived restore (GetQueryState should work) var qOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &qOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &qOut, jsonx.V1())) queryID := qOut["QueryId"].(string) state, err := b2.GetQueryState(queryID) diff --git a/services/lambda/account_settings_test.go b/services/lambda/account_settings_test.go index 8afa095077..961e1b89f1 100644 --- a/services/lambda/account_settings_test.go +++ b/services/lambda/account_settings_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -25,7 +27,7 @@ func TestAccountUsage_FunctionCount_Increments(t *testing.T) { require.Equal(t, http.StatusOK, rec0.Code) var s0 lambda.AccountSettingsOutput - require.NoError(t, json.NewDecoder(rec0.Body).Decode(&s0)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec0.Body), &s0, jsonx.V1())) require.NotNil(t, s0.AccountUsage) assert.Equal(t, 0, s0.AccountUsage.FunctionCount) @@ -36,7 +38,7 @@ func TestAccountUsage_FunctionCount_Increments(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var s2 lambda.AccountSettingsOutput - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&s2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &s2, jsonx.V1())) require.NotNil(t, s2.AccountUsage) assert.Equal(t, 2, s2.AccountUsage.FunctionCount) } @@ -50,7 +52,7 @@ func TestAccountUsage_AccountLimit_Fields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var s lambda.AccountSettingsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&s)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &s, jsonx.V1())) require.NotNil(t, s.AccountLimit) assert.Positive(t, s.AccountLimit.TotalCodeSize) assert.Positive(t, s.AccountLimit.ConcurrentExecutions) @@ -110,7 +112,7 @@ func TestGetAccountSettings_UnreservedConcurrency(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) limit, ok := out["AccountLimit"].(map[string]any) require.True(t, ok, "AccountLimit must be present") @@ -133,7 +135,7 @@ func TestGetAccountSettings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.AccountSettingsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.AccountLimit) assert.Positive(t, out.AccountLimit.ConcurrentExecutions) assert.Positive(t, out.AccountLimit.TotalCodeSize) @@ -152,6 +154,6 @@ func TestGetAccountSettings_FunctionCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.AccountSettingsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, 2, out.AccountUsage.FunctionCount) } diff --git a/services/lambda/async_destinations.go b/services/lambda/async_destinations.go index b80c23d773..f4bddfecd9 100644 --- a/services/lambda/async_destinations.go +++ b/services/lambda/async_destinations.go @@ -3,9 +3,11 @@ package lambda import ( "cmp" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -50,8 +52,8 @@ type asyncOutcome struct { // asyncDestinationRecord is the JSON invocation record AWS Lambda delivers to // OnSuccess/OnFailure destinations. type asyncDestinationRecord struct { - RequestPayload json.RawMessage `json:"requestPayload,omitempty"` - ResponsePayload json.RawMessage `json:"responsePayload,omitempty"` + RequestPayload jsontext.Value `json:"requestPayload,omitempty"` + ResponsePayload jsontext.Value `json:"responsePayload,omitempty"` Timestamp string `json:"timestamp"` Version string `json:"version"` RequestContext asyncRequestContext `json:"requestContext"` @@ -100,7 +102,7 @@ func (b *InMemoryBackend) dispatchAsyncOutcome(ctx context.Context, out asyncOut return } - body, marshalErr := json.Marshal(buildAsyncDestinationRecord(out, functionArn)) + body, marshalErr := json.Marshal(buildAsyncDestinationRecord(out, functionArn), jsonx.V1()) if marshalErr != nil { log.WarnContext(ctx, "lambda: failed to marshal destination record", "function", out.functionName, "error", marshalErr) @@ -197,18 +199,18 @@ func buildAsyncDestinationRecord(out asyncOutcome, functionArn string) asyncDest // rawJSONOrNull returns b as raw JSON when it is valid JSON, otherwise a JSON // string containing the raw bytes so the record always marshals cleanly. -func rawJSONOrNull(b []byte) json.RawMessage { +func rawJSONOrNull(b []byte) jsontext.Value { if len(b) == 0 { - return json.RawMessage("null") + return jsontext.Value("null") } - if json.Valid(b) { - return json.RawMessage(b) + if jsontext.Value(b).IsValid(jsonx.V1()) { + return jsontext.Value(b) } - quoted, err := json.Marshal(string(b)) + quoted, err := json.Marshal(string(b), jsonx.V1()) if err != nil { - return json.RawMessage("null") + return jsontext.Value("null") } return quoted diff --git a/services/lambda/capacity_providers_test.go b/services/lambda/capacity_providers_test.go index e92eff569a..5e4b04234b 100644 --- a/services/lambda/capacity_providers_test.go +++ b/services/lambda/capacity_providers_test.go @@ -2,7 +2,8 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/lambda" @@ -231,7 +233,7 @@ func TestCapacityProvider_StateTransitionAndTags(t *testing.T) { CapacityProviderArn string `json:"CapacityProviderArn"` } `json:"CapacityProvider"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec = callInMemoryHandler(t, h, http.MethodGet, "/2025-11-30/capacity-providers/tagged", "") require.Equal(t, http.StatusOK, rec.Code) @@ -544,7 +546,7 @@ func TestCapacityProvider_GetDeleteUpdateList(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut lambda.UpdateCapacityProviderOutput - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&updateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &updateOut, jsonx.V1())) require.NotNil(t, updateOut.CapacityProvider) require.NotNil(t, updateOut.CapacityProvider.CapacityProviderScalingConfig) require.NotNil(t, updateOut.CapacityProvider.CapacityProviderScalingConfig.MaxVCpuCount) @@ -555,7 +557,7 @@ func TestCapacityProvider_GetDeleteUpdateList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListCapacityProvidersOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.CapacityProviders, 1) // Delete — real AWS returns 200 with the deleted provider's state, not 204 @@ -564,7 +566,7 @@ func TestCapacityProvider_GetDeleteUpdateList(t *testing.T) { require.Equal(t, http.StatusOK, delRec.Code) var deleteOut lambda.DeleteCapacityProviderOutput - require.NoError(t, json.NewDecoder(delRec.Body).Decode(&deleteOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(delRec.Body), &deleteOut, jsonx.V1())) require.NotNil(t, deleteOut.CapacityProvider) assert.True(t, strings.HasSuffix(deleteOut.CapacityProvider.CapacityProviderArn, "capacity-provider:test-cp")) @@ -573,7 +575,7 @@ func TestCapacityProvider_GetDeleteUpdateList(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var listOut2 lambda.ListCapacityProvidersOutput - require.NoError(t, json.NewDecoder(listRec2.Body).Decode(&listOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec2.Body), &listOut2, jsonx.V1())) assert.Empty(t, listOut2.CapacityProviders) } @@ -626,7 +628,7 @@ func TestListFunctionVersionsByCapacityProvider(t *testing.T) { var out struct { FunctionVersions []any `json:"FunctionVersions"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Empty(t, out.FunctionVersions) } }) @@ -667,7 +669,7 @@ func TestCapacityProvider_TelemetryConfig(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createOut lambda.CreateCapacityProviderOutput - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) require.NotNil(t, createOut.CapacityProvider.TelemetryConfig) require.NotNil(t, createOut.CapacityProvider.TelemetryConfig.LoggingConfig) assert.Equal(t, "WARN", createOut.CapacityProvider.TelemetryConfig.LoggingConfig.SystemLogLevel) @@ -678,7 +680,7 @@ func TestCapacityProvider_TelemetryConfig(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut lambda.UpdateCapacityProviderOutput - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&updateOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &updateOut, jsonx.V1())) require.NotNil(t, updateOut.CapacityProvider.TelemetryConfig) require.NotNil(t, updateOut.CapacityProvider.TelemetryConfig.LoggingConfig) assert.Equal(t, "DEBUG", updateOut.CapacityProvider.TelemetryConfig.LoggingConfig.SystemLogLevel) diff --git a/services/lambda/code_signing_test.go b/services/lambda/code_signing_test.go index ddef6bae16..e6da21bc3a 100644 --- a/services/lambda/code_signing_test.go +++ b/services/lambda/code_signing_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -48,7 +50,7 @@ func TestCodeSigningConfig_Lifecycle(t *testing.T) { if tt.wantStatus == http.StatusCreated { var out lambda.CreateCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.CodeSigningConfig) assert.NotEmpty(t, out.CodeSigningConfig.CodeSigningConfigArn) assert.Equal(t, tt.wantDesc, out.CodeSigningConfig.Description) @@ -73,7 +75,7 @@ func TestCodeSigningConfig_GetDeleteUpdate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut lambda.CreateCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) cscARN := createOut.CodeSigningConfig.CodeSigningConfigArn // Get @@ -97,7 +99,7 @@ func TestCodeSigningConfig_GetDeleteUpdate(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListCodeSigningConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.CodeSigningConfigs, 1) // Delete @@ -128,7 +130,7 @@ func TestFunctionCodeSigningConfig(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createOut lambda.CreateCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createOut, jsonx.V1())) cscARN := createOut.CodeSigningConfig.CodeSigningConfigArn // Put function code signing config @@ -138,7 +140,7 @@ func TestFunctionCodeSigningConfig(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putOut lambda.PutFunctionCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&putOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &putOut, jsonx.V1())) assert.Equal(t, cscARN, putOut.CodeSigningConfigArn) assert.Equal(t, "csc-test-fn", putOut.FunctionName) @@ -148,7 +150,7 @@ func TestFunctionCodeSigningConfig(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut lambda.GetFunctionCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, cscARN, getOut.CodeSigningConfigArn) // List functions by code signing config @@ -157,7 +159,7 @@ func TestFunctionCodeSigningConfig(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListFunctionsByCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.NotEmpty(t, listOut.FunctionArns) // Delete function code signing config @@ -171,7 +173,7 @@ func TestFunctionCodeSigningConfig(t *testing.T) { assert.Equal(t, http.StatusOK, getRec2.Code) var getOut2 lambda.GetFunctionCodeSigningConfigOutput - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&getOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &getOut2, jsonx.V1())) assert.Empty(t, getOut2.CodeSigningConfigArn) } @@ -234,7 +236,7 @@ func TestCodeSigning(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cscOut lambda.CreateCodeSigningConfigOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cscOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cscOut, jsonx.V1())) require.NotNil(t, cscOut.CodeSigningConfig) cscArn := cscOut.CodeSigningConfig.CodeSigningConfigArn assert.NotEmpty(t, cscArn) @@ -251,7 +253,7 @@ func TestCodeSigning(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listOut lambda.ListCodeSigningConfigsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.CodeSigningConfigs, 1) // UpdateCodeSigningConfig. @@ -280,7 +282,7 @@ func TestCodeSigning(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getCSCOut lambda.GetFunctionCodeSigningConfigOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCSCOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCSCOut, jsonx.V1())) assert.Equal(t, cscArn, getCSCOut.CodeSigningConfigArn) assert.Equal(t, "csc-fn", getCSCOut.FunctionName) diff --git a/services/lambda/concurrency_test.go b/services/lambda/concurrency_test.go index 4956b11798..c577ac50e9 100644 --- a/services/lambda/concurrency_test.go +++ b/services/lambda/concurrency_test.go @@ -1,13 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -110,7 +112,7 @@ func TestPutFunctionConcurrency(t *testing.T) { } var out lambda.FunctionConcurrency - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantReserved, out.ReservedConcurrentExecutions) }) } @@ -199,14 +201,14 @@ func TestGetFunctionConcurrency(t *testing.T) { if tt.wantAbsent { var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.NotContains(t, raw, "ReservedConcurrentExecutions") return } var out lambda.FunctionConcurrency - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantReserved, out.ReservedConcurrentExecutions) }) } @@ -448,7 +450,7 @@ func TestGetFunction_ReservedConcurrentExecutions(t *testing.T) { var out struct { Configuration *lambda.FunctionConfiguration `json:"Configuration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Configuration) if tt.wantReserved == nil { @@ -550,7 +552,7 @@ func TestConcurrency_PutGetDelete(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var conc lambda.FunctionConcurrency - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&conc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &conc, jsonx.V1())) assert.Equal(t, 50, conc.ReservedConcurrentExecutions) // Get @@ -559,7 +561,7 @@ func TestConcurrency_PutGetDelete(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getConc lambda.FunctionConcurrency - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getConc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getConc, jsonx.V1())) assert.Equal(t, 50, getConc.ReservedConcurrentExecutions) // Delete @@ -575,7 +577,7 @@ func TestConcurrency_PutGetDelete(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var raw map[string]any - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&raw)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &raw, jsonx.V1())) assert.NotContains(t, raw, "ReservedConcurrentExecutions") } diff --git a/services/lambda/ddb_esm_test.go b/services/lambda/ddb_esm_test.go index fd0ec94a3b..9c6921b85f 100644 --- a/services/lambda/ddb_esm_test.go +++ b/services/lambda/ddb_esm_test.go @@ -2,13 +2,14 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -260,7 +261,7 @@ func TestLambda_DDB_Poller_InvokesLambdaWithRecords(t *testing.T) { AWSRegion string `json:"awsRegion"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payloads[0], &event)) + require.NoError(t, json.Unmarshal(payloads[0], &event, jsonx.V1())) require.Len(t, event.Records, 2) assert.Equal(t, "event-1", event.Records[0].EventID) diff --git a/services/lambda/delete_function_cleanup_test.go b/services/lambda/delete_function_cleanup_test.go index 1edf60ebad..dc79dab4b1 100644 --- a/services/lambda/delete_function_cleanup_test.go +++ b/services/lambda/delete_function_cleanup_test.go @@ -1,13 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -61,7 +63,7 @@ func TestDeleteFunction_ClearsSideState(t *testing.T) { require.Equal(t, http.StatusOK, aliasListRec.Code) var aliasOut lambda.ListAliasesOutput - require.NoError(t, json.NewDecoder(aliasListRec.Body).Decode(&aliasOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(aliasListRec.Body), &aliasOut, jsonx.V1())) assert.Empty(t, aliasOut.Aliases, "recreated function must not inherit the deleted function's aliases") @@ -70,7 +72,7 @@ func TestDeleteFunction_ClearsSideState(t *testing.T) { require.Equal(t, http.StatusOK, concGetRec.Code) var concOut map[string]any - require.NoError(t, json.NewDecoder(concGetRec.Body).Decode(&concOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(concGetRec.Body), &concOut, jsonx.V1())) assert.NotContains(t, concOut, "ReservedConcurrentExecutions", "recreated function must not inherit the deleted function's reserved concurrency") } diff --git a/services/lambda/dispatch_test.go b/services/lambda/dispatch_test.go index be3fc2493b..1f6713c3e2 100644 --- a/services/lambda/dispatch_test.go +++ b/services/lambda/dispatch_test.go @@ -1,13 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -223,7 +225,7 @@ func TestListFunctionURLConfigs_All(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListFunctionURLConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.FunctionURLConfigs, 2) } @@ -250,7 +252,7 @@ func TestHandlerReset_ClearsState(t *testing.T) { require.Equal(t, http.StatusOK, listCSC.Code) var listOut lambda.ListCodeSigningConfigsOutput - require.NoError(t, json.NewDecoder(listCSC.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listCSC.Body), &listOut, jsonx.V1())) require.NotEmpty(t, listOut.CodeSigningConfigs) // Reset @@ -261,7 +263,7 @@ func TestHandlerReset_ClearsState(t *testing.T) { require.Equal(t, http.StatusOK, listCSC2.Code) var listOut2 lambda.ListCodeSigningConfigsOutput - require.NoError(t, json.NewDecoder(listCSC2.Body).Decode(&listOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listCSC2.Body), &listOut2, jsonx.V1())) assert.Empty(t, listOut2.CodeSigningConfigs) // Verify capacity providers are cleared @@ -269,6 +271,6 @@ func TestHandlerReset_ClearsState(t *testing.T) { require.Equal(t, http.StatusOK, listCP.Code) var listOutCP lambda.ListCapacityProvidersOutput - require.NoError(t, json.NewDecoder(listCP.Body).Decode(&listOutCP)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listCP.Body), &listOutCP, jsonx.V1())) assert.Empty(t, listOutCP.CapacityProviders) } diff --git a/services/lambda/durable_execution_test.go b/services/lambda/durable_execution_test.go index cc2801c685..7954a01139 100644 --- a/services/lambda/durable_execution_test.go +++ b/services/lambda/durable_execution_test.go @@ -1,7 +1,7 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -49,7 +50,7 @@ func TestDurableExecution_GetHistory(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetDurableExecutionHistoryOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Events, 3) assert.Equal(t, "ExecutionStarted", out.Events[0].EventType) assert.Equal(t, "StepStarted", out.Events[1].EventType) @@ -81,7 +82,7 @@ func TestDurableExecution_GetState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetDurableExecutionStateOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Operations, 1) assert.Equal(t, "execution", out.Operations[0].ID) assert.Equal(t, lambda.DurableOperationTypeExecution, out.Operations[0].Type) @@ -111,7 +112,7 @@ func TestDurableExecution_Stop(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var stopOut lambda.StopDurableExecutionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopOut, jsonx.V1())) assert.Positive(t, stopOut.StopTimestamp) rec = callInMemoryHandler(t, h, http.MethodGet, durableExecURL(""), "{}") @@ -212,7 +213,7 @@ func TestDurableExecution_ListByFunction(t *testing.T) { assert.Contains(t, rec.Body.String(), "DurableExecutions") var out lambda.ListDurableExecutionsByFunctionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Neither exec-1 nor exec-2 is associated with "some-func": checkpoint-only // auto-creation has no function-association entry point (see // durableExecutionStore's doc comment) — the correctly wired, correctly @@ -275,7 +276,7 @@ func TestDurableExecution_WireShapeFields(t *testing.T) { rec := callInMemoryHandler(t, h, http.MethodGet, durableExecURL(""), "{}") var out lambda.GetDurableExecutionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, durableExecARN, out.DurableExecutionArn) assert.Equal(t, "test-exec-1", out.DurableExecutionName) assert.NotEqual(t, out.DurableExecutionArn, out.DurableExecutionName) @@ -306,7 +307,7 @@ func TestDurableExecution_WireShapeFields(t *testing.T) { rec := callInMemoryHandler(t, h, http.MethodGet, durableExecURL("")+"?IncludeExecutionData=false", "{}") var out lambda.GetDurableExecutionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.ExecutionDataIncluded) assert.False(t, *out.ExecutionDataIncluded) assert.Nil(t, out.InputPayload) @@ -323,7 +324,7 @@ func TestDurableExecution_WireShapeFields(t *testing.T) { rec := callInMemoryHandler(t, h, http.MethodGet, durableExecURL(""), "{}") var out lambda.GetDurableExecutionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.ExecutionDataIncluded) assert.True(t, *out.ExecutionDataIncluded) }, @@ -346,7 +347,7 @@ func TestDurableExecution_WireShapeFields(t *testing.T) { check: func(t *testing.T, _ *lambda.Handler) { t.Helper() - b, err := json.Marshal(lambda.DurableExecutionStatusTimedOut) + b, err := json.Marshal(lambda.DurableExecutionStatusTimedOut, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, `"TIMED_OUT"`, string(b)) }, @@ -400,7 +401,7 @@ func TestDurableExecution_CheckpointUpdatesOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var checkpointOut lambda.CheckpointDurableExecutionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &checkpointOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &checkpointOut, jsonx.V1())) require.NotNil(t, checkpointOut.NewExecutionState) require.Len(t, checkpointOut.NewExecutionState.Operations, 1) assert.Equal(t, opID, checkpointOut.NewExecutionState.Operations[0].ID) @@ -411,7 +412,7 @@ func TestDurableExecution_CheckpointUpdatesOperations(t *testing.T) { histRec := callInMemoryHandler(t, h, http.MethodGet, durableExecURL("/history"), "{}") var histOut lambda.GetDurableExecutionHistoryOutput - require.NoError(t, json.Unmarshal(histRec.Body.Bytes(), &histOut)) + require.NoError(t, json.Unmarshal(histRec.Body.Bytes(), &histOut, jsonx.V1())) eventTypes := make([]string, 0, len(histOut.Events)) for _, ev := range histOut.Events { @@ -422,7 +423,7 @@ func TestDurableExecution_CheckpointUpdatesOperations(t *testing.T) { stateRec := callInMemoryHandler(t, h, http.MethodGet, durableExecURL("/state"), "{}") var stateOut lambda.GetDurableExecutionStateOutput - require.NoError(t, json.Unmarshal(stateRec.Body.Bytes(), &stateOut)) + require.NoError(t, json.Unmarshal(stateRec.Body.Bytes(), &stateOut, jsonx.V1())) require.Len(t, stateOut.Operations, 2) // implicit root EXECUTION + this one var found bool @@ -506,7 +507,7 @@ func TestDurableExecution_ListByFunctionQueryParams(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListDurableExecutionsByFunctionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) }) } } @@ -562,7 +563,7 @@ func TestDurableExecution_StopVariants(t *testing.T) { if tt.name == "with_custom_error" { var out lambda.GetDurableExecutionOutput - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Error) require.NotNil(t, out.Error.ErrorMessage) assert.Equal(t, "cancelled by operator", *out.Error.ErrorMessage) diff --git a/services/lambda/esm_config_test.go b/services/lambda/esm_config_test.go index a36ae4362f..419d6edf70 100644 --- a/services/lambda/esm_config_test.go +++ b/services/lambda/esm_config_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -33,7 +35,7 @@ func TestESM_HTTPLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) uuid := created["UUID"].(string) // List by function name @@ -78,7 +80,7 @@ func TestESM_BatchWindow(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(60), out["MaximumBatchingWindowInSeconds"], 0) } @@ -98,7 +100,7 @@ func TestESM_TumblingWindow(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(300), out["TumblingWindowInSeconds"], 0) } @@ -117,7 +119,7 @@ func TestESM_FilterCriteria(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) fc, ok := out["FilterCriteria"].(map[string]any) require.True(t, ok) filters := fc["Filters"].([]any) @@ -140,7 +142,7 @@ func TestESM_BisectBatchOnError(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, true, out["BisectBatchOnFunctionError"]) } @@ -160,7 +162,7 @@ func TestESM_MaximumRecordAge(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(3600), out["MaximumRecordAgeInSeconds"], 0) } @@ -180,7 +182,7 @@ func TestESM_ParallelizationFactor(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(5), out["ParallelizationFactor"], 0) } @@ -200,7 +202,7 @@ func TestESM_DestinationConfig(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) dc, ok := out["DestinationConfig"].(map[string]any) require.True(t, ok) onFail := dc["OnFailure"].(map[string]any) @@ -222,7 +224,7 @@ func TestESM_UpdateBatchWindow(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) uuid := created["UUID"].(string) updRec := callInMemoryHandler(t, h, http.MethodPut, @@ -231,7 +233,7 @@ func TestESM_UpdateBatchWindow(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updated map[string]any - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updated)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updated, jsonx.V1())) assert.InDelta(t, float64(30), updated["MaximumBatchingWindowInSeconds"], 0) } @@ -253,7 +255,7 @@ func TestESM_SQS_FullOptions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(10), out["BatchSize"], 0) assert.InDelta(t, float64(5), out["MaximumBatchingWindowInSeconds"], 0) assert.NotNil(t, out["FilterCriteria"]) @@ -279,7 +281,7 @@ func TestESM_DDB_FullOptions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(50), out["BatchSize"], 0) assert.Equal(t, true, out["BisectBatchOnFunctionError"]) assert.InDelta(t, float64(3), out["MaximumRetryAttempts"], 0) @@ -307,7 +309,7 @@ func TestESM_Kinesis_AllWindowOptions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(200), out["BatchSize"], 0) assert.InDelta(t, float64(120), out["MaximumBatchingWindowInSeconds"], 0) assert.InDelta(t, float64(60), out["TumblingWindowInSeconds"], 0) @@ -336,7 +338,7 @@ func TestESM_ListByFunction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) mappings := out["EventSourceMappings"].([]any) assert.Len(t, mappings, 3) } @@ -353,7 +355,7 @@ func TestESM_EnableDisable(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) uuid := created["UUID"].(string) // Disable @@ -365,7 +367,7 @@ func TestESM_EnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updOut map[string]any - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updOut, jsonx.V1())) assert.Equal(t, "Disabled", updOut["State"]) // Re-enable @@ -375,7 +377,7 @@ func TestESM_EnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, enableRec.Code) var enableOut map[string]any - require.NoError(t, json.NewDecoder(enableRec.Body).Decode(&enableOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(enableRec.Body), &enableOut, jsonx.V1())) assert.Equal(t, "Enabled", enableOut["State"]) } @@ -391,7 +393,7 @@ func TestESM_DeleteReturnsMapping(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) uuid := created["UUID"].(string) delRec := callInMemoryHandler(t, h, http.MethodDelete, @@ -399,7 +401,7 @@ func TestESM_DeleteReturnsMapping(t *testing.T) { require.Equal(t, http.StatusOK, delRec.Code) var delOut map[string]any - require.NoError(t, json.NewDecoder(delRec.Body).Decode(&delOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(delRec.Body), &delOut, jsonx.V1())) assert.Equal(t, uuid, delOut["UUID"]) // Get after delete → 404 @@ -425,7 +427,7 @@ func TestESM_MSK_Topics_And_ConsumerGroup(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) topics, _ := out["Topics"].([]any) require.Len(t, topics, 1) @@ -452,7 +454,7 @@ func TestESM_MSK_Get_PreservesConfig(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) id, _ := created["UUID"].(string) require.NotEmpty(t, id) @@ -460,7 +462,7 @@ func TestESM_MSK_Get_PreservesConfig(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &got, jsonx.V1())) topics, _ := got["Topics"].([]any) require.Len(t, topics, 1) @@ -494,7 +496,7 @@ func TestESM_SelfManagedKafka_Create(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) topics, _ := out["Topics"].([]any) require.Len(t, topics, 1) @@ -533,7 +535,7 @@ func TestESM_SelfManagedKafka_UpdateSourceAccessConfigs(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) id := created["UUID"].(string) updateBody := `{ @@ -546,7 +548,7 @@ func TestESM_SelfManagedKafka_UpdateSourceAccessConfigs(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(updateRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updateRec.Body), &out, jsonx.V1())) sacs, _ := out["SourceAccessConfigurations"].([]any) assert.Len(t, sacs, 2) @@ -570,7 +572,7 @@ func TestESM_AmazonMQ_Queues(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queues, _ := out["Queues"].([]any) require.Len(t, queues, 1) @@ -600,7 +602,7 @@ func TestESM_DocumentDB_Create(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) docdb, _ := out["DocumentDBEventSourceConfig"].(map[string]any) require.NotNil(t, docdb) @@ -627,14 +629,14 @@ func TestESM_DocumentDB_Get_PreservesConfig(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) id := created["UUID"].(string) getRec := callInMemoryHandler(t, h, http.MethodGet, "/2015-03-31/event-source-mappings/"+id, "") require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &got, jsonx.V1())) docdb, _ := got["DocumentDBEventSourceConfig"].(map[string]any) require.NotNil(t, docdb) @@ -657,7 +659,7 @@ func TestESM_DocumentDB_NoCollectionName_Omitted(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) docdb, _ := out["DocumentDBEventSourceConfig"].(map[string]any) require.NotNil(t, docdb) @@ -713,7 +715,7 @@ func TestSourceAccessConfiguration_TypeAndURI(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) sacs, _ := out["SourceAccessConfigurations"].([]any) require.Len(t, sacs, 1) @@ -779,7 +781,7 @@ func TestListESM_EventSourceArnFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) esms, _ := out["EventSourceMappings"].([]any) assert.Len(t, esms, tc.wantCount, "EventSourceMappings count mismatch for filter=%q", tc.filter) diff --git a/services/lambda/esm_failure.go b/services/lambda/esm_failure.go index 4a944f7fba..c1f9f0e447 100644 --- a/services/lambda/esm_failure.go +++ b/services/lambda/esm_failure.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -211,7 +212,7 @@ func logDeliveryFailure(ctx context.Context, uuidStr, dest string, err error) { } func marshalFailure(rec failureRecord) ([]byte, error) { - body, err := json.Marshal(rec) + body, err := json.Marshal(rec, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal failure record: %w", err) } diff --git a/services/lambda/esm_failure_internal_test.go b/services/lambda/esm_failure_internal_test.go index 5ed0c92b51..c31639d2c3 100644 --- a/services/lambda/esm_failure_internal_test.go +++ b/services/lambda/esm_failure_internal_test.go @@ -2,7 +2,8 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" "sync" @@ -12,6 +13,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type deliveredMsg struct { @@ -130,10 +133,10 @@ func newFailureHarness( defer mu.Unlock() var ev struct { - Records []json.RawMessage `json:"Records"` + Records []jsontext.Value `json:"Records"` } - require.NoError(t, json.Unmarshal(payload, &ev)) + require.NoError(t, json.Unmarshal(payload, &ev, jsonx.V1())) *h.calls = append(*h.calls, len(ev.Records)) if failOn(string(payload)) { @@ -321,7 +324,7 @@ func TestStreamRetryAndOnFailureDestination(t *testing.T) { } `json:"requestContext"` } - require.NoError(t, json.Unmarshal(body, &rec)) + require.NoError(t, json.Unmarshal(body, &rec, jsonx.V1())) assert.Equal(t, "RetryAttemptsExhausted", rec.RequestContext.Condition) assert.Equal(t, testFnARN, rec.RequestContext.FunctionArn) assert.Equal(t, tt.wantInvokeCnt, rec.RequestContext.Count) @@ -470,7 +473,7 @@ func TestKafkaRetryExhaustionDestinations(t *testing.T) { } `json:"KafkaBatchInfo"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.Equal(t, "RetryAttemptsExhausted", out.RequestContext.Condition) assert.Equal(t, "0", out.KafkaBatchInfo.RecordInfo.Offset) assert.Equal(t, 1, out.KafkaBatchInfo.BatchSize) @@ -642,7 +645,7 @@ func TestESMSystemLogLevels(t *testing.T) { Processor string `json:"eventProcessorId"` } - require.NoError(t, json.Unmarshal([]byte(line), &rec)) + require.NoError(t, json.Unmarshal([]byte(line), &rec, jsonx.V1())) assert.Equal(t, "u-3/0", rec.Processor) got = append(got, rec.EventType) @@ -781,7 +784,7 @@ func TestDynamoDBStreamOnFailureDestination(t *testing.T) { } `json:"requestContext"` } - require.NoError(t, json.Unmarshal(del.msgs[0].body, &out)) + require.NoError(t, json.Unmarshal(del.msgs[0].body, &out, jsonx.V1())) assert.Equal(t, "800000000003126276362", out.Info.Start) assert.Equal(t, "2019-11-14T00:13:19Z", out.Info.First) assert.Equal(t, streamARN, out.Info.StreamArn) @@ -807,7 +810,7 @@ func TestESMMaximumRetryAttemptsWire(t *testing.T) { t.Parallel() m := &EventSourceMapping{FunctionARN: testFnARN, MaximumRetryAttempts: tt.in} - body, err := json.Marshal(toJSONESMResponse(m)) + body, err := json.Marshal(toJSONESMResponse(m), jsonx.V1()) require.NoError(t, err) if tt.want == "" { diff --git a/services/lambda/esm_kafka_failure.go b/services/lambda/esm_kafka_failure.go index 7ce353dd11..4cf13e0a6e 100644 --- a/services/lambda/esm_kafka_failure.go +++ b/services/lambda/esm_kafka_failure.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "strconv" @@ -117,7 +117,7 @@ func (p *EventSourcePoller) deliverKafkaFailure( switch { case strings.HasPrefix(dest, kafkaDestinationPrefix): - rec.Payload = json.RawMessage(event) + rec.Payload = jsontext.Value(event) body, mErr := marshalFailure(rec) if mErr != nil { return mErr diff --git a/services/lambda/esm_logging.go b/services/lambda/esm_logging.go index 8bec9b6bab..95a45512ef 100644 --- a/services/lambda/esm_logging.go +++ b/services/lambda/esm_logging.go @@ -2,11 +2,12 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "maps" "strconv" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -97,7 +98,7 @@ func (l esmLogger) emit(ctx context.Context, at, eventType string, extra map[str maps.Copy(rec, extra) - body, err := json.Marshal(rec) + body, err := json.Marshal(rec, jsonx.V1()) if err != nil { return } diff --git a/services/lambda/esm_partial_batch_test.go b/services/lambda/esm_partial_batch_test.go index 446428065b..032eab635f 100644 --- a/services/lambda/esm_partial_batch_test.go +++ b/services/lambda/esm_partial_batch_test.go @@ -2,13 +2,14 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -204,7 +205,7 @@ func TestLambda_ESM_FunctionResponseTypes_StoredAndRetrieved(t *testing.T) { } func mustMarshal(v any) []byte { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { panic(err) } diff --git a/services/lambda/esm_stream_retry.go b/services/lambda/esm_stream_retry.go index 8330cc8f28..9b8a38ae00 100644 --- a/services/lambda/esm_stream_retry.go +++ b/services/lambda/esm_stream_retry.go @@ -3,13 +3,15 @@ package lambda import ( "cmp" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "time" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -18,7 +20,7 @@ type streamEvent struct { arrival time.Time seq string key string - raw json.RawMessage + raw jsontext.Value } // streamBatch is a batch awaiting a successful invocation. @@ -39,14 +41,14 @@ type streamShardRef struct { } func streamEventPayload(events []streamEvent) ([]byte, error) { - raws := make([]json.RawMessage, len(events)) + raws := make([]jsontext.Value, len(events)) for i, e := range events { raws[i] = e.raw } return json.Marshal(struct { - Records []json.RawMessage `json:"Records"` - }{Records: raws}) + Records []jsontext.Value `json:"Records"` + }{Records: raws}, jsonx.V1()) } func (p *EventSourcePoller) frontStreamBatch(key string) *streamBatch { diff --git a/services/lambda/esm_stream_window.go b/services/lambda/esm_stream_window.go index 13374b33d2..0c3531d931 100644 --- a/services/lambda/esm_stream_window.go +++ b/services/lambda/esm_stream_window.go @@ -3,7 +3,8 @@ package lambda import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "hash/fnv" "math" @@ -12,6 +13,8 @@ import ( "strings" "sync" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -30,7 +33,7 @@ var ( type tumblingWindow struct { start time.Time end time.Time - state json.RawMessage + state jsontext.Value finalQueued bool } @@ -289,13 +292,13 @@ type windowRange struct { } type windowedPayload struct { - State json.RawMessage `json:"state"` - Window windowRange `json:"window"` - ShardID string `json:"shardId"` - EventSourceARN string `json:"eventSourceARN"` - Records []json.RawMessage `json:"Records"` - IsFinalInvokeForWindow bool `json:"isFinalInvokeForWindow"` - IsWindowTerminatedEarly bool `json:"isWindowTerminatedEarly"` + State jsontext.Value `json:"state"` + Window windowRange `json:"window"` + ShardID string `json:"shardId"` + EventSourceARN string `json:"eventSourceARN"` + Records []jsontext.Value `json:"Records"` + IsFinalInvokeForWindow bool `json:"isFinalInvokeForWindow"` + IsWindowTerminatedEarly bool `json:"isWindowTerminatedEarly"` } func (p *EventSourcePoller) streamBatchPayload( @@ -310,10 +313,10 @@ func (p *EventSourcePoller) streamBatchPayload( p.mu.RUnlock() if len(state) == 0 { - state = json.RawMessage(emptyJSONObject) + state = jsontext.Value(emptyJSONObject) } - raws := make([]json.RawMessage, len(b.events)) + raws := make([]jsontext.Value, len(b.events)) for i, e := range b.events { raws[i] = e.raw } @@ -329,21 +332,21 @@ func (p *EventSourcePoller) streamBatchPayload( EventSourceARN: m.EventSourceARN, IsFinalInvokeForWindow: b.final, IsWindowTerminatedEarly: b.early, - }) + }, jsonx.V1()) } // windowResponseState extracts the required state property of a tumbling-window response. -func windowResponseState(body []byte) (json.RawMessage, bool) { +func windowResponseState(body []byte) (jsontext.Value, bool) { var resp struct { - State *json.RawMessage `json:"state"` + State *jsontext.Value `json:"state"` } - if err := json.Unmarshal(body, &resp); err != nil || resp.State == nil { + if err := json.Unmarshal(body, &resp, jsonx.V1()); err != nil || resp.State == nil { return nil, false } if bytes.Equal(bytes.TrimSpace(*resp.State), []byte("null")) { - return json.RawMessage(emptyJSONObject), true + return jsontext.Value(emptyJSONObject), true } return *resp.State, true @@ -358,7 +361,8 @@ func batchFailureIndex(body []byte, events []streamEvent) int { } `json:"batchItemFailures"` } - if len(bytes.TrimSpace(body)) == 0 || json.Unmarshal(body, &resp) != nil || len(resp.BatchItemFailures) == 0 { + if len(bytes.TrimSpace(body)) == 0 || json.Unmarshal(body, &resp, jsonx.V1()) != nil || + len(resp.BatchItemFailures) == 0 { return -1 } diff --git a/services/lambda/esm_stream_window_internal_test.go b/services/lambda/esm_stream_window_internal_test.go index cec3b22455..cc816d27b3 100644 --- a/services/lambda/esm_stream_window_internal_test.go +++ b/services/lambda/esm_stream_window_internal_test.go @@ -2,7 +2,8 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" "sync" @@ -11,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type scriptedKinesisReader struct { @@ -39,13 +42,13 @@ func (r *scriptedKinesisReader) GetRecords(string, int) ([]KinesisRecord, string } type streamCall struct { - Window *windowRange `json:"window"` - State map[string]any `json:"state"` - ShardID string `json:"shardId"` - EventSourceARN string `json:"eventSourceARN"` - Records []json.RawMessage `json:"Records"` - IsFinalInvokeForWindow bool `json:"isFinalInvokeForWindow"` - IsWindowTerminatedEarly bool `json:"isWindowTerminatedEarly"` + Window *windowRange `json:"window"` + State map[string]any `json:"state"` + ShardID string `json:"shardId"` + EventSourceARN string `json:"eventSourceARN"` + Records []jsontext.Value `json:"Records"` + IsFinalInvokeForWindow bool `json:"isFinalInvokeForWindow"` + IsWindowTerminatedEarly bool `json:"isWindowTerminatedEarly"` } func (c streamCall) seqs(t *testing.T) []string { @@ -60,7 +63,7 @@ func (c streamCall) seqs(t *testing.T) []string { } `json:"kinesis"` } - require.NoError(t, json.Unmarshal(r, &rec)) + require.NoError(t, json.Unmarshal(r, &rec, jsonx.V1())) out = append(out, rec.Kinesis.Seq) } @@ -104,7 +107,7 @@ func newStreamHarness( h.p.streamInvoker = func(_ context.Context, _ string, payload []byte) invokeOutcome { var c streamCall - require.NoError(t, json.Unmarshal(payload, &c)) + require.NoError(t, json.Unmarshal(payload, &c, jsonx.V1())) h.mu.Lock() h.calls = append(h.calls, c) @@ -229,7 +232,7 @@ func TestStreamParallelizationFactor(t *testing.T) { } `json:"kinesis"` } - require.NoError(t, json.Unmarshal(r, &rec)) + require.NoError(t, json.Unmarshal(r, &rec, jsonx.V1())) l := laneIndex(rec.Kinesis.PK, tt.factor) if laneOf >= 0 { @@ -360,7 +363,7 @@ func TestStreamTumblingWindow(t *testing.T) { assert.Equal(t, testKinesisARN, c.EventSourceARN) require.NotNil(t, c.Window) - state, err := json.Marshal(c.State) + state, err := json.Marshal(c.State, jsonx.V1()) require.NoError(t, err) assert.JSONEq(t, w.state, string(state), "call %d", i) } @@ -676,7 +679,7 @@ func TestStreamDynamoDBParallelizationKeepsItemOrder(t *testing.T) { } `json:"Records"` } - require.NoError(t, json.Unmarshal(payload, &ev)) + require.NoError(t, json.Unmarshal(payload, &ev, jsonx.V1())) got := make([]string, 0, len(ev.Records)) for _, r := range ev.Records { diff --git a/services/lambda/esm_test.go b/services/lambda/esm_test.go index 13062ac39a..ffa10c8e5e 100644 --- a/services/lambda/esm_test.go +++ b/services/lambda/esm_test.go @@ -2,7 +2,7 @@ package lambda_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -37,7 +38,7 @@ func doESMRequest(t *testing.T, h *lambda.Handler, method, path string, body any var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -87,7 +88,7 @@ func TestLambda_ESM_FilterCriteria_RoundTrip(t *testing.T) { } `json:"Filters"` } `json:"FilterCriteria"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) require.NotEmpty(t, createResp.UUID) require.Len(t, createResp.FilterCriteria.Filters, 1) assert.JSONEq(t, `{"data":{"orderType":["premium"]}}`, createResp.FilterCriteria.Filters[0].Pattern) @@ -113,7 +114,7 @@ func TestLambda_ESM_FilterCriteria_RoundTrip(t *testing.T) { } `json:"Filters"` } `json:"FilterCriteria"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) require.Len(t, getResp.FilterCriteria.Filters, 1) assert.JSONEq(t, `{"data":{"orderType":["standard"]}}`, getResp.FilterCriteria.Filters[0].Pattern) } @@ -144,7 +145,7 @@ func TestLambda_ESM_CRUD(t *testing.T) { State string `json:"State"` BatchSize int `json:"BatchSize"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotEmpty(t, createResp.UUID) assert.Equal(t, streamARN, createResp.EventSourceARN) assert.Equal(t, "Enabled", createResp.State) @@ -160,7 +161,7 @@ func TestLambda_ESM_CRUD(t *testing.T) { UUID string `json:"UUID"` State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, esmUUID, getResp.UUID) // ListEventSourceMappings @@ -172,7 +173,7 @@ func TestLambda_ESM_CRUD(t *testing.T) { UUID string `json:"UUID"` } `json:"EventSourceMappings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.EventSourceMappings, 1) assert.Equal(t, esmUUID, listResp.EventSourceMappings[0].UUID) @@ -204,7 +205,7 @@ func TestLambda_ESM_CreateDisabled(t *testing.T) { var resp struct { State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Disabled", resp.State) } @@ -226,7 +227,7 @@ func TestLambda_ESM_CreateNilEnabled(t *testing.T) { var resp struct { State string `json:"State"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Enabled", resp.State) } @@ -256,7 +257,7 @@ func TestLambda_ESM_ListByFunctionName(t *testing.T) { var allResp struct { EventSourceMappings []any `json:"EventSourceMappings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &allResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &allResp, jsonx.V1())) assert.Len(t, allResp.EventSourceMappings, 2) // List for function-a only @@ -268,7 +269,7 @@ func TestLambda_ESM_ListByFunctionName(t *testing.T) { FunctionARN string `json:"FunctionArn"` } `json:"EventSourceMappings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtResp, jsonx.V1())) assert.Len(t, filtResp.EventSourceMappings, 1) assert.Contains(t, filtResp.EventSourceMappings[0].FunctionARN, "function-a") } diff --git a/services/lambda/esm_tuning_validation_test.go b/services/lambda/esm_tuning_validation_test.go index 408e4ce3f2..723acdf96b 100644 --- a/services/lambda/esm_tuning_validation_test.go +++ b/services/lambda/esm_tuning_validation_test.go @@ -1,12 +1,14 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestESM_TuningValidation(t *testing.T) { @@ -124,7 +126,7 @@ func TestESM_UpdateTuningValidation(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) uuid, _ := created["UUID"].(string) diff --git a/services/lambda/event_filter.go b/services/lambda/event_filter.go index 063b34bf40..9b5927c447 100644 --- a/services/lambda/event_filter.go +++ b/services/lambda/event_filter.go @@ -2,10 +2,13 @@ package lambda import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // eventFilterMatches reports whether the given event source record satisfies the @@ -29,8 +32,8 @@ func eventFilterMatches(fc *FilterCriteria, record map[string]any) bool { continue } - var pattern map[string]json.RawMessage - if err := json.Unmarshal([]byte(f.Pattern), &pattern); err != nil { + var pattern map[string]jsontext.Value + if err := json.Unmarshal([]byte(f.Pattern), &pattern, jsonx.V1()); err != nil { continue } @@ -47,7 +50,7 @@ func eventFilterMatches(fc *FilterCriteria, record map[string]any) bool { // the "$or" combinator documented for "Or (multiple fields)": its value is an // array of sibling pattern fragments evaluated against the same value, and the // object matches only if at least one fragment does. -func patternMatchesObject(pattern map[string]json.RawMessage, value map[string]any) bool { +func patternMatchesObject(pattern map[string]jsontext.Value, value map[string]any) bool { for key, rawRule := range pattern { if key == "$or" { if !orClauseMatches(rawRule, value) { @@ -69,15 +72,15 @@ func patternMatchesObject(pattern map[string]json.RawMessage, value map[string]a // orClauseMatches evaluates a "$or" array of sibling pattern-fragment objects // against value, matching if any fragment matches. -func orClauseMatches(rawRule json.RawMessage, value map[string]any) bool { - var branches []json.RawMessage - if err := json.Unmarshal(rawRule, &branches); err != nil { +func orClauseMatches(rawRule jsontext.Value, value map[string]any) bool { + var branches []jsontext.Value + if err := json.Unmarshal(rawRule, &branches, jsonx.V1()); err != nil { return false } for _, branch := range branches { - var branchPattern map[string]json.RawMessage - if err := json.Unmarshal(branch, &branchPattern); err != nil { + var branchPattern map[string]jsontext.Value + if err := json.Unmarshal(branch, &branchPattern, jsonx.V1()); err != nil { continue } @@ -91,7 +94,7 @@ func orClauseMatches(rawRule json.RawMessage, value map[string]any) bool { // fieldMatchesRule evaluates a single pattern field. rawRule is either a nested // pattern object (recurse) or a JSON array of match rules (any of which may match). -func fieldMatchesRule(rawRule json.RawMessage, fieldVal any, present bool) bool { +func fieldMatchesRule(rawRule jsontext.Value, fieldVal any, present bool) bool { trimmed := strings.TrimSpace(string(rawRule)) // Nested object pattern: recurse into the sub-document. @@ -101,8 +104,8 @@ func fieldMatchesRule(rawRule json.RawMessage, fieldVal any, present bool) bool return false } - var subPattern map[string]json.RawMessage - if err := json.Unmarshal(rawRule, &subPattern); err != nil { + var subPattern map[string]jsontext.Value + if err := json.Unmarshal(rawRule, &subPattern, jsonx.V1()); err != nil { return false } @@ -110,8 +113,8 @@ func fieldMatchesRule(rawRule json.RawMessage, fieldVal any, present bool) bool } // Rule array: the field matches if any element in the array matches. - var rules []json.RawMessage - if err := json.Unmarshal(rawRule, &rules); err != nil { + var rules []jsontext.Value + if err := json.Unmarshal(rawRule, &rules, jsonx.V1()); err != nil { return false } @@ -126,7 +129,7 @@ func fieldMatchesRule(rawRule json.RawMessage, fieldVal any, present bool) bool // ruleMatchesValue evaluates one rule element against the field value. A rule is // either a scalar (exact match) or an operator object like {"prefix":"x"}. -func ruleMatchesValue(rule json.RawMessage, fieldVal any, present bool) bool { +func ruleMatchesValue(rule jsontext.Value, fieldVal any, present bool) bool { trimmed := strings.TrimSpace(string(rule)) if strings.HasPrefix(trimmed, "{") { @@ -137,13 +140,13 @@ func ruleMatchesValue(rule json.RawMessage, fieldVal any, present bool) bool { } // scalarMatches performs an exact comparison between a scalar rule and the field. -func scalarMatches(rule json.RawMessage, fieldVal any, present bool) bool { +func scalarMatches(rule jsontext.Value, fieldVal any, present bool) bool { if !present { return false } var ruleVal any - if err := json.Unmarshal(rule, &ruleVal); err != nil { + if err := json.Unmarshal(rule, &ruleVal, jsonx.V1()); err != nil { return false } @@ -169,9 +172,9 @@ func scalarMatches(rule json.RawMessage, fieldVal any, present bool) bool { // operatorMatches evaluates an operator rule object such as {"prefix":"x"}, // {"exists":true}, {"anything-but":[...]}, or {"numeric":[">",5]}. -func operatorMatches(rule json.RawMessage, fieldVal any, present bool) bool { - var op map[string]json.RawMessage - if err := json.Unmarshal(rule, &op); err != nil { +func operatorMatches(rule jsontext.Value, fieldVal any, present bool) bool { + var op map[string]jsontext.Value + if err := json.Unmarshal(rule, &op, jsonx.V1()); err != nil { return false } @@ -185,7 +188,7 @@ func operatorMatches(rule json.RawMessage, fieldVal any, present bool) bool { } // singleOperatorMatches dispatches one named operator. -func singleOperatorMatches(name string, arg json.RawMessage, fieldVal any, present bool) bool { +func singleOperatorMatches(name string, arg jsontext.Value, fieldVal any, present bool) bool { switch name { case "exists": return existsMatches(arg, fieldVal, present) @@ -209,9 +212,9 @@ func singleOperatorMatches(name string, arg json.RawMessage, fieldVal any, prese // match intermediate nodes" -- a field present as a nested object is an // intermediate node, not a leaf, so it never satisfies exists regardless of // the requested polarity. -func existsMatches(arg json.RawMessage, fieldVal any, present bool) bool { +func existsMatches(arg jsontext.Value, fieldVal any, present bool) bool { var want bool - if err := json.Unmarshal(arg, &want); err != nil { + if err := json.Unmarshal(arg, &want, jsonx.V1()); err != nil { return false } @@ -222,13 +225,13 @@ func existsMatches(arg json.RawMessage, fieldVal any, present bool) bool { return want == present } -func affixMatches(arg json.RawMessage, fieldVal any, present bool, fn func(string, string) bool) bool { +func affixMatches(arg jsontext.Value, fieldVal any, present bool, fn func(string, string) bool) bool { if !present { return false } var want string - if err := json.Unmarshal(arg, &want); err != nil { + if err := json.Unmarshal(arg, &want, jsonx.V1()); err != nil { return false } @@ -237,13 +240,13 @@ func affixMatches(arg json.RawMessage, fieldVal any, present bool, fn func(strin return ok && fn(fv, want) } -func equalsIgnoreCaseMatches(arg json.RawMessage, fieldVal any, present bool) bool { +func equalsIgnoreCaseMatches(arg jsontext.Value, fieldVal any, present bool) bool { if !present { return false } var want string - if err := json.Unmarshal(arg, &want); err != nil { + if err := json.Unmarshal(arg, &want, jsonx.V1()); err != nil { return false } @@ -254,7 +257,7 @@ func equalsIgnoreCaseMatches(arg json.RawMessage, fieldVal any, present bool) bo // anythingButMatches matches when the field is present and NOT equal to any of // the listed values. The argument may be a scalar or an array of scalars. -func anythingButMatches(arg json.RawMessage, fieldVal any, present bool) bool { +func anythingButMatches(arg jsontext.Value, fieldVal any, present bool) bool { if !present { return false } @@ -264,8 +267,8 @@ func anythingButMatches(arg json.RawMessage, fieldVal any, present bool) bool { return !scalarMatches(arg, fieldVal, present) } - var values []json.RawMessage - if err := json.Unmarshal(arg, &values); err != nil { + var values []jsontext.Value + if err := json.Unmarshal(arg, &values, jsonx.V1()); err != nil { return false } @@ -279,7 +282,7 @@ func anythingButMatches(arg json.RawMessage, fieldVal any, present bool) bool { } // numericMatches evaluates a numeric rule like ["=",1] or [">",0,"<=",10]. -func numericMatches(arg json.RawMessage, fieldVal any, present bool) bool { +func numericMatches(arg jsontext.Value, fieldVal any, present bool) bool { if !present { return false } @@ -289,19 +292,19 @@ func numericMatches(arg json.RawMessage, fieldVal any, present bool) bool { return false } - var tokens []json.RawMessage - if err := json.Unmarshal(arg, &tokens); err != nil { + var tokens []jsontext.Value + if err := json.Unmarshal(arg, &tokens, jsonx.V1()); err != nil { return false } for i := 0; i+1 < len(tokens); i += 2 { var operator string - if err := json.Unmarshal(tokens[i], &operator); err != nil { + if err := json.Unmarshal(tokens[i], &operator, jsonx.V1()); err != nil { return false } var operand float64 - if err := json.Unmarshal(tokens[i+1], &operand); err != nil { + if err := json.Unmarshal(tokens[i+1], &operand, jsonx.V1()); err != nil { return false } @@ -339,7 +342,7 @@ func toFloat(v any) (float64, bool) { switch n := v.(type) { case float64: return n, true - case json.Number: + case jsonx.Number: f, err := n.Float64() return f, err == nil @@ -440,7 +443,7 @@ func parseJSONObjectBytes(b []byte) (map[string]any, bool) { } var obj map[string]any - if err := json.Unmarshal(b, &obj); err != nil { + if err := json.Unmarshal(b, &obj, jsonx.V1()); err != nil { return nil, false } diff --git a/services/lambda/event_invoke_config_test.go b/services/lambda/event_invoke_config_test.go index 2f669779b9..6f96052867 100644 --- a/services/lambda/event_invoke_config_test.go +++ b/services/lambda/event_invoke_config_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -150,7 +152,7 @@ func TestPutFunctionEventInvokeConfig(t *testing.T) { if tt.wantRetries != nil || tt.wantAge != nil { var cfg lambda.FunctionEventInvokeConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) if tt.wantRetries != nil { require.NotNil(t, cfg.MaximumRetryAttempts) @@ -186,7 +188,7 @@ func TestFunctionEventInvokeConfig_LastModifiedIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) lastModified, ok := raw["LastModified"] require.True(t, ok, "response must include LastModified") @@ -358,7 +360,7 @@ func TestUpdateFunctionEventInvokeConfig(t *testing.T) { if tt.wantRetries != nil { var cfg lambda.FunctionEventInvokeConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) require.NotNil(t, cfg.MaximumRetryAttempts) assert.Equal(t, *tt.wantRetries, *cfg.MaximumRetryAttempts) } @@ -518,7 +520,7 @@ func TestListFunctionEventInvokeConfigs(t *testing.T) { } var out lambda.ListFunctionEventInvokeConfigsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.FunctionEventInvokeConfigs, tt.wantCount) }) } @@ -709,7 +711,7 @@ func TestEventInvokeConfig_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var cfg lambda.FunctionEventInvokeConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &cfg, jsonx.V1())) require.NotNil(t, cfg.MaximumRetryAttempts) assert.Equal(t, maxRetry, *cfg.MaximumRetryAttempts) require.NotNil(t, cfg.MaximumEventAgeInSeconds) @@ -721,7 +723,7 @@ func TestEventInvokeConfig_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getCfg lambda.FunctionEventInvokeConfig - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getCfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getCfg, jsonx.V1())) assert.Equal(t, maxRetry, *getCfg.MaximumRetryAttempts) // Update config @@ -736,7 +738,7 @@ func TestEventInvokeConfig_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListFunctionEventInvokeConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.NotEmpty(t, listOut.FunctionEventInvokeConfigs) // Delete config @@ -767,7 +769,7 @@ func TestEventInvokeConfig_WithDestinations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cfg lambda.FunctionEventInvokeConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) require.NotNil(t, cfg.DestinationConfig) require.NotNil(t, cfg.DestinationConfig.OnSuccess) assert.Contains(t, cfg.DestinationConfig.OnSuccess.Destination, "success-q") @@ -802,7 +804,7 @@ func TestFunctionDestinations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.FunctionEventInvokeConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.DestinationConfig) require.NotNil(t, out.DestinationConfig.OnSuccess) require.NotNil(t, out.DestinationConfig.OnFailure) diff --git a/services/lambda/event_source_poller.go b/services/lambda/event_source_poller.go index 2b0763b355..ee36106443 100644 --- a/services/lambda/event_source_poller.go +++ b/services/lambda/event_source_poller.go @@ -3,7 +3,7 @@ package lambda import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" @@ -11,6 +11,7 @@ import ( "time" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -641,7 +642,7 @@ func (p *EventSourcePoller) invokeLambda( InvokeIdentityArn: m.FunctionARN, AWSRegion: p.lambdaBackend.region, EventSourceARN: m.EventSourceARN, - }) + }, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "event source poller: failed to marshal event", "error", err) @@ -863,7 +864,7 @@ func (p *EventSourcePoller) invokeLambdaForDDB( NewImage: r.NewImage, OldImage: r.OldImage, }, - }) + }, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "event source poller: failed to marshal DDB event", "error", err) @@ -876,7 +877,7 @@ func (p *EventSourcePoller) invokeLambdaForDDB( arrival = time.Time{} } - keyJSON, _ := json.Marshal(r.Keys) + keyJSON, _ := json.Marshal(r.Keys, jsonx.V1()) events = append(events, streamEvent{arrival: arrival, seq: r.SequenceNumber, key: string(keyJSON), raw: raw}) } @@ -1068,7 +1069,7 @@ func buildSQSEventPayload(region, esmARN string, msgs []*SQSMessage) ([]byte, er } } - payload, err := json.Marshal(sqsEvent{Records: records}) + payload, err := json.Marshal(sqsEvent{Records: records}, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal sqs event: %w", err) } @@ -1161,7 +1162,12 @@ func filterByBatchItemFailures(reportFailures bool, respBody []byte, msgs []*SQS } var failures batchItemFailures - if jsonErr := json.Unmarshal(respBody, &failures); jsonErr != nil || len(failures.BatchItemFailures) == 0 { + if jsonErr := json.Unmarshal( + respBody, + &failures, + jsonx.V1(), + ); jsonErr != nil || + len(failures.BatchItemFailures) == 0 { return nil } diff --git a/services/lambda/event_source_poller_test.go b/services/lambda/event_source_poller_test.go index 3a8ef8defc..4a9865edf3 100644 --- a/services/lambda/event_source_poller_test.go +++ b/services/lambda/event_source_poller_test.go @@ -2,7 +2,7 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -248,7 +249,7 @@ func TestLambda_BuildSQSEventPayload(t *testing.T) { EventSourceARN string `json:"eventSourceARN"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) require.Len(t, event.Records, 1) rec := event.Records[0] diff --git a/services/lambda/extensions_api.go b/services/lambda/extensions_api.go index 906d1c3610..598fec872e 100644 --- a/services/lambda/extensions_api.go +++ b/services/lambda/extensions_api.go @@ -2,7 +2,8 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -197,7 +199,7 @@ type registerResponse struct { func writeExtError(w http.ResponseWriter, status int, typ, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(extensionError{Type: typ, Message: msg}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), extensionError{Type: typ, Message: msg}) } // register adds an extension; it returns the response, identifier or an HTTP failure. @@ -290,7 +292,7 @@ func (r *extensionRegistry) fanoutInvoke(reqID string, deadline time.Time, fnARN body["tracing"] = map[string]string{"type": "X-Amzn-Trace-Id", "value": trace} } - raw, _ := json.Marshal(body) + raw, _ := json.Marshal(body, jsonx.V1()) wait := &invokeWait{pending: map[string]struct{}{}, done: make(chan struct{}), deadline: deadline} for id, ext := range r.exts { @@ -433,7 +435,7 @@ func (r *extensionRegistry) shutdown(ctx context.Context, reason string) { deadline := time.Now().Add(r.shutdownTimeout) raw, _ := json.Marshal(map[string]any{ jsonKeyEventType: extEventShutdown, "shutdownReason": reason, "deadlineMs": deadline.UnixMilli(), - }) + }, jsonx.V1()) var taken []chan struct{} @@ -488,7 +490,7 @@ func (s *runtimeServer) handleExtensionRegister(w http.ResponseWriter, r *http.R body, _ := io.ReadAll(http.MaxBytesReader(w, r.Body, maxExtensionBodyBytes)) if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { writeExtError(w, http.StatusBadRequest, "Extension.InvalidRequestBody", "malformed registration body") return @@ -512,7 +514,7 @@ func (s *runtimeServer) handleExtensionRegister(w http.ResponseWriter, r *http.R w.Header().Set(headerExtensionID, id) w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(resp) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), resp) } // handleExtensionNext serves GET /2020-01-01/extension/event/next (long poll). diff --git a/services/lambda/extensions_api_internal_test.go b/services/lambda/extensions_api_internal_test.go index 428d36c629..ee6799a254 100644 --- a/services/lambda/extensions_api_internal_test.go +++ b/services/lambda/extensions_api_internal_test.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -82,7 +83,7 @@ func (h *extHarness) do( func (h *extHarness) register(t *testing.T, name string, events ...string) string { t.Helper() - b, err := json.Marshal(map[string]any{"events": events}) + b, err := json.Marshal(map[string]any{"events": events}, jsonx.V1()) require.NoError(t, err) status, hdr, body := h.do(t, http.MethodPost, "/2020-01-01/extension/register", @@ -177,7 +178,7 @@ func TestExtensionRegister(t *testing.T) { if tt.wantStatus != http.StatusOK { var e extensionError - require.NoError(t, json.Unmarshal([]byte(body), &e)) + require.NoError(t, json.Unmarshal([]byte(body), &e, jsonx.V1())) assert.Equal(t, tt.wantType, e.Type) return @@ -187,7 +188,7 @@ func TestExtensionRegister(t *testing.T) { var resp map[string]string - require.NoError(t, json.Unmarshal([]byte(body), &resp)) + require.NoError(t, json.Unmarshal([]byte(body), &resp, jsonx.V1())) assert.Equal(t, "ext-fn", resp["functionName"]) assert.Equal(t, "$LATEST", resp["functionVersion"]) assert.Equal(t, "index.handler", resp["handler"]) @@ -250,7 +251,7 @@ func TestExtensionInvokeFanout(t *testing.T) { var ev map[string]any - require.NoError(t, json.Unmarshal([]byte(<-otherEv), &ev)) + require.NoError(t, json.Unmarshal([]byte(<-otherEv), &ev, jsonx.V1())) assert.Equal(t, "INVOKE", ev["eventType"]) assert.Equal(t, reqID, ev["requestId"]) assert.Equal(t, extTestFnARN, ev["invokedFunctionArn"]) @@ -337,7 +338,7 @@ func TestExtensionShutdown(t *testing.T) { var got map[string]any - require.NoError(t, json.Unmarshal([]byte(<-ev), &got)) + require.NoError(t, json.Unmarshal([]byte(<-ev), &got, jsonx.V1())) assert.Equal(t, "SHUTDOWN", got["eventType"]) assert.Equal(t, tt.wantReason, got["shutdownReason"]) assert.Positive(t, got["deadlineMs"]) diff --git a/services/lambda/fis.go b/services/lambda/fis.go index 3558dc44fb..6dd891fe95 100644 --- a/services/lambda/fis.go +++ b/services/lambda/fis.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "math/rand/v2" "net/http" @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -338,7 +339,7 @@ func (b *InMemoryBackend) applyFISFaultToInvocation( errPayload, _ := json.Marshal(map[string]string{ "errorMessage": faultErr.Error(), "errorType": "FISFaultInjection", - }) + }, jsonx.V1()) return errPayload, http.StatusOK, nil } diff --git a/services/lambda/function_fields_test.go b/services/lambda/function_fields_test.go index 46b7c2e995..aafbeab50b 100644 --- a/services/lambda/function_fields_test.go +++ b/services/lambda/function_fields_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -42,7 +44,7 @@ func TestVpcConfig_Ipv6AllowedForDualStack_Create(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) vpc, _ := out["VpcConfig"].(map[string]any) require.NotNil(t, vpc) @@ -63,7 +65,7 @@ func TestVpcConfig_Ipv6AllowedForDualStack_Update(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) vpc, _ := out["VpcConfig"].(map[string]any) require.NotNil(t, vpc) assert.Equal(t, true, vpc["Ipv6AllowedForDualStack"]) @@ -87,7 +89,7 @@ func TestVpcConfig_Ipv6AllowedForDualStack_NotSet_Omitted(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) vpc, _ := out["VpcConfig"].(map[string]any) require.NotNil(t, vpc) // omitempty: nil pointer → field absent in JSON @@ -118,7 +120,7 @@ func TestVpcConfig_Ipv6AllowedForDualStack_GetConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out, jsonx.V1())) vpc, _ := out["VpcConfig"].(map[string]any) require.NotNil(t, vpc) assert.Equal(t, true, vpc["Ipv6AllowedForDualStack"]) @@ -142,7 +144,7 @@ func TestVpcConfig_CreateAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var created lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &created, jsonx.V1())) require.NotNil(t, created.VpcConfig) assert.Equal(t, []string{"subnet-abc"}, created.VpcConfig.SubnetIDs) assert.Equal(t, []string{"sg-xyz"}, created.VpcConfig.SecurityGroupIDs) @@ -151,10 +153,10 @@ func TestVpcConfig_CreateAndGet(t *testing.T) { rec2 := auditGetFunction(t, h, "vpc-fn") require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out)) + var out map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out, jsonx.V1())) var cfg lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(out["Configuration"], &cfg)) + require.NoError(t, json.Unmarshal(out["Configuration"], &cfg, jsonx.V1())) require.NotNil(t, cfg.VpcConfig) assert.Equal(t, []string{"subnet-abc"}, cfg.VpcConfig.SubnetIDs) } @@ -171,7 +173,7 @@ func TestVpcConfig_UpdateAndGet(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, rec2.Body.String()) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.VpcConfig) assert.Equal(t, []string{"subnet-new"}, fn.VpcConfig.SubnetIDs) } @@ -186,7 +188,7 @@ func TestTracingConfig_CreateDefault(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) // Default TracingConfig is PassThrough require.NotNil(t, fn.TracingConfig) assert.Equal(t, "PassThrough", fn.TracingConfig.Mode) @@ -208,7 +210,7 @@ func TestTracingConfig_CreateActive(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.TracingConfig) assert.Equal(t, "Active", fn.TracingConfig.Mode) } @@ -224,7 +226,7 @@ func TestTracingConfig_Update(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.TracingConfig) assert.Equal(t, "Active", fn.TracingConfig.Mode) } @@ -249,7 +251,7 @@ func TestFileSystemConfigs_CreateAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.Len(t, fn.FileSystemConfigs, 1) assert.Equal(t, "arn:aws:elasticfilesystem:us-east-1:000000000000:access-point/fsap-abc", fn.FileSystemConfigs[0].Arn) @@ -270,7 +272,7 @@ func TestFileSystemConfigs_Update(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.Len(t, fn.FileSystemConfigs, 1) assert.Equal(t, "/mnt/new", fn.FileSystemConfigs[0].LocalMountPath) } @@ -293,7 +295,7 @@ func TestDeadLetterConfig_CreateAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.DeadLetterConfig) assert.Equal(t, "arn:aws:sqs:us-east-1:123:my-dlq", fn.DeadLetterConfig.TargetArn) } @@ -310,7 +312,7 @@ func TestDeadLetterConfig_Update(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.DeadLetterConfig) assert.Equal(t, "arn:aws:sns:us-east-1:123:my-topic", fn.DeadLetterConfig.TargetArn) } @@ -339,7 +341,7 @@ func TestDurableConfig_CreateAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.DurableConfig) require.NotNil(t, fn.DurableConfig.ExecutionTimeout) assert.Equal(t, int32(3600), *fn.DurableConfig.ExecutionTimeout) @@ -360,7 +362,7 @@ func TestDurableConfig_Update(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.DurableConfig) require.NotNil(t, fn.DurableConfig.ExecutionTimeout) assert.Equal(t, int32(7200), *fn.DurableConfig.ExecutionTimeout) @@ -386,7 +388,7 @@ func TestDurableConfig_PublishedVersionCarriesConfig(t *testing.T) { require.Equal(t, http.StatusCreated, pubRec.Code) var ver lambda.FunctionVersion - require.NoError(t, json.NewDecoder(pubRec.Body).Decode(&ver)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pubRec.Body), &ver, jsonx.V1())) require.NotNil(t, ver.DurableConfig) require.NotNil(t, ver.DurableConfig.ExecutionTimeout) assert.Equal(t, int32(1800), *ver.DurableConfig.ExecutionTimeout) @@ -631,7 +633,7 @@ func TestEphemeralStorage_DefaultOn_Create(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.EphemeralStorage) assert.Equal(t, int32(512), fn.EphemeralStorage.Size) } @@ -673,7 +675,7 @@ func TestEphemeralStorage_Create(t *testing.T) { if tc.wantCode == http.StatusCreated { var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.EphemeralStorage) assert.Equal(t, int32(tc.size), fn.EphemeralStorage.Size) } @@ -716,7 +718,7 @@ func TestEphemeralStorage_Update(t *testing.T) { if tc.wantCode == http.StatusOK { var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.EphemeralStorage) assert.Equal(t, int32(tc.size), fn.EphemeralStorage.Size) } @@ -742,7 +744,7 @@ func TestImageConfig_Persisted_Create(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.ImageConfigResponse) require.NotNil(t, fn.ImageConfigResponse.ImageConfig) assert.Equal(t, []string{"serve"}, fn.ImageConfigResponse.ImageConfig.Command) @@ -768,10 +770,10 @@ func TestImageConfig_InGetFunction(t *testing.T) { rec2 := auditGetFunction(t, h, "imgcfg-get-fn") require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out)) + var out map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out, jsonx.V1())) var cfg lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(out["Configuration"], &cfg)) + require.NoError(t, json.Unmarshal(out["Configuration"], &cfg, jsonx.V1())) require.NotNil(t, cfg.ImageConfigResponse) require.NotNil(t, cfg.ImageConfigResponse.ImageConfig) assert.Equal(t, []string{"run"}, cfg.ImageConfigResponse.ImageConfig.Command) @@ -785,7 +787,7 @@ func TestImageConfig_NotSetForZip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Nil(t, fn.ImageConfigResponse) } @@ -928,7 +930,7 @@ func TestAllNewFields_PersistInGetConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.VpcConfig) assert.Equal(t, []string{"subnet-1"}, fn.VpcConfig.SubnetIDs) @@ -1049,14 +1051,14 @@ func TestVpcConfig_JSONShape(t *testing.T) { rec := auditCreateFunction(t, h, body) require.Equal(t, http.StatusCreated, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.NewDecoder(rec.Body).Decode(&raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &raw, jsonx.V1())) vpcRaw, ok := raw["VpcConfig"] require.True(t, ok, "VpcConfig key missing from response") - var vpc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(vpcRaw, &vpc)) + var vpc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(vpcRaw, &vpc, jsonx.V1())) assert.Contains(t, vpc, "SubnetIds") assert.Contains(t, vpc, "SecurityGroupIds") } @@ -1068,8 +1070,8 @@ func TestTracingConfig_JSONShape(t *testing.T) { rec := auditCreateFunction(t, h, baseImageFn("tracing-shape-fn")) require.Equal(t, http.StatusCreated, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.NewDecoder(rec.Body).Decode(&raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &raw, jsonx.V1())) _, ok := raw["TracingConfig"] assert.True(t, ok, "TracingConfig key missing from response") @@ -1104,7 +1106,7 @@ func TestMultipleFields_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) // VpcConfig updated require.NotNil(t, fn.VpcConfig) @@ -1153,7 +1155,7 @@ func TestGetFunctionConfiguration_IncludesNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &fn, jsonx.V1())) require.NotNil(t, fn.VpcConfig) assert.Equal(t, []string{"subnet-gc"}, fn.VpcConfig.SubnetIDs) @@ -1192,10 +1194,10 @@ func TestListFunctions_IncludesNewFields(t *testing.T) { require.NoError(t, h.Handler()(c)) require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out)) + var out map[string]jsontext.Value + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out, jsonx.V1())) var fns []lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(out["Functions"], &fns)) + require.NoError(t, json.Unmarshal(out["Functions"], &fns, jsonx.V1())) require.Len(t, fns, 1) require.NotNil(t, fns[0].VpcConfig) assert.Equal(t, []string{"subnet-list"}, fns[0].VpcConfig.SubnetIDs) diff --git a/services/lambda/function_settings_test.go b/services/lambda/function_settings_test.go index 9f83f62afc..e00c840113 100644 --- a/services/lambda/function_settings_test.go +++ b/services/lambda/function_settings_test.go @@ -2,7 +2,8 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -61,7 +63,7 @@ func TestFunctionScalingConfig_PutGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetFunctionScalingConfigOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.AppliedFunctionScalingConfig) require.NotNil(t, out.AppliedFunctionScalingConfig.MaxExecutionEnvironments) assert.Equal(t, int32(maxEnv), *out.AppliedFunctionScalingConfig.MaxExecutionEnvironments) @@ -96,7 +98,7 @@ func TestFunctionScalingConfig_DistinctPerQualifier(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetFunctionScalingConfigOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.AppliedFunctionScalingConfig) require.NotNil(t, out.AppliedFunctionScalingConfig.MaxExecutionEnvironments) @@ -147,7 +149,7 @@ func TestRuntimeManagementConfig_GetDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cfg lambda.RuntimeManagementConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.Equal(t, "Auto", cfg.UpdateRuntimeOn) } @@ -163,7 +165,7 @@ func TestRuntimeManagementConfig_Put(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var cfg lambda.RuntimeManagementConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &cfg, jsonx.V1())) assert.Equal(t, "FunctionUpdate", cfg.UpdateRuntimeOn) } @@ -180,7 +182,7 @@ func TestRuntimeManagementConfig_PutManualWithARN(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var cfg lambda.RuntimeManagementConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &cfg, jsonx.V1())) assert.Equal(t, "Manual", cfg.UpdateRuntimeOn) assert.Equal(t, rva, cfg.RuntimeVersionArn) } @@ -196,7 +198,7 @@ func TestRuntimeManagementConfig_FunctionARNInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cfg lambda.RuntimeManagementConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.Contains(t, cfg.FunctionArn, "rmc-arn-fn") } @@ -215,7 +217,7 @@ func TestRecursionConfig_GetDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var cfg lambda.FunctionRecursionConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.Equal(t, "Terminate", cfg.RecursiveLoop) } @@ -231,7 +233,7 @@ func TestRecursionConfig_Put_Allow(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var cfg lambda.FunctionRecursionConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &cfg, jsonx.V1())) assert.Equal(t, "Allow", cfg.RecursiveLoop) } @@ -251,7 +253,7 @@ func TestRecursionConfig_Put_Terminate(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var cfg lambda.FunctionRecursionConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &cfg, jsonx.V1())) assert.Equal(t, "Terminate", cfg.RecursiveLoop) } diff --git a/services/lambda/function_tenancy_capacity_test.go b/services/lambda/function_tenancy_capacity_test.go index fab249dfca..c29a7c0878 100644 --- a/services/lambda/function_tenancy_capacity_test.go +++ b/services/lambda/function_tenancy_capacity_test.go @@ -1,12 +1,14 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateFunction_TenancyAndCapacityProviderConfig(t *testing.T) { @@ -52,7 +54,7 @@ func TestCreateFunction_TenancyAndCapacityProviderConfig(t *testing.T) { } var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, "PER_TENANT", created["TenancyConfig"].(map[string]any)["TenantIsolationMode"]) rec = callInMemoryHandler(t, h, http.MethodPost, "/2015-03-31/functions/tenant-fn/versions", `{}`) @@ -68,7 +70,7 @@ func TestCreateFunction_TenancyAndCapacityProviderConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) cp, _ := got["CapacityProviderConfig"].(map[string]any) require.NotNil(t, cp, "published version keeps the capacity provider config") diff --git a/services/lambda/function_url_host_test.go b/services/lambda/function_url_host_test.go index 2c21d7be27..5f046fa60c 100644 --- a/services/lambda/function_url_host_test.go +++ b/services/lambda/function_url_host_test.go @@ -5,7 +5,7 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -298,7 +299,7 @@ func TestFunctionURLHost_EventShape(t *testing.T) { Cookies []string `json:"cookies"` IsBase64 bool `json:"isBase64Encoded"` } - require.NoError(t, json.Unmarshal(<-env.events, &ev)) + require.NoError(t, json.Unmarshal(<-env.events, &ev, jsonx.V1())) assert.Equal(t, "2.0", ev.Version) assert.Equal(t, "/a%20b/c", ev.RawPath) @@ -373,7 +374,7 @@ func TestFunctionURLHost_IAMAuthorizerContext(t *testing.T) { } `json:"authorizer"` } `json:"requestContext"` } - require.NoError(t, json.Unmarshal(<-env.events, &ev)) + require.NoError(t, json.Unmarshal(<-env.events, &ev, jsonx.V1())) assert.Equal(t, "AKIDEXAMPLE", ev.RequestContext.Authorizer.IAM.AccessKey) assert.Equal(t, "000000000000", ev.RequestContext.Authorizer.IAM.AccountID) } @@ -501,7 +502,7 @@ func TestFunctionURLConfig_Validation(t *testing.T) { if tt.want == http.StatusCreated { var cfg lambda.FunctionURLConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) assert.Regexp(t, `^https://[0-9a-f]{32}\.lambda-url\.us-east-1\.on\.aws/$`, cfg.FunctionURL) up := callHandler(t, h, http.MethodPut, "/2021-10-31/functions/val-fn/url", `{"InvokeMode":"X"}`, nil) diff --git a/services/lambda/function_url_http.go b/services/lambda/function_url_http.go index b7e91f8ea6..c6d6935582 100644 --- a/services/lambda/function_url_http.go +++ b/services/lambda/function_url_http.go @@ -3,7 +3,7 @@ package lambda import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -20,6 +20,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -275,7 +276,7 @@ func writeFunctionURLError(w http.ResponseWriter, status int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - body, _ := json.Marshal(map[string]string{"Message": message}) + body, _ := json.Marshal(map[string]string{"Message": message}, jsonx.V1()) _, _ = w.Write(body) } @@ -448,7 +449,7 @@ func (b *InMemoryBackend) buildURLEvent(r *http.Request, meta urlRequestMeta) ([ event.Body, event.IsBase64Encoded = encodeURLBody(bodyBytes, r.Header.Get("Content-Type")) - return json.Marshal(event) + return json.Marshal(event, jsonx.V1()) } func urlIAMAuthorizer(r *http.Request, accountID string) *lambdaURLAuthorizer { @@ -590,7 +591,7 @@ func writeFunctionURLResult(w http.ResponseWriter, result []byte, streaming bool if streaming { if prelude, rest, ok := bytes.Cut(result, []byte(functionURLStreamSep)); ok && - json.Unmarshal(prelude, &resp) == nil { + json.Unmarshal(prelude, &resp, jsonx.V1()) == nil { writeURLResponseHead(w, &resp) writeFlushed(w, rest) @@ -598,7 +599,7 @@ func writeFunctionURLResult(w http.ResponseWriter, result []byte, streaming bool } } - if json.Unmarshal(result, &resp) != nil || resp.StatusCode == 0 { + if json.Unmarshal(result, &resp, jsonx.V1()) != nil || resp.StatusCode == 0 { w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(http.StatusOK) diff --git a/services/lambda/function_urls_test.go b/services/lambda/function_urls_test.go index bdf9ac83ae..1f20db4ee4 100644 --- a/services/lambda/function_urls_test.go +++ b/services/lambda/function_urls_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -78,7 +80,7 @@ func TestFunctionURL_CORS(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) require.NotNil(t, cfg.Cors) assert.Equal(t, []string{"https://example.com"}, cfg.Cors.AllowOrigins) assert.Equal(t, 300, cfg.Cors.MaxAge) @@ -96,7 +98,7 @@ func TestFunctionURL_InvokeMode(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.Equal(t, "RESPONSE_STREAM", cfg.InvokeMode) } @@ -112,7 +114,7 @@ func TestFunctionURL_AWSIAMAuth(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.Equal(t, "AWS_IAM", cfg.AuthType) } @@ -128,7 +130,7 @@ func TestFunctionURL_FunctionURLIsSet(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(rec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &cfg, jsonx.V1())) assert.NotEmpty(t, cfg.FunctionURL) } @@ -177,7 +179,7 @@ func TestUpdateFunctionURLConfig_CORS(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &cfg, jsonx.V1())) assert.Equal(t, tt.wantAuthType, cfg.AuthType) @@ -213,7 +215,7 @@ func TestFunctionURL_CorsAllowOrigins_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) cors, _ := out["Cors"].(map[string]any) require.NotNil(t, cors) origins, _ := cors["AllowOrigins"].([]any) @@ -235,7 +237,7 @@ func TestFunctionURLConfig_2021(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &cfg, jsonx.V1())) assert.Equal(t, "NONE", cfg.AuthType) // Get via 2021 path @@ -249,7 +251,7 @@ func TestFunctionURLConfig_2021(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListFunctionURLConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.FunctionURLConfigs, 1) // Update via 2021 path @@ -291,7 +293,7 @@ func TestFunctionURLConfig_2021_ListEmpty(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListFunctionURLConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Empty(t, listOut.FunctionURLConfigs) } @@ -381,7 +383,7 @@ func TestLambda_FunctionURL_EventPayloadEnrichment(t *testing.T) { Headers map[string]string `json:"headers"` Cookies []string `json:"cookies"` } - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) assert.ElementsMatch(t, []string{"session=abc", "theme=dark"}, event.Cookies) assert.Equal(t, "bar", event.QueryStringParameters["foo"]) diff --git a/services/lambda/functions_test.go b/services/lambda/functions_test.go index 849ccd9aaa..1a92663ad5 100644 --- a/services/lambda/functions_test.go +++ b/services/lambda/functions_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -80,7 +82,7 @@ func TestUpdateConfig_EnvVars(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.Environment) assert.Equal(t, "val", fn.Environment.Variables["KEY"]) assert.Equal(t, "bar", fn.Environment.Variables["FOO"]) @@ -98,7 +100,7 @@ func TestUpdateConfig_TimeoutAndMemory(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) assert.Equal(t, 30, fn.Timeout) assert.Equal(t, 512, fn.MemorySize) } @@ -115,7 +117,7 @@ func TestUpdateConfig_VPC(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.VpcConfig) assert.Equal(t, []string{"subnet-abc"}, fn.VpcConfig.SubnetIDs) assert.Equal(t, []string{"sg-xyz"}, fn.VpcConfig.SecurityGroupIDs) @@ -133,7 +135,7 @@ func TestUpdateConfig_DLQ(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.DeadLetterConfig) assert.Equal(t, "arn:aws:sqs:us-east-1:000000000000:dlq", fn.DeadLetterConfig.TargetArn) } @@ -150,7 +152,7 @@ func TestUpdateConfig_Tracing(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.TracingConfig) assert.Equal(t, "Active", fn.TracingConfig.Mode) } @@ -168,7 +170,7 @@ func TestUpdateConfig_FileSystem(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.Len(t, fn.FileSystemConfigs, 1) assert.Equal(t, "/mnt/data", fn.FileSystemConfigs[0].LocalMountPath) } @@ -186,7 +188,7 @@ func TestUpdateConfig_Layers(t *testing.T) { require.Equal(t, http.StatusCreated, pubRec.Code) var layerOut lambda.PublishLayerVersionOutput - require.NoError(t, json.NewDecoder(pubRec.Body).Decode(&layerOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pubRec.Body), &layerOut, jsonx.V1())) layerARN := layerOut.LayerVersionArn // Update function to use layer @@ -196,7 +198,7 @@ func TestUpdateConfig_Layers(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &fn, jsonx.V1())) require.Len(t, fn.Layers, 1) assert.Contains(t, fn.Layers[0].Arn, "my-layer") } @@ -239,7 +241,7 @@ func TestGetFunctionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, 15, fn.Timeout) assert.Equal(t, 256, fn.MemorySize) } @@ -259,7 +261,7 @@ func TestArchitectures_CreateArm64(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.Equal(t, []string{"arm64"}, fn.Architectures) } @@ -274,7 +276,7 @@ func TestArchitectures_CreateX86(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, []string{"x86_64"}, fn.Architectures) } @@ -290,7 +292,7 @@ func TestArchitectures_UpdateCode(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, []string{"arm64"}, fn.Architectures) } @@ -305,7 +307,7 @@ func TestArchitectures_DefaultIsX86(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetFunctionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) // may be empty or x86_64 — just confirm it doesn't return arm64 for _, arch := range out.Configuration.Architectures { assert.NotEqual(t, "arm64", arch) @@ -328,7 +330,7 @@ func TestSnapStart_CreateWithPublishedVersions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.SnapStart) assert.Equal(t, "PublishedVersions", fn.SnapStart.ApplyOn) assert.Equal(t, "On", fn.SnapStart.OptimizationStatus) @@ -345,7 +347,7 @@ func TestSnapStart_CreateWithNone(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.SnapStart) assert.Equal(t, "None", fn.SnapStart.ApplyOn) assert.Equal(t, "Off", fn.SnapStart.OptimizationStatus) @@ -363,7 +365,7 @@ func TestSnapStart_UpdateFunctionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.SnapStart) assert.Equal(t, "PublishedVersions", fn.SnapStart.ApplyOn) assert.Equal(t, "On", fn.SnapStart.OptimizationStatus) @@ -384,7 +386,7 @@ func TestSnapStart_DisableViaUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) require.NotNil(t, fn.SnapStart) assert.Equal(t, "None", fn.SnapStart.ApplyOn) assert.Equal(t, "Off", fn.SnapStart.OptimizationStatus) @@ -403,7 +405,7 @@ func TestSnapStart_GetFunctionReflectsState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetFunctionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.Configuration.SnapStart) assert.Equal(t, "PublishedVersions", out.Configuration.SnapStart.ApplyOn) } @@ -423,7 +425,7 @@ func TestFunctionState_ActiveOnCreate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.GetFunctionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, lambda.FunctionStateActive, out.Configuration.State) } @@ -445,7 +447,7 @@ func TestFunctionState_ListedFunctionsHaveState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListFunctionsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) for _, fn := range out.Functions { assert.NotEmpty(t, string(fn.State), "function %s has empty State", fn.FunctionName) } @@ -462,7 +464,7 @@ func TestFunctionState_VersionHasState(t *testing.T) { require.Equal(t, http.StatusCreated, pubRec.Code) var v lambda.FunctionVersion - require.NoError(t, json.NewDecoder(pubRec.Body).Decode(&v)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pubRec.Body), &v, jsonx.V1())) assert.NotEmpty(t, string(v.State)) } @@ -488,7 +490,7 @@ func TestCreateFunction_AllFields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, "full-fn", fn.FunctionName) assert.Equal(t, 1024, fn.MemorySize) assert.Equal(t, 60, fn.Timeout) @@ -590,7 +592,7 @@ func TestUpdateFunctionCode_Publish(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, tt.wantVersion, fn.Version, "UpdateFunctionCode Publish=%v: Version must be %q", tt.publish, tt.wantVersion) @@ -647,7 +649,7 @@ func TestCreateFunction_Tags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out lambda.GetFunctionOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) for k, v := range tt.wantTags { assert.Equal(t, v, out.Tags[k], @@ -706,7 +708,7 @@ func TestCreateFunction_MemorySizeNotDivisibleBy64(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterValueException", errResp["__type"], "error type must be InvalidParameterValueException, not ServiceException") } @@ -737,7 +739,7 @@ func TestSnapStart_GetConfiguration_OptimizationStatus(t *testing.T) { require.Equal(t, http.StatusOK, cfgRec.Code) var cfg map[string]any - require.NoError(t, json.NewDecoder(cfgRec.Body).Decode(&cfg)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cfgRec.Body), &cfg, jsonx.V1())) snap, _ := cfg["SnapStart"].(map[string]any) require.NotNil(t, snap) assert.Equal(t, "PublishedVersions", snap["ApplyOn"]) @@ -834,7 +836,7 @@ func TestListFunctions_FunctionVersionAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) fns, _ := out["Functions"].([]any) assert.GreaterOrEqual(t, len(fns), tc.wantMinCount, "FunctionVersion=ALL must include $LATEST and all published versions") @@ -876,7 +878,7 @@ func TestUpdateFunctionCodeArchitectures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) require.Equal(t, []string{"arm64"}, fn.Architectures) // GetFunctionConfiguration returns the stored architectures. @@ -885,7 +887,7 @@ func TestUpdateFunctionCodeArchitectures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn2 lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn2, jsonx.V1())) assert.Equal(t, []string{"arm64"}, fn2.Architectures) } diff --git a/services/lambda/get_function_qualifier_test.go b/services/lambda/get_function_qualifier_test.go index efb0ea5636..b91f0de193 100644 --- a/services/lambda/get_function_qualifier_test.go +++ b/services/lambda/get_function_qualifier_test.go @@ -2,7 +2,7 @@ package lambda_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -21,7 +22,7 @@ func doFnRequest(t *testing.T, h *lambda.Handler, method, path string, body any) var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -148,7 +149,7 @@ func TestLambda_GetFunction_ByQualifier(t *testing.T) { Version string `json:"Version"` } `json:"Configuration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Equal(t, tc.wantVersion, out.Configuration.Version) require.Contains(t, out.Configuration.FunctionArn, tc.wantArnSfx) }) @@ -172,7 +173,7 @@ func TestLambda_GetFunctionConfiguration_ByQualifier(t *testing.T) { FunctionArn string `json:"FunctionArn"` Version string `json:"Version"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) require.Equal(t, "1", cfg.Version) require.Contains(t, cfg.FunctionArn, ":function:cfgfn:1") } diff --git a/services/lambda/handler_capacity_providers.go b/services/lambda/handler_capacity_providers.go index f8d487f860..d4b7a7422e 100644 --- a/services/lambda/handler_capacity_providers.go +++ b/services/lambda/handler_capacity_providers.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Capacity provider handlers --- @@ -66,7 +67,7 @@ func (h *Handler) handleCreateCapacityProvider(c *echo.Context, bk *InMemoryBack var input CreateCapacityProviderInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -153,7 +154,7 @@ func (h *Handler) handleUpdateCapacityProvider(c *echo.Context, bk *InMemoryBack var input UpdateCapacityProviderInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_code_signing.go b/services/lambda/handler_code_signing.go index 1e3707f632..e7c1d70d1f 100644 --- a/services/lambda/handler_code_signing.go +++ b/services/lambda/handler_code_signing.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handle2020FunctionRoute handles routes under /2020-06-30/functions/{name}/... @@ -72,7 +73,7 @@ func (h *Handler) handlePutFunctionCodeSigningConfig(c *echo.Context, bk *InMemo } var input PutFunctionCodeSigningConfigInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } @@ -173,7 +174,7 @@ func (h *Handler) handleCreateCodeSigningConfig(c *echo.Context, bk *InMemoryBac var input CreateCodeSigningConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -234,7 +235,7 @@ func (h *Handler) handleUpdateCodeSigningConfig(c *echo.Context, bk *InMemoryBac var input UpdateCodeSigningConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_concurrency.go b/services/lambda/handler_concurrency.go index 9524e8c347..717df6b429 100644 --- a/services/lambda/handler_concurrency.go +++ b/services/lambda/handler_concurrency.go @@ -1,13 +1,15 @@ package lambda import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const opListProvisionedConcurrencyConfigs = "ListProvisionedConcurrencyConfigs" @@ -29,8 +31,8 @@ func (h *Handler) handlePutFunctionConcurrency(c *echo.Context, name string) err "ReservedConcurrentExecutions is required") } - var raw map[string]json.RawMessage - if unmarshalErr := json.Unmarshal(body, &raw); unmarshalErr != nil { + var raw map[string]jsontext.Value + if unmarshalErr := json.Unmarshal(body, &raw, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } @@ -40,7 +42,7 @@ func (h *Handler) handlePutFunctionConcurrency(c *echo.Context, name string) err } var input PutFunctionConcurrencyInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } @@ -131,7 +133,7 @@ func (h *Handler) handlePutProvisionedConcurrencyConfig(c *echo.Context, name st } var input PutProvisionedConcurrencyConfigInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } diff --git a/services/lambda/handler_durable_execution.go b/services/lambda/handler_durable_execution.go index 2c43335034..67e45bb4f0 100644 --- a/services/lambda/handler_durable_execution.go +++ b/services/lambda/handler_durable_execution.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -284,7 +285,7 @@ func replayClosedDurableExecution(ex *DurableExecution, invType string) ([]byte, msg = ptrconv.String(ex.Error.ErrorMessage) } - payload, _ := json.Marshal(map[string]string{"errorMessage": msg}) + payload, _ := json.Marshal(map[string]string{"errorMessage": msg}, jsonx.V1()) return payload, "Unhandled", http.StatusOK } @@ -300,7 +301,7 @@ func (h *Handler) handleCheckpointDurableExecution(c *echo.Context, path string) var body CheckpointDurableExecutionInput if raw, err := httputils.ReadBody(c.Request()); err == nil && len(raw) > 0 { - _ = json.Unmarshal(raw, &body) + _ = json.Unmarshal(raw, &body, jsonx.V1()) } return c.JSON(http.StatusOK, store.checkpoint(arn, body.Updates)) @@ -430,7 +431,7 @@ func (h *Handler) handleStopDurableExecution(c *echo.Context) error { // struct here silently dropped every real client's Error every time. var parsedErr ErrorObject if raw, err := httputils.ReadBody(c.Request()); err == nil && len(raw) > 0 { - _ = json.Unmarshal(raw, &parsedErr) + _ = json.Unmarshal(raw, &parsedErr, jsonx.V1()) } var errObj *ErrorObject @@ -479,7 +480,7 @@ func (h *Handler) handleSendDurableExecutionCallbackFailure(c *echo.Context) err // the request's top-level JSON body, not {"Error": {...}}. var parsedErr ErrorObject if raw, err := httputils.ReadBody(c.Request()); err == nil && len(raw) > 0 { - _ = json.Unmarshal(raw, &parsedErr) + _ = json.Unmarshal(raw, &parsedErr, jsonx.V1()) } var errObj *ErrorObject diff --git a/services/lambda/handler_event_invoke_config.go b/services/lambda/handler_event_invoke_config.go index 14f5745016..50517ae11c 100644 --- a/services/lambda/handler_event_invoke_config.go +++ b/services/lambda/handler_event_invoke_config.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handlePutFunctionEventInvokeConfig handles PUT /2015-03-31/functions/{name}/event-invoke-config. @@ -25,7 +26,7 @@ func (h *Handler) handlePutFunctionEventInvokeConfig(c *echo.Context, name strin var input PutFunctionEventInvokeConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -67,7 +68,7 @@ func (h *Handler) handleUpdateFunctionEventInvokeConfig(c *echo.Context, name st var input PutFunctionEventInvokeConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_event_source_mappings.go b/services/lambda/handler_event_source_mappings.go index 51030ca6d7..88cbd51d14 100644 --- a/services/lambda/handler_event_source_mappings.go +++ b/services/lambda/handler_event_source_mappings.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleESMRoute dispatches event-source-mapping REST API requests. @@ -74,7 +75,7 @@ func (h *Handler) handleCreateESM(c *echo.Context) error { var req handleCreateESMInput - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } @@ -216,7 +217,7 @@ func (h *Handler) handleUpdateESM(c *echo.Context, id string) error { } var req handleUpdateESMInput - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } diff --git a/services/lambda/handler_function_settings.go b/services/lambda/handler_function_settings.go index 477b49004a..066c4e3332 100644 --- a/services/lambda/handler_function_settings.go +++ b/services/lambda/handler_function_settings.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleRuntimeMgmtRoute handles /2021-07-20/functions/{name}/runtime-management-config routes. @@ -51,7 +52,7 @@ func (h *Handler) handleRuntimeMgmtRoute(c *echo.Context, path, method string) e var input PutRuntimeManagementConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -112,7 +113,7 @@ func (h *Handler) handleRecursionConfigRoute(c *echo.Context, path, method strin var input PutFunctionRecursionConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -179,7 +180,7 @@ func (h *Handler) handleScalingConfigRoute(c *echo.Context, path, method string) var input PutFunctionScalingConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_function_urls.go b/services/lambda/handler_function_urls.go index 1e29d68538..339f9cda4b 100644 --- a/services/lambda/handler_function_urls.go +++ b/services/lambda/handler_function_urls.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateFunctionURLConfig(c *echo.Context, name string) error { @@ -24,7 +25,7 @@ func (h *Handler) handleCreateFunctionURLConfig(c *echo.Context, name string) er var input CreateFunctionURLConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -173,7 +174,7 @@ func (h *Handler) handleUpdateFunctionURLConfig(c *echo.Context, name string) er var input UpdateFunctionURLConfigInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_function_urls_test.go b/services/lambda/handler_function_urls_test.go index 1e32de7aea..472adf2075 100644 --- a/services/lambda/handler_function_urls_test.go +++ b/services/lambda/handler_function_urls_test.go @@ -1,7 +1,7 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -62,7 +63,7 @@ func TestFunctionUrl_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cfg lambda.FunctionURLConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) assert.NotEmpty(t, cfg.FunctionURL) assert.Equal(t, "NONE", cfg.AuthType) assert.NotEmpty(t, cfg.FunctionArn) @@ -72,7 +73,7 @@ func TestFunctionUrl_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getCfg lambda.FunctionURLConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getCfg, jsonx.V1())) assert.Equal(t, cfg.FunctionURL, getCfg.FunctionURL) rec = callHandler(t, h, http.MethodDelete, "/2015-03-31/functions/"+tt.funcName+"/url", "", nil) diff --git a/services/lambda/handler_functions.go b/services/lambda/handler_functions.go index a5bd895e69..8fe2739bad 100644 --- a/services/lambda/handler_functions.go +++ b/services/lambda/handler_functions.go @@ -4,7 +4,7 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -371,7 +372,7 @@ func (h *Handler) handleCreateFunction(c *echo.Context) error { } var input CreateFunctionInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid request body") } @@ -589,7 +590,7 @@ func (h *Handler) handleUpdateFunctionCode(c *echo.Context, name string) error { } var input UpdateFunctionCodeInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid request body") } @@ -765,7 +766,7 @@ func (h *Handler) handleUpdateFunctionConfiguration(c *echo.Context, name string } var input UpdateFunctionConfigurationInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid request body") } diff --git a/services/lambda/handler_invocation.go b/services/lambda/handler_invocation.go index 4bbb2aa1e2..5945ecd57e 100644 --- a/services/lambda/handler_invocation.go +++ b/services/lambda/handler_invocation.go @@ -4,7 +4,8 @@ import ( "bytes" "context" "encoding/binary" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "hash/crc32" "math" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // errInvalidInvocationType and errInvalidLogType are returned unwritten by @@ -129,7 +131,7 @@ func writeInvokeResult(c *echo.Context, result []byte, functionError string, sta } func rejectNonJSONPayload(h *Handler, c *echo.Context, body []byte) bool { - if len(body) == 0 || json.Valid(body) { + if len(body) == 0 || jsontext.Value(body).IsValid(jsonx.V1()) { return false } @@ -326,10 +328,10 @@ func isLambdaFunctionErrorPayload(result []byte) bool { } var probe struct { - ErrorMessage *json.RawMessage `json:"errorMessage"` - ErrorType *json.RawMessage `json:"errorType"` + ErrorMessage *jsontext.Value `json:"errorMessage"` + ErrorType *jsontext.Value `json:"errorType"` } - if err := json.Unmarshal(trimmed, &probe); err != nil { + if err := json.Unmarshal(trimmed, &probe, jsonx.V1()); err != nil { return false } diff --git a/services/lambda/handler_layers.go b/services/lambda/handler_layers.go index 0e2104e4ac..42fec6da16 100644 --- a/services/lambda/handler_layers.go +++ b/services/lambda/handler_layers.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleLayersRoute dispatches Lambda Layers REST API requests. @@ -149,7 +150,7 @@ func (h *Handler) handlePublishLayerVersion(c *echo.Context, bk *InMemoryBackend var input PublishLayerVersionInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -233,7 +234,7 @@ func (h *Handler) handleAddLayerVersionPermission( var input AddLayerVersionPermissionInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } diff --git a/services/lambda/handler_permissions.go b/services/lambda/handler_permissions.go index ffad594aaa..f0c36c0dd3 100644 --- a/services/lambda/handler_permissions.go +++ b/services/lambda/handler_permissions.go @@ -1,13 +1,14 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- AddPermission handler --- @@ -25,7 +26,7 @@ func (h *Handler) handleAddPermission(c *echo.Context, name string) error { } var input AddPermissionInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } diff --git a/services/lambda/handler_resource_policy.go b/services/lambda/handler_resource_policy.go index 442ef00dd5..70317f2706 100644 --- a/services/lambda/handler_resource_policy.go +++ b/services/lambda/handler_resource_policy.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resourcePolicyBody is PutResourcePolicy's real request body shape @@ -60,7 +61,7 @@ func (h *Handler) handlePutResourcePolicy(c *echo.Context, b *InMemoryBackend, r } var input resourcePolicyBody - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } diff --git a/services/lambda/handler_runtime_test.go b/services/lambda/handler_runtime_test.go index 487665403d..84ee5c501d 100644 --- a/services/lambda/handler_runtime_test.go +++ b/services/lambda/handler_runtime_test.go @@ -2,7 +2,7 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/container" "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/image" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" gophercontainer "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" @@ -500,7 +501,7 @@ func assertLambdaError(t *testing.T, rec *httptest.ResponseRecorder, errType str t.Helper() var lambdaErr lambda.Error - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lambdaErr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lambdaErr, jsonx.V1())) assert.Equal(t, errType, lambdaErr.Type) } diff --git a/services/lambda/handler_tags.go b/services/lambda/handler_tags.go index 5082eb4077..725d7052fb 100644 --- a/services/lambda/handler_tags.go +++ b/services/lambda/handler_tags.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -102,7 +103,7 @@ func (h *Handler) handleTagsRoute(c *echo.Context, method string) error { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "failed to read body") } var input lambdaTagsInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid body") } var kv map[string]string diff --git a/services/lambda/handler_tags_iam_test.go b/services/lambda/handler_tags_iam_test.go index b5c9f9cfea..3fc64525f4 100644 --- a/services/lambda/handler_tags_iam_test.go +++ b/services/lambda/handler_tags_iam_test.go @@ -1,7 +1,7 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -154,7 +155,7 @@ func TestHandler_TagsRoute(t *testing.T) { if tt.wantTagsNotNil || len(tt.wantTagValues) > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantTagsNotNil { assert.NotNil(t, resp["Tags"]) } @@ -173,7 +174,7 @@ func TestHandler_TagsRoute(t *testing.T) { if len(tt.verifyTagValues) > 0 || len(tt.verifyTagAbsent) > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].(map[string]any) require.True(t, ok) for k, v := range tt.verifyTagValues { diff --git a/services/lambda/handler_test.go b/services/lambda/handler_test.go index 8bb26ea9fe..1923715af0 100644 --- a/services/lambda/handler_test.go +++ b/services/lambda/handler_test.go @@ -2,7 +2,7 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -294,7 +295,7 @@ func TestCreateFunction(t *testing.T) { } var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) assert.Equal(t, tt.wantFunctionName, fn.FunctionName) if tt.wantFunctionArn != "" { assert.Equal(t, tt.wantFunctionArn, fn.FunctionArn) @@ -375,7 +376,7 @@ func TestGetFunction(t *testing.T) { if tt.wantCode == http.StatusOK { var out lambda.GetFunctionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Configuration) assert.Equal(t, tt.funcName, out.Configuration.FunctionName) require.NotNil(t, out.Code) @@ -421,7 +422,7 @@ func TestListFunctions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListFunctionsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Functions, tt.wantCount) }) } @@ -544,7 +545,7 @@ func TestUpdateFunctionCode(t *testing.T) { if tt.wantImageURI != "" { var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) assert.Equal(t, tt.wantImageURI, fn.ImageURI) assert.NotEmpty(t, fn.RevisionID) if tt.wantLastUpdateStatus != "" { @@ -645,7 +646,7 @@ func TestUpdateFunctionConfiguration(t *testing.T) { } var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) if tt.wantDescription != "" { assert.Equal(t, tt.wantDescription, fn.Description) } diff --git a/services/lambda/handler_versions_aliases.go b/services/lambda/handler_versions_aliases.go index edac4b399c..31a5b1013f 100644 --- a/services/lambda/handler_versions_aliases.go +++ b/services/lambda/handler_versions_aliases.go @@ -1,7 +1,7 @@ package lambda import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -83,7 +84,7 @@ func (h *Handler) handlePublishVersion(c *echo.Context, name string) error { var input publishVersionInput if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } } @@ -160,7 +161,7 @@ func (h *Handler) handleCreateAlias(c *echo.Context, name string) error { } var input CreateAliasInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } @@ -264,7 +265,7 @@ func (h *Handler) handleUpdateAlias(c *echo.Context, name, aliasName string) err } var input UpdateAliasInput - if unmarshalErr := json.Unmarshal(body, &input); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &input, jsonx.V1()); unmarshalErr != nil { return h.writeError(c, http.StatusBadRequest, "InvalidParameterValueException", "invalid JSON") } diff --git a/services/lambda/handler_versions_aliases_test.go b/services/lambda/handler_versions_aliases_test.go index acdcbc5f65..2a37e76891 100644 --- a/services/lambda/handler_versions_aliases_test.go +++ b/services/lambda/handler_versions_aliases_test.go @@ -1,13 +1,14 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -69,7 +70,7 @@ func TestPublishVersion(t *testing.T) { if tt.wantVersion != "" { var ver lambda.FunctionVersion - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ver)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ver, jsonx.V1())) assert.Equal(t, tt.wantVersion, ver.Version) assert.Equal(t, tt.wantDesc, ver.Description) assert.Equal(t, tt.funcName, ver.FunctionName) @@ -134,7 +135,7 @@ func TestListVersionsByFunction(t *testing.T) { if tt.wantCount > 0 { var out lambda.ListVersionsByFunctionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Versions, tt.wantCount) for i, v := range tt.wantVersions { assert.Equal(t, v, out.Versions[i].Version) @@ -238,7 +239,7 @@ func TestCreateAlias(t *testing.T) { if tt.wantName != "" { var alias lambda.FunctionAlias - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias, jsonx.V1())) assert.Equal(t, tt.wantName, alias.Name) assert.Equal(t, tt.wantVersion, alias.FunctionVersion) } @@ -312,7 +313,7 @@ func TestGetAlias(t *testing.T) { if tt.wantName != "" { var alias lambda.FunctionAlias - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias, jsonx.V1())) assert.Equal(t, tt.wantName, alias.Name) } }) @@ -378,7 +379,7 @@ func TestListAliases(t *testing.T) { if tt.wantCount > 0 { var out lambda.ListAliasesOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Aliases, tt.wantCount) } }) @@ -456,7 +457,7 @@ func TestUpdateAlias(t *testing.T) { if tt.wantVersion != "" { var alias lambda.FunctionAlias - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias, jsonx.V1())) assert.Equal(t, tt.wantVersion, alias.FunctionVersion) } }) diff --git a/services/lambda/invocation_test.go b/services/lambda/invocation_test.go index 75f6953721..9880c7aaff 100644 --- a/services/lambda/invocation_test.go +++ b/services/lambda/invocation_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -598,14 +599,14 @@ func TestHandleInvokeAsync_Returns202Immediately(t *testing.T) { if tt.wantStatus == http.StatusAccepted { var body map[string]int - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, http.StatusAccepted, body["Status"], "InvokeAsync body must contain {\"Status\":202}") } if tt.wantErrType != "" { var errBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Contains(t, errBody["__type"], tt.wantErrType) } }) diff --git a/services/lambda/kafka_event.go b/services/lambda/kafka_event.go index 8537ab7b34..eb5a1f4e69 100644 --- a/services/lambda/kafka_event.go +++ b/services/lambda/kafka_event.go @@ -2,11 +2,13 @@ package lambda import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "strconv" "strings" "time" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -101,7 +103,7 @@ func buildKafkaEventPayload(eventSource, eventSourceARN, bootstrap string, recs }) } - return json.Marshal(ev) + return json.Marshal(ev, jsonx.V1()) } // kafkaFilterView is the record shape FilterCriteria is matched against; only "value" is a data key. diff --git a/services/lambda/kafka_event_internal_test.go b/services/lambda/kafka_event_internal_test.go index e247ff7fdf..e5320a0961 100644 --- a/services/lambda/kafka_event_internal_test.go +++ b/services/lambda/kafka_event_internal_test.go @@ -2,12 +2,14 @@ package lambda import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func kafkaTestRecord(topic string, partition int32, offset int64, value string) KafkaRecord { @@ -63,7 +65,7 @@ func TestBuildKafkaEventPayload(t *testing.T) { var ev map[string]any - require.NoError(t, json.Unmarshal(raw, &ev)) + require.NoError(t, json.Unmarshal(raw, &ev, jsonx.V1())) assert.Equal(t, tt.source, ev["eventSource"]) assert.Equal(t, "b1:9092,b2:9092", ev["bootstrapServers"]) diff --git a/services/lambda/kafka_integration_internal_test.go b/services/lambda/kafka_integration_internal_test.go index d612bf3d87..086f42a6b2 100644 --- a/services/lambda/kafka_integration_internal_test.go +++ b/services/lambda/kafka_integration_internal_test.go @@ -3,7 +3,7 @@ package lambda import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net" "net/netip" "strconv" @@ -17,6 +17,8 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "github.com/twmb/franz-go/pkg/kgo" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -89,7 +91,7 @@ func awaitDelivery(t *testing.T, ch <-chan []byte) map[string]any { case raw := <-ch: var ev map[string]any - require.NoError(t, json.Unmarshal(raw, &ev)) + require.NoError(t, json.Unmarshal(raw, &ev, jsonx.V1())) return ev case <-time.After(kafkaDeliveryTimeout): diff --git a/services/lambda/kafka_msk_internal_test.go b/services/lambda/kafka_msk_internal_test.go index 785655c4db..74e9d8d5ac 100644 --- a/services/lambda/kafka_msk_internal_test.go +++ b/services/lambda/kafka_msk_internal_test.go @@ -2,12 +2,14 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const mskTestARN = "arn:aws:kafka:us-east-1:000000000000:cluster/c/uuid" @@ -82,7 +84,7 @@ func TestMSKESMFollowsClusterBroker(t *testing.T) { var ev map[string]any - require.NoError(t, json.Unmarshal(<-deliveries, &ev)) + require.NoError(t, json.Unmarshal(<-deliveries, &ev, jsonx.V1())) assert.Equal(t, "aws:kafka", ev["eventSource"]) assert.Equal(t, mskTestARN, ev["eventSourceArn"]) assert.Equal(t, "127.0.0.1:19092", ev["bootstrapServers"]) diff --git a/services/lambda/kafka_worker_internal_test.go b/services/lambda/kafka_worker_internal_test.go index e2e447af5d..290a5e692c 100644 --- a/services/lambda/kafka_worker_internal_test.go +++ b/services/lambda/kafka_worker_internal_test.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync" "sync/atomic" @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errKafkaTestFunction = errors.New("boom") @@ -202,7 +204,7 @@ func TestKafkaWorkerBatching(t *testing.T) { Records map[string][]struct{} `json:"records"` } - require.NoError(t, json.Unmarshal(payload, &ev)) + require.NoError(t, json.Unmarshal(payload, &ev, jsonx.V1())) n := 0 for _, rs := range ev.Records { diff --git a/services/lambda/layer_permissions_test.go b/services/lambda/layer_permissions_test.go index f7cee14828..1a4df24183 100644 --- a/services/lambda/layer_permissions_test.go +++ b/services/lambda/layer_permissions_test.go @@ -1,13 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -166,7 +168,7 @@ func TestLayer_Policy_AddAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, addRec.Code) var addOut lambda.AddLayerVersionPermissionOutput - require.NoError(t, json.NewDecoder(addRec.Body).Decode(&addOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(addRec.Body), &addOut, jsonx.V1())) assert.NotEmpty(t, addOut.Statement) assert.NotEmpty(t, addOut.RevisionID) @@ -175,7 +177,7 @@ func TestLayer_Policy_AddAndGet(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var pol lambda.LayerVersionPolicy - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&pol)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &pol, jsonx.V1())) assert.NotEmpty(t, pol.Policy) } @@ -222,7 +224,7 @@ func TestLayerVersionPermission_RevisionId(t *testing.T) { require.Equal(t, http.StatusCreated, addRec.Code) var addOut lambda.AddLayerVersionPermissionOutput - require.NoError(t, json.NewDecoder(addRec.Body).Decode(&addOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(addRec.Body), &addOut, jsonx.V1())) rev1 := addOut.RevisionID require.NotEmpty(t, rev1) @@ -234,7 +236,7 @@ func TestLayerVersionPermission_RevisionId(t *testing.T) { getRec := callInMemoryHandler(t, h, http.MethodGet, policyPath, "") var pol lambda.LayerVersionPolicy - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&pol)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &pol, jsonx.V1())) assert.NotContains(t, pol.Policy, "s2") // Stale RevisionId on Remove is rejected and leaves the statement in place. @@ -244,7 +246,7 @@ func TestLayerVersionPermission_RevisionId(t *testing.T) { getRec2 := callInMemoryHandler(t, h, http.MethodGet, policyPath, "") var pol2 lambda.LayerVersionPolicy - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&pol2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &pol2, jsonx.V1())) assert.Contains(t, pol2.Policy, "s1") // The correct RevisionId succeeds. diff --git a/services/lambda/layers.go b/services/lambda/layers.go index c574aeced3..e1cd08b96e 100644 --- a/services/lambda/layers.go +++ b/services/lambda/layers.go @@ -3,7 +3,7 @@ package lambda import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -346,7 +347,7 @@ func (b *InMemoryBackend) AddLayerVersionPermission( stmts[input.StatementID] = stmt - stmtJSON, marshalErr := json.Marshal(stmt) + stmtJSON, marshalErr := json.Marshal(stmt, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } @@ -454,7 +455,7 @@ func buildLayerPolicy(stmts map[string]*LayerVersionStatement) (string, error) { Statement: statements, } - data, err := json.Marshal(doc) + data, err := json.Marshal(doc, jsonx.V1()) if err != nil { return "", err } diff --git a/services/lambda/layers_http_test.go b/services/lambda/layers_http_test.go index 65c537e760..4f0b987f9c 100644 --- a/services/lambda/layers_http_test.go +++ b/services/lambda/layers_http_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -50,7 +52,7 @@ func TestLayer_PublishAndGet(t *testing.T) { require.Equal(t, http.StatusCreated, pubRec.Code) var pub lambda.PublishLayerVersionOutput - require.NoError(t, json.NewDecoder(pubRec.Body).Decode(&pub)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pubRec.Body), &pub, jsonx.V1())) assert.Equal(t, int64(1), pub.Version) assert.Equal(t, "v1", pub.Description) assert.Equal(t, []string{"python3.12", "python3.11"}, pub.CompatibleRuntimes) @@ -63,7 +65,7 @@ func TestLayer_PublishAndGet(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got lambda.GetLayerVersionOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &got, jsonx.V1())) assert.Equal(t, int64(1), got.Version) } @@ -79,7 +81,7 @@ func TestLayer_MultipleVersions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var pub lambda.PublishLayerVersionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&pub)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &pub, jsonx.V1())) assert.Equal(t, int64(i+1), pub.Version) } } @@ -99,7 +101,7 @@ func TestLayer_ListVersions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListLayerVersionsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.LayerVersions, 2) } @@ -134,7 +136,7 @@ func TestLayer_ListLayers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListLayersOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.Layers), 2) } @@ -149,14 +151,14 @@ func TestLayer_GetVersionByArn(t *testing.T) { require.Equal(t, http.StatusCreated, pubRec.Code) var pub lambda.PublishLayerVersionOutput - require.NoError(t, json.NewDecoder(pubRec.Body).Decode(&pub)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pubRec.Body), &pub, jsonx.V1())) rec := callInMemoryHandler(t, h, http.MethodGet, "/2018-10-31/layers?find=LayerVersion&Arn="+pub.LayerVersionArn, "") require.Equal(t, http.StatusOK, rec.Code) var got lambda.GetLayerVersionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &got, jsonx.V1())) assert.Equal(t, pub.LayerVersionArn, got.LayerVersionArn) } @@ -229,7 +231,7 @@ func TestListLayers_CompatibleRuntimeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) layers, _ := out["Layers"].([]any) assert.Len(t, layers, tc.wantCount, "layer count mismatch for runtime=%q", tc.compatibleRuntime) }) diff --git a/services/lambda/layers_test.go b/services/lambda/layers_test.go index 73ad3303c8..3c67b61683 100644 --- a/services/lambda/layers_test.go +++ b/services/lambda/layers_test.go @@ -3,7 +3,7 @@ package lambda_test import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -591,7 +592,7 @@ func TestCreateFunctionWithLayers(t *testing.T) { "Layers": tt.layers, } - bodyBytes, _ := json.Marshal(bodyMap) + bodyBytes, _ := json.Marshal(bodyMap, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/2015-03-31/functions", strings.NewReader(string(bodyBytes))) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() diff --git a/services/lambda/list_functions_master_region_test.go b/services/lambda/list_functions_master_region_test.go index 83ab63e7ad..390b4c8eb4 100644 --- a/services/lambda/list_functions_master_region_test.go +++ b/services/lambda/list_functions_master_region_test.go @@ -1,12 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListFunctions_MasterRegion(t *testing.T) { @@ -50,9 +53,9 @@ func TestListFunctions_MasterRegion(t *testing.T) { } var out struct { - Functions []json.RawMessage `json:"Functions"` + Functions []jsontext.Value `json:"Functions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Functions, tt.wantCount) }) } diff --git a/services/lambda/mq_event.go b/services/lambda/mq_event.go index 519b5d4586..6a2455f5f5 100644 --- a/services/lambda/mq_event.go +++ b/services/lambda/mq_event.go @@ -2,10 +2,12 @@ package lambda import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "strconv" "time" "unicode/utf8" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Event shapes follow https://docs.aws.amazon.com/lambda/latest/dg/with-mq.html. @@ -113,10 +115,10 @@ func strPtr(s string) *string { // buildMQEventPayload shapes msgs into the documented aws:mq or aws:rmq event. func buildMQEventPayload(eventSource, eventSourceARN, vhost string, msgs []mqMessage) ([]byte, error) { if eventSource == mqEventSourceRabbitMQ { - return json.Marshal(buildRabbitMQEvent(eventSourceARN, vhost, msgs)) + return json.Marshal(buildRabbitMQEvent(eventSourceARN, vhost, msgs), jsonx.V1()) } - return json.Marshal(buildActiveMQEvent(eventSourceARN, msgs)) + return json.Marshal(buildActiveMQEvent(eventSourceARN, msgs), jsonx.V1()) } func buildActiveMQEvent(eventSourceARN string, msgs []mqMessage) activeMQEvent { diff --git a/services/lambda/mq_event_internal_test.go b/services/lambda/mq_event_internal_test.go index f4ef964725..07e5106a92 100644 --- a/services/lambda/mq_event_internal_test.go +++ b/services/lambda/mq_event_internal_test.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync" "testing" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -244,7 +246,7 @@ func TestMQESMPollsRealBrokerOnly(t *testing.T) { var ev map[string]any - require.NoError(t, json.Unmarshal(<-deliveries, &ev)) + require.NoError(t, json.Unmarshal(<-deliveries, &ev, jsonx.V1())) assert.Equal(t, "aws:rmq", ev["eventSource"]) assert.Equal(t, mqTestBrokerARN, ev["eventSourceArn"]) assert.Contains(t, ev["rmqMessagesByQueue"], "q::/") diff --git a/services/lambda/mq_worker.go b/services/lambda/mq_worker.go index e113c567da..8cd85afb81 100644 --- a/services/lambda/mq_worker.go +++ b/services/lambda/mq_worker.go @@ -2,11 +2,12 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "reflect" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -169,7 +170,7 @@ func (p *EventSourcePoller) mqCredentials( } raw, err := secrets.SecretString(ctx, secretARN) - if err != nil || json.Unmarshal([]byte(raw), &creds) != nil { + if err != nil || json.Unmarshal([]byte(raw), &creds, jsonx.V1()) != nil { return creds, false } diff --git a/services/lambda/mq_worker_internal_test.go b/services/lambda/mq_worker_internal_test.go index 412554a10e..097c8bcbf2 100644 --- a/services/lambda/mq_worker_internal_test.go +++ b/services/lambda/mq_worker_internal_test.go @@ -2,7 +2,7 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync" "sync/atomic" @@ -12,6 +12,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errMQTestFunction = errors.New("boom") @@ -203,7 +205,7 @@ func TestMQWorkerBatching(t *testing.T) { Messages []struct{} `json:"messages"` } - require.NoError(t, json.Unmarshal(payload, &ev)) + require.NoError(t, json.Unmarshal(payload, &ev, jsonx.V1())) sizes = append(sizes, len(ev.Messages)) diff --git a/services/lambda/multi_region_test.go b/services/lambda/multi_region_test.go index 48b3c9615b..9147477dd5 100644 --- a/services/lambda/multi_region_test.go +++ b/services/lambda/multi_region_test.go @@ -3,7 +3,8 @@ package lambda_test import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -48,7 +50,7 @@ func regionCall(t *testing.T, h *lambda.Handler, region, method, path string, bo var buf bytes.Buffer if body != nil { - require.NoError(t, json.NewEncoder(&buf).Encode(body)) + require.NoError(t, json.MarshalEncode(jsontext.NewEncoder(&buf, jsonx.V1()), body)) } req := httptest.NewRequest(method, path, &buf).WithContext(regionCtx(region)) @@ -77,7 +79,7 @@ func createRegionFunction(t *testing.T, h *lambda.Handler, region, name string) FunctionArn string `json:"FunctionArn"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out.FunctionArn } @@ -95,7 +97,7 @@ func regionFunctionNames(t *testing.T, h *lambda.Handler, region string) []strin } `json:"Functions"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) names := make([]string, 0, len(out.Functions)) @@ -316,9 +318,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.remote, hasRegions) diff --git a/services/lambda/permissions.go b/services/lambda/permissions.go index f3e1efe019..a92e01a18c 100644 --- a/services/lambda/permissions.go +++ b/services/lambda/permissions.go @@ -4,13 +4,15 @@ import ( "bytes" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "strconv" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AddPermission adds a permission statement to a function's resource-based policy. @@ -271,10 +273,9 @@ func buildPermissionStatementJSON(p *FunctionPermission, resourceArn string) str var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) + enc := jsontext.NewEncoder(&buf, jsonx.V1(), jsontext.EscapeForHTML(false)) - if err := enc.Encode(stmt); err != nil { + if err := json.MarshalEncode(enc, stmt); err != nil { return "" } diff --git a/services/lambda/permissions_test.go b/services/lambda/permissions_test.go index 7cac66ff08..9994e43bf0 100644 --- a/services/lambda/permissions_test.go +++ b/services/lambda/permissions_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -29,7 +31,7 @@ func TestPermission_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, addRec.Code) var addOut lambda.AddPermissionOutput - require.NoError(t, json.NewDecoder(addRec.Body).Decode(&addOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(addRec.Body), &addOut, jsonx.V1())) require.NotNil(t, addOut.Statement) assert.NotEmpty(t, *addOut.Statement) @@ -44,7 +46,7 @@ func TestPermission_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var pol lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&pol)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &pol, jsonx.V1())) require.NotNil(t, pol.Policy) assert.Contains(t, *pol.Policy, "AllowS3") assert.Contains(t, *pol.Policy, "AllowSNS") @@ -63,7 +65,7 @@ func TestPermission_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var pol2 lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&pol2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &pol2, jsonx.V1())) assert.NotContains(t, *pol2.Policy, "AllowS3") assert.Contains(t, *pol2.Policy, "AllowSNS") } @@ -129,7 +131,7 @@ func TestPermission_SourceArn(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var addOut lambda.AddPermissionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&addOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &addOut, jsonx.V1())) require.NotNil(t, addOut.Statement) assert.NotEmpty(t, *addOut.Statement) // Statement must include the StatementId @@ -274,7 +276,7 @@ func TestPolicyJSON_SourceArnCondition(t *testing.T) { require.NotEmpty(t, policyStr) var policy map[string]any - require.NoError(t, json.Unmarshal([]byte(policyStr), &policy)) + require.NoError(t, json.Unmarshal([]byte(policyStr), &policy, jsonx.V1())) stmts, _ := policy["Statement"].([]any) require.Len(t, stmts, 1) stmt := stmts[0].(map[string]any) @@ -368,7 +370,7 @@ func TestAddPermission(t *testing.T) { if tt.wantStatus == http.StatusCreated { var out lambda.AddPermissionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.Statement) assert.NotEmpty(t, *out.Statement) } @@ -416,7 +418,7 @@ func TestAddPermission_FunctionUrlConditions(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out lambda.AddPermissionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.Statement) assert.Contains(t, *out.Statement, `"StringEquals":{"lambda:FunctionUrlAuthType":"NONE"}`) assert.Contains(t, *out.Statement, `"Bool":{"lambda:InvokedViaFunctionUrl":"true"}`) @@ -425,7 +427,7 @@ func TestAddPermission_FunctionUrlConditions(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var pol lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&pol)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &pol, jsonx.V1())) require.NotNil(t, pol.Policy) assert.Contains(t, *pol.Policy, "lambda:FunctionUrlAuthType") assert.Contains(t, *pol.Policy, "lambda:InvokedViaFunctionUrl") @@ -453,7 +455,7 @@ func TestAddPermission_RevisionId(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var pol lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&pol)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &pol, jsonx.V1())) require.NotNil(t, pol.RevisionID) rev1 := *pol.RevisionID assert.NotEmpty(t, rev1) @@ -466,7 +468,7 @@ func TestAddPermission_RevisionId(t *testing.T) { getRec2 := callInMemoryHandler(t, h, http.MethodGet, policyPath, "") var pol2 lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec2.Body).Decode(&pol2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec2.Body), &pol2, jsonx.V1())) assert.Equal(t, rev1, *pol2.RevisionID, "rejected AddPermission must not change the revision") assert.NotContains(t, *pol2.Policy, "s2") @@ -478,7 +480,7 @@ func TestAddPermission_RevisionId(t *testing.T) { getRec3 := callInMemoryHandler(t, h, http.MethodGet, policyPath, "") var pol3 lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec3.Body).Decode(&pol3)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec3.Body), &pol3, jsonx.V1())) rev3 := *pol3.RevisionID assert.NotEqual(t, rev1, rev3, "a real mutation must change the revision") @@ -489,7 +491,7 @@ func TestAddPermission_RevisionId(t *testing.T) { getRec4 := callInMemoryHandler(t, h, http.MethodGet, policyPath, "") var pol4 lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec4.Body).Decode(&pol4)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec4.Body), &pol4, jsonx.V1())) assert.Contains(t, *pol4.Policy, "s1", "rejected RemovePermission must not delete the statement") // RemovePermission with the correct RevisionId succeeds. @@ -519,7 +521,7 @@ func TestPermission_PolicyJSONEscapesValues(t *testing.T) { reqBody, err := json.Marshal(map[string]string{ "StatementId": tt.sid, "Action": "lambda:InvokeFunction", "Principal": "s3.amazonaws.com", "SourceArn": tt.sourceArn, - }) + }, jsonx.V1()) require.NoError(t, err) addRec := callInMemoryHandler(t, h, http.MethodPost, "/2015-03-31/functions/fn/policy", string(reqBody)) @@ -529,7 +531,7 @@ func TestPermission_PolicyJSONEscapesValues(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out lambda.GetPolicyOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) require.NotNil(t, out.Policy) type statement struct { @@ -540,7 +542,7 @@ func TestPermission_PolicyJSONEscapesValues(t *testing.T) { var doc struct { Statement []statement `json:"Statement"` } - require.NoError(t, json.Unmarshal([]byte(*out.Policy), &doc)) + require.NoError(t, json.Unmarshal([]byte(*out.Policy), &doc, jsonx.V1())) require.Len(t, doc.Statement, 1) assert.Equal(t, tt.sid, doc.Statement[0].Sid) assert.Equal(t, tt.sourceArn, doc.Statement[0].Condition["ArnLike"]["AWS:SourceArn"]) diff --git a/services/lambda/persistence.go b/services/lambda/persistence.go index 74141f40dc..b79df07bdc 100644 --- a/services/lambda/persistence.go +++ b/services/lambda/persistence.go @@ -2,10 +2,12 @@ package lambda import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -160,7 +162,7 @@ type backendSnapshot struct { // b.ephemeralRegistry (codeSigningConfigs, // provisionedConcurrencies) are deliberately NOT included here -- they // were never persisted before this refactor and must stay that way. - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` // The fields below have no pure, self-contained identity (see // store_setup.go's exclusion list) so they cannot become store.Tables, but // they WERE persisted before this refactor and must remain so. @@ -258,7 +260,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "persistence: snapshot marshal failed", "service", "lambda", "error", err) @@ -382,7 +384,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreFunctionsFromDTO restores b.functions from its "functions" DTO // entry in tables (gopherstack-rluhj; b.functions is not on b.registry, see // store_setup.go's package doc and functionConfigurationSnapshot above). -func (b *InMemoryBackend) restoreFunctionsFromDTO(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreFunctionsFromDTO(tables map[string]jsontext.Value) error { funcDTOReg := store.NewRegistry() funcDTOs := store.Register(funcDTOReg, "functions", store.New(functionConfigurationSnapshotKey)) @@ -403,7 +405,7 @@ func (b *InMemoryBackend) restoreFunctionsFromDTO(tables map[string]json.RawMess // restorePermissionsFromDTO restores b.permissions from its "permissions" // DTO entry in tables (b.permissions is not on b.registry, see // store_setup.go's package doc). -func (b *InMemoryBackend) restorePermissionsFromDTO(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restorePermissionsFromDTO(tables map[string]jsontext.Value) error { permDTOReg := store.NewRegistry() permDTOs := store.Register(permDTOReg, "permissions", store.New(permissionSnapshotKey)) diff --git a/services/lambda/provisioned_concurrency_test.go b/services/lambda/provisioned_concurrency_test.go index f53503851f..584dfa3d4b 100644 --- a/services/lambda/provisioned_concurrency_test.go +++ b/services/lambda/provisioned_concurrency_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -139,7 +141,7 @@ func TestPutProvisionedConcurrencyConfig(t *testing.T) { } var out lambda.ProvisionedConcurrencyConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantRequested, out.RequestedProvisionedConcurrentExecutions) assert.Equal(t, tt.wantRequested, out.AllocatedProvisionedConcurrentExecutions) assert.Equal(t, "READY", out.Status) @@ -233,7 +235,7 @@ func TestGetProvisionedConcurrencyConfig(t *testing.T) { } var out lambda.ProvisionedConcurrencyConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantRequested, out.RequestedProvisionedConcurrentExecutions) assert.Equal(t, "READY", out.Status) }) @@ -407,7 +409,7 @@ func TestListProvisionedConcurrencyConfigs(t *testing.T) { } var out lambda.ListProvisionedConcurrencyConfigsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.ProvisionedConcurrencyConfigs, tt.wantCount) }) } @@ -469,7 +471,7 @@ func TestProvisionedConcurrency_Lifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, putRec.Code) var pc lambda.ProvisionedConcurrencyConfig - require.NoError(t, json.NewDecoder(putRec.Body).Decode(&pc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putRec.Body), &pc, jsonx.V1())) assert.Equal(t, 5, pc.RequestedProvisionedConcurrentExecutions) assert.Equal(t, "READY", pc.Status) @@ -479,7 +481,7 @@ func TestProvisionedConcurrency_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getPC lambda.ProvisionedConcurrencyConfig - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getPC)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getPC, jsonx.V1())) assert.Equal(t, 5, getPC.RequestedProvisionedConcurrentExecutions) // List @@ -488,7 +490,7 @@ func TestProvisionedConcurrency_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListProvisionedConcurrencyConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.ProvisionedConcurrencyConfigs, 1) // Delete @@ -500,7 +502,7 @@ func TestProvisionedConcurrency_Lifecycle(t *testing.T) { listRec2 := callInMemoryHandler(t, h, http.MethodGet, "/2019-09-30/functions/pc-fn/provisioned-concurrency", "") var listOut2 lambda.ListProvisionedConcurrencyConfigsOutput - require.NoError(t, json.NewDecoder(listRec2.Body).Decode(&listOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec2.Body), &listOut2, jsonx.V1())) assert.Empty(t, listOut2.ProvisionedConcurrencyConfigs) } @@ -549,7 +551,7 @@ func TestProvisionedConcurrency_StatusReady(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.InDelta(t, float64(5), out["RequestedProvisionedConcurrentExecutions"], 0) assert.InDelta(t, float64(5), out["AllocatedProvisionedConcurrentExecutions"], 0) assert.InDelta(t, float64(5), out["AvailableProvisionedConcurrentExecutions"], 0) @@ -587,7 +589,7 @@ func TestProvisionedConcurrency_List_IncludesAllQualifiers(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out lambda.ListProvisionedConcurrencyConfigsOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.ProvisionedConcurrencyConfigs, 2) } @@ -658,7 +660,7 @@ func TestProvisionedConcurrency_FullCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var pcOut lambda.ProvisionedConcurrencyConfig - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pcOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pcOut, jsonx.V1())) assert.Equal(t, 5, pcOut.RequestedProvisionedConcurrentExecutions) assert.Equal(t, "READY", pcOut.Status) @@ -673,7 +675,7 @@ func TestProvisionedConcurrency_FullCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listOut lambda.ListProvisionedConcurrencyConfigsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.ProvisionedConcurrencyConfigs, 1) // DeleteProvisionedConcurrencyConfig. diff --git a/services/lambda/publish_latest_published_test.go b/services/lambda/publish_latest_published_test.go index 0e787f689f..c243a051e3 100644 --- a/services/lambda/publish_latest_published_test.go +++ b/services/lambda/publish_latest_published_test.go @@ -1,7 +1,7 @@ package lambda_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPublishVersion_PublishTo(t *testing.T) { @@ -44,13 +46,13 @@ func TestPublishVersion_PublishTo(t *testing.T) { } var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantVer, got["Version"]) assert.Contains(t, got["FunctionArn"], ":"+tt.wantVer) rec = callInMemoryHandler(t, h, http.MethodPost, "/2015-03-31/functions/pub-fn/versions", tt.body) require.Equal(t, http.StatusCreated, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) if tt.wantVer == "$LATEST.PUBLISHED" { assert.Equal(t, tt.wantVer, got["Version"], "republish keeps the single version") diff --git a/services/lambda/snapstart_extra_test.go b/services/lambda/snapstart_extra_test.go index 8a1baa6f02..ff80310a03 100644 --- a/services/lambda/snapstart_extra_test.go +++ b/services/lambda/snapstart_extra_test.go @@ -1,13 +1,15 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -62,7 +64,7 @@ func TestSnapStart_ReportedOnPublishedVersion(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ver lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ver)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ver, jsonx.V1())) require.NotNil(t, ver.SnapStart) assert.Equal(t, "PublishedVersions", ver.SnapStart.ApplyOn) assert.Equal(t, "On", ver.SnapStart.OptimizationStatus) @@ -85,6 +87,6 @@ func TestSnapStart_OmittedWhenUnset(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var ver lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ver)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ver, jsonx.V1())) assert.Nil(t, ver.SnapStart) } diff --git a/services/lambda/store_test.go b/services/lambda/store_test.go index b9fa96403c..1ae5fbbaf3 100644 --- a/services/lambda/store_test.go +++ b/services/lambda/store_test.go @@ -3,7 +3,7 @@ package lambda_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/portalloc" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -596,7 +597,7 @@ func TestBuildURLEventPayload(t *testing.T) { require.NoError(t, err) var event map[string]any - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) if tt.wantVersion != "" { assert.Equal(t, tt.wantVersion, event["version"]) diff --git a/services/lambda/tags_test.go b/services/lambda/tags_test.go index a10b54ac09..91903620ca 100644 --- a/services/lambda/tags_test.go +++ b/services/lambda/tags_test.go @@ -2,13 +2,15 @@ package lambda_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -26,7 +28,7 @@ func TestTags_TagAndListAndUntag(t *testing.T) { getRec := callInMemoryHandler(t, h, http.MethodGet, "/2015-03-31/functions/tags-fn", "") var out lambda.GetFunctionOutput - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) fnARN := out.Configuration.FunctionArn // Tag @@ -41,7 +43,7 @@ func TestTags_TagAndListAndUntag(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsOut map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&tagsOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -57,7 +59,7 @@ func TestTags_TagAndListAndUntag(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var tagsOut2 map[string]any - require.NoError(t, json.NewDecoder(listRec2.Body).Decode(&tagsOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec2.Body), &tagsOut2, jsonx.V1())) tags2, _ := tagsOut2["Tags"].(map[string]any) _, hasEnv := tags2["env"] assert.False(t, hasEnv) @@ -85,7 +87,7 @@ func TestGetFunction_ReturnsTopLevelTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) rawTags, ok := out["Tags"].(map[string]any) require.True(t, ok, "GetFunctionOutput must have a top-level Tags field, got: %v", out["Tags"]) @@ -103,7 +105,7 @@ func TestTags_CreateFunctionWithTags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) // Tags may be in the function config or accessible separately _ = fn } @@ -142,7 +144,7 @@ func TestTaggedFunctions_CreationTags(t *testing.T) { h, _ := newInMemoryHandler(t) - tagsJSON, err := json.Marshal(tt.tags) + tagsJSON, err := json.Marshal(tt.tags, jsonx.V1()) require.NoError(t, err) body := `{"FunctionName":"` + tt.fnName + `","PackageType":"Image","Code":{"ImageUri":"x"},` + diff --git a/services/lambda/test_helpers_test.go b/services/lambda/test_helpers_test.go index 5550ebb40e..4a05416224 100644 --- a/services/lambda/test_helpers_test.go +++ b/services/lambda/test_helpers_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "fmt" "hash/crc32" "io" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -163,7 +164,7 @@ func lambdaParseBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]an t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/lambda/versions_aliases_test.go b/services/lambda/versions_aliases_test.go index adc5a70e20..debae34d6b 100644 --- a/services/lambda/versions_aliases_test.go +++ b/services/lambda/versions_aliases_test.go @@ -1,7 +1,8 @@ package lambda_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -83,7 +85,7 @@ func TestPublishVersion_Basic(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var v lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&v)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &v, jsonx.V1())) assert.Equal(t, "1", v.Version) assert.Equal(t, "ver-fn", v.FunctionName) assert.NotEmpty(t, v.FunctionArn) @@ -101,7 +103,7 @@ func TestPublishVersion_Increments(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var v lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&v)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &v, jsonx.V1())) assert.Equal(t, strconv.Itoa(i), v.Version) } } @@ -117,7 +119,7 @@ func TestPublishVersion_WithDescription(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var v lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&v)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &v, jsonx.V1())) assert.Equal(t, "my-snapshot", v.Description) assert.Equal(t, "1", v.Version) } @@ -143,7 +145,7 @@ func TestListVersionsByFunction_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListVersionsByFunctionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) // $LATEST is always present require.NotEmpty(t, out.Versions) assert.Equal(t, "$LATEST", out.Versions[0].Version) @@ -163,7 +165,7 @@ func TestListVersionsByFunction_AfterPublish(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out lambda.ListVersionsByFunctionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) // $LATEST + 2 numbered versions assert.GreaterOrEqual(t, len(out.Versions), 2) @@ -196,7 +198,7 @@ func TestPublishVersion_Publish_CreateFunction(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, "1", fn.Version) } @@ -211,7 +213,7 @@ func TestFunctionVersion_ARNFormat(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var v lambda.FunctionVersion - require.NoError(t, json.NewDecoder(rec.Body).Decode(&v)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &v, jsonx.V1())) // versioned ARN should end with :1 assert.True(t, strings.HasSuffix(v.FunctionArn, ":1"), "expected ARN to end with :1, got %s", v.FunctionArn) } @@ -235,7 +237,7 @@ func TestAlias_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var alias lambda.FunctionAlias - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&alias)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &alias, jsonx.V1())) assert.Equal(t, "live", alias.Name) assert.Equal(t, "1", alias.FunctionVersion) assert.Equal(t, "prod alias", alias.Description) @@ -247,7 +249,7 @@ func TestAlias_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getAlias lambda.FunctionAlias - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getAlias)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getAlias, jsonx.V1())) assert.Equal(t, "live", getAlias.Name) // List aliases @@ -256,7 +258,7 @@ func TestAlias_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listOut lambda.ListAliasesOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.Aliases, 1) // Delete alias @@ -284,7 +286,7 @@ func TestAlias_RoutingConfig_WeightedTraffic(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var alias lambda.FunctionAlias - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&alias)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &alias, jsonx.V1())) require.NotNil(t, alias.RoutingConfig) assert.InDelta(t, 0.1, alias.RoutingConfig.AdditionalVersionWeights["2"], 0.001) } @@ -308,7 +310,7 @@ func TestAlias_Update(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updated lambda.FunctionAlias - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updated)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updated, jsonx.V1())) assert.Equal(t, "2", updated.FunctionVersion) assert.Equal(t, "now v2", updated.Description) } @@ -331,7 +333,7 @@ func TestAlias_UpdateRoutingConfig(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updated lambda.FunctionAlias - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&updated)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &updated, jsonx.V1())) require.NotNil(t, updated.RoutingConfig) assert.InDelta(t, 0.2, updated.RoutingConfig.AdditionalVersionWeights["2"], 0.001) } @@ -388,14 +390,14 @@ func TestAlias_RevisionID_Changes(t *testing.T) { `{"Name":"rev","FunctionVersion":"1"}`) require.Equal(t, http.StatusCreated, createRec.Code) var a1 lambda.FunctionAlias - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&a1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &a1, jsonx.V1())) updRec := callInMemoryHandler(t, h, http.MethodPut, "/2015-03-31/functions/alias-rev-fn/aliases/rev", `{"FunctionVersion":"2"}`) require.Equal(t, http.StatusOK, updRec.Code) var a2 lambda.FunctionAlias - require.NoError(t, json.NewDecoder(updRec.Body).Decode(&a2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(updRec.Body), &a2, jsonx.V1())) assert.NotEqual(t, a1.RevisionID, a2.RevisionID) } @@ -418,7 +420,7 @@ func TestAlias_RevisionID_Precondition(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var a1 lambda.FunctionAlias - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&a1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &a1, jsonx.V1())) staleRec := callInMemoryHandler(t, h, http.MethodPut, "/2015-03-31/functions/alias-precond-fn/aliases/pc", @@ -428,7 +430,7 @@ func TestAlias_RevisionID_Precondition(t *testing.T) { getRec := callInMemoryHandler(t, h, http.MethodGet, "/2015-03-31/functions/alias-precond-fn/aliases/pc", "") var got lambda.FunctionAlias - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &got, jsonx.V1())) assert.Equal(t, "1", got.FunctionVersion, "rejected update must not change the alias") okRec := callInMemoryHandler(t, h, http.MethodPut, @@ -459,7 +461,7 @@ func TestPublishVersion_RevisionID_Precondition(t *testing.T) { listRec := callInMemoryHandler(t, h, http.MethodGet, "/2015-03-31/functions/publish-precond-fn/versions", "") var list lambda.ListVersionsByFunctionOutput - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&list)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &list, jsonx.V1())) assert.Len(t, list.Versions, 1, "rejected publish must not create a numbered version ($LATEST only)") okRec := callInMemoryHandler(t, h, http.MethodPost, @@ -502,7 +504,7 @@ func TestVersion_AlwaysLatestInCreateFunction(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, tt.wantVersion, fn.Version, "CreateFunction response must include Version=%q", tt.wantVersion) }) @@ -541,7 +543,7 @@ func TestVersion_AlwaysLatestInGetFunctionConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, "$LATEST", fn.Version, "GetFunctionConfiguration must always return Version=$LATEST for live code") }) @@ -578,7 +580,7 @@ func TestVersion_LiveFunctionStaysLatestAfterPublish(t *testing.T) { `{"ImageUri":"x:v2","Publish":true}`) require.Equal(t, http.StatusOK, rec.Code) var upd lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&upd)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &upd, jsonx.V1())) assert.Equal(t, "1", upd.Version, "UpdateFunctionCode Publish=true response must show numbered version") }, @@ -599,7 +601,7 @@ func TestVersion_LiveFunctionStaysLatestAfterPublish(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fn)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fn, jsonx.V1())) assert.Equal(t, "$LATEST", fn.Version, "GetFunctionConfiguration after publish must still return Version=$LATEST") }) @@ -636,7 +638,7 @@ func TestAliasRouting(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var alias lambda.FunctionAlias - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alias, jsonx.V1())) assert.Equal(t, "prod", alias.Name) assert.Equal(t, v1.Version, alias.FunctionVersion) @@ -651,7 +653,7 @@ func TestAliasRouting(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listOut lambda.ListAliasesOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Aliases, 1) // UpdateAlias — point to v2 with weighted routing 10% to v1. @@ -664,7 +666,7 @@ func TestAliasRouting(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated lambda.FunctionAlias - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, v2.Version, updated.FunctionVersion) require.NotNil(t, updated.RoutingConfig) assert.InDelta(t, 0.1, updated.RoutingConfig.AdditionalVersionWeights[v1.Version], 0.001) diff --git a/services/lambda/zip_handler_test.go b/services/lambda/zip_handler_test.go index a4631874ca..f027588474 100644 --- a/services/lambda/zip_handler_test.go +++ b/services/lambda/zip_handler_test.go @@ -3,13 +3,14 @@ package lambda_test import ( "archive/zip" "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lambda" ) @@ -150,7 +151,7 @@ func TestZipHandler_CreateFunction(t *testing.T) { h, _ := newHandler(t) - body, err := json.Marshal(tt.input) + body, err := json.Marshal(tt.input, jsonx.V1()) require.NoError(t, err) rec := callHandler(t, h, http.MethodPost, "/2015-03-31/functions", string(body), nil) @@ -162,7 +163,7 @@ func TestZipHandler_CreateFunction(t *testing.T) { } var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) if tt.wantFnName != "" { assert.Equal(t, tt.wantFnName, fn.FunctionName) @@ -192,7 +193,7 @@ func TestZipHandler_UpdateFunctionCode(t *testing.T) { zipBody := func() string { zipBytes := makeTestZip(t, `def handler(event, context): return "updated"`) - b, err := json.Marshal(map[string]any{"ZipFile": zipBytes}) + b, err := json.Marshal(map[string]any{"ZipFile": zipBytes}, jsonx.V1()) require.NoError(t, err) return string(b) @@ -244,7 +245,7 @@ func TestZipHandler_UpdateFunctionCode(t *testing.T) { if tt.wantRevisionID { var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) assert.NotEmpty(t, fn.RevisionID) } }) @@ -292,7 +293,7 @@ func TestZipHandler_UpdateFunctionConfiguration(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var fn lambda.FunctionConfiguration - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &fn, jsonx.V1())) assert.Equal(t, tt.wantRuntime, fn.Runtime) assert.Equal(t, tt.wantHandler, fn.Handler) }) @@ -348,7 +349,7 @@ func TestZipHandler_BuildCodeLocation(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var out lambda.GetFunctionOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.Code) if tt.wantImageURI != "" { diff --git a/services/lightsail/error_envelope_test.go b/services/lightsail/error_envelope_test.go index 57df4b49cc..0b0810ffc0 100644 --- a/services/lightsail/error_envelope_test.go +++ b/services/lightsail/error_envelope_test.go @@ -2,7 +2,7 @@ package lightsail_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/lightsail" @@ -53,7 +54,7 @@ func TestErrorEnvelope_NotFoundDecodesToTypedError(t *testing.T) { require.Equal(t, http.StatusBadRequest, rawStatus) var envelope map[string]any - require.NoError(t, json.Unmarshal(rawBody, &envelope)) + require.NoError(t, json.Unmarshal(rawBody, &envelope, jsonx.V1())) require.Equal(t, "NotFoundException", envelope["__type"], "raw response must carry the JSON body key __type the SDK's getProtocolErrorInfo reads") msg, ok := envelope["message"] diff --git a/services/lightsail/handler.go b/services/lightsail/handler.go index e0d3052076..ff51a6b0cb 100644 --- a/services/lightsail/handler.go +++ b/services/lightsail/handler.go @@ -2,7 +2,8 @@ package lightsail import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -197,8 +199,8 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } - var m map[string]json.RawMessage - if unmarshalErr := json.Unmarshal(body, &m); unmarshalErr != nil { + var m map[string]jsontext.Value + if unmarshalErr := json.Unmarshal(body, &m, jsonx.V1()); unmarshalErr != nil { return "" } @@ -210,11 +212,11 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ) } -func extractFirstResourceByKeys(body map[string]json.RawMessage, keys ...string) string { +func extractFirstResourceByKeys(body map[string]jsontext.Value, keys ...string) string { for _, key := range keys { if raw, ok := body[key]; ok { var v string - if err := json.Unmarshal(raw, &v); err == nil && v != "" { + if err := json.Unmarshal(raw, &v, jsonx.V1()); err == nil && v != "" { return v } } @@ -292,7 +294,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin } body := map[string]string{"__type": errType, "message": err.Error()} - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.1") @@ -306,7 +308,7 @@ func decodeBody[T any](body []byte) (*T, error) { var v T if len(body) > 0 { - if err := json.Unmarshal(body, &v); err != nil { + if err := json.Unmarshal(body, &v, jsonx.V1()); err != nil { return nil, validationError("malformed request body: " + err.Error()) } } @@ -318,7 +320,7 @@ func decodeBody[T any](body []byte) (*T, error) { // exception (marshaling our own well-typed wire structs should never // fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, serviceError("lightsail: marshal response: " + err.Error()) } diff --git a/services/lightsail/multi_region_test.go b/services/lightsail/multi_region_test.go index b8107c369f..615a342aed 100644 --- a/services/lightsail/multi_region_test.go +++ b/services/lightsail/multi_region_test.go @@ -1,7 +1,7 @@ package lightsail_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lightsail" ) @@ -37,7 +38,7 @@ func lightsailCall(t *testing.T, h *lightsail.Handler, region, op, body string) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/lightsail/persistence.go b/services/lightsail/persistence.go index ddd806cc0f..a0d9bebfd5 100644 --- a/services/lightsail/persistence.go +++ b/services/lightsail/persistence.go @@ -2,7 +2,7 @@ package lightsail import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -18,14 +18,14 @@ const lightsailSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Lightsail backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - DefaultKeyPair *KeyPair `json:"defaultKeyPair,omitempty"` - Partner *PartnerProfile `json:"partner,omitempty"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` - NextAccessKeySeq int `json:"nextAccessKeySeq"` - VpcPeered bool `json:"vpcPeered"` + Tables map[string]jsontext.Value `json:"tables"` + DefaultKeyPair *KeyPair `json:"defaultKeyPair,omitempty"` + Partner *PartnerProfile `json:"partner,omitempty"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` + NextAccessKeySeq int `json:"nextAccessKeySeq"` + VpcPeered bool `json:"vpcPeered"` } // Snapshot serialises the backend state to JSON. diff --git a/services/lightsail/sdk_roundtrip_field_fixes_test.go b/services/lightsail/sdk_roundtrip_field_fixes_test.go index eeba85eb37..f20fa02f69 100644 --- a/services/lightsail/sdk_roundtrip_field_fixes_test.go +++ b/services/lightsail/sdk_roundtrip_field_fixes_test.go @@ -1,7 +1,8 @@ package lightsail_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "time" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/lightsail" ) @@ -160,22 +162,22 @@ func testGetRelationalDatabaseEventsDuration(t *testing.T) { func backdateOldestEvent(t *testing.T, backend *lightsail.InMemoryBackend, dbName string, when time.Time) { t.Helper() - type snapshot = map[string]json.RawMessage + type snapshot = map[string]jsontext.Value raw := backend.Snapshot(t.Context()) require.NotEmpty(t, raw) var snap snapshot - require.NoError(t, json.Unmarshal(raw, &snap)) + require.NoError(t, json.Unmarshal(raw, &snap, jsonx.V1())) var tables snapshot - require.NoError(t, json.Unmarshal(snap["tables"], &tables)) + require.NoError(t, json.Unmarshal(snap["tables"], &tables, jsonx.V1())) // Decoded as untyped maps, not lightsail.RelationalDatabase, so this // edit doesn't need (and musttag doesn't demand) a json tag on an // internal persistence struct that intentionally has none. var dbs []map[string]any - require.NoError(t, json.Unmarshal(tables["databases"], &dbs)) + require.NoError(t, json.Unmarshal(tables["databases"], &dbs, jsonx.V1())) require.Len(t, dbs, 1) require.Equal(t, dbName, dbs[0]["Name"]) @@ -187,15 +189,15 @@ func backdateOldestEvent(t *testing.T, backend *lightsail.InMemoryBackend, dbNam require.True(t, ok) event["CreatedAt"] = when.Format(time.RFC3339Nano) - newDBs, err := json.Marshal(dbs) + newDBs, err := json.Marshal(dbs, jsonx.V1()) require.NoError(t, err) tables["databases"] = newDBs - newTables, err := json.Marshal(tables) + newTables, err := json.Marshal(tables, jsonx.V1()) require.NoError(t, err) snap["tables"] = newTables - newSnap, err := json.Marshal(snap) + newSnap, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) require.NoError(t, backend.Restore(t.Context(), newSnap)) diff --git a/services/macie2/handler_administrator_test.go b/services/macie2/handler_administrator_test.go index 5874cae533..4fdab48fe8 100644 --- a/services/macie2/handler_administrator_test.go +++ b/services/macie2/handler_administrator_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -27,7 +28,7 @@ func TestAdministratorMaster(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Nil(t, resp["administrator"]) }, }, @@ -44,7 +45,7 @@ func TestAdministratorMaster(t *testing.T) { // GetAdministratorAccount rec := doRequest(t, h, http.MethodGet, "/administrator", nil) var adminResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp, jsonx.V1())) assert.NotNil(t, adminResp["administrator"]) // DisassociateFromAdministratorAccount @@ -54,7 +55,7 @@ func TestAdministratorMaster(t *testing.T) { // Verify cleared rec = doRequest(t, h, http.MethodGet, "/administrator", nil) var afterResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterResp, jsonx.V1())) assert.Nil(t, afterResp["administrator"]) }, }, @@ -72,7 +73,7 @@ func TestAdministratorMaster(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) master, _ := resp["master"].(map[string]any) assert.Equal(t, "legacy-admin", master["accountId"]) }, @@ -92,7 +93,7 @@ func TestAdministratorMaster(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/master", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Nil(t, resp["master"]) }, }, diff --git a/services/macie2/handler_allow_lists.go b/services/macie2/handler_allow_lists.go index 343219a293..23b9150b2f 100644 --- a/services/macie2/handler_allow_lists.go +++ b/services/macie2/handler_allow_lists.go @@ -1,13 +1,14 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseAllowListPath(method string, parts []string) (string, string) { @@ -77,7 +78,7 @@ func (h *Handler) handleCreateAllowList(body []byte) (any, int, error) { Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -132,7 +133,7 @@ func (h *Handler) handleUpdateAllowList(id string, body []byte) (any, int, error Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_allow_lists_test.go b/services/macie2/handler_allow_lists_test.go index 63f55e96e3..ee4b6d594e 100644 --- a/services/macie2/handler_allow_lists_test.go +++ b/services/macie2/handler_allow_lists_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "regexp" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -37,7 +38,7 @@ func TestMacie2_AllowLists(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]string - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"]) assert.NotEmpty(t, resp["id"]) assert.Contains(t, resp["arn"], "arn:aws:macie2:") @@ -54,7 +55,7 @@ func TestMacie2_AllowLists(t *testing.T) { "description": "my desc", }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"] }, @@ -63,7 +64,7 @@ func TestMacie2_AllowLists(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "my-list", resp["name"]) assert.Equal(t, "my desc", resp["description"]) assert.NotNil(t, resp["criteria"]) @@ -89,7 +90,7 @@ func TestMacie2_AllowLists(t *testing.T) { "criteria": map[string]any{"regex": "orig"}, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"] }, @@ -103,7 +104,7 @@ func TestMacie2_AllowLists(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]string - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"]) assert.NotEmpty(t, resp["id"]) }, @@ -116,7 +117,7 @@ func TestMacie2_AllowLists(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "allowLists") }, }, @@ -130,7 +131,7 @@ func TestMacie2_AllowLists(t *testing.T) { "criteria": map[string]any{"regex": ".+"}, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"] }, @@ -173,7 +174,7 @@ func createTestAllowListARN(t *testing.T, h *macie2.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["arn"] } @@ -195,7 +196,7 @@ func TestAllowListARNShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["arn"] require.NotEmpty(t, arn, "CreateAllowList must return arn") @@ -213,7 +214,7 @@ func TestListAllowListsEmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) v, ok := resp["allowLists"] require.True(t, ok, "response must contain allowLists key") diff --git a/services/macie2/handler_automated_discovery.go b/services/macie2/handler_automated_discovery.go index 6d31bbfee6..a364156b10 100644 --- a/services/macie2/handler_automated_discovery.go +++ b/services/macie2/handler_automated_discovery.go @@ -1,10 +1,12 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseAutomatedDiscoveryPath(method string, parts []string) (string, string) { @@ -75,7 +77,7 @@ func (h *Handler) handleUpdateAutomatedDiscoveryConfiguration(body []byte) (int, Status string `json:"status"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -111,7 +113,7 @@ func (h *Handler) handleBatchUpdateAutomatedDiscoveryAccounts(body []byte) (int, Accounts []AutoDiscoveryAccountUpdate `json:"accounts"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_automated_discovery_test.go b/services/macie2/handler_automated_discovery_test.go index ed67b3a651..86d2751d07 100644 --- a/services/macie2/handler_automated_discovery_test.go +++ b/services/macie2/handler_automated_discovery_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -28,7 +29,7 @@ func TestAutomatedDiscovery(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var cfg map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cfg, jsonx.V1())) assert.Equal(t, "DISABLED", cfg["status"]) // UpdateAutomatedDiscoveryConfiguration @@ -41,7 +42,7 @@ func TestAutomatedDiscovery(t *testing.T) { // Verify rec = doRequest(t, h, http.MethodGet, "/automated-discovery/configuration", nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "ENABLED", updated["status"]) assert.Equal(t, "NEW", updated["autoEnableOrganizationMembers"]) }, @@ -65,7 +66,7 @@ func TestAutomatedDiscovery(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["items"].([]any) assert.Len(t, items, 2) }, diff --git a/services/macie2/handler_buckets.go b/services/macie2/handler_buckets.go index a9740f9b53..fe0150adb2 100644 --- a/services/macie2/handler_buckets.go +++ b/services/macie2/handler_buckets.go @@ -1,8 +1,10 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseDatasourcesPath(method string, parts []string) (string, string) { @@ -71,7 +73,7 @@ func (h *Handler) handleDescribeBuckets(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -104,7 +106,7 @@ func (h *Handler) handleGetBucketStatistics(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -208,7 +210,7 @@ func (h *Handler) handleSearchResources(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/macie2/handler_buckets_test.go b/services/macie2/handler_buckets_test.go index 3e7b8f0e3a..7b43425e5a 100644 --- a/services/macie2/handler_buckets_test.go +++ b/services/macie2/handler_buckets_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -30,7 +31,7 @@ func TestBucketsAndBatchCustomDataIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) buckets, _ := resp["buckets"].([]any) assert.Empty(t, buckets) }, @@ -44,7 +45,7 @@ func TestBucketsAndBatchCustomDataIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) count, _ := resp["bucketCount"].(float64) assert.InDelta(t, 0, count, 0.0001) }, @@ -62,7 +63,7 @@ func TestBucketsAndBatchCustomDataIdentifiers(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cdiID := createResp["customDataIdentifierId"] // BatchGetCustomDataIdentifiers @@ -72,7 +73,7 @@ func TestBucketsAndBatchCustomDataIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp, jsonx.V1())) items, _ := batchResp["customDataIdentifiers"].([]any) assert.Len(t, items, 1) }, @@ -88,7 +89,7 @@ func TestBucketsAndBatchCustomDataIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, _ := resp["matchingResources"].([]any) assert.Empty(t, results) }, @@ -156,7 +157,7 @@ func describeBuckets(t *testing.T, h *macie2.Handler, criteria map[string]any) [ require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) buckets, ok := resp["buckets"].([]any) if !ok { @@ -173,7 +174,7 @@ func getBucketStatistics(t *testing.T, h *macie2.Handler) map[string]any { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/macie2/handler_classification_jobs.go b/services/macie2/handler_classification_jobs.go index d270b5d1b2..b7b6284620 100644 --- a/services/macie2/handler_classification_jobs.go +++ b/services/macie2/handler_classification_jobs.go @@ -1,13 +1,14 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" "slices" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseJobPath(method string, parts []string) (string, string) { @@ -148,7 +149,7 @@ func (h *Handler) handleCreateClassificationJob(body []byte) (any, int, error) { InitialRun bool `json:"initialRun"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -211,7 +212,7 @@ func (h *Handler) handleListClassificationJobs(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -241,7 +242,7 @@ func (h *Handler) handleUpdateClassificationJob(jobID string, body []byte) (int, JobStatus string `json:"jobStatus"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -278,7 +279,7 @@ func (h *Handler) handlePutClassificationExportConfiguration(body []byte) (int, Configuration *ClassificationExportConfig `json:"configuration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -321,7 +322,7 @@ func (h *Handler) handleUpdateClassificationScope(scopeID string, body []byte) ( S3 *ClassificationScopeS3Update `json:"s3"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_classification_jobs_test.go b/services/macie2/handler_classification_jobs_test.go index dc01b1354f..f381f910f4 100644 --- a/services/macie2/handler_classification_jobs_test.go +++ b/services/macie2/handler_classification_jobs_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -26,7 +27,7 @@ func TestUpdateClassificationScope_ExcludedBucketsSurviveIndependentAdds(t *test require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) scopes, _ := listResp["classificationScopes"].([]any) require.Len(t, scopes, 1) scope0, _ := scopes[0].(map[string]any) @@ -59,7 +60,7 @@ func TestUpdateClassificationScope_ExcludedBucketsSurviveIndependentAdds(t *test require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) s3, ok := got["s3"].(map[string]any) require.True(t, ok, "got %#v", got) excludes, ok := s3["excludes"].(map[string]any) @@ -79,7 +80,7 @@ func TestUpdateClassificationScope_ExcludedBucketsSurviveIndependentAdds(t *test require.Equal(t, http.StatusOK, rec.Code) rec = doRequest(t, h, http.MethodGet, "/classification-scopes/"+scopeID, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) s3, ok = got["s3"].(map[string]any) require.True(t, ok) excludes, ok = s3["excludes"].(map[string]any) @@ -111,7 +112,7 @@ func TestClassificationJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp["jobId"] assert.NotEmpty(t, jobID) assert.Equal(t, "RUNNING", createResp["jobStatus"]) @@ -123,7 +124,7 @@ func TestClassificationJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, jobID, descResp["jobId"]) assert.Equal(t, "ONE_TIME", descResp["jobType"]) assert.Equal(t, "test-job", descResp["name"]) @@ -135,7 +136,7 @@ func TestClassificationJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["items"].([]any) require.Len(t, items, 1) item0 := items[0].(map[string]any) @@ -155,7 +156,7 @@ func TestClassificationJobs(t *testing.T) { // Verify update rec = doRequest(t, h, http.MethodGet, "/jobs/"+jobID, nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "CANCELLED", updated["jobStatus"]) }, }, @@ -184,11 +185,11 @@ func TestClassificationJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/jobs/"+resp["jobId"], nil) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "IDLE", desc["jobStatus"]) }, }, @@ -219,7 +220,7 @@ func TestClassificationConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) cfg, _ := getResp["configuration"].(map[string]any) assert.Nil(t, cfg["s3Destination"]) @@ -238,7 +239,7 @@ func TestClassificationConfig(t *testing.T) { // Verify rec = doRequest(t, h, http.MethodGet, "/classification-export-configuration", nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) updCfg, _ := updated["configuration"].(map[string]any) s3Dest, _ := updCfg["s3Destination"].(map[string]any) assert.Equal(t, "my-export-bucket", s3Dest["bucketName"]) @@ -254,7 +255,7 @@ func TestClassificationConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) scopes, _ := listResp["classificationScopes"].([]any) require.Len(t, scopes, 1) @@ -267,7 +268,7 @@ func TestClassificationConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var scopeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &scopeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &scopeResp, jsonx.V1())) assert.Equal(t, scopeID, scopeResp["id"]) // UpdateClassificationScope @@ -321,7 +322,7 @@ func createTestJob(t *testing.T, h *macie2.Handler, name, jobType string) (strin require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["jobId"], resp["jobArn"] } @@ -351,7 +352,7 @@ func TestListClassificationJobsFilterAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -374,7 +375,7 @@ func TestListClassificationJobsFilterAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["items"].([]any) require.True(t, ok) @@ -393,7 +394,7 @@ func TestListClassificationJobsFilterAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -406,7 +407,7 @@ func TestListClassificationJobsFilterAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) items2, ok := resp2["items"].([]any) require.True(t, ok) require.Len(t, items2, 1) @@ -431,7 +432,7 @@ func TestClassificationJobUserPausedDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var paused map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &paused)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &paused, jsonx.V1())) details, ok := paused["userPausedDetails"].(map[string]any) require.True(t, ok, "userPausedDetails must be present while USER_PAUSED") assert.NotEmpty(t, details["jobPausedAt"]) @@ -444,7 +445,7 @@ func TestClassificationJobUserPausedDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resumed map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resumed)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resumed, jsonx.V1())) assert.Nil(t, resumed["userPausedDetails"], "userPausedDetails must clear once no longer USER_PAUSED") } @@ -544,7 +545,7 @@ func TestClassificationJobTagging(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "test", tags["env"]) @@ -567,13 +568,13 @@ func TestClassificationJobCreateFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var al map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &al)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &al, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/custom-data-identifiers", map[string]any{"name": "cdi", "regex": `.+`}) require.Equal(t, http.StatusOK, rec.Code) var cdi map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cdi)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cdi, jsonx.V1())) rec = doRequest(t, h, http.MethodPost, "/jobs", map[string]any{ "name": "full-field-job", @@ -589,13 +590,13 @@ func TestClassificationJobCreateFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/jobs/"+created["jobId"], nil) require.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) allowListIDs, ok := desc["allowListIds"].([]any) require.True(t, ok) diff --git a/services/macie2/handler_custom_data_identifiers.go b/services/macie2/handler_custom_data_identifiers.go index 24ded90b5b..bf1e9fee8a 100644 --- a/services/macie2/handler_custom_data_identifiers.go +++ b/services/macie2/handler_custom_data_identifiers.go @@ -1,11 +1,12 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseCustomDataIDPath(method string, parts []string) (string, string) { @@ -98,7 +99,7 @@ func (h *Handler) handleCreateCustomDataID(body []byte) (any, int, error) { SeverityLevels []SeverityLevel `json:"severityLevels"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -152,7 +153,7 @@ func (h *Handler) handleListCustomDataIDs(body []byte) (any, int, error) { MaxResults *int32 `json:"maxResults"` NextToken string `json:"nextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) limit := 0 if req.MaxResults != nil { @@ -181,7 +182,7 @@ func (h *Handler) handleTestCustomDataID(body []byte) (any, int, error) { Keywords []string `json:"keywords"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -214,7 +215,7 @@ func (h *Handler) handleBatchGetCustomDataIdentifiers(body []byte) (any, int, er IDs []string `json:"ids"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -250,7 +251,7 @@ func (h *Handler) handleListManagedDataIdentifiers(body []byte) (any, int, error } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/macie2/handler_custom_data_identifiers_test.go b/services/macie2/handler_custom_data_identifiers_test.go index fa424d00ab..467ab93b49 100644 --- a/services/macie2/handler_custom_data_identifiers_test.go +++ b/services/macie2/handler_custom_data_identifiers_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -35,7 +36,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]string - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["customDataIdentifierId"]) }, }, @@ -50,7 +51,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { "keywords": []string{"phone", "tel"}, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["customDataIdentifierId"] }, @@ -59,7 +60,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "phone-detector", resp["name"]) assert.Equal(t, "detects phone numbers", resp["description"]) assert.NotEmpty(t, resp["arn"]) @@ -78,7 +79,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.InDelta(t, float64(2), resp["matchCount"], 0.001) }, }, @@ -91,7 +92,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "items") }, }, @@ -104,7 +105,7 @@ func TestMacie2_CustomDataIdentifiers(t *testing.T) { "regex": `.+`, }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["customDataIdentifierId"] }, @@ -208,7 +209,7 @@ func TestMacie2_CustomDataIdentifierWireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["customDataIdentifierId"] doRequest(t, h, http.MethodDelete, "/custom-data-identifiers/"+id, nil) @@ -267,14 +268,14 @@ func TestCustomDataIdentifierDeletedField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["customDataIdentifierId"] rec2 := doRequest(t, h, http.MethodGet, "/custom-data-identifiers/"+id, nil) require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) deleted, hasDeleted := resp["deleted"] require.True(t, hasDeleted, "GetCustomDataIdentifier must include a 'deleted' field") assert.Equal(t, false, deleted) @@ -286,7 +287,7 @@ func TestCustomDataIdentifierDeletedField(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code, "Get on a soft-deleted identifier must not 404") var afterDelete map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &afterDelete)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &afterDelete, jsonx.V1())) assert.Equal(t, true, afterDelete["deleted"]) // BatchGetCustomDataIdentifiers must also return the soft-deleted @@ -297,7 +298,7 @@ func TestCustomDataIdentifierDeletedField(t *testing.T) { require.Equal(t, http.StatusOK, batchRec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp, jsonx.V1())) items, ok := batchResp["customDataIdentifiers"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -328,13 +329,13 @@ func TestCustomDataIdentifierSeverityLevels(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec2 := doRequest(t, h, http.MethodGet, "/custom-data-identifiers/"+created["customDataIdentifierId"], nil) require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) levels, ok := resp["severityLevels"].([]any) require.True(t, ok) require.Len(t, levels, 2) @@ -355,7 +356,7 @@ func TestListCustomDataIDsEmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) v, ok := resp["items"] require.True(t, ok, "response must contain items key") diff --git a/services/macie2/handler_enablement.go b/services/macie2/handler_enablement.go index f5900897e5..3930578674 100644 --- a/services/macie2/handler_enablement.go +++ b/services/macie2/handler_enablement.go @@ -1,12 +1,13 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "time" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseMaciePath(method string, parts []string) (string, string) { @@ -78,7 +79,7 @@ func (h *Handler) handleEnableMacie(body []byte) (int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } @@ -117,7 +118,7 @@ func (h *Handler) handleUpdateMacieSession(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/macie2/handler_enablement_test.go b/services/macie2/handler_enablement_test.go index 0832390b60..6d791bed22 100644 --- a/services/macie2/handler_enablement_test.go +++ b/services/macie2/handler_enablement_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -54,7 +55,7 @@ func TestMacie2_Session(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ONE_HOUR", resp["findingPublishingFrequency"]) assert.Equal(t, "ENABLED", resp["status"]) assert.NotEmpty(t, resp["serviceRole"]) @@ -132,7 +133,7 @@ func TestDisableMacie_DeletesResources(t *testing.T) { require.Equal(t, http.StatusOK, jobRec.Code) var jobResp map[string]string - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jobResp, jsonx.V1())) jobID := jobResp["jobId"] require.NotEmpty(t, jobID) @@ -143,7 +144,7 @@ func TestDisableMacie_DeletesResources(t *testing.T) { require.Equal(t, http.StatusOK, cdiRec.Code) var cdiResp map[string]string - require.NoError(t, json.Unmarshal(cdiRec.Body.Bytes(), &cdiResp)) + require.NoError(t, json.Unmarshal(cdiRec.Body.Bytes(), &cdiResp, jsonx.V1())) cdiID := cdiResp["customDataIdentifierId"] require.NotEmpty(t, cdiID) @@ -154,7 +155,7 @@ func TestDisableMacie_DeletesResources(t *testing.T) { require.Equal(t, http.StatusOK, allowListRec.Code) var allowListResp map[string]string - require.NoError(t, json.Unmarshal(allowListRec.Body.Bytes(), &allowListResp)) + require.NoError(t, json.Unmarshal(allowListRec.Body.Bytes(), &allowListResp, jsonx.V1())) allowListID := allowListResp["id"] require.NotEmpty(t, allowListID) @@ -297,7 +298,7 @@ func TestSessionTimestampsPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range []string{"createdAt", "updatedAt"} { raw, ok := resp[field] @@ -320,7 +321,7 @@ func TestSessionUpdatedAtAdvances(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var before map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &before, jsonx.V1())) createdAt := before["createdAt"].(string) time.Sleep(1001 * time.Millisecond) @@ -332,7 +333,7 @@ func TestSessionUpdatedAtAdvances(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var after map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &after, jsonx.V1())) assert.Equal(t, createdAt, after["createdAt"].(string), "createdAt must not change after update") assert.NotEqual(t, after["createdAt"], after["updatedAt"], diff --git a/services/macie2/handler_findings.go b/services/macie2/handler_findings.go index b63eec3a12..071159e651 100644 --- a/services/macie2/handler_findings.go +++ b/services/macie2/handler_findings.go @@ -1,11 +1,12 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseFindingsPath(method string, parts []string) (string, string) { @@ -83,7 +84,7 @@ func (h *Handler) handleGetFindings(body []byte) (any, int, error) { FindingIDs []string `json:"findingIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -111,7 +112,7 @@ func (h *Handler) handleListFindings(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } @@ -147,7 +148,7 @@ func (h *Handler) handleCreateSampleFindings(body []byte) (int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } @@ -167,7 +168,7 @@ func (h *Handler) handleGetFindingStatistics(body []byte) (any, int, error) { Size int `json:"size"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -223,7 +224,7 @@ func (h *Handler) handleGetFindingsPublicationConfiguration() (any, int, error) func (h *Handler) handlePutFindingsPublicationConfiguration(body []byte) (int, error) { var cfg FindingsPublicationConfig - if err := json.Unmarshal(body, &cfg); err != nil { + if err := json.Unmarshal(body, &cfg, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_findings_filters.go b/services/macie2/handler_findings_filters.go index a0ba4f4d7e..86d043084e 100644 --- a/services/macie2/handler_findings_filters.go +++ b/services/macie2/handler_findings_filters.go @@ -1,7 +1,7 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +9,7 @@ import ( "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseFindingsFilterPath(method string, parts []string) (string, string) { @@ -80,7 +81,7 @@ func (h *Handler) handleCreateFindingsFilter(body []byte) (any, int, error) { Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -138,7 +139,7 @@ func (h *Handler) handleUpdateFindingsFilter(id string, body []byte) (any, int, Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_findings_filters_test.go b/services/macie2/handler_findings_filters_test.go index 1288d9a670..a002eeed77 100644 --- a/services/macie2/handler_findings_filters_test.go +++ b/services/macie2/handler_findings_filters_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -36,7 +37,7 @@ func TestMacie2_FindingsFilters(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]string - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"]) assert.NotEmpty(t, resp["id"]) assert.Contains(t, resp["arn"], "findings-filter") @@ -53,7 +54,7 @@ func TestMacie2_FindingsFilters(t *testing.T) { "position": int32(3), }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"] }, @@ -62,7 +63,7 @@ func TestMacie2_FindingsFilters(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "test-filter", resp["name"]) assert.Equal(t, "NOOP", resp["action"]) assert.Equal(t, "test desc", resp["description"]) @@ -77,7 +78,7 @@ func TestMacie2_FindingsFilters(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "findingsFilterListItems") }, }, @@ -90,7 +91,7 @@ func TestMacie2_FindingsFilters(t *testing.T) { "action": "ARCHIVE", }) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"] }, @@ -130,7 +131,7 @@ func TestListFindingsFiltersEmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) v, ok := resp["findingsFilterListItems"] require.True(t, ok, "response must contain findingsFilterListItems key") diff --git a/services/macie2/handler_findings_test.go b/services/macie2/handler_findings_test.go index 46c17c0313..190d9ea785 100644 --- a/services/macie2/handler_findings_test.go +++ b/services/macie2/handler_findings_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -46,7 +47,7 @@ func TestMacie2_Findings(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "findingIds") ids, ok := resp["findingIds"].([]any) @@ -68,7 +69,7 @@ func TestMacie2_Findings(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "findings") }, }, @@ -86,7 +87,7 @@ func TestMacie2_Findings(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp, "countsByGroup") }, }, @@ -107,7 +108,7 @@ func TestMacie2_Findings(t *testing.T) { if tt.name == "GetFindings returns findings array" { listRec := doRequest(t, h, http.MethodPost, "/findings", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ids := listResp["findingIds"].([]any) body = map[string]any{"findingIds": ids} } @@ -131,7 +132,7 @@ func TestListFindingsEmptyNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) v, ok := resp["findingIds"] require.True(t, ok, "response must contain findingIds key") @@ -151,7 +152,7 @@ func TestGetFindingsEmptyInputNotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) v, ok := resp["findings"] require.True(t, ok, "response must contain findings key") @@ -175,7 +176,7 @@ func TestCreateSampleFindingsFieldShape(t *testing.T) { listRec := doRequest(t, h, http.MethodPost, "/findings", map[string]any{}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ids, ok := listResp["findingIds"].([]any) require.True(t, ok) require.Len(t, ids, 2) @@ -184,7 +185,7 @@ func TestCreateSampleFindingsFieldShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) findings, ok := getResp["findings"].([]any) require.True(t, ok) require.Len(t, findings, 2) @@ -266,7 +267,7 @@ func TestFindingsPublicationConfig(t *testing.T) { // Verify rec = doRequest(t, h, http.MethodGet, "/findings-publication-configuration", nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) _, hasTopClassification := updated["publishClassificationFindings"] _, hasTopPolicy := updated["publishPolicyFindings"] diff --git a/services/macie2/handler_members.go b/services/macie2/handler_members.go index ab1c595ddf..626d071fee 100644 --- a/services/macie2/handler_members.go +++ b/services/macie2/handler_members.go @@ -1,7 +1,7 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -9,6 +9,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseMembersPath(method string, parts []string) (string, string) { @@ -156,7 +157,7 @@ func (h *Handler) handleCreateMember(body []byte) (int, error) { Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -249,7 +250,7 @@ func (h *Handler) handleUpdateMemberSession(accountID string, body []byte) (int, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } @@ -272,7 +273,7 @@ func (h *Handler) handleCreateInvitations(body []byte) (any, int, error) { DisableEmailNotification bool `json:"disableEmailNotification"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -291,7 +292,7 @@ func (h *Handler) handleAcceptInvitation(body []byte) (int, error) { MasterAccount string `json:"masterAccount"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -312,7 +313,7 @@ func (h *Handler) handleDeclineInvitations(body []byte) (any, int, error) { AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } @@ -329,7 +330,7 @@ func (h *Handler) handleDeleteInvitations(body []byte) (any, int, error) { AccountIDs []string `json:"accountIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_members_test.go b/services/macie2/handler_members_test.go index f2593080e0..6331f64e13 100644 --- a/services/macie2/handler_members_test.go +++ b/services/macie2/handler_members_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -38,7 +39,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getMem map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getMem)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getMem, jsonx.V1())) assert.Equal(t, "111111111111", getMem["accountId"]) assert.Equal(t, "member@example.com", getMem["email"]) assert.Equal(t, "Created", getMem["relationshipStatus"]) @@ -52,7 +53,7 @@ func TestMembers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) members, _ := listResp["members"].([]any) assert.Len(t, members, 1) @@ -63,7 +64,7 @@ func TestMembers(t *testing.T) { // List after delete rec = doRequest(t, h, http.MethodGet, "/members", nil) var afterList map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterList)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterList, jsonx.V1())) membersAfter, _ := afterList["members"].([]any) assert.Empty(t, membersAfter) }, @@ -87,7 +88,7 @@ func TestMembers(t *testing.T) { // Verify status changed rec = doRequest(t, h, http.MethodGet, "/members/222222222222", nil) var mem map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mem)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mem, jsonx.V1())) assert.Equal(t, "Removed", mem["relationshipStatus"]) }, }, @@ -111,7 +112,7 @@ func TestMembers(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/members/333333333333", nil) var mem map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mem)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mem, jsonx.V1())) assert.Equal(t, "PAUSED", mem["relationshipStatus"]) }, }, @@ -175,7 +176,7 @@ func TestListMembersOnlyAssociatedAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members, ok := resp["members"].([]any) require.True(t, ok) require.Len(t, members, 1) @@ -192,7 +193,7 @@ func TestListMembersOnlyAssociatedAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members, ok := resp["members"].([]any) require.True(t, ok) assert.Len(t, members, 2) @@ -205,7 +206,7 @@ func TestListMembersOnlyAssociatedAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members, ok := resp["members"].([]any) require.True(t, ok) require.Len(t, members, 1) @@ -221,7 +222,7 @@ func TestListMembersOnlyAssociatedAndPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) members2, ok := resp2["members"].([]any) require.True(t, ok) require.Len(t, members2, 1) @@ -255,7 +256,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var countResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &countResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &countResp, jsonx.V1())) count, _ := countResp["invitationsCount"].(float64) assert.InDelta(t, 2, count, 0.0001) @@ -264,7 +265,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) invitations, _ := listResp["invitations"].([]any) assert.Len(t, invitations, 2) }, @@ -285,7 +286,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var adminResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &adminResp, jsonx.V1())) admin, _ := adminResp["administrator"].(map[string]any) assert.Equal(t, "123456789012", admin["accountId"]) }, @@ -306,7 +307,7 @@ func TestInvitations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var declineResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &declineResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &declineResp, jsonx.V1())) assert.Empty(t, declineResp["unprocessedAccounts"]) // DeleteInvitations diff --git a/services/macie2/handler_organization.go b/services/macie2/handler_organization.go index 51a40772aa..3feec867ea 100644 --- a/services/macie2/handler_organization.go +++ b/services/macie2/handler_organization.go @@ -1,11 +1,12 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseAdminPath(method string, parts []string) (string, string) { @@ -70,7 +71,7 @@ func (h *Handler) handleEnableOrganizationAdminAccount(body []byte) (int, error) AdminAccountID string `json:"adminAccountId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } @@ -116,7 +117,7 @@ func (h *Handler) handleUpdateOrganizationConfiguration(body []byte) (int, error AutoEnable bool `json:"autoEnable"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_organization_test.go b/services/macie2/handler_organization_test.go index 8db0e3f2c1..096cf8f486 100644 --- a/services/macie2/handler_organization_test.go +++ b/services/macie2/handler_organization_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -34,7 +35,7 @@ func TestOrgAdmin(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) accounts, _ := listResp["adminAccounts"].([]any) assert.Len(t, accounts, 1) @@ -45,7 +46,7 @@ func TestOrgAdmin(t *testing.T) { // Verify removed rec = doRequest(t, h, http.MethodGet, "/admin", nil) var afterList map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterList)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &afterList, jsonx.V1())) accountsAfter, _ := afterList["adminAccounts"].([]any) assert.Empty(t, accountsAfter) }, @@ -60,7 +61,7 @@ func TestOrgAdmin(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) autoEnable, _ := descResp["autoEnable"].(bool) assert.False(t, autoEnable) @@ -73,7 +74,7 @@ func TestOrgAdmin(t *testing.T) { // Verify change rec = doRequest(t, h, http.MethodGet, "/admin/configuration", nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.True(t, updated["autoEnable"].(bool)) }, }, @@ -100,7 +101,7 @@ func TestDescribeOrganizationConfigurationWireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "autoEnable") assert.Contains(t, resp, "maxAccountLimitReached") diff --git a/services/macie2/handler_resource_profiles.go b/services/macie2/handler_resource_profiles.go index c82c4a95ce..95eca6269a 100644 --- a/services/macie2/handler_resource_profiles.go +++ b/services/macie2/handler_resource_profiles.go @@ -1,8 +1,10 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseResourceProfilesPath(method string, parts []string) (string, string) { @@ -81,7 +83,7 @@ func (h *Handler) handleUpdateResourceProfile(resourceARN string, body []byte) ( } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } @@ -117,7 +119,7 @@ func (h *Handler) handleUpdateResourceProfileDetections(resourceARN string, body } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } } diff --git a/services/macie2/handler_resource_profiles_test.go b/services/macie2/handler_resource_profiles_test.go index 34f1e182df..fd66781717 100644 --- a/services/macie2/handler_resource_profiles_test.go +++ b/services/macie2/handler_resource_profiles_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -30,7 +31,7 @@ func TestResourceProfiles(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var profile map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &profile, jsonx.V1())) assert.Equal(t, arn, profile["resourceArn"]) // UpdateResourceProfile @@ -42,7 +43,7 @@ func TestResourceProfiles(t *testing.T) { // Verify rec = doRequest(t, h, http.MethodGet, "/resource-profiles?resourceArn="+arn, nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.EqualValues(t, 75, updated["sensitivityScore"]) assert.True(t, updated["sensitivityScoreOverridden"].(bool)) }, @@ -59,7 +60,7 @@ func TestResourceProfiles(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var artResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &artResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &artResp, jsonx.V1())) artifacts, _ := artResp["artifacts"].([]any) assert.Empty(t, artifacts) @@ -68,7 +69,7 @@ func TestResourceProfiles(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var detResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detResp, jsonx.V1())) detections, _ := detResp["detections"].([]any) assert.Empty(t, detections) diff --git a/services/macie2/handler_reveal_configuration.go b/services/macie2/handler_reveal_configuration.go index f10c21e53c..fc379b834c 100644 --- a/services/macie2/handler_reveal_configuration.go +++ b/services/macie2/handler_reveal_configuration.go @@ -1,12 +1,13 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseRevealCfgPath(method string, _ []string) (string, string) { @@ -57,7 +58,7 @@ func (h *Handler) handleUpdateRevealConfiguration(body []byte) (any, int, error) } `json:"retrievalConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_reveal_configuration_test.go b/services/macie2/handler_reveal_configuration_test.go index d10f2a64c5..65052be1b4 100644 --- a/services/macie2/handler_reveal_configuration_test.go +++ b/services/macie2/handler_reveal_configuration_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -28,7 +29,7 @@ func TestRevealConfiguration(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) cfg, _ := getResp["configuration"].(map[string]any) assert.Equal(t, "DISABLED", cfg["status"]) @@ -44,7 +45,7 @@ func TestRevealConfiguration(t *testing.T) { // Verify rec = doRequest(t, h, http.MethodGet, "/reveal-configuration", nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) updCfg, _ := updated["configuration"].(map[string]any) assert.Equal(t, "ENABLED", updCfg["status"]) assert.Equal(t, "arn:aws:kms:us-east-1:000000000000:key/reveal-key", updCfg["kmsKeyId"]) @@ -93,7 +94,7 @@ func TestSensitiveDataOccurrences(t *testing.T) { // Get finding IDs rec := doRequest(t, h, http.MethodPost, "/findings", nil) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) findingIDs, _ := listResp["findingIds"].([]any) require.NotEmpty(t, findingIDs) findingID, _ := findingIDs[0].(string) @@ -107,7 +108,7 @@ func TestSensitiveDataOccurrences(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var avail map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &avail)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &avail, jsonx.V1())) assert.Equal(t, "AVAILABLE", avail["code"]) }, }, diff --git a/services/macie2/handler_sensitivity_inspection.go b/services/macie2/handler_sensitivity_inspection.go index 644fc6353d..0c5eb245ce 100644 --- a/services/macie2/handler_sensitivity_inspection.go +++ b/services/macie2/handler_sensitivity_inspection.go @@ -1,12 +1,13 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseTemplatesPath(method string, parts []string) (string, string) { @@ -85,7 +86,7 @@ func (h *Handler) handleUpdateSensitivityInspectionTemplate(templateID string, b Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_sensitivity_inspection_test.go b/services/macie2/handler_sensitivity_inspection_test.go index 26dd8177f3..daf66f8d48 100644 --- a/services/macie2/handler_sensitivity_inspection_test.go +++ b/services/macie2/handler_sensitivity_inspection_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -25,7 +26,7 @@ func TestUpdateSensitivityInspectionTemplate_FieldsSurviveIndependentUpdates(t * require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) templates, _ := listResp["sensitivityInspectionTemplates"].([]any) require.Len(t, templates, 1) tmpl0, _ := templates[0].(map[string]any) @@ -53,7 +54,7 @@ func TestUpdateSensitivityInspectionTemplate_FieldsSurviveIndependentUpdates(t * require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "original description", updated["description"], "description must survive an Update that never mentioned it") @@ -84,7 +85,7 @@ func TestSensitivityInspectionTemplates(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) templates, _ := listResp["sensitivityInspectionTemplates"].([]any) require.Len(t, templates, 1) @@ -97,7 +98,7 @@ func TestSensitivityInspectionTemplates(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, templateID, getResp["sensitivityInspectionTemplateId"]) // UpdateSensitivityInspectionTemplate @@ -118,7 +119,7 @@ func TestSensitivityInspectionTemplates(t *testing.T) { // Verify update rec = doRequest(t, h, http.MethodGet, "/templates/sensitivity-inspections/"+templateID, nil) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "Updated description", updated["description"]) }, }, diff --git a/services/macie2/handler_tags.go b/services/macie2/handler_tags.go index 7c3525992d..0fd9fcf6be 100644 --- a/services/macie2/handler_tags.go +++ b/services/macie2/handler_tags.go @@ -1,10 +1,12 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseTagPath(method string, parts []string) (string, string) { @@ -63,7 +65,7 @@ func (h *Handler) handleTagResource(resourceARN string, body []byte) (int, error Tags map[string]string `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return http.StatusBadRequest, ErrValidation } diff --git a/services/macie2/handler_tags_test.go b/services/macie2/handler_tags_test.go index b5140e1dea..2fe0598b6a 100644 --- a/services/macie2/handler_tags_test.go +++ b/services/macie2/handler_tags_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -48,7 +49,7 @@ func TestMacie2_Tags(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -169,7 +170,7 @@ func TestMacie2_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["customDataIdentifierId"] doRequest(t, h, http.MethodDelete, "/custom-data-identifiers/"+id, nil) @@ -220,14 +221,14 @@ func TestCreationTagsVisibleViaTagAPI(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) arn := created["arn"] rec2 := doRequest(t, h, http.MethodGet, "/tags/"+arn, nil) require.Equal(t, http.StatusOK, rec2.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp, jsonx.V1())) tags, ok := tagResp["tags"].(map[string]any) require.True(t, ok, "tags must be an object") @@ -248,7 +249,7 @@ func TestUntagResourceRemovesSpecificKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) arn := created["arn"] doRequest(t, h, http.MethodPost, "/tags/"+arn, @@ -260,7 +261,7 @@ func TestUntagResourceRemovesSpecificKey(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp, jsonx.V1())) tags, ok := tagResp["tags"].(map[string]any) require.True(t, ok) @@ -285,7 +286,7 @@ func TestTagResourceMergesWithCreationTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) arn := created["arn"] doRequest(t, h, http.MethodPost, "/tags/"+arn, @@ -295,7 +296,7 @@ func TestTagResourceMergesWithCreationTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagResp, jsonx.V1())) tags, ok := tagResp["tags"].(map[string]any) require.True(t, ok) diff --git a/services/macie2/handler_test.go b/services/macie2/handler_test.go index 8544fd75fb..98338c2256 100644 --- a/services/macie2/handler_test.go +++ b/services/macie2/handler_test.go @@ -2,7 +2,7 @@ package macie2_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *macie2.Handler, method, path string, body any) * if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/macie2/handler_usage.go b/services/macie2/handler_usage.go index 0ef8e37fa4..26e4807e1d 100644 --- a/services/macie2/handler_usage.go +++ b/services/macie2/handler_usage.go @@ -1,8 +1,10 @@ package macie2 import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseUsagePath(method string, parts []string) (string, string) { @@ -46,7 +48,7 @@ func (h *Handler) handleGetUsageStatistics(body []byte) (any, int, error) { } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, http.StatusBadRequest, ErrValidation } } diff --git a/services/macie2/handler_usage_test.go b/services/macie2/handler_usage_test.go index 6a1bc63c44..f7dad328f7 100644 --- a/services/macie2/handler_usage_test.go +++ b/services/macie2/handler_usage_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -29,7 +30,7 @@ func TestUsageAndManagedIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, _ := resp["records"].([]any) assert.Empty(t, records) }, @@ -43,7 +44,7 @@ func TestUsageAndManagedIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) totals, _ := resp["usageTotals"].([]any) assert.NotEmpty(t, totals) }, @@ -57,7 +58,7 @@ func TestUsageAndManagedIdentifiers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["items"].([]any) assert.NotEmpty(t, items) diff --git a/services/macie2/list_filter_params_test.go b/services/macie2/list_filter_params_test.go index 4c35ab824e..8ccdd887b2 100644 --- a/services/macie2/list_filter_params_test.go +++ b/services/macie2/list_filter_params_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -31,7 +32,7 @@ func TestBuckets_DescribeBuckets_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Buckets []map[string]any `json:"buckets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.Buckets, 1, "maxResults=1 must limit the page to 1 item") require.NotEmpty(t, page1.NextToken, "a partial page must return a nextToken") @@ -44,7 +45,7 @@ func TestBuckets_DescribeBuckets_MaxResultsHonoured(t *testing.T) { NextToken string `json:"nextToken"` Buckets []map[string]any `json:"buckets"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Buckets, 2, "second page must return the remainder") } @@ -72,7 +73,7 @@ func TestBuckets_DescribeBuckets_SortCriteriaHonoured(t *testing.T) { var resp struct { Buckets []map[string]any `json:"buckets"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Buckets, 3) assert.Equal(t, "bbb-bucket", resp.Buckets[0]["bucketName"]) @@ -106,7 +107,7 @@ func TestGetFindingStatistics_FindingCriteriaHonoured(t *testing.T) { var stats struct { CountsByGroup []map[string]any `json:"countsByGroup"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &stats)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &stats, jsonx.V1())) require.Len(t, stats.CountsByGroup, 1, "FindingCriteria must exclude TypeB's group") assert.Equal(t, "Policy:IAMUser/TypeA", stats.CountsByGroup[0]["groupKey"]) } @@ -142,7 +143,7 @@ func TestFindingsFilter_ArchiveActionHonoured(t *testing.T) { var listResp struct { FindingIDs []string `json:"findingIds"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.FindingIDs, 2) rec4 := doRequest(t, h, http.MethodPost, "/findings/describe", map[string]any{ @@ -153,7 +154,7 @@ func TestFindingsFilter_ArchiveActionHonoured(t *testing.T) { var describeResp struct { Findings []map[string]any `json:"findings"` } - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &describeResp, jsonx.V1())) require.Len(t, describeResp.Findings, 2) for _, f := range describeResp.Findings { @@ -189,7 +190,7 @@ func TestListFindings_SortCriteriaHonoured(t *testing.T) { var listResp struct { FindingIDs []string `json:"findingIds"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.FindingIDs, 2) rec3 := doRequest(t, h, http.MethodPost, "/findings/describe", map[string]any{ @@ -200,7 +201,7 @@ func TestListFindings_SortCriteriaHonoured(t *testing.T) { var describeResp struct { Findings []macie2.Finding `json:"findings"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &describeResp, jsonx.V1())) byID := make(map[string]string, len(describeResp.Findings)) for _, f := range describeResp.Findings { @@ -236,7 +237,7 @@ func TestListClassificationJobs_SortCriteriaHonoured(t *testing.T) { var resp struct { Items []map[string]any `json:"items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Items, 2) assert.Equal(t, "zzz-job", resp.Items[0]["name"]) diff --git a/services/macie2/multi_region_test.go b/services/macie2/multi_region_test.go index b307d1465d..b37c623342 100644 --- a/services/macie2/multi_region_test.go +++ b/services/macie2/multi_region_test.go @@ -2,7 +2,8 @@ package macie2_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -180,8 +182,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/macie2/pagination_tie_test.go b/services/macie2/pagination_tie_test.go index 11063a361a..58ec372854 100644 --- a/services/macie2/pagination_tie_test.go +++ b/services/macie2/pagination_tie_test.go @@ -1,7 +1,7 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -54,7 +55,7 @@ func TestDescribeBuckets_TiedObjectCount_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) buckets, _ := resp["buckets"].([]any) for _, item := range buckets { @@ -105,7 +106,7 @@ func TestListClassificationJobs_TiedName_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) created[resp["jobId"]] = true } @@ -120,7 +121,7 @@ func TestListClassificationJobs_TiedName_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["items"].([]any) for _, item := range items { @@ -168,7 +169,7 @@ func TestListCustomDataIdentifiers_TiedName_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) created[resp["customDataIdentifierId"]] = true } @@ -180,7 +181,7 @@ func TestListCustomDataIdentifiers_TiedName_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["items"].([]any) for _, item := range items { @@ -228,7 +229,7 @@ func TestListAllowLists_TiedName_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) created[resp["id"]] = true } @@ -240,7 +241,7 @@ func TestListAllowLists_TiedName_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["allowLists"].([]any) for _, item := range items { @@ -286,7 +287,7 @@ func TestListFindingsFilters_TiedPosition_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) created[resp["id"]] = true } @@ -298,7 +299,7 @@ func TestListFindingsFilters_TiedPosition_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["findingsFilterListItems"].([]any) for _, item := range items { @@ -348,7 +349,7 @@ func TestListFindings_TiedType_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, truthRec.Code) var truthResp map[string]any - require.NoError(t, json.Unmarshal(truthRec.Body.Bytes(), &truthResp)) + require.NoError(t, json.Unmarshal(truthRec.Body.Bytes(), &truthResp, jsonx.V1())) truthIDs, _ := truthResp["findingIds"].([]any) require.Len(t, truthIDs, dupCount) @@ -369,7 +370,7 @@ func TestListFindings_TiedType_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) ids, _ := resp["findingIds"].([]any) for _, id := range ids { diff --git a/services/macie2/persistence.go b/services/macie2/persistence.go index b3b7a57e14..75bb9ea699 100644 --- a/services/macie2/persistence.go +++ b/services/macie2/persistence.go @@ -2,7 +2,7 @@ package macie2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -52,7 +52,7 @@ const macie2SnapshotVersion = 3 // ClassExportConfig, FindingsPubConfig, and RevealConfig are single pointers, // not collections, and are persisted directly alongside the tables. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Session *Session `json:"session,omitempty"` Tags map[string]map[string]string `json:"tags"` Administrator *AdministratorAccount `json:"administrator,omitempty"` diff --git a/services/macie2/tag_leak_test.go b/services/macie2/tag_leak_test.go index a7214cfe9c..02bc109c6f 100644 --- a/services/macie2/tag_leak_test.go +++ b/services/macie2/tag_leak_test.go @@ -1,13 +1,14 @@ package macie2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/macie2" ) @@ -31,7 +32,7 @@ func TestMacie2_Delete_ClearsTags(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) arnStr, id := created["arn"], created["id"] require.NotEmpty(t, arnStr) @@ -45,7 +46,7 @@ func TestMacie2_Delete_ClearsTags(t *testing.T) { }) require.Equal(t, http.StatusOK, otherRec.Code) var otherCreated map[string]string - require.NoError(t, json.Unmarshal(otherRec.Body.Bytes(), &otherCreated)) + require.NoError(t, json.Unmarshal(otherRec.Body.Bytes(), &otherCreated, jsonx.V1())) otherArn := otherCreated["arn"] otherRec = doRequest( t, h, http.MethodPost, "/tags/"+otherArn, map[string]any{"tags": map[string]string{"k": "v"}}, @@ -61,7 +62,7 @@ func TestMacie2_Delete_ClearsTags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/tags/"+otherArn, nil) require.Equal(t, http.StatusOK, rec.Code) var otherTags map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &otherTags)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &otherTags, jsonx.V1())) assert.NotEmpty(t, otherTags["tags"]) }) @@ -76,7 +77,7 @@ func TestMacie2_Delete_ClearsTags(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var created map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) arnStr, id := created["arn"], created["id"] require.NotEmpty(t, arnStr) @@ -96,6 +97,6 @@ func assertMacie2SnapshotTagsCleared(t *testing.T, h *macie2.Handler, arnStr str var probe struct { Tags map[string]map[string]string `json:"tags"` } - require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &probe)) + require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &probe, jsonx.V1())) assert.NotContains(t, probe.Tags, arnStr) } diff --git a/services/managedblockchain/accessors_test.go b/services/managedblockchain/accessors_test.go index aa0245f9c8..a3e131bf45 100644 --- a/services/managedblockchain/accessors_test.go +++ b/services/managedblockchain/accessors_test.go @@ -2,7 +2,8 @@ package managedblockchain_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -70,7 +72,7 @@ func TestHandler_CreateAccessor(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) } }) @@ -111,7 +113,7 @@ func TestHandler_GetAccessor(t *testing.T) { var out struct { AccessorID string `json:"AccessorId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &out, jsonx.V1())) id := tt.accessorID if id == "" { @@ -123,7 +125,7 @@ func TestHandler_GetAccessor(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Accessor") } }) @@ -155,7 +157,7 @@ func TestHandler_DeleteAccessor(t *testing.T) { var out struct { AccessorID string `json:"AccessorId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &out, jsonx.V1())) rec := doRequest(t, h, http.MethodDelete, "/accessors/"+out.AccessorID, nil) assert.Equal(t, tt.wantStatus, rec.Code) @@ -171,7 +173,7 @@ func TestHandler_DeleteAccessor(t *testing.T) { Status string `json:"Status"` } `json:"Accessor"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, "PENDING_DELETION", getOut.Accessor.Status) }) } @@ -208,7 +210,7 @@ func TestHandler_ListAccessors(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accessors, ok := resp["Accessors"].([]any) require.True(t, ok) @@ -246,7 +248,7 @@ func TestHandler_AccessorRoundTrip(t *testing.T) { BillingToken string `json:"BillingToken"` NetworkType string `json:"NetworkType"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut.AccessorID) assert.NotEmpty(t, createOut.BillingToken) assert.Equal(t, "ETHEREUM_MAINNET", createOut.NetworkType) @@ -258,7 +260,7 @@ func TestHandler_AccessorRoundTrip(t *testing.T) { var getOut struct { Accessor map[string]any `json:"Accessor"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, "AVAILABLE", getOut.Accessor["Status"]) // List. @@ -268,7 +270,7 @@ func TestHandler_AccessorRoundTrip(t *testing.T) { var listOut struct { Accessors []map[string]any `json:"Accessors"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listOut, jsonx.V1())) assert.Len(t, listOut.Accessors, 1) // Delete. @@ -284,7 +286,7 @@ func TestHandler_AccessorRoundTrip(t *testing.T) { var listOut2 struct { Accessors []map[string]any `json:"Accessors"` } - require.NoError(t, json.NewDecoder(listRec2.Body).Decode(&listOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec2.Body), &listOut2, jsonx.V1())) require.Len(t, listOut2.Accessors, 1) assert.Equal(t, "PENDING_DELETION", listOut2.Accessors[0]["Status"]) }) @@ -359,7 +361,7 @@ func TestHandler_ListAccessorsFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) accessors := resp["Accessors"].([]any) assert.Len(t, accessors, tt.wantCount) @@ -474,7 +476,7 @@ func TestHandler_AccessorLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) accessorID, _ := createResp["AccessorId"].(string) require.NotEmpty(t, accessorID) @@ -485,7 +487,7 @@ func TestHandler_AccessorLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) accessor := getResp["Accessor"].(map[string]any) assert.Equal(t, accessorID, accessor["Id"]) @@ -496,7 +498,7 @@ func TestHandler_AccessorLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) accessors := listResp["Accessors"].([]any) assert.Len(t, accessors, 1) @@ -511,7 +513,7 @@ func TestHandler_AccessorLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec5.Code) var getResp5 map[string]any - require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &getResp5)) + require.NoError(t, json.Unmarshal(rec5.Body.Bytes(), &getResp5, jsonx.V1())) accessor5 := getResp5["Accessor"].(map[string]any) assert.Equal(t, "PENDING_DELETION", accessor5["Status"]) }) diff --git a/services/managedblockchain/framework_attributes_test.go b/services/managedblockchain/framework_attributes_test.go index 8b0f9edb1f..d4bac9d0a1 100644 --- a/services/managedblockchain/framework_attributes_test.go +++ b/services/managedblockchain/framework_attributes_test.go @@ -1,12 +1,14 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_CreateNetwork_FrameworkConfiguration verifies CreateNetwork's @@ -73,7 +75,7 @@ func TestHandler_CreateNetwork_FrameworkConfiguration(t *testing.T) { var createResp struct { NetworkID string `json:"NetworkId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/networks/"+createResp.NetworkID, nil) require.Equal(t, http.StatusOK, rec.Code) @@ -89,7 +91,7 @@ func TestHandler_CreateNetwork_FrameworkConfiguration(t *testing.T) { VpcEndpointServiceName string `json:"VpcEndpointServiceName"` } `json:"Network"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotEmpty(t, getResp.Network.VpcEndpointServiceName, "VpcEndpointServiceName must be populated regardless of FrameworkConfiguration") @@ -294,7 +296,7 @@ func TestHandler_CreateMember_FrameworkAttributesRoundTrip(t *testing.T) { var createResp struct { MemberID string `json:"MemberId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/networks/"+networkID+"/members/"+createResp.MemberID, nil) require.Equal(t, http.StatusOK, rec.Code) @@ -310,7 +312,7 @@ func TestHandler_CreateMember_FrameworkAttributesRoundTrip(t *testing.T) { } `json:"FrameworkAttributes"` } `json:"Member"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "admin", getResp.Member.FrameworkAttributes.Fabric.AdminUsername) assert.NotEmpty(t, getResp.Member.FrameworkAttributes.Fabric.CaEndpoint) @@ -364,7 +366,7 @@ func TestHandler_CreateNode_FrameworkAttributesRoundTrip(t *testing.T) { var createResp struct { NodeID string `json:"NodeId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/networks/"+networkID+"/nodes/"+createResp.NodeID+"?memberId="+memberID, nil) @@ -382,7 +384,7 @@ func TestHandler_CreateNode_FrameworkAttributesRoundTrip(t *testing.T) { } `json:"FrameworkAttributes"` } `json:"Node"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.wantStateDB, getResp.Node.StateDB) assert.Equal(t, "AWS Owned KMS Key", getResp.Node.KmsKeyArn, diff --git a/services/managedblockchain/handler_accessors.go b/services/managedblockchain/handler_accessors.go index 20dba28190..e3ca32fbcf 100644 --- a/services/managedblockchain/handler_accessors.go +++ b/services/managedblockchain/handler_accessors.go @@ -1,16 +1,18 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateAccessor(c *echo.Context, body []byte) error { var req createAccessorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } diff --git a/services/managedblockchain/handler_members.go b/services/managedblockchain/handler_members.go index 24ca90d926..680e8ae539 100644 --- a/services/managedblockchain/handler_members.go +++ b/services/managedblockchain/handler_members.go @@ -1,10 +1,12 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateMember(c *echo.Context, networkID string, body []byte) error { @@ -14,7 +16,7 @@ func (h *Handler) handleCreateMember(c *echo.Context, networkID string, body []b var req createMemberRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } @@ -124,7 +126,7 @@ func (h *Handler) handleUpdateMember(c *echo.Context, resource string, body []by var req updateMemberRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } diff --git a/services/managedblockchain/handler_networks.go b/services/managedblockchain/handler_networks.go index 1416e6b303..31856782ea 100644 --- a/services/managedblockchain/handler_networks.go +++ b/services/managedblockchain/handler_networks.go @@ -1,16 +1,18 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateNetwork(c *echo.Context, body []byte) error { var req createNetworkRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } diff --git a/services/managedblockchain/handler_nodes.go b/services/managedblockchain/handler_nodes.go index 4e049d2c03..4fd90fdb96 100644 --- a/services/managedblockchain/handler_nodes.go +++ b/services/managedblockchain/handler_nodes.go @@ -1,10 +1,12 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateNode handles POST /networks/{networkId}/nodes. Unlike member @@ -17,7 +19,7 @@ func (h *Handler) handleCreateNode(c *echo.Context, networkID string, body []byt } var req createNodeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } @@ -141,7 +143,7 @@ func (h *Handler) handleUpdateNode(c *echo.Context, resource string, body []byte var req updateNodeRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } } diff --git a/services/managedblockchain/handler_proposals.go b/services/managedblockchain/handler_proposals.go index 356250c66f..10c846f519 100644 --- a/services/managedblockchain/handler_proposals.go +++ b/services/managedblockchain/handler_proposals.go @@ -1,10 +1,12 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateProposal(c *echo.Context, networkID string, body []byte) error { @@ -14,7 +16,7 @@ func (h *Handler) handleCreateProposal(c *echo.Context, networkID string, body [ var req createProposalRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } @@ -128,7 +130,7 @@ func (h *Handler) handleVoteOnProposal(c *echo.Context, resource string, body [] var req voteOnProposalRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } diff --git a/services/managedblockchain/handler_sdk_route_table_test.go b/services/managedblockchain/handler_sdk_route_table_test.go index 3898d2a8e3..b4b62e31b7 100644 --- a/services/managedblockchain/handler_sdk_route_table_test.go +++ b/services/managedblockchain/handler_sdk_route_table_test.go @@ -1,7 +1,7 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -116,7 +117,7 @@ func TestExtractOperation_SDKRouteTable(t *testing.T) { var resp struct { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEqual(t, "resource not found", resp.Message, "method=%s path=%s op=%s: dispatched to the unmatched-route default", tc.method, tc.path, tc.op) }) diff --git a/services/managedblockchain/handler_tags.go b/services/managedblockchain/handler_tags.go index 4175bd5551..a8b283de87 100644 --- a/services/managedblockchain/handler_tags.go +++ b/services/managedblockchain/handler_tags.go @@ -1,11 +1,13 @@ package managedblockchain import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTagsForResource(c *echo.Context, resourceARN string) error { @@ -30,7 +32,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string, body [] var req tagResourceRequest - if parseErr := json.Unmarshal(body, &req); parseErr != nil { + if parseErr := json.Unmarshal(body, &req, jsonx.V1()); parseErr != nil { return writeError(c, http.StatusBadRequest, "InvalidRequestException", "invalid request body") } diff --git a/services/managedblockchain/handler_test.go b/services/managedblockchain/handler_test.go index 0afbe440a6..3598b04f8a 100644 --- a/services/managedblockchain/handler_test.go +++ b/services/managedblockchain/handler_test.go @@ -1,7 +1,7 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_HandlerMetadata(t *testing.T) { @@ -341,7 +343,7 @@ func TestHandler_ErrorResponseHasCodeField(t *testing.T) { assert.Equal(t, tt.wantHTTP, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantCode, resp["Code"], "error response must include Code field; body: %s", rec.Body.String()) assert.NotEmpty(t, resp["message"], @@ -388,7 +390,7 @@ func TestHandler_ErrorResponseMessageNotEmpty(t *testing.T) { assert.NotEqual(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["message"], "error response must include non-empty message") assert.NotEmpty(t, resp["Code"], "error response must include non-empty Code") }) diff --git a/services/managedblockchain/input_validation_test.go b/services/managedblockchain/input_validation_test.go index 7be832329f..77ebc98b15 100644 --- a/services/managedblockchain/input_validation_test.go +++ b/services/managedblockchain/input_validation_test.go @@ -1,13 +1,15 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func networkBody(mutate func(body, member, fabric map[string]any)) map[string]any { @@ -108,7 +110,7 @@ func TestCreateNetwork_InputValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["Code"]) assert.NotContains(t, resp["message"], "InvalidRequestException") }) @@ -122,7 +124,7 @@ func TestCreateNetwork_RealisticIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Regexp(t, `^n-[A-Z2-7]{26}$`, resp["NetworkId"]) assert.Regexp(t, `^m-[A-Z2-7]{26}$`, resp["MemberId"]) } @@ -134,7 +136,7 @@ func TestErrorMessages_NoCodePrefix(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["Code"]) assert.NotContains(t, resp["message"], "ResourceNotFoundException") } diff --git a/services/managedblockchain/invitations_test.go b/services/managedblockchain/invitations_test.go index 8e4c01d8a7..0bf1d22962 100644 --- a/services/managedblockchain/invitations_test.go +++ b/services/managedblockchain/invitations_test.go @@ -1,13 +1,15 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -42,7 +44,7 @@ func TestHandler_ListInvitations(t *testing.T) { var resp struct { Invitations []map[string]any `json:"Invitations"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Len(t, resp.Invitations, tt.wantLen) }) } @@ -119,7 +121,7 @@ func TestHandler_InvitationNetworkSummary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) invitations := resp["Invitations"].([]any) require.Len(t, invitations, 1) @@ -153,7 +155,7 @@ func TestHandler_InvitationLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) invitations := listResp["Invitations"].([]any) assert.Len(t, invitations, 1) @@ -199,7 +201,7 @@ func TestHandler_InvitationOmitsTopLevelNetworkFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) invitations := resp["Invitations"].([]any) require.Len(t, invitations, 1) diff --git a/services/managedblockchain/members_test.go b/services/managedblockchain/members_test.go index ef58dec0ac..53be6e30b3 100644 --- a/services/managedblockchain/members_test.go +++ b/services/managedblockchain/members_test.go @@ -1,7 +1,7 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -128,7 +129,7 @@ func TestHandler_MemberLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createNetResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createNetResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createNetResp, jsonx.V1())) networkID := createNetResp["NetworkId"].(string) // Create member @@ -142,7 +143,7 @@ func TestHandler_MemberLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createMemResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createMemResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createMemResp, jsonx.V1())) memberID := createMemResp["MemberId"].(string) assert.NotEmpty(t, memberID) @@ -155,7 +156,7 @@ func TestHandler_MemberLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) members, ok := listResp["Members"].([]any) require.True(t, ok) assert.Len(t, members, 2) @@ -430,7 +431,7 @@ func TestHandler_ListMembersFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members := resp["Members"].([]any) assert.Len(t, members, tt.wantCount) @@ -523,7 +524,7 @@ func TestHandler_MemberSummaryIsOwned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) members := resp["Members"].([]any) require.Len(t, members, 1) @@ -552,7 +553,7 @@ func TestHandler_MemberObjectIsOwned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) member := resp["Member"].(map[string]any) isOwned, ok := member["IsOwned"] @@ -610,7 +611,7 @@ func TestHandler_UpdateMemberLogPublishingConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) member := getResp["Member"].(map[string]any) logConfig, ok := member["LogPublishingConfiguration"] @@ -708,7 +709,7 @@ func TestHandler_CreateMemberWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) memberID, _ := resp["MemberId"].(string) require.NotEmpty(t, memberID) @@ -718,7 +719,7 @@ func TestHandler_CreateMemberWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var memResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1())) member := memResp["Member"].(map[string]any) tags := member["Tags"].(map[string]any) diff --git a/services/managedblockchain/multi_region_test.go b/services/managedblockchain/multi_region_test.go index d530c57313..5824f23b7e 100644 --- a/services/managedblockchain/multi_region_test.go +++ b/services/managedblockchain/multi_region_test.go @@ -2,7 +2,8 @@ package managedblockchain_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -198,8 +200,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/managedblockchain/networks_test.go b/services/managedblockchain/networks_test.go index 851fb6bbf6..3265ceec9d 100644 --- a/services/managedblockchain/networks_test.go +++ b/services/managedblockchain/networks_test.go @@ -2,7 +2,7 @@ package managedblockchain_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -275,7 +276,7 @@ func TestHandler_CreateNetwork(t *testing.T) { if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) } }) @@ -316,7 +317,7 @@ func TestHandler_GetNetwork(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) networkID := createResp["NetworkId"].(string) id := tt.networkID @@ -329,7 +330,7 @@ func TestHandler_GetNetwork(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Network") } }) @@ -376,7 +377,7 @@ func TestHandler_ListNetworks(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) networks, ok := resp["Networks"].([]any) require.True(t, ok) @@ -466,7 +467,7 @@ func TestHandler_VotingPolicyStoredAndReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) networkID := createResp["NetworkId"].(string) @@ -474,7 +475,7 @@ func TestHandler_VotingPolicyStoredAndReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) network := getResp["Network"].(map[string]any) @@ -549,7 +550,7 @@ func TestHandler_ListNetworksFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) networks := resp["Networks"].([]any) assert.Len(t, networks, tt.wantCount) @@ -674,7 +675,7 @@ func TestHandler_CreateNetworkWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) networkID, _ := resp["NetworkId"].(string) require.NotEmpty(t, networkID) @@ -684,7 +685,7 @@ func TestHandler_CreateNetworkWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &netResp, jsonx.V1())) network := netResp["Network"].(map[string]any) tags := network["Tags"].(map[string]any) @@ -713,7 +714,7 @@ func TestHandler_CreateNetworkFoundingMemberTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) networkID, _ := resp["NetworkId"].(string) memberID, _ := resp["MemberId"].(string) @@ -724,7 +725,7 @@ func TestHandler_CreateNetworkFoundingMemberTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var memResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &memResp, jsonx.V1())) member := memResp["Member"].(map[string]any) tags := member["Tags"].(map[string]any) diff --git a/services/managedblockchain/nodes_test.go b/services/managedblockchain/nodes_test.go index d6eccfe9a5..bb9b415d2d 100644 --- a/services/managedblockchain/nodes_test.go +++ b/services/managedblockchain/nodes_test.go @@ -1,7 +1,8 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -39,7 +41,7 @@ func TestHandler_NodeLifecycle_RealWireShape(t *testing.T) { var createOut struct { NodeID string `json:"NodeId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) require.NotEmpty(t, createOut.NodeID) nodePath := "/networks/" + netID + "/nodes/" + createOut.NodeID + "?memberId=" + memID @@ -163,7 +165,7 @@ func TestHandler_GetNode(t *testing.T) { var createOut struct { NodeID string `json:"NodeId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) getPath := fmt.Sprintf("%s/%s?memberId=%s", nodesPath, createOut.NodeID, memID) rec := doRequest(t, h, http.MethodGet, getPath, nil) @@ -210,7 +212,7 @@ func TestHandler_ListNodes(t *testing.T) { var out struct { Nodes []map[string]any `json:"Nodes"` } - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &out, jsonx.V1())) assert.Len(t, out.Nodes, tt.wantLen) }) } @@ -244,7 +246,7 @@ func TestHandler_DeleteNode(t *testing.T) { var createOut struct { NodeID string `json:"NodeId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) delPath := fmt.Sprintf("%s/%s?memberId=%s", nodesPath, createOut.NodeID, memID) rec := doRequest(t, h, http.MethodDelete, delPath, nil) @@ -377,7 +379,7 @@ func TestHandler_ListNodesFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nodes := resp["Nodes"].([]any) assert.Len(t, nodes, tt.wantCount) @@ -477,7 +479,7 @@ func TestHandler_NodeSummaryAvailabilityZone(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp, jsonx.V1())) nodes := listResp["Nodes"].([]any) require.Len(t, nodes, 1) @@ -550,7 +552,7 @@ func TestHandler_UpdateNodeLogPublishingConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) nodeResp := getResp["Node"].(map[string]any) logConfig, ok := nodeResp["LogPublishingConfiguration"] @@ -598,7 +600,7 @@ func TestHandler_CreateNodeWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nodeID, _ := resp["NodeId"].(string) require.NotEmpty(t, nodeID) @@ -609,7 +611,7 @@ func TestHandler_CreateNodeWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var nodeResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &nodeResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &nodeResp, jsonx.V1())) node := nodeResp["Node"].(map[string]any) tags := node["Tags"].(map[string]any) @@ -674,7 +676,7 @@ func TestHandler_NodeLifecycleBasicViaHTTP(t *testing.T) { } var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) nodeID, _ := createResp["NodeId"].(string) require.NotEmpty(t, nodeID) @@ -685,7 +687,7 @@ func TestHandler_NodeLifecycleBasicViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) node := getResp["Node"].(map[string]any) assert.Equal(t, nodeID, node["Id"]) @@ -697,7 +699,7 @@ func TestHandler_NodeLifecycleBasicViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) nodes := listResp["Nodes"].([]any) assert.Len(t, nodes, 1) diff --git a/services/managedblockchain/pagination_test.go b/services/managedblockchain/pagination_test.go index 011d631e39..7a3a13e8f9 100644 --- a/services/managedblockchain/pagination_test.go +++ b/services/managedblockchain/pagination_test.go @@ -1,13 +1,15 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListNetworks_Pagination verifies ListNetworks honors @@ -33,7 +35,7 @@ func TestHandler_ListNetworks_Pagination(t *testing.T) { rec := doRequestWithQuery(t, h, "/networks", map[string]string{"maxResults": "2"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Networks, 2) require.NotEmpty(t, page1.NextToken, "a 5-item list capped at maxResults=2 must return a NextToken") @@ -41,7 +43,7 @@ func TestHandler_ListNetworks_Pagination(t *testing.T) { rec = doRequestWithQuery(t, h, "/networks", map[string]string{"maxResults": "2", "nextToken": page1.NextToken}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Networks, 2) require.NotEmpty(t, page2.NextToken) @@ -49,7 +51,7 @@ func TestHandler_ListNetworks_Pagination(t *testing.T) { rec = doRequestWithQuery(t, h, "/networks", map[string]string{"maxResults": "2", "nextToken": page2.NextToken}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page3)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page3, jsonx.V1())) assert.Len(t, page3.Networks, 1) assert.Empty(t, page3.NextToken, "the final page must omit NextToken") @@ -69,7 +71,7 @@ func TestHandler_ListNetworks_Pagination(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/networks", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &unpaged)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &unpaged, jsonx.V1())) assert.Len(t, unpaged.Networks, 5) assert.Empty(t, unpaged.NextToken) } @@ -109,7 +111,7 @@ func TestHandler_ListMembers_Pagination(t *testing.T) { rec := doRequestWithQuery(t, h, "/networks/"+networkID+"/members", map[string]string{"maxResults": "3"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Members, 3) require.NotEmpty(t, page1.NextToken) @@ -121,7 +123,7 @@ func TestHandler_ListMembers_Pagination(t *testing.T) { rec = doRequestWithQuery(t, h, "/networks/"+networkID+"/members", map[string]string{"maxResults": "3", "nextToken": page1.NextToken}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Members, 1) assert.Empty(t, page2.NextToken) } diff --git a/services/managedblockchain/persistence.go b/services/managedblockchain/persistence.go index 68fc780427..d41dd9dc04 100644 --- a/services/managedblockchain/persistence.go +++ b/services/managedblockchain/persistence.go @@ -2,7 +2,7 @@ package managedblockchain import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -63,8 +63,8 @@ func buildProposalVoteDTORegistry() (*store.Registry, *store.Table[proposalVoteD // is a derived reverse-lookup cache rebuilt from the tables above by // rebuildARNIndexLocked on Restore, exactly as it was before this refactor. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -141,7 +141,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreTablesLocked rebuilds every store.Table on b from tables' per-table // JSON, factored out of Restore to keep Restore's own cognitive complexity // low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreTablesLocked(tables map[string]jsontext.Value) error { if err := b.registry.RestoreAll(tables); err != nil { return fmt.Errorf("managedblockchain: restore tables: %w", err) } diff --git a/services/managedblockchain/persistence_test.go b/services/managedblockchain/persistence_test.go index c693ad79dd..aee491df59 100644 --- a/services/managedblockchain/persistence_test.go +++ b/services/managedblockchain/persistence_test.go @@ -1,12 +1,13 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -366,11 +367,11 @@ func TestManagedBlockchain_PersistenceVersionMismatch(t *testing.T) { require.NotNil(t, snap) var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) tt.mutate(t, raw) - mutated, err := json.Marshal(raw) + mutated, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) // Seed b2 with unrelated pre-existing state so a discarded diff --git a/services/managedblockchain/proposals_test.go b/services/managedblockchain/proposals_test.go index 4d666de547..12be123952 100644 --- a/services/managedblockchain/proposals_test.go +++ b/services/managedblockchain/proposals_test.go @@ -2,7 +2,8 @@ package managedblockchain_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -75,7 +77,7 @@ func TestHandler_CreateProposal(t *testing.T) { if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) } }) @@ -104,7 +106,7 @@ func TestCreateProposal_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) proposalID, _ := resp["ProposalId"].(string) require.NotEmpty(t, proposalID) @@ -112,7 +114,7 @@ func TestCreateProposal_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) proposal := getResp["Proposal"].(map[string]any) proposalArn, _ := proposal["Arn"].(string) require.NotEmpty(t, proposalArn) @@ -158,7 +160,7 @@ func TestHandler_GetProposal(t *testing.T) { var createOut struct { ProposalID string `json:"ProposalId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) pid := tt.proposalID if pid == "" { @@ -170,7 +172,7 @@ func TestHandler_GetProposal(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Proposal") } }) @@ -210,7 +212,7 @@ func TestHandler_ListProposals(t *testing.T) { var resp struct { Proposals []map[string]any `json:"Proposals"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Len(t, resp.Proposals, tt.wantLen) }) } @@ -243,7 +245,7 @@ func TestHandler_ListProposalVotes(t *testing.T) { var createOut struct { ProposalID string `json:"ProposalId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) path := fmt.Sprintf("/networks/%s/proposals/%s/votes", netID, createOut.ProposalID) rec := doRequest(t, h, http.MethodGet, path, nil) @@ -252,7 +254,7 @@ func TestHandler_ListProposalVotes(t *testing.T) { var resp struct { ProposalVotes []map[string]any `json:"ProposalVotes"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Len(t, resp.ProposalVotes, tt.wantLen) }) } @@ -314,7 +316,7 @@ func TestHandler_ProposalRoundTrip(t *testing.T) { var createOut struct { ProposalID string `json:"ProposalId"` } - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut.ProposalID) // Get. @@ -325,7 +327,7 @@ func TestHandler_ProposalRoundTrip(t *testing.T) { var getOut struct { Proposal map[string]any `json:"Proposal"` } - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &getOut, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", getOut.Proposal["Status"]) // List votes. @@ -336,7 +338,7 @@ func TestHandler_ProposalRoundTrip(t *testing.T) { var votesOut struct { ProposalVotes []map[string]any `json:"ProposalVotes"` } - require.NoError(t, json.NewDecoder(votesRec.Body).Decode(&votesOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(votesRec.Body), &votesOut, jsonx.V1())) assert.Empty(t, votesOut.ProposalVotes) }) } @@ -442,7 +444,7 @@ func TestHandler_ProposalActionsStoredAndReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) proposalID := createResp["ProposalId"].(string) @@ -450,7 +452,7 @@ func TestHandler_ProposalActionsStoredAndReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) proposal := getResp["Proposal"].(map[string]any) @@ -498,7 +500,7 @@ func TestHandler_ProposalSummaryOmitsNetworkID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) proposals := resp["Proposals"].([]any) require.Len(t, proposals, 1) @@ -535,7 +537,7 @@ func TestHandler_ListProposalsStatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) proposals, _ := listResp["Proposals"].([]any) assert.Len(t, proposals, 2, @@ -548,7 +550,7 @@ func TestHandler_ListProposalsStatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, listApprovedRec.Code) var listApprovedResp map[string]any - require.NoError(t, json.Unmarshal(listApprovedRec.Body.Bytes(), &listApprovedResp)) + require.NoError(t, json.Unmarshal(listApprovedRec.Body.Bytes(), &listApprovedResp, jsonx.V1())) approvedProposals, _ := listApprovedResp["Proposals"].([]any) assert.Empty(t, approvedProposals, @@ -579,7 +581,7 @@ func TestHandler_ListProposalsNoStatusFilterReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) ownerID := netResp["MemberId"].(string) @@ -590,7 +592,7 @@ func TestHandler_ListProposalsNoStatusFilterReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, propRec1.Code) var prop1 map[string]any - require.NoError(t, json.Unmarshal(propRec1.Body.Bytes(), &prop1)) + require.NoError(t, json.Unmarshal(propRec1.Body.Bytes(), &prop1, jsonx.V1())) propID1 := prop1["ProposalId"].(string) @@ -609,7 +611,7 @@ func TestHandler_ListProposalsNoStatusFilterReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) proposals, _ := listResp["Proposals"].([]any) assert.Len(t, proposals, 2, "listing without status filter must return all proposals") @@ -629,7 +631,7 @@ func TestHandler_ListProposalsNoStatusFilterReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, listApprovedRec.Code) var listApprovedResp map[string]any - require.NoError(t, json.Unmarshal(listApprovedRec.Body.Bytes(), &listApprovedResp)) + require.NoError(t, json.Unmarshal(listApprovedRec.Body.Bytes(), &listApprovedResp, jsonx.V1())) approvedProposals, _ := listApprovedResp["Proposals"].([]any) assert.Len(t, approvedProposals, 1, "filtering by APPROVED must return only approved proposals") @@ -671,7 +673,7 @@ func TestHandler_ProposalExpirationDateViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) proposalID, _ := createResp["ProposalId"].(string) require.NotEmpty(t, proposalID) @@ -680,7 +682,7 @@ func TestHandler_ProposalExpirationDateViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) proposal := getResp["Proposal"].(map[string]any) assert.NotEmpty(t, proposal["ExpirationDate"]) @@ -706,7 +708,7 @@ func TestHandler_ProposalLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) proposalID, _ := createResp["ProposalId"].(string) require.NotEmpty(t, proposalID) @@ -716,7 +718,7 @@ func TestHandler_ProposalLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) proposal := getResp["Proposal"].(map[string]any) assert.Equal(t, proposalID, proposal["ProposalId"]) @@ -728,7 +730,7 @@ func TestHandler_ProposalLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) proposals := listResp["Proposals"].([]any) assert.Len(t, proposals, 1) @@ -737,7 +739,7 @@ func TestHandler_ProposalLifecycleViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var votesResp map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &votesResp)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &votesResp, jsonx.V1())) votes := votesResp["ProposalVotes"].([]any) assert.Empty(t, votes) } diff --git a/services/managedblockchain/proposals_voting_test.go b/services/managedblockchain/proposals_voting_test.go index 4688be42ee..2f5dc2999d 100644 --- a/services/managedblockchain/proposals_voting_test.go +++ b/services/managedblockchain/proposals_voting_test.go @@ -1,7 +1,7 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -175,7 +176,7 @@ func TestHandler_ProposalOutstandingVoteCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["Proposal"].(map[string]any) outstanding := int(p["OutstandingVoteCount"].(float64)) @@ -257,7 +258,7 @@ func TestHandler_ProposalStatusTransitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createNetResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createNetResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createNetResp, jsonx.V1())) networkID := createNetResp["NetworkId"].(string) firstMemberID := createNetResp["MemberId"].(string) @@ -275,7 +276,7 @@ func TestHandler_ProposalStatusTransitions(t *testing.T) { require.Equal(t, http.StatusOK, memRec.Code) var createMemResp map[string]any - require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &createMemResp)) + require.NoError(t, json.Unmarshal(memRec.Body.Bytes(), &createMemResp, jsonx.V1())) extraMemberIDs = append(extraMemberIDs, createMemResp["MemberId"].(string)) } @@ -288,7 +289,7 @@ func TestHandler_ProposalStatusTransitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createPropResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createPropResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createPropResp, jsonx.V1())) proposalID := createPropResp["ProposalId"].(string) @@ -313,7 +314,7 @@ func TestHandler_ProposalStatusTransitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["Proposal"].(map[string]any) assert.Equal(t, tt.wantStatus, p["Status"]) @@ -353,7 +354,7 @@ func TestHandler_VoteOnProposalAlreadyCompleted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &netResp, jsonx.V1())) networkID := netResp["NetworkId"].(string) memberID := netResp["MemberId"].(string) @@ -365,7 +366,7 @@ func TestHandler_VoteOnProposalAlreadyCompleted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &propResp, jsonx.V1())) proposalID := propResp["ProposalId"].(string) votePath := fmt.Sprintf("/networks/%s/proposals/%s/votes", networkID, proposalID) @@ -382,7 +383,7 @@ func TestHandler_VoteOnProposalAlreadyCompleted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["Proposal"].(map[string]any) assert.Equal(t, "APPROVED", p["Status"]) @@ -415,7 +416,7 @@ func TestHandler_VoteThresholdFloatPrecision(t *testing.T) { require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) ownerMemberID := netResp["MemberId"].(string) @@ -450,7 +451,7 @@ func TestHandler_VoteThresholdFloatPrecision(t *testing.T) { require.Equal(t, http.StatusOK, propRec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp, jsonx.V1())) propID := propResp["ProposalId"].(string) votePath := fmt.Sprintf("/networks/%s/proposals/%s/votes", netID, propID) @@ -465,7 +466,7 @@ func TestHandler_VoteThresholdFloatPrecision(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["Proposal"].(map[string]any) assert.Equal(t, "APPROVED", p["Status"], @@ -497,7 +498,7 @@ func TestHandler_ApprovedProposalExecutesInvitationActions(t *testing.T) { require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) ownerMemberID := netResp["MemberId"].(string) @@ -507,7 +508,7 @@ func TestHandler_ApprovedProposalExecutesInvitationActions(t *testing.T) { require.Equal(t, http.StatusOK, listBefore.Code) var listBeforeResp map[string]any - require.NoError(t, json.Unmarshal(listBefore.Body.Bytes(), &listBeforeResp)) + require.NoError(t, json.Unmarshal(listBefore.Body.Bytes(), &listBeforeResp, jsonx.V1())) assert.Empty(t, listBeforeResp["Invitations"], "no invitations should exist before proposal approval") @@ -525,7 +526,7 @@ func TestHandler_ApprovedProposalExecutesInvitationActions(t *testing.T) { require.Equal(t, http.StatusOK, propRec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp, jsonx.V1())) propID := propResp["ProposalId"].(string) @@ -540,7 +541,7 @@ func TestHandler_ApprovedProposalExecutesInvitationActions(t *testing.T) { require.Equal(t, http.StatusOK, listAfter.Code) var listAfterResp map[string]any - require.NoError(t, json.Unmarshal(listAfter.Body.Bytes(), &listAfterResp)) + require.NoError(t, json.Unmarshal(listAfter.Body.Bytes(), &listAfterResp, jsonx.V1())) invitations, _ := listAfterResp["Invitations"].([]any) assert.Len(t, invitations, 1, @@ -573,7 +574,7 @@ func TestHandler_RejectionThresholdImpossibleApproval(t *testing.T) { require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) m0ID := netResp["MemberId"].(string) @@ -594,7 +595,7 @@ func TestHandler_RejectionThresholdImpossibleApproval(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r, jsonx.V1())) return r["MemberId"].(string) } @@ -610,7 +611,7 @@ func TestHandler_RejectionThresholdImpossibleApproval(t *testing.T) { require.Equal(t, http.StatusOK, propRec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp, jsonx.V1())) propID := propResp["ProposalId"].(string) votePath := fmt.Sprintf("/networks/%s/proposals/%s/votes", netID, propID) @@ -631,7 +632,7 @@ func TestHandler_RejectionThresholdImpossibleApproval(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["Proposal"].(map[string]any) assert.Equal(t, "REJECTED", p["Status"], @@ -662,7 +663,7 @@ func TestHandler_ProposalExpiresAfterExpirationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) p, ok := resp["Proposal"].(map[string]any) require.True(t, ok) assert.Equal(t, "EXPIRED", p["Status"]) @@ -677,7 +678,7 @@ func TestHandler_ProposalExpiresAfterExpirationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) proposals, ok := resp["Proposals"].([]any) require.True(t, ok) require.Len(t, proposals, 1) @@ -741,7 +742,7 @@ func TestHandler_ApprovedRemovalProposalCascadeDeletesEmptyNetwork(t *testing.T) require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) ownerMemberID := netResp["MemberId"].(string) @@ -759,7 +760,7 @@ func TestHandler_ApprovedRemovalProposalCascadeDeletesEmptyNetwork(t *testing.T) require.Equal(t, http.StatusOK, propRec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp, jsonx.V1())) propID := propResp["ProposalId"].(string) @@ -802,7 +803,7 @@ func TestHandler_ApprovedProposalActionFailedWhenTargetMemberAlreadyGone(t *test require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) netID := netResp["NetworkId"].(string) ownerID := netResp["MemberId"].(string) @@ -823,7 +824,7 @@ func TestHandler_ApprovedProposalActionFailedWhenTargetMemberAlreadyGone(t *test require.Equal(t, http.StatusOK, propRec.Code) var propResp map[string]any - require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp)) + require.NoError(t, json.Unmarshal(propRec.Body.Bytes(), &propResp, jsonx.V1())) propID := propResp["ProposalId"].(string) @@ -842,7 +843,7 @@ func TestHandler_ApprovedProposalActionFailedWhenTargetMemberAlreadyGone(t *test require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) proposal, ok := getResp["Proposal"].(map[string]any) require.True(t, ok) diff --git a/services/managedblockchain/store_test.go b/services/managedblockchain/store_test.go index df45f9acfb..b08fc0cfa5 100644 --- a/services/managedblockchain/store_test.go +++ b/services/managedblockchain/store_test.go @@ -2,7 +2,8 @@ package managedblockchain_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -61,7 +63,7 @@ func doRequest(t *testing.T, h *managedblockchain.Handler, method, path string, if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -133,7 +135,7 @@ func doRoutedRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -206,7 +208,7 @@ func createTestNetwork(t *testing.T, h *managedblockchain.Handler) (string, stri NetworkID string `json:"NetworkId"` MemberID string `json:"MemberId"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) return out.NetworkID, out.MemberID } diff --git a/services/managedblockchain/tags_test.go b/services/managedblockchain/tags_test.go index 088bc4d2f7..858a99ffaa 100644 --- a/services/managedblockchain/tags_test.go +++ b/services/managedblockchain/tags_test.go @@ -1,7 +1,7 @@ package managedblockchain_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/managedblockchain" ) @@ -138,7 +139,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) // Get the network to find its ARN networkID := createResp["NetworkId"].(string) @@ -146,7 +147,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &netResp, jsonx.V1())) network := netResp["Network"].(map[string]any) arn := network["Arn"].(string) assert.NotEmpty(t, arn) @@ -161,7 +162,7 @@ func TestHandler_TagOperations(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) @@ -233,7 +234,7 @@ func TestHandler_UntagResourceQuery(t *testing.T) { require.Equal(t, http.StatusOK, netRec.Code) var netResp map[string]any - require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp)) + require.NoError(t, json.Unmarshal(netRec.Body.Bytes(), &netResp, jsonx.V1())) network := netResp["Network"].(map[string]any) arn := network["Arn"].(string) @@ -298,7 +299,7 @@ func TestHandler_TagNodeViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].(map[string]any) assert.Equal(t, "gold", tags["tier"]) diff --git a/services/marketplaceentitlementservice/cbor.go b/services/marketplaceentitlementservice/cbor.go index 84aff4ef14..651350c58a 100644 --- a/services/marketplaceentitlementservice/cbor.go +++ b/services/marketplaceentitlementservice/cbor.go @@ -3,12 +3,15 @@ package marketplaceentitlementservice import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "strconv" "strings" "github.com/aws/smithy-go/encoding/cbor" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // The pinned SDK speaks Smithy RPCv2 CBOR (POST /service/AWSMPEntitlementService/operation/); @@ -33,7 +36,7 @@ func cborToJSONBytes(body []byte) ([]byte, error) { return nil, fail(ErrInvalidParameter, "expected a CBOR map") } - return json.Marshal(cborToAny(m)) + return json.Marshal(cborToAny(m), jsonx.V1()) } func cborToAny(v cbor.Value) any { @@ -55,13 +58,13 @@ func cborToAny(v cbor.Value) any { case cbor.String: return string(t) case cbor.Uint: - return json.Number(strconv.FormatUint(uint64(t), 10)) + return jsonx.Number(strconv.FormatUint(uint64(t), 10)) case cbor.NegInt: if uint64(t) > math.MaxInt64 { return float64(-1) - float64(t) } - return json.Number(strconv.FormatInt(-1-int64(t), 10)) //nolint:gosec // bounded by the MaxInt64 check above + return jsonx.Number(strconv.FormatInt(-1-int64(t), 10)) //nolint:gosec // bounded by the MaxInt64 check above case cbor.Float64: return float64(t) case cbor.Float32: @@ -78,11 +81,10 @@ func cborToAny(v cbor.Value) any { } func jsonToCBORBytes(body []byte) ([]byte, error) { - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fail(ErrInternalServiceError, "encode response: %v", err) } @@ -109,14 +111,14 @@ func anyToCBOR(v any, key string) cbor.Value { return cbor.String(t) case bool: return cbor.Bool(t) - case json.Number: + case jsonx.Number: return numberToCBOR(t, cborTimeKeys[key]) default: return &cbor.Nil{} } } -func numberToCBOR(n json.Number, timestamp bool) cbor.Value { +func numberToCBOR(n jsonx.Number, timestamp bool) cbor.Value { if !timestamp && !strings.ContainsAny(string(n), ".eE") { if i, err := n.Int64(); err == nil { if i >= 0 { diff --git a/services/marketplaceentitlementservice/handler.go b/services/marketplaceentitlementservice/handler.go index 82f1326fdc..85dca8cac0 100644 --- a/services/marketplaceentitlementservice/handler.go +++ b/services/marketplaceentitlementservice/handler.go @@ -1,7 +1,7 @@ package marketplaceentitlementservice import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -46,7 +47,7 @@ func decode(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return fail(ErrInvalidParameter, "malformed request body: %v", err) } @@ -148,7 +149,7 @@ func (h *Handler) Handler() echo.HandlerFunc { } func (h *Handler) writeCBOR(c *echo.Context, out any) error { - raw, err := json.Marshal(out) + raw, err := json.Marshal(out, jsonx.V1()) if err == nil { var enc []byte diff --git a/services/marketplaceentitlementservice/persistence.go b/services/marketplaceentitlementservice/persistence.go index e008500154..c664f97fce 100644 --- a/services/marketplaceentitlementservice/persistence.go +++ b/services/marketplaceentitlementservice/persistence.go @@ -2,7 +2,7 @@ package marketplaceentitlementservice import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/marketplacemetering/customers.go b/services/marketplacemetering/customers.go index 77cd241b3d..24318f4f85 100644 --- a/services/marketplacemetering/customers.go +++ b/services/marketplacemetering/customers.go @@ -5,11 +5,12 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ent "github.com/blackbirdworks/gopherstack/services/marketplaceentitlementservice" ) @@ -236,7 +237,7 @@ func (b *InMemoryBackend) IssueRegistrationToken(productCode, accountID string) issued := b.now().Unix() b.mu.RUnlock() - raw, _ := json.Marshal(tokenClaims{P: productCode, A: accountID, I: issued}) + raw, _ := json.Marshal(tokenClaims{P: productCode, A: accountID, I: issued}, jsonx.V1()) payload := base64.RawURLEncoding.EncodeToString(raw) return payload + "." + mac(payload), nil @@ -257,7 +258,8 @@ func (b *InMemoryBackend) ResolveCustomer(token string) (*ResolvedCustomer, erro var claims tokenClaims - if err != nil || json.Unmarshal(raw, &claims) != nil || !validProductCode(claims.P) || !validAccountID(claims.A) { + if err != nil || json.Unmarshal(raw, &claims, jsonx.V1()) != nil || !validProductCode(claims.P) || + !validAccountID(claims.A) { return nil, fail(ErrInvalidToken, "the registration token is not valid") } diff --git a/services/marketplacemetering/customers_test.go b/services/marketplacemetering/customers_test.go index b250d0d20a..e898132092 100644 --- a/services/marketplacemetering/customers_test.go +++ b/services/marketplacemetering/customers_test.go @@ -2,7 +2,7 @@ package marketplacemetering_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" mm "github.com/blackbirdworks/gopherstack/services/marketplacemetering" ) @@ -87,7 +88,7 @@ func TestRegisterUsage(t *testing.T) { require.NoError(t, decErr) var claims map[string]any - require.NoError(t, json.Unmarshal(raw, &claims)) + require.NoError(t, json.Unmarshal(raw, &claims, jsonx.V1())) assert.Equal(t, tt.product, claims["productCode"]) assert.Equal(t, "n-1", claims["nonce"]) assert.Equal(t, testAccount, claims["customerAWSAccountId"]) diff --git a/services/marketplacemetering/handler.go b/services/marketplacemetering/handler.go index f9d559dc86..cc2733c612 100644 --- a/services/marketplacemetering/handler.go +++ b/services/marketplacemetering/handler.go @@ -2,7 +2,7 @@ package marketplacemetering import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -82,7 +83,7 @@ func decode(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return fail(ErrValidation, "malformed request body: %v", err) } diff --git a/services/marketplacemetering/persistence.go b/services/marketplacemetering/persistence.go index 61161f167f..39f22270ce 100644 --- a/services/marketplacemetering/persistence.go +++ b/services/marketplacemetering/persistence.go @@ -2,7 +2,7 @@ package marketplacemetering import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/marketplacemetering/register.go b/services/marketplacemetering/register.go index 3db3223d05..42c26faef7 100644 --- a/services/marketplacemetering/register.go +++ b/services/marketplacemetering/register.go @@ -3,11 +3,12 @@ package marketplacemetering import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // RegisterUsageInput is the RegisterUsage request. @@ -67,11 +68,11 @@ func signRegistration(p *Product, in *RegisterUsageInput, account string, issued enc := base64.RawURLEncoding header, _ := json.Marshal(map[string]string{ "alg": "HS256", "typ": "JWT", "kid": "v" + strconv.Itoa(int(p.PublicKeyVersion)), - }) + }, jsonx.V1()) claims, _ := json.Marshal(map[string]any{ "productCode": in.ProductCode, "nonce": in.Nonce, "customerAWSAccountId": account, "publicKeyVersion": p.PublicKeyVersion, "iat": issued, - }) + }, jsonx.V1()) signing := enc.EncodeToString(header) + "." + enc.EncodeToString(claims) return signing + "." + enc.EncodeToString([]byte(mac(signing))) diff --git a/services/mediaconvert/endpoints_test.go b/services/mediaconvert/endpoints_test.go index 49036e6033..897666ac8c 100644 --- a/services/mediaconvert/endpoints_test.go +++ b/services/mediaconvert/endpoints_test.go @@ -2,7 +2,8 @@ package mediaconvert_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -41,7 +43,7 @@ func TestDescribeEndpoints_POST(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) endpoints, ok := out["endpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 1) @@ -73,7 +75,7 @@ func TestDescribeEndpoints_WithModeAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) endpoints, ok := out["endpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 1) @@ -93,7 +95,7 @@ func TestDescribeEndpoints_MaxResultsCaps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) endpoints, ok := out["endpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 1) diff --git a/services/mediaconvert/handler_certificates.go b/services/mediaconvert/handler_certificates.go index c1fda617d2..e9318f7c55 100644 --- a/services/mediaconvert/handler_certificates.go +++ b/services/mediaconvert/handler_certificates.go @@ -1,11 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseCertificateRoute(method, suffix string) mcRoute { @@ -34,7 +36,7 @@ type associateCertificateInput struct { func (h *Handler) handleAssociateCertificate(c *echo.Context, body []byte) error { var in associateCertificateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_endpoints.go b/services/mediaconvert/handler_endpoints.go index e8f314d7ab..905460443f 100644 --- a/services/mediaconvert/handler_endpoints.go +++ b/services/mediaconvert/handler_endpoints.go @@ -1,10 +1,12 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Endpoints handler --- @@ -27,7 +29,7 @@ type endpointEntry struct { func (h *Handler) handleDescribeEndpoints(c *echo.Context, body []byte) error { var in describeEndpointsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } } diff --git a/services/mediaconvert/handler_job_templates.go b/services/mediaconvert/handler_job_templates.go index faac04a49d..08ee60a3d7 100644 --- a/services/mediaconvert/handler_job_templates.go +++ b/services/mediaconvert/handler_job_templates.go @@ -1,12 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -60,7 +61,7 @@ type jobTemplatesListOutput struct { func (h *Handler) handleCreateJobTemplate(c *echo.Context, body []byte) error { var in createJobTemplateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -138,7 +139,7 @@ type updateJobTemplateInput struct { func (h *Handler) handleUpdateJobTemplate(c *echo.Context, name string, body []byte) error { var in updateJobTemplateInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_jobs.go b/services/mediaconvert/handler_jobs.go index 0efa62f2be..985fb45d2c 100644 --- a/services/mediaconvert/handler_jobs.go +++ b/services/mediaconvert/handler_jobs.go @@ -1,12 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -67,7 +68,7 @@ type jobsListOutput struct { func (h *Handler) handleCreateJob(c *echo.Context, body []byte) error { var in createJobInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -113,7 +114,7 @@ type updateJobInput struct { func (h *Handler) handleUpdateJob(c *echo.Context, id string, body []byte) error { var in updateJobInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_jobs_query.go b/services/mediaconvert/handler_jobs_query.go index a7b2c2e80c..73d4386dc7 100644 --- a/services/mediaconvert/handler_jobs_query.go +++ b/services/mediaconvert/handler_jobs_query.go @@ -1,11 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parseJobsQueriesRoute(method, suffix string) mcRoute { @@ -61,7 +63,7 @@ type startJobsQueryOutput struct { func (h *Handler) handleStartJobsQuery(c *echo.Context, body []byte) error { var in startJobsQueryInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_policy.go b/services/mediaconvert/handler_policy.go index 584cb4ff63..9c2c02817b 100644 --- a/services/mediaconvert/handler_policy.go +++ b/services/mediaconvert/handler_policy.go @@ -1,10 +1,12 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func parsePolicyRoute(method string) mcRoute { @@ -55,7 +57,7 @@ type policyInputEntry struct { func (h *Handler) handlePutPolicy(c *echo.Context, body []byte) error { var in putPolicyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_presets.go b/services/mediaconvert/handler_presets.go index 46f3cb2068..2ca68a8446 100644 --- a/services/mediaconvert/handler_presets.go +++ b/services/mediaconvert/handler_presets.go @@ -1,12 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -55,7 +56,7 @@ type presetsListOutput struct { func (h *Handler) handleCreatePreset(c *echo.Context, body []byte) error { var in createPresetInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -120,7 +121,7 @@ type updatePresetInput struct { func (h *Handler) handleUpdatePreset(c *echo.Context, name string, body []byte) error { var in updatePresetInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_probe.go b/services/mediaconvert/handler_probe.go index 2bd1ec93d9..f904e47523 100644 --- a/services/mediaconvert/handler_probe.go +++ b/services/mediaconvert/handler_probe.go @@ -1,10 +1,12 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Probe handler --- @@ -19,7 +21,7 @@ type probeOutput struct { func (h *Handler) handleProbe(c *echo.Context, body []byte) error { var in probeInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_queues.go b/services/mediaconvert/handler_queues.go index f8e0e4b64d..8864d2e773 100644 --- a/services/mediaconvert/handler_queues.go +++ b/services/mediaconvert/handler_queues.go @@ -1,12 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -62,7 +63,7 @@ type queuesListOutput struct { func (h *Handler) handleCreateQueue(c *echo.Context, body []byte) error { var in createQueueInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -124,7 +125,7 @@ type updateQueueInput struct { func (h *Handler) handleUpdateQueue(c *echo.Context, name string, body []byte) error { var in updateQueueInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_resource_shares.go b/services/mediaconvert/handler_resource_shares.go index 5e2092ddb9..89df8d11f2 100644 --- a/services/mediaconvert/handler_resource_shares.go +++ b/services/mediaconvert/handler_resource_shares.go @@ -1,10 +1,12 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- Resource share handlers --- @@ -16,7 +18,7 @@ type createResourceShareInput struct { func (h *Handler) handleCreateResourceShare(c *echo.Context, body []byte) error { var in createResourceShareInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_tags.go b/services/mediaconvert/handler_tags.go index 5740771c51..d97f9b1cd4 100644 --- a/services/mediaconvert/handler_tags.go +++ b/services/mediaconvert/handler_tags.go @@ -1,11 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseTagRoute maps tag-path requests to operations. Per the real @@ -70,7 +72,7 @@ func (h *Handler) handleListTagsForResource(c *echo.Context, resourceARN string) func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { var in tagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -85,7 +87,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { func (h *Handler) handleUntagResource(c *echo.Context, resourceARN string, body []byte) error { var in untagResourceInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mediaconvert/handler_test.go b/services/mediaconvert/handler_test.go index 3feef9e596..c3272fad80 100644 --- a/services/mediaconvert/handler_test.go +++ b/services/mediaconvert/handler_test.go @@ -2,7 +2,8 @@ package mediaconvert_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -30,7 +32,7 @@ func doRequest(t *testing.T, h *mediaconvert.Handler, method, path string, body var reqBody *bytes.Reader if body != nil { - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(bodyBytes) } else { @@ -71,7 +73,7 @@ func parseJSONResponse(t *testing.T, h *mediaconvert.Handler, method, path strin var resp map[string]any if rec.Body.Len() > 0 { - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) } return resp, rec.Code @@ -357,7 +359,7 @@ func TestErrValidationMappedTo400(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) job := resp["job"].(map[string]any) id := job["id"].(string) @@ -406,7 +408,7 @@ func TestListHandlers_ReturnEmptySlices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) list, ok := resp[tt.key] require.True(t, ok, "key %q missing from response", tt.key) diff --git a/services/mediaconvert/janitor_test.go b/services/mediaconvert/janitor_test.go index 93240829e3..9b5b88f3ed 100644 --- a/services/mediaconvert/janitor_test.go +++ b/services/mediaconvert/janitor_test.go @@ -1,7 +1,8 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -404,7 +406,7 @@ func TestFullJobLifecycle_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) jobID := jobData["id"].(string) assert.Equal(t, "SUBMITTED", jobData["status"]) @@ -418,7 +420,7 @@ func TestFullJobLifecycle_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out2 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out2, jsonx.V1())) j2 := out2["job"].(map[string]any) assert.Equal(t, "COMPLETE", j2["status"]) timing := j2["timing"].(map[string]any) diff --git a/services/mediaconvert/job_templates_test.go b/services/mediaconvert/job_templates_test.go index f22ef32d39..a7a6e3da5f 100644 --- a/services/mediaconvert/job_templates_test.go +++ b/services/mediaconvert/job_templates_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -27,7 +29,7 @@ func TestMediaConvert_JobTemplate_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) jtData, ok := createResp["jobTemplate"].(map[string]any) require.True(t, ok) assert.Equal(t, templateName, jtData["name"]) @@ -53,7 +55,7 @@ func TestMediaConvert_JobTemplate_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) updatedJT, ok := updateResp["jobTemplate"].(map[string]any) require.True(t, ok) assert.Equal(t, "updated description", updatedJT["description"]) @@ -118,7 +120,7 @@ func TestCreateJobTemplate_WithTags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) jt := resp["jobTemplate"].(map[string]any) tags := jt["tags"].(map[string]any) assert.Equal(t, "12345", tags["cost-center"]) @@ -165,7 +167,7 @@ func TestCreateJobTemplate_AccelerationHopDestinationsStatusUpdateInterval(t *te require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) jt := resp["jobTemplate"].(map[string]any) accel := jt["accelerationSettings"].(map[string]any) @@ -238,7 +240,7 @@ func TestUpdateJobTemplate_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) jt := resp["jobTemplate"].(map[string]any) accel := jt["accelerationSettings"].(map[string]any) diff --git a/services/mediaconvert/jobs_test.go b/services/mediaconvert/jobs_test.go index 7543ff2220..cc5a04bbf4 100644 --- a/services/mediaconvert/jobs_test.go +++ b/services/mediaconvert/jobs_test.go @@ -1,7 +1,8 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -23,7 +25,7 @@ func createTestJob(t *testing.T, h *mediaconvert.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) job, ok := resp["job"].(map[string]any) require.True(t, ok) id, _ := job["id"].(string) @@ -68,7 +70,7 @@ func TestMediaConvert_Job_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) jobData, ok := createResp["job"].(map[string]any) require.True(t, ok) assert.Equal(t, role, jobData["role"]) @@ -130,7 +132,7 @@ func TestCreateJob_WithTags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) j := resp["job"].(map[string]any) tags := j["tags"].(map[string]any) assert.Equal(t, "nightly", tags["run"]) @@ -279,7 +281,7 @@ func TestListJobs_OmitsTotalCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotContains(t, out, "totalCount") assert.Len(t, out["jobs"], 2) } @@ -336,7 +338,7 @@ func TestCreateJob_TimingViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) job := out["job"].(map[string]any) timing := job["timing"].(map[string]any) assert.Greater(t, timing["submitTime"], float64(0)) @@ -463,7 +465,7 @@ func TestUpdateJob_ViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData, ok := out["job"].(map[string]any) require.True(t, ok) assert.InDelta(t, float64(5), jobData["priority"], 0) @@ -511,7 +513,7 @@ func TestCreateJob_HopDestinationsViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) hops, ok := jobData["hopDestinations"].([]any) require.True(t, ok) @@ -588,7 +590,7 @@ func TestCreateJob_StatusUpdateIntervalViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) assert.Equal(t, "SECONDS_20", jobData["statusUpdateInterval"]) assert.Equal(t, "ENABLED", jobData["simulateReservedQueue"]) @@ -608,7 +610,7 @@ func TestCreateJob_AccelerationViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) assert.Equal(t, "PREFERRED", jobData["accelerationStatus"]) as := jobData["accelerationSettings"].(map[string]any) @@ -688,7 +690,7 @@ func TestCreateJob_MessagesFieldViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) _, hasMessages := jobData["messages"] assert.True(t, hasMessages, "messages field should be present") @@ -786,14 +788,14 @@ func TestCreateJob_ClientRequestTokenDedupViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec1.Code) var out1 map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&out1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &out1, jsonx.V1())) id1 := out1["job"].(map[string]any)["id"].(string) rec2 := doRequest(t, h, http.MethodPost, "/2017-08-29/jobs", body) require.Equal(t, http.StatusCreated, rec2.Code) var out2 map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &out2, jsonx.V1())) id2 := out2["job"].(map[string]any)["id"].(string) assert.Equal(t, id1, id2, "duplicate token should return same job ID") @@ -872,7 +874,7 @@ func TestCreateJob_JobEngineVersionViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) assert.Equal(t, "2017-08-29", jobData["jobEngineVersionUsed"]) assert.Equal(t, "2017-08-29", jobData["jobEngineVersionRequested"]) @@ -956,7 +958,7 @@ func TestCreateJob_PriorityViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobData := out["job"].(map[string]any) assert.InDelta(t, float64(15), jobData["priority"], 0) } @@ -1188,7 +1190,7 @@ func TestListJobs_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobs, ok := out["jobs"].([]any) require.True(t, ok) diff --git a/services/mediaconvert/multi_region_test.go b/services/mediaconvert/multi_region_test.go index e1921199bc..7a174f4823 100644 --- a/services/mediaconvert/multi_region_test.go +++ b/services/mediaconvert/multi_region_test.go @@ -2,7 +2,8 @@ package mediaconvert_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -172,8 +174,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/mediaconvert/persistence.go b/services/mediaconvert/persistence.go index abce59e334..3eb22ed0ba 100644 --- a/services/mediaconvert/persistence.go +++ b/services/mediaconvert/persistence.go @@ -2,10 +2,12 @@ package mediaconvert import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -75,7 +77,7 @@ func newDirtyDTORegistry() (*store.Registry, *store.Table[counterSnapshot], *sto // absent: it was never persisted pre-Phase-3.3 either (see the old Restore, // which always reset it to empty). type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` Certificates map[string]struct{} `json:"certificates"` Policy *Policy `json:"policy,omitempty"` @@ -142,7 +144,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "mediaconvert: failed to marshal snapshot", "error", err) @@ -222,7 +224,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreDirtyTablesLocked restores the "dirty" tables (queueCounters, // tokenIndex) from tables via the ephemeral DTO registry, converting each DTO // back to its live type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, cDTOs, tDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/mediaconvert/policy_test.go b/services/mediaconvert/policy_test.go index 27698a9271..ac0cbebac8 100644 --- a/services/mediaconvert/policy_test.go +++ b/services/mediaconvert/policy_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -86,7 +88,7 @@ func TestMediaConvert_Policy_DeleteAfterGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) policy, ok := out["policy"].(map[string]any) require.True(t, ok) assert.Equal(t, "ALLOWED", policy["httpInputs"]) @@ -114,7 +116,7 @@ func TestPutPolicy_Success(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) pol, ok := out["policy"].(map[string]any) require.True(t, ok) assert.Equal(t, "ALLOWED", pol["httpInputs"]) @@ -142,7 +144,7 @@ func TestGetPolicy_AfterPut(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) pol := out["policy"].(map[string]any) assert.Equal(t, "DISABLED", pol["s3Inputs"]) } diff --git a/services/mediaconvert/presets_test.go b/services/mediaconvert/presets_test.go index 5547f44942..f4a083ea63 100644 --- a/services/mediaconvert/presets_test.go +++ b/services/mediaconvert/presets_test.go @@ -1,7 +1,8 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -28,7 +30,7 @@ func TestMediaConvert_Preset_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) pData, ok := createResp["preset"].(map[string]any) require.True(t, ok) assert.Equal(t, presetName, pData["name"]) @@ -135,7 +137,7 @@ func TestCreatePreset_WithTags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) p := resp["preset"].(map[string]any) tags := p["tags"].(map[string]any) assert.Equal(t, "alpha", tags["project"]) @@ -216,7 +218,7 @@ func TestUpdatePreset_Success(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) preset, ok := out["preset"].(map[string]any) require.True(t, ok) assert.Equal(t, "updated desc", preset["description"]) diff --git a/services/mediaconvert/queues_test.go b/services/mediaconvert/queues_test.go index d6d4b13685..c1093cd51b 100644 --- a/services/mediaconvert/queues_test.go +++ b/services/mediaconvert/queues_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -59,7 +61,7 @@ func TestMediaConvert_Queue_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) queueData, ok := createResp["queue"].(map[string]any) require.True(t, ok) assert.Equal(t, queueName, queueData["name"]) @@ -86,7 +88,7 @@ func TestMediaConvert_Queue_FullLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) updatedQueue, ok := updateResp["queue"].(map[string]any) require.True(t, ok) assert.Equal(t, "PAUSED", updatedQueue["status"]) @@ -140,7 +142,7 @@ func TestMediaConvert_ListQueues_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queues, ok := out["queues"].([]any) require.True(t, ok) require.Len(t, queues, 1) @@ -191,7 +193,7 @@ func TestMediaConvert_UpdateQueue_ConcurrentJobsAndReservationPlanSettings(t *te require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queueData := out["queue"].(map[string]any) assert.InDelta(t, float64(7), queueData["concurrentJobs"], 0) @@ -248,7 +250,7 @@ func TestMediaConvert_CreateQueue_MaximumConcurrentFeeds(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) createQueue, ok := createResp["queue"].(map[string]any) require.True(t, ok) tt.check(t, createQueue) @@ -258,7 +260,7 @@ func TestMediaConvert_CreateQueue_MaximumConcurrentFeeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) getQueue, ok := getResp["queue"].(map[string]any) require.True(t, ok) tt.check(t, getQueue) @@ -284,7 +286,7 @@ func TestMediaConvert_UpdateQueue_MaximumConcurrentFeeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queueData, ok := out["queue"].(map[string]any) require.True(t, ok) assert.InDelta(t, float64(9), queueData["maximumConcurrentFeeds"], 0) @@ -319,7 +321,7 @@ func TestCreateQueue_WithTags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) queue := resp["queue"].(map[string]any) tags := queue["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) @@ -459,7 +461,7 @@ func TestListQueues_JobCountsViaHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queues, ok := out["queues"].([]any) require.True(t, ok) require.Len(t, queues, 2) @@ -505,7 +507,7 @@ func TestCreateQueue_ReservationPlanViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queueData := out["queue"].(map[string]any) rp, ok := queueData["reservationPlan"].(map[string]any) require.True(t, ok, "reservationPlan should be in response") @@ -525,7 +527,7 @@ func TestQueue_NoLegacyReservationPlanNameField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queueData := out["queue"].(map[string]any) _, hasOldField := queueData["reservationPlanName"] assert.False(t, hasOldField, "legacy reservationPlanName field should not be present") @@ -555,7 +557,7 @@ func TestCreateQueue_ConcurrentJobsViaHTTP(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) queueData := out["queue"].(map[string]any) assert.InDelta(t, float64(4), queueData["concurrentJobs"], 0) } @@ -576,7 +578,7 @@ func TestCreateQueue_ConcurrentJobsZeroDistinctFromUnset(t *testing.T) { require.Equal(t, http.StatusCreated, unset.Code) var unsetOut map[string]any - require.NoError(t, json.NewDecoder(unset.Body).Decode(&unsetOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(unset.Body), &unsetOut, jsonx.V1())) unsetQueue := unsetOut["queue"].(map[string]any) _, hasField := unsetQueue["concurrentJobs"] assert.False(t, hasField, "concurrentJobs must be omitted entirely when the client never sent it") @@ -588,7 +590,7 @@ func TestCreateQueue_ConcurrentJobsZeroDistinctFromUnset(t *testing.T) { require.Equal(t, http.StatusCreated, explicitZero.Code) var zeroOut map[string]any - require.NoError(t, json.NewDecoder(explicitZero.Body).Decode(&zeroOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(explicitZero.Body), &zeroOut, jsonx.V1())) zeroQueue := zeroOut["queue"].(map[string]any) zeroVal, hasZeroField := zeroQueue["concurrentJobs"] require.True(t, hasZeroField, "an explicit concurrentJobs:0 must round-trip as a present field, not be dropped") diff --git a/services/mediaconvert/resource_shares.go b/services/mediaconvert/resource_shares.go index 35bd9a957b..cbc130061f 100644 --- a/services/mediaconvert/resource_shares.go +++ b/services/mediaconvert/resource_shares.go @@ -1,11 +1,13 @@ package mediaconvert import ( - "encoding/json" + "encoding/json/v2" "fmt" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // CreateResourceShare records a resource-share request for the given job ID. @@ -38,7 +40,7 @@ func (b *InMemoryBackend) CreateResourceShare(jobID, supportCaseID string) (stri details, err := json.Marshal(struct { ShareToken string `json:"shareToken"` SharedAt float64 `json:"sharedAt"` - }{ShareToken: token, SharedAt: epochSeconds(time.Now())}) + }{ShareToken: token, SharedAt: epochSeconds(time.Now())}, jsonx.V1()) if err != nil { return "", fmt.Errorf("encode share details: %w", err) } diff --git a/services/mediaconvert/resource_shares_test.go b/services/mediaconvert/resource_shares_test.go index 6a1b0c0750..18e4350b5b 100644 --- a/services/mediaconvert/resource_shares_test.go +++ b/services/mediaconvert/resource_shares_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -20,7 +22,7 @@ func TestMediaConvert_CreateResourceShare_TableTests(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) job, _ := resp["job"].(map[string]any) id, _ := job["id"].(string) @@ -121,7 +123,7 @@ func TestCreateResourceShare_SetsShareStatus(t *testing.T) { ShareToken string `json:"shareToken"` SharedAt float64 `json:"sharedAt"` } - require.NoError(t, json.Unmarshal([]byte(*got.LastShareDetails), &details)) + require.NoError(t, json.Unmarshal([]byte(*got.LastShareDetails), &details, jsonx.V1())) assert.NotEmpty(t, details.ShareToken) assert.Greater(t, details.SharedAt, float64(0)) } diff --git a/services/mediaconvert/search_test.go b/services/mediaconvert/search_test.go index 6c831c3520..cafbc5a779 100644 --- a/services/mediaconvert/search_test.go +++ b/services/mediaconvert/search_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -74,7 +76,7 @@ func TestSearchJobs_QueueFilter(t *testing.T) { require.Equal(t, http.StatusCreated, recQ.Code) var qResp map[string]any - require.NoError(t, json.Unmarshal(recQ.Body.Bytes(), &qResp)) + require.NoError(t, json.Unmarshal(recQ.Body.Bytes(), &qResp, jsonx.V1())) qMap, _ := qResp["queue"].(map[string]any) qARN, _ := qMap["arn"].(string) @@ -239,7 +241,7 @@ func TestSearchJobs_FilterByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) jobs, _ := out["jobs"].([]any) assert.Len(t, jobs, tt.wantCount) }) diff --git a/services/mediaconvert/tags_test.go b/services/mediaconvert/tags_test.go index 6178a0f26f..34af452930 100644 --- a/services/mediaconvert/tags_test.go +++ b/services/mediaconvert/tags_test.go @@ -1,13 +1,15 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediaconvert" ) @@ -40,7 +42,7 @@ func TestMediaConvert_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsOut map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&tagsOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &tagsOut, jsonx.V1())) resourceTags, _ := tagsOut["resourceTags"].(map[string]any) tags, _ := resourceTags["tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) @@ -55,7 +57,7 @@ func TestMediaConvert_Tags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/2017-08-29/tags/"+arn, nil) require.Equal(t, http.StatusOK, rec.Code) tagsOut = map[string]any{} - require.NoError(t, json.NewDecoder(rec.Body).Decode(&tagsOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &tagsOut, jsonx.V1())) resourceTags, _ = tagsOut["resourceTags"].(map[string]any) assert.Empty(t, resourceTags["tags"]) } diff --git a/services/mediaconvert/versions_test.go b/services/mediaconvert/versions_test.go index d20677857e..8100cc54db 100644 --- a/services/mediaconvert/versions_test.go +++ b/services/mediaconvert/versions_test.go @@ -1,12 +1,14 @@ package mediaconvert_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListVersions_ReturnsVersions(t *testing.T) { @@ -39,7 +41,7 @@ func TestListVersions_ContainsKnownVersion(t *testing.T) { } `json:"versions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Versions) found := false diff --git a/services/medialive/handler.go b/services/medialive/handler.go index fa8998ce13..2b518528f0 100644 --- a/services/medialive/handler.go +++ b/services/medialive/handler.go @@ -1,7 +1,8 @@ package medialive import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -448,7 +450,7 @@ func (h *Handler) handleREST(c *echo.Context) error { var body map[string]any if c.Request().ContentLength != 0 { - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && err.Error() != "EOF" { c.Response().Header().Set(amznErrorTypeHeader, "BadRequestException") diff --git a/services/medialive/handler_batch_test.go b/services/medialive/handler_batch_test.go index c269fda308..0ba2abc079 100644 --- a/services/medialive/handler_batch_test.go +++ b/services/medialive/handler_batch_test.go @@ -1,12 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestBatch_StartStopDelete(t *testing.T) { @@ -80,7 +82,7 @@ func TestBatch_StartStopKnownChannels(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) successful := startResp["successful"].([]any) assert.Len(t, successful, 1) assert.Equal(t, chID, successful[0].(map[string]any)["id"]) @@ -97,7 +99,7 @@ func TestBatch_StartStopKnownChannels(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp, jsonx.V1())) delSuccessful := delResp["successful"].([]any) assert.Len(t, delSuccessful, 1) } @@ -111,7 +113,7 @@ func TestBatch_StartNotFound(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failed := resp["failed"].([]any) assert.Len(t, failed, 1) } @@ -127,7 +129,7 @@ func TestBatch_DeleteInputSecurityGroups(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/prod/inputSecurityGroups", map[string]any{}) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) groupID := created["securityGroup"].(map[string]any)["id"].(string) rec = doRequest(t, h, http.MethodPost, "/prod/batch/delete", map[string]any{ @@ -135,7 +137,7 @@ func TestBatch_DeleteInputSecurityGroups(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) successful := resp["successful"].([]any) require.Len(t, successful, 1) assert.Equal(t, groupID, successful[0].(map[string]any)["id"]) diff --git a/services/medialive/handler_channels_test.go b/services/medialive/handler_channels_test.go index 596fb2283f..78776c2d5d 100644 --- a/services/medialive/handler_channels_test.go +++ b/services/medialive/handler_channels_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -36,7 +37,7 @@ func TestChannel_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) ch := resp["channel"].(map[string]any) assert.Contains(t, ch["arn"], "arn:aws:medialive:us-east-1:000000000000:channel:") assert.Equal(t, "CREATING", ch["state"]) @@ -76,7 +77,7 @@ func TestChannel_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/channels/"+channelID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-channel", descResp["name"]) assert.Equal(t, "IDLE", descResp["state"]) @@ -86,7 +87,7 @@ func TestChannel_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) ch := updateResp["channel"].(map[string]any) assert.Equal(t, "updated-channel", ch["name"]) @@ -94,7 +95,7 @@ func TestChannel_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/channels", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["channels"], 1) // Delete @@ -117,7 +118,7 @@ func TestChannel_StartStop(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/prod/channels/"+channelID+"/start", nil) assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.Equal(t, "STARTING", startResp["state"]) // Start again returns conflict @@ -128,7 +129,7 @@ func TestChannel_StartStop(t *testing.T) { rec = doRequest(t, h, http.MethodPost, "/prod/channels/"+channelID+"/stop", nil) assert.Equal(t, http.StatusOK, rec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Equal(t, "STOPPING", stopResp["state"]) } @@ -144,19 +145,19 @@ func TestChannel_PipelinesRunningCount(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/channels/"+channelID, nil) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.InDelta(t, 0, descResp["pipelinesRunningCount"], 0, "IDLE channel reports 0 running pipelines") rec = doRequest(t, h, http.MethodPost, "/prod/channels/"+channelID+"/start", nil) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.InDelta(t, 0, startResp["pipelinesRunningCount"], 0, "STARTING channel reports 0 running pipelines") // The backend advances internal state to RUNNING immediately (see // StartChannel's doc comment), so an immediate Describe should already // report the class's full pipeline count. rec = doRequest(t, h, http.MethodGet, "/prod/channels/"+channelID, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "RUNNING", descResp["state"]) assert.InDelta(t, 2, descResp["pipelinesRunningCount"], 0, "RUNNING STANDARD channel reports 2 running pipelines") } @@ -227,7 +228,7 @@ func TestListChannels_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["channels"]) } diff --git a/services/medialive/handler_cloudwatch_alarm_templates_test.go b/services/medialive/handler_cloudwatch_alarm_templates_test.go index 49aafbc968..bbbc5fed2a 100644 --- a/services/medialive/handler_cloudwatch_alarm_templates_test.go +++ b/services/medialive/handler_cloudwatch_alarm_templates_test.go @@ -1,12 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCWAlarmTemplateGroup_CRUD(t *testing.T) { @@ -25,7 +27,7 @@ func TestCWAlarmTemplateGroup_CRUD(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["id"]) assert.Equal(t, "test-cw-group", resp["name"]) assert.NotEmpty(t, resp["createdAt"]) @@ -73,7 +75,7 @@ func TestCWAlarmTemplateGroup_GetUpdateListDelete(t *testing.T) { ) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) // Get @@ -92,14 +94,14 @@ func TestCWAlarmTemplateGroup_GetUpdateListDelete(t *testing.T) { ) require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "cw-group-updated", updated["name"]) // List rec = doRequest(t, h, http.MethodGet, "/prod/cloudwatch-alarm-template-groups", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["cloudWatchAlarmTemplateGroups"].([]any) assert.Len(t, items, 1) @@ -169,7 +171,7 @@ func TestCWAlarmTemplate_CRUD(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["id"]) assert.Equal(t, "InputLossSeconds", resp["metricName"]) assert.NotEmpty(t, resp["createdAt"]) @@ -202,7 +204,7 @@ func TestCWAlarmTemplate_GetUpdateListDelete(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) // Get by ID @@ -219,14 +221,14 @@ func TestCWAlarmTemplate_GetUpdateListDelete(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "ActiveAlerts", updated["metricName"]) // List rec = doRequest(t, h, http.MethodGet, "/prod/cloudwatch-alarm-templates", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["cloudWatchAlarmTemplates"].([]any) assert.Len(t, items, 1) diff --git a/services/medialive/handler_cluster_test.go b/services/medialive/handler_cluster_test.go index 04cb47a09b..8d903a2d84 100644 --- a/services/medialive/handler_cluster_test.go +++ b/services/medialive/handler_cluster_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -23,7 +24,7 @@ func createTestCluster(t *testing.T, h *medialive.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -48,7 +49,7 @@ func TestCluster_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:medialive:us-east-1:000000000000:cluster:") assert.Equal(t, "ACTIVE", resp["state"]) assert.NotEmpty(t, resp["id"]) @@ -91,7 +92,7 @@ func TestCluster_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/clusters/"+clusterID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, clusterID, descResp["id"]) assert.Equal(t, "ACTIVE", descResp["state"]) @@ -101,14 +102,14 @@ func TestCluster_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated-cluster", updateResp["name"]) // List rec = doRequest(t, h, http.MethodGet, "/prod/clusters", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) clusters := listResp["clusters"].([]any) assert.Len(t, clusters, 1) @@ -116,7 +117,7 @@ func TestCluster_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodDelete, "/prod/clusters/"+clusterID, nil) assert.Equal(t, http.StatusOK, rec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp, jsonx.V1())) assert.Equal(t, "DELETED", delResp["state"]) assert.Equal(t, 0, medialive.ClusterCount(h.Backend.(*medialive.InMemoryBackend))) @@ -158,7 +159,7 @@ func TestClusterAlerts_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) alerts := resp["alerts"].([]any) assert.Empty(t, alerts) } @@ -179,7 +180,7 @@ func TestNodeRegistrationScript(t *testing.T) { assert.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) script, ok := resp["nodeRegistrationScript"].(string) assert.True(t, ok) assert.NotEmpty(t, script) @@ -198,7 +199,7 @@ func TestNode_CRUD(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) nodeID := createResp["id"].(string) assert.NotEmpty(t, nodeID) assert.Equal(t, "ACTIVE", createResp["state"]) @@ -215,7 +216,7 @@ func TestNode_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/clusters/"+clusterID+"/nodes/"+nodeID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, nodeID, descResp["id"]) // Update node @@ -231,7 +232,7 @@ func TestNode_CRUD(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated-node", updateResp["name"]) assert.Equal(t, "BACKUP", updateResp["role"]) @@ -247,14 +248,14 @@ func TestNode_CRUD(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var stateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stateResp, jsonx.V1())) assert.Equal(t, "DRAINING", stateResp["state"]) // List nodes rec = doRequest(t, h, http.MethodGet, "/prod/clusters/"+clusterID+"/nodes", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) nodes := listResp["nodes"].([]any) assert.Len(t, nodes, 1) @@ -356,7 +357,7 @@ func TestListClusterAlerts(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) alerts := resp["alerts"].([]any) if tt.wantEmpty { diff --git a/services/medialive/handler_error_type_test.go b/services/medialive/handler_error_type_test.go index df89404e59..9c742f9a75 100644 --- a/services/medialive/handler_error_type_test.go +++ b/services/medialive/handler_error_type_test.go @@ -2,7 +2,7 @@ package medialive_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/medialive" @@ -90,7 +91,7 @@ func TestDescribeChannel_UnknownChannelRawEnvelope(t *testing.T) { require.NoError(t, err) var envelope map[string]any - require.NoError(t, json.Unmarshal(raw, &envelope)) + require.NoError(t, json.Unmarshal(raw, &envelope, jsonx.V1())) _, hasMessage := envelope["Message"] assert.True(t, hasMessage, "raw body must carry a Message key restjson.GetErrorInfo reads: %s", raw) diff --git a/services/medialive/handler_event_bridge_rule_templates_test.go b/services/medialive/handler_event_bridge_rule_templates_test.go index aa74931c2d..49bc5375cd 100644 --- a/services/medialive/handler_event_bridge_rule_templates_test.go +++ b/services/medialive/handler_event_bridge_rule_templates_test.go @@ -1,12 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEBRuleTemplateGroup_CRUD(t *testing.T) { @@ -24,7 +26,7 @@ func TestEBRuleTemplateGroup_CRUD(t *testing.T) { ) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) assert.NotEmpty(t, id) assert.NotEmpty(t, created["createdAt"]) @@ -50,7 +52,7 @@ func TestEBRuleTemplateGroup_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/eventbridge-rule-template-groups", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["eventBridgeRuleTemplateGroups"].([]any), 1) // Delete @@ -135,7 +137,7 @@ func TestEBRuleTemplate_CRUD(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) assert.NotEmpty(t, created["createdAt"]) assert.NotEmpty(t, created["modifiedAt"]) @@ -152,14 +154,14 @@ func TestEBRuleTemplate_CRUD(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, "MEDIALIVE_MULTIPLEX_ALERT", updated["eventType"]) // List rec = doRequest(t, h, http.MethodGet, "/prod/eventbridge-rule-templates", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["eventBridgeRuleTemplates"].([]any), 1) // Delete diff --git a/services/medialive/handler_input_security_groups_test.go b/services/medialive/handler_input_security_groups_test.go index 9a6dd7e99a..86b3619913 100644 --- a/services/medialive/handler_input_security_groups_test.go +++ b/services/medialive/handler_input_security_groups_test.go @@ -1,13 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -26,7 +27,7 @@ func TestInputSecurityGroup_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) sg := createResp["securityGroup"].(map[string]any) groupID := sg["id"].(string) @@ -53,7 +54,7 @@ func TestInputSecurityGroup_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) updatedSG := updateResp["securityGroup"].(map[string]any) updatedRules := updatedSG["whitelistRules"].([]any) assert.Len(t, updatedRules, 2) @@ -62,7 +63,7 @@ func TestInputSecurityGroup_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/inputSecurityGroups", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["inputSecurityGroups"], 1) // Delete: real AWS's DeleteInputSecurityGroup marks the group DELETED @@ -77,7 +78,7 @@ func TestInputSecurityGroup_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/inputSecurityGroups/"+groupID, nil) assert.Equal(t, http.StatusOK, rec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "DELETED", describeResp["state"]) } @@ -89,6 +90,6 @@ func TestListInputSecurityGroups_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["inputSecurityGroups"]) } diff --git a/services/medialive/handler_inputdevice_test.go b/services/medialive/handler_inputdevice_test.go index 87305f1f3d..d83214b698 100644 --- a/services/medialive/handler_inputdevice_test.go +++ b/services/medialive/handler_inputdevice_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -100,7 +101,7 @@ func TestHandlerListInputDevices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) devices := resp["inputDevices"].([]any) assert.Len(t, devices, tt.wantCount) }) @@ -146,7 +147,7 @@ func TestHandlerDescribeInputDevice(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["name"]) } }) @@ -192,7 +193,7 @@ func TestHandlerUpdateInputDevice(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["name"]) } }) @@ -337,7 +338,7 @@ func TestHandlerTransferInputDevice_WireCasing(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) transfers := resp["inputDeviceTransfers"].([]any) require.Len(t, transfers, 1) @@ -432,7 +433,7 @@ func TestHandlerListInputDeviceTransfers(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) transfers := resp["inputDeviceTransfers"].([]any) assert.Len(t, transfers, tt.wantCount) } @@ -494,7 +495,7 @@ func TestStartInputDeviceMaintenanceWindow(t *testing.T) { // keys and would pass this test even with the phantom field // still present. var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "maintenanceWindowActive") } }) diff --git a/services/medialive/handler_inputs_test.go b/services/medialive/handler_inputs_test.go index 2efbe79479..d5c9e81d95 100644 --- a/services/medialive/handler_inputs_test.go +++ b/services/medialive/handler_inputs_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -26,7 +27,7 @@ func TestInput_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/inputs/"+inputID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-input", descResp["name"]) assert.Equal(t, "DETACHED", descResp["state"]) assert.Contains(t, descResp["arn"], "arn:aws:medialive:us-east-1:000000000000:input:") @@ -37,7 +38,7 @@ func TestInput_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) inp := updateResp["input"].(map[string]any) assert.Equal(t, "updated-input", inp["name"]) @@ -45,7 +46,7 @@ func TestInput_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/inputs", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["inputs"], 1) // Delete: real terraform-provider-aws's waitInputDeleted polls @@ -60,7 +61,7 @@ func TestInput_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/inputs/"+inputID, nil) assert.Equal(t, http.StatusOK, rec.Code) var deletedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deletedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deletedResp, jsonx.V1())) assert.Equal(t, "DELETED", deletedResp["state"]) } @@ -196,7 +197,7 @@ func TestListInputs_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["inputs"]) } diff --git a/services/medialive/handler_multiplex_test.go b/services/medialive/handler_multiplex_test.go index 926b0b73c3..2a45767c82 100644 --- a/services/medialive/handler_multiplex_test.go +++ b/services/medialive/handler_multiplex_test.go @@ -1,13 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -25,7 +26,7 @@ func createTestMultiplex(t *testing.T, h *medialive.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["multiplex"].(map[string]any) return m["id"].(string) @@ -55,7 +56,7 @@ func TestMultiplex_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) m := resp["multiplex"].(map[string]any) assert.Contains(t, m["arn"], "arn:aws:medialive:us-east-1:000000000000:multiplex:") assert.Equal(t, "CREATING", m["state"]) @@ -100,7 +101,7 @@ func TestMultiplex_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/multiplexes/"+multiplexID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-multiplex", descResp["name"]) assert.Equal(t, "IDLE", descResp["state"]) @@ -114,7 +115,7 @@ func TestMultiplex_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) m := updateResp["multiplex"].(map[string]any) assert.Equal(t, "updated-multiplex", m["name"]) settings := m["multiplexSettings"].(map[string]any) @@ -124,7 +125,7 @@ func TestMultiplex_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["multiplexes"], 1) // Delete: real AWS's DeleteMultiplex marks the multiplex DELETED rather @@ -139,7 +140,7 @@ func TestMultiplex_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes/"+multiplexID, nil) assert.Equal(t, http.StatusOK, rec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "DELETED", describeResp["state"]) } @@ -153,7 +154,7 @@ func TestMultiplex_StartStop(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/prod/multiplexes/"+multiplexID+"/start", nil) assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.Equal(t, "STARTING", startResp["state"]) // Start again returns conflict @@ -164,7 +165,7 @@ func TestMultiplex_StartStop(t *testing.T) { rec = doRequest(t, h, http.MethodPost, "/prod/multiplexes/"+multiplexID+"/stop", nil) assert.Equal(t, http.StatusOK, rec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Equal(t, "STOPPING", stopResp["state"]) // Stop again returns conflict @@ -239,7 +240,7 @@ func TestMultiplex_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["multiplexes"]) } @@ -270,7 +271,7 @@ func TestMultiplexProgram_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) prog := createResp["multiplexProgram"].(map[string]any) assert.Equal(t, "prog-1", prog["programName"]) settings := prog["multiplexProgramSettings"].(map[string]any) @@ -290,7 +291,7 @@ func TestMultiplexProgram_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes/"+multiplexID+"/programs/prog-1", nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "prog-1", descResp["programName"]) // Update @@ -312,7 +313,7 @@ func TestMultiplexProgram_CRUD(t *testing.T) { ) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) updatedProg := updateResp["multiplexProgram"].(map[string]any) updatedSettings := updatedProg["multiplexProgramSettings"].(map[string]any) assert.Equal(t, 2, int(updatedSettings["programNumber"].(float64))) @@ -322,7 +323,7 @@ func TestMultiplexProgram_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes/"+multiplexID+"/programs", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["multiplexPrograms"], 1) // Delete @@ -431,7 +432,7 @@ func TestMultiplexProgram_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["multiplexPrograms"]) } @@ -450,14 +451,14 @@ func TestMultiplexAlerts(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) mux := created["multiplex"].(map[string]any) muxID := mux["id"].(string) rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes/"+muxID+"/alerts", nil) require.Equal(t, http.StatusOK, rec.Code) var alertsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alertsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &alertsResp, jsonx.V1())) assert.NotNil(t, alertsResp["alerts"]) rec = doRequest(t, h, http.MethodGet, "/prod/multiplexes/missing/alerts", nil) diff --git a/services/medialive/handler_reservations_test.go b/services/medialive/handler_reservations_test.go index 0ec5f47197..549547d2d9 100644 --- a/services/medialive/handler_reservations_test.go +++ b/services/medialive/handler_reservations_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -27,7 +28,7 @@ func TestOfferings_ListDescribe(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) offerings := resp["offerings"].([]any) assert.GreaterOrEqual(t, len(offerings), 3) }, @@ -54,7 +55,7 @@ func TestOfferings_Describe(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/prod/offerings/87654321", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "87654321", resp["offeringId"]) assert.NotEmpty(t, resp["region"]) _, hasName := resp["name"] @@ -77,7 +78,7 @@ func TestReservations_PurchaseListDescribeDeleteUpdate(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var purchaseResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &purchaseResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &purchaseResp, jsonx.V1())) resv := purchaseResp["reservation"].(map[string]any) reservationID := resv["reservationId"].(string) assert.NotEmpty(t, reservationID) @@ -92,7 +93,7 @@ func TestReservations_PurchaseListDescribeDeleteUpdate(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/reservations", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["reservations"].([]any), 1) // Update name @@ -101,7 +102,7 @@ func TestReservations_PurchaseListDescribeDeleteUpdate(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var updatedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updatedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updatedResp, jsonx.V1())) updatedResv := updatedResp["reservation"].(map[string]any) assert.Equal(t, "renamed-reservation", updatedResv["name"]) diff --git a/services/medialive/handler_schedules_test.go b/services/medialive/handler_schedules_test.go index 72f8eacbdd..f28c7e7b4f 100644 --- a/services/medialive/handler_schedules_test.go +++ b/services/medialive/handler_schedules_test.go @@ -1,12 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSchedule_DescribeDelete(t *testing.T) { @@ -53,7 +55,7 @@ func TestBatchUpdateSchedule(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) creates := resp["creates"].(map[string]any)["scheduleActions"].([]any) assert.Len(t, creates, 1) diff --git a/services/medialive/handler_signal_maps_test.go b/services/medialive/handler_signal_maps_test.go index 27833083de..2427d5c7c9 100644 --- a/services/medialive/handler_signal_maps_test.go +++ b/services/medialive/handler_signal_maps_test.go @@ -1,12 +1,14 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSignalMap_CRUD(t *testing.T) { @@ -23,7 +25,7 @@ func TestSignalMap_CRUD(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["id"]) assert.Equal(t, "CREATE_COMPLETE", resp["status"]) monitorDeployment, _ := resp["monitorDeployment"].(map[string]any) @@ -60,7 +62,7 @@ func TestSignalMap_GetListDelete(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) // Get by ID @@ -75,7 +77,7 @@ func TestSignalMap_GetListDelete(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/signal-maps", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["signalMaps"].([]any) assert.Len(t, items, 1) @@ -89,7 +91,7 @@ func TestSignalMap_GetListDelete(t *testing.T) { rec = doRequest(t, h, http.MethodPost, "/prod/signal-maps/"+id+"/monitor-deployment", nil) require.Equal(t, http.StatusAccepted, rec.Code) var deployResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deployResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deployResp, jsonx.V1())) deployMonitorDeployment, _ := deployResp["monitorDeployment"].(map[string]any) assert.Equal(t, "DEPLOYMENT_COMPLETE", deployMonitorDeployment["status"]) @@ -127,7 +129,7 @@ func TestListSignalMaps_OmitsGetOnlyFields(t *testing.T) { var out struct { SignalMaps []map[string]any `json:"signalMaps"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.SignalMaps, 1) item := out.SignalMaps[0] diff --git a/services/medialive/handler_tags_test.go b/services/medialive/handler_tags_test.go index f2a1e040e5..0697aa7e49 100644 --- a/services/medialive/handler_tags_test.go +++ b/services/medialive/handler_tags_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags(t *testing.T) { @@ -20,7 +22,7 @@ func TestTags(t *testing.T) { // Get ARN rec := doRequest(t, h, http.MethodGet, "/prod/channels/"+channelID, nil) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) resourceARN := descResp["arn"].(string) // CreateTags @@ -33,7 +35,7 @@ func TestTags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/tags/"+resourceARN, nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -48,7 +50,7 @@ func TestTags(t *testing.T) { // Verify tag removed rec = doRequest(t, h, http.MethodGet, "/prod/tags/"+resourceARN, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags = listResp["tags"].(map[string]any) assert.NotContains(t, tags, "env") assert.Equal(t, "platform", tags["team"]) @@ -74,14 +76,14 @@ func TestTags_StaySyncedWithResource(t *testing.T) { }) require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) ch := created["channel"].(map[string]any) channelARN := ch["arn"].(string) rec = doRequest(t, h, http.MethodGet, "/prod/tags/"+channelARN, nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["tags"].(map[string]any) assert.Equal(t, "video-team", tags["owner"]) @@ -96,7 +98,7 @@ func TestTags_StaySyncedWithResource(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/prod/channels/"+channelID, nil) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) descTags := descResp["tags"].(map[string]any) assert.Equal(t, "video-team", descTags["owner"]) assert.Equal(t, "1234", descTags["cost-center"]) diff --git a/services/medialive/handler_test.go b/services/medialive/handler_test.go index 6dae23bd1c..fd04dd3ad4 100644 --- a/services/medialive/handler_test.go +++ b/services/medialive/handler_test.go @@ -2,7 +2,7 @@ package medialive_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -39,7 +40,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -64,7 +65,7 @@ func decodeBody(t *testing.T, body []byte) map[string]any { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) return resp } @@ -79,7 +80,7 @@ func createTestChannel(t *testing.T, h *medialive.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ch := resp["channel"].(map[string]any) return ch["id"].(string) @@ -95,7 +96,7 @@ func createTestInput(t *testing.T, h *medialive.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) inp := resp["input"].(map[string]any) return inp["id"].(string) diff --git a/services/medialive/list_summary_shapes_test.go b/services/medialive/list_summary_shapes_test.go index 409a4d6912..b6a6abfe17 100644 --- a/services/medialive/list_summary_shapes_test.go +++ b/services/medialive/list_summary_shapes_test.go @@ -1,7 +1,7 @@ package medialive_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/medialive/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListChannels_SummaryTagsRoundTrip proves ChannelSummary.Tags (over-wide @@ -57,7 +59,7 @@ func TestListNetworks_RawBodyOmitsTags(t *testing.T) { var listResp struct { Networks []map[string]any `json:"networks"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Networks, 1) assert.NotContains(t, listResp.Networks[0], "tags", "Network has no tags field on any CRUD/List shape") assert.Equal(t, "net-leak", listResp.Networks[0]["name"]) diff --git a/services/medialive/multi_region_test.go b/services/medialive/multi_region_test.go index c41173597f..e3f57b54ac 100644 --- a/services/medialive/multi_region_test.go +++ b/services/medialive/multi_region_test.go @@ -2,7 +2,8 @@ package medialive_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/medialive" ) @@ -174,8 +176,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/medialive/persistence.go b/services/medialive/persistence.go index 49edf6e6ce..ab5164d596 100644 --- a/services/medialive/persistence.go +++ b/services/medialive/persistence.go @@ -2,7 +2,7 @@ package medialive import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -44,7 +44,7 @@ const medialiveSnapshotVersion = 1 // intentionally absent: it is an ephemeral reverse index rebuilt from // InputDevices on Restore, and was never part of the persisted shape. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` ScheduleActions map[string][]*storedScheduleAction `json:"scheduleActions"` AccountKmsKeyID string `json:"accountKmsKeyId"` diff --git a/services/medialive/whitebox_test.go b/services/medialive/whitebox_test.go index a74c3d1d1d..3ef48fae64 100644 --- a/services/medialive/whitebox_test.go +++ b/services/medialive/whitebox_test.go @@ -2,7 +2,7 @@ package medialive import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func tagStoreCount(b *InMemoryBackend) int { @@ -27,7 +29,7 @@ func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body any) if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -50,7 +52,7 @@ func decodeWhiteboxBody(t *testing.T, body []byte) map[string]any { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) return resp } diff --git a/services/mediapackage/handler.go b/services/mediapackage/handler.go index 7f01e80a2b..05603e71a2 100644 --- a/services/mediapackage/handler.go +++ b/services/mediapackage/handler.go @@ -1,7 +1,8 @@ package mediapackage import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strconv" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -163,7 +165,12 @@ func (h *Handler) handleREST(c *echo.Context) error { var body map[string]any if c.Request().ContentLength != 0 { - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && err.Error() != "EOF" { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &body, + jsonx.V1(), + ); err != nil && + err.Error() != "EOF" { return c.JSON(http.StatusBadRequest, map[string]any{ keyMessage: "invalid JSON body", keyType: "UnprocessableEntityException", diff --git a/services/mediapackage/handler_channels_test.go b/services/mediapackage/handler_channels_test.go index c9382bb512..9eefe29458 100644 --- a/services/mediapackage/handler_channels_test.go +++ b/services/mediapackage/handler_channels_test.go @@ -1,7 +1,7 @@ package mediapackage_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediapackage" ) @@ -29,7 +30,7 @@ func TestChannel_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:mediapackage:us-east-1:000000000000:channels/my-channel") assert.Equal(t, "my-channel", resp["id"]) assert.Equal(t, "live stream", resp["description"]) @@ -76,7 +77,7 @@ func TestChannel_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/channels/"+channelID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, channelID, descResp["id"]) // Update @@ -85,14 +86,14 @@ func TestChannel_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated description", updateResp["description"]) // List rec = doRequest(t, h, http.MethodGet, "/channels", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["channels"], 1) // Delete @@ -150,7 +151,7 @@ func TestChannel_RotateCredentials(t *testing.T) { // Get original credentials rec := doRequest(t, h, http.MethodGet, "/channels/"+channelID, nil) var before map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before, jsonx.V1())) hls := before["hlsIngest"].(map[string]any) eps := hls["ingestEndpoints"].([]any) originalPassword := eps[0].(map[string]any)["password"].(string) @@ -160,7 +161,7 @@ func TestChannel_RotateCredentials(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var after map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after, jsonx.V1())) hls = after["hlsIngest"].(map[string]any) eps = hls["ingestEndpoints"].([]any) newPassword := eps[0].(map[string]any)["password"].(string) @@ -181,7 +182,7 @@ func TestChannel_ConfigureLogs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, channelID, resp["id"]) } @@ -193,7 +194,7 @@ func TestChannel_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["channels"]) } @@ -281,7 +282,7 @@ func TestHandler_RotateChannelCredentials_RealPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, channelID, resp["id"]) // The old (fictional) path must no longer be recognized as this operation. @@ -309,7 +310,7 @@ func TestHandler_ConfigureLogs_ReflectsInDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) egress, ok := resp["egressAccessLogs"].(map[string]any) require.True(t, ok, "egressAccessLogs should be present after ConfigureLogs") @@ -333,7 +334,7 @@ func TestHandler_Channel_CreatedAtPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["createdAt"]) } @@ -354,7 +355,7 @@ func TestRotateIngestEndpointCredentials(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["id"]) hls := resp["hlsIngest"].(map[string]any) @@ -401,7 +402,7 @@ func TestRotateIngestEndpointCredentials(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var chResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &chResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &chResp, jsonx.V1())) hls := chResp["hlsIngest"].(map[string]any) eps := hls["ingestEndpoints"].([]any) require.NotEmpty(t, eps) diff --git a/services/mediapackage/handler_harvest_jobs_test.go b/services/mediapackage/handler_harvest_jobs_test.go index 4e4900543b..b0bd02c259 100644 --- a/services/mediapackage/handler_harvest_jobs_test.go +++ b/services/mediapackage/handler_harvest_jobs_test.go @@ -1,7 +1,7 @@ package mediapackage_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediapackage" ) @@ -22,7 +23,7 @@ func createTestOriginEndpointForHarvest(t *testing.T, h *mediapackage.Handler, c require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -61,7 +62,7 @@ func TestHarvestJob_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "job-1", resp["id"]) assert.Equal(t, "IN_PROGRESS", resp["status"]) assert.NotEmpty(t, resp["arn"]) @@ -175,7 +176,7 @@ func TestHarvestJob_Describe(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "job-desc", resp["id"]) assert.Equal(t, "IN_PROGRESS", resp["status"]) assert.NotEmpty(t, resp["arn"]) @@ -238,7 +239,7 @@ func TestHarvestJob_List(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) jobs := resp["harvestJobs"].([]any) assert.Len(t, jobs, 4) }, @@ -251,7 +252,7 @@ func TestHarvestJob_List(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) jobs := resp["harvestJobs"].([]any) assert.Len(t, jobs, 3) for _, j := range jobs { @@ -269,7 +270,7 @@ func TestHarvestJob_List(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) jobs := resp["harvestJobs"].([]any) assert.Len(t, jobs, 4) for _, j := range jobs { @@ -360,7 +361,7 @@ func TestHarvestJob_CycleCreateDescribeList(t *testing.T) { assert.Equal(t, 1, mediapackage.HarvestJobCount(backend)) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, "cycle-job", created["id"]) assert.Contains(t, created["arn"].(string), "harvest_jobs/cycle-job") @@ -369,7 +370,7 @@ func TestHarvestJob_CycleCreateDescribeList(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &described, jsonx.V1())) assert.Equal(t, "cycle-job", described["id"]) assert.Equal(t, created["arn"], described["arn"]) assert.Equal(t, "2024-06-01T00:00:00Z", described["startTime"]) @@ -380,7 +381,7 @@ func TestHarvestJob_CycleCreateDescribeList(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var listed map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listed, jsonx.V1())) jobs := listed["harvestJobs"].([]any) assert.Len(t, jobs, 1) @@ -418,7 +419,7 @@ func TestHarvestJob_Create_NeverClaimsSuccessBeforeWorkHappens(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", resp["status"]) } diff --git a/services/mediapackage/handler_origin_endpoints_test.go b/services/mediapackage/handler_origin_endpoints_test.go index 685fe24af6..1226f7d125 100644 --- a/services/mediapackage/handler_origin_endpoints_test.go +++ b/services/mediapackage/handler_origin_endpoints_test.go @@ -1,13 +1,14 @@ package mediapackage_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediapackage" ) @@ -34,7 +35,7 @@ func TestOriginEndpoint_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:mediapackage:us-east-1:000000000000:origin_endpoints/ep1") assert.Equal(t, "ep1", resp["id"]) assert.Equal(t, "ch1", resp["channelId"]) @@ -89,7 +90,7 @@ func TestOriginEndpoint_CRUD(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/origin_endpoints/"+epID, nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, epID, descResp["id"]) assert.Equal(t, channelID, descResp["channelId"]) @@ -100,7 +101,7 @@ func TestOriginEndpoint_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated endpoint", updateResp["description"]) assert.Equal(t, "DENY", updateResp["origination"]) @@ -108,7 +109,7 @@ func TestOriginEndpoint_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/origin_endpoints", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["originEndpoints"], 1) // Delete @@ -158,7 +159,7 @@ func TestOriginEndpoint_DefaultOrigination(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ALLOW", resp["origination"]) assert.Equal(t, "index", resp["manifestName"]) } @@ -171,7 +172,7 @@ func TestOriginEndpoint_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["originEndpoints"]) } @@ -251,7 +252,7 @@ func TestHandler_OriginEndpoint_CreatedAtPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["createdAt"]) } @@ -277,7 +278,7 @@ func TestHandler_OriginEndpoint_PackagingConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) require.Contains(t, created, "authorization") require.Contains(t, created, "hlsPackage") assert.NotContains(t, created, "dashPackage", "unset packaging blocks should be omitted, not null-filled") @@ -286,7 +287,7 @@ func TestHandler_OriginEndpoint_PackagingConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described, jsonx.V1())) auth, ok := described["authorization"].(map[string]any) require.True(t, ok) assert.Equal(t, "arn:aws:iam::000000000000:role/r", auth["secretsRoleArn"]) @@ -300,7 +301,7 @@ func TestHandler_OriginEndpoint_PackagingConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) endpoints, ok := listResp["originEndpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 1) @@ -364,7 +365,7 @@ func TestHandler_OriginEndpoint_MssPackage_FullDepthRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described, jsonx.V1())) mss, ok := described["mssPackage"].(map[string]any) require.True(t, ok) diff --git a/services/mediapackage/handler_tags_test.go b/services/mediapackage/handler_tags_test.go index 3d521666eb..257e33df1a 100644 --- a/services/mediapackage/handler_tags_test.go +++ b/services/mediapackage/handler_tags_test.go @@ -1,7 +1,7 @@ package mediapackage_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags(t *testing.T) { @@ -20,7 +22,7 @@ func TestTags(t *testing.T) { // Get ARN rec := doRequest(t, h, http.MethodGet, "/channels/"+channelID, nil) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) resourceARN := descResp["arn"].(string) // TagResource @@ -33,7 +35,7 @@ func TestTags(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/tags/"+resourceARN, nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -48,7 +50,7 @@ func TestTags(t *testing.T) { // Verify tag removed rec = doRequest(t, h, http.MethodGet, "/tags/"+resourceARN, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags = listResp["tags"].(map[string]any) assert.NotContains(t, tags, "env") assert.Equal(t, "platform", tags["team"]) diff --git a/services/mediapackage/handler_test.go b/services/mediapackage/handler_test.go index b8ac228e93..b61970078c 100644 --- a/services/mediapackage/handler_test.go +++ b/services/mediapackage/handler_test.go @@ -2,7 +2,7 @@ package mediapackage_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediapackage" ) @@ -30,7 +31,7 @@ func doRequest(t *testing.T, h *mediapackage.Handler, method, path string, body if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -62,7 +63,7 @@ func doRequestJSON(t *testing.T, h *mediapackage.Handler, method, path string, b } var out map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) return rec.Code, out } @@ -77,7 +78,7 @@ func createTestChannel(t *testing.T, h *mediapackage.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } @@ -92,7 +93,7 @@ func createTestOriginEndpoint(t *testing.T, h *mediapackage.Handler, channelID s require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["id"].(string) } diff --git a/services/mediapackage/multi_region_test.go b/services/mediapackage/multi_region_test.go index 4b9ac9eef4..bd66b6e01c 100644 --- a/services/mediapackage/multi_region_test.go +++ b/services/mediapackage/multi_region_test.go @@ -2,7 +2,8 @@ package mediapackage_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/mediapackage" ) @@ -172,8 +174,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/mediapackage/persistence.go b/services/mediapackage/persistence.go index 5dc4b45d58..b2f98b2943 100644 --- a/services/mediapackage/persistence.go +++ b/services/mediapackage/persistence.go @@ -2,7 +2,7 @@ package mediapackage import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -31,7 +31,7 @@ const mediapackageSnapshotVersion = 1 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountId"` Region string `json:"region"` diff --git a/services/mediastore/containers.go b/services/mediastore/containers.go index 706c0ed7f2..00c5f2cca0 100644 --- a/services/mediastore/containers.go +++ b/services/mediastore/containers.go @@ -2,7 +2,7 @@ package mediastore import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "regexp" @@ -10,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -199,7 +200,7 @@ func (b *InMemoryBackend) ListContainers( // PutContainerPolicy stores a container access policy. func (b *InMemoryBackend) PutContainerPolicy(ctx context.Context, name, policy string) error { - if !json.Valid([]byte(policy)) { + if !jsontext.Value([]byte(policy)).IsValid(jsonx.V1()) { return ErrInvalidPolicy } diff --git a/services/mediastore/handler.go b/services/mediastore/handler.go index 41ae9a09d2..f62dd59c65 100644 --- a/services/mediastore/handler.go +++ b/services/mediastore/handler.go @@ -2,7 +2,7 @@ package mediastore import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "sort" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -131,7 +132,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -221,7 +222,7 @@ func (h *Handler) dispatch(c *echo.Context, op string, body []byte) error { func (h *Handler) handleCreateContainer(c *echo.Context, body []byte) error { var req createContainerRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -244,7 +245,7 @@ func (h *Handler) handleCreateContainer(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteContainer(c *echo.Context, body []byte) error { var req deleteContainerRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -262,7 +263,7 @@ func (h *Handler) handleDeleteContainer(c *echo.Context, body []byte) error { func (h *Handler) handleDescribeContainer(c *echo.Context, body []byte) error { var req describeContainerRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -283,7 +284,7 @@ func (h *Handler) handleDescribeContainer(c *echo.Context, body []byte) error { func (h *Handler) handleListContainers(c *echo.Context, body []byte) error { var req listContainersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -309,7 +310,7 @@ func (h *Handler) handleListContainers(c *echo.Context, body []byte) error { func (h *Handler) handlePutContainerPolicy(c *echo.Context, body []byte) error { var req putContainerPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -327,7 +328,7 @@ func (h *Handler) handlePutContainerPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleGetContainerPolicy(c *echo.Context, body []byte) error { var req getContainerPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -346,7 +347,7 @@ func (h *Handler) handleGetContainerPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteContainerPolicy(c *echo.Context, body []byte) error { var req deleteContainerPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -364,7 +365,7 @@ func (h *Handler) handleDeleteContainerPolicy(c *echo.Context, body []byte) erro func (h *Handler) handlePutCorsPolicy(c *echo.Context, body []byte) error { var req putCorsPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -382,7 +383,7 @@ func (h *Handler) handlePutCorsPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleGetCorsPolicy(c *echo.Context, body []byte) error { var req getCorsPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -401,7 +402,7 @@ func (h *Handler) handleGetCorsPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteCorsPolicy(c *echo.Context, body []byte) error { var req deleteCorsPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -419,7 +420,7 @@ func (h *Handler) handleDeleteCorsPolicy(c *echo.Context, body []byte) error { func (h *Handler) handlePutLifecyclePolicy(c *echo.Context, body []byte) error { var req putLifecyclePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -437,7 +438,7 @@ func (h *Handler) handlePutLifecyclePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleGetLifecyclePolicy(c *echo.Context, body []byte) error { var req getLifecyclePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -456,7 +457,7 @@ func (h *Handler) handleGetLifecyclePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteLifecyclePolicy(c *echo.Context, body []byte) error { var req deleteLifecyclePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -474,7 +475,7 @@ func (h *Handler) handleDeleteLifecyclePolicy(c *echo.Context, body []byte) erro func (h *Handler) handlePutMetricPolicy(c *echo.Context, body []byte) error { var req putMetricPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -492,7 +493,7 @@ func (h *Handler) handlePutMetricPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleGetMetricPolicy(c *echo.Context, body []byte) error { var req getMetricPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -511,7 +512,7 @@ func (h *Handler) handleGetMetricPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDeleteMetricPolicy(c *echo.Context, body []byte) error { var req deleteMetricPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -529,7 +530,7 @@ func (h *Handler) handleDeleteMetricPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleStartAccessLogging(c *echo.Context, body []byte) error { var req startAccessLoggingRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -547,7 +548,7 @@ func (h *Handler) handleStartAccessLogging(c *echo.Context, body []byte) error { func (h *Handler) handleStopAccessLogging(c *echo.Context, body []byte) error { var req stopAccessLoggingRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -565,7 +566,7 @@ func (h *Handler) handleStopAccessLogging(c *echo.Context, body []byte) error { func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -585,7 +586,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -603,7 +604,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { func (h *Handler) handleListTagsForResource(c *echo.Context, body []byte) error { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/mediastore/handler_tags_test.go b/services/mediastore/handler_tags_test.go index 37ccfff6dc..bafde795f1 100644 --- a/services/mediastore/handler_tags_test.go +++ b/services/mediastore/handler_tags_test.go @@ -1,12 +1,14 @@ package mediastore_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Tags(t *testing.T) { @@ -48,7 +50,7 @@ func TestHandler_Tags(t *testing.T) { require.Equal(t, http.StatusOK, setupRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(setupRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(setupRec.Body.Bytes(), &createResp, jsonx.V1())) containerMap, _ := createResp["Container"].(map[string]any) containerARN, _ := containerMap["ARN"].(string) diff --git a/services/mediastore/handler_test.go b/services/mediastore/handler_test.go index 753201ff9f..1addee30a1 100644 --- a/services/mediastore/handler_test.go +++ b/services/mediastore/handler_test.go @@ -2,7 +2,7 @@ package mediastore_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediastore" ) @@ -32,7 +33,7 @@ func doRequest(t *testing.T, h *mediastore.Handler, op string, body any) *httpte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -65,7 +66,7 @@ func unmarshalBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -235,7 +236,7 @@ func TestHandler_ExtractResource(t *testing.T) { h := newTestHandler(t) e := echo.New() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) diff --git a/services/mediastore/lifecycle_policy.go b/services/mediastore/lifecycle_policy.go index 1070178e24..63ae3f799b 100644 --- a/services/mediastore/lifecycle_policy.go +++ b/services/mediastore/lifecycle_policy.go @@ -2,13 +2,15 @@ package mediastore import ( "context" - "encoding/json" + "encoding/json/jsontext" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutLifecyclePolicy stores a lifecycle policy for a container. func (b *InMemoryBackend) PutLifecyclePolicy(ctx context.Context, name, policy string) error { - if !json.Valid([]byte(policy)) { + if !jsontext.Value([]byte(policy)).IsValid(jsonx.V1()) { return ErrInvalidPolicy } diff --git a/services/mediastoredata/handler_test.go b/services/mediastoredata/handler_test.go index 2d984b9470..83d28ff3d6 100644 --- a/services/mediastoredata/handler_test.go +++ b/services/mediastoredata/handler_test.go @@ -6,7 +6,7 @@ import ( "bytes" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediastoredata" ) @@ -196,7 +197,7 @@ func TestMediaStoreData_PutObject(t *testing.T) { if tt.wantETag { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ETag"]) assert.NotEmpty(t, resp["StorageClass"]) } @@ -354,7 +355,7 @@ func TestMediaStoreData_ListItems(t *testing.T) { } `json:"Items"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Items, len(tt.wantNames)) for i, item := range resp.Items { @@ -468,7 +469,7 @@ func TestMediaStoreData_Lifecycle(t *testing.T) { } `json:"Items"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Items, 1) assert.Equal(t, tt.wantFolder, listResp.Items[0].Name) assert.Equal(t, "FOLDER", listResp.Items[0].Type) @@ -496,7 +497,7 @@ func TestMediaStoreData_PutObject_SHA256InResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, wantSHA, resp["ContentSHA256"]) assert.NotEmpty(t, rec.Header().Get("ETag")) } @@ -559,7 +560,7 @@ func TestMediaStoreData_PathValidation(t *testing.T) { // AWS-wide/gopherstack-wide convention for parameter // validation failures (see errors.go). var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -605,7 +606,7 @@ func TestMediaStoreData_StorageClassValidation(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -645,7 +646,7 @@ func TestMediaStoreData_UploadAvailabilityValidation(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -692,7 +693,7 @@ func TestMediaStoreData_ObjectSizeLimit(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -819,7 +820,7 @@ func TestMediaStoreData_RangeRequests(t *testing.T) { // only matches this exact string, not a fabricated // "InvalidRangeException". var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "RequestedRangeNotSatisfiableException", resp["__type"]) assert.NotEmpty(t, rec.Header().Get("Content-Range")) } @@ -848,7 +849,7 @@ func TestMediaStoreData_ListItems_Pagination(t *testing.T) { } `json:"Items"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) assert.Len(t, page1.Items, 2) require.NotNil(t, page1.NextToken) @@ -863,7 +864,7 @@ func TestMediaStoreData_ListItems_Pagination(t *testing.T) { } `json:"Items"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.Items, 2) // Names must not overlap pages. @@ -932,7 +933,7 @@ func TestMediaStoreData_ContentSHA256Verification(t *testing.T) { // payload hash that doesn't match the actual body), not a fabricated // mediastoredata-specific "InvalidContentSHA256Exception". var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "XAmzContentSHA256Mismatch", resp["__type"]) }) diff --git a/services/mediatailor/handler.go b/services/mediatailor/handler.go index 192492dfcb..194f4fb4f5 100644 --- a/services/mediatailor/handler.go +++ b/services/mediatailor/handler.go @@ -1,7 +1,8 @@ package mediatailor import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -277,7 +279,12 @@ func (h *Handler) handleREST(c *echo.Context) error { var body map[string]any if c.Request().ContentLength != 0 { - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && err.Error() != "EOF" { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &body, + jsonx.V1(), + ); err != nil && + err.Error() != "EOF" { c.Response().Header().Set(amznErrorTypeHeader, "BadRequestException") return c.JSON(http.StatusBadRequest, map[string]any{keyMessage: "invalid JSON body"}) diff --git a/services/mediatailor/handler_alerts_test.go b/services/mediatailor/handler_alerts_test.go index 8f1255ce3d..ce9dbc8dce 100644 --- a/services/mediatailor/handler_alerts_test.go +++ b/services/mediatailor/handler_alerts_test.go @@ -1,12 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListAlerts(t *testing.T) { @@ -37,7 +39,7 @@ func TestListAlerts(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if !tt.wantItems { assert.Equal(t, "BadRequestException", rec.Header().Get("X-Amzn-Errortype")) diff --git a/services/mediatailor/handler_channel_policy_test.go b/services/mediatailor/handler_channel_policy_test.go index 7e75eba6fc..649146ba0c 100644 --- a/services/mediatailor/handler_channel_policy_test.go +++ b/services/mediatailor/handler_channel_policy_test.go @@ -1,13 +1,15 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestChannelPolicy_FullCycle's subtests share one Handler and depend on @@ -43,7 +45,7 @@ func TestChannelPolicy_FullCycle(t *testing.T) { //nolint:tparallel // subtests t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["Policy"], "2012-10-17") }, }, diff --git a/services/mediatailor/handler_channels_test.go b/services/mediatailor/handler_channels_test.go index b456ef12ec..691529e14e 100644 --- a/services/mediatailor/handler_channels_test.go +++ b/services/mediatailor/handler_channels_test.go @@ -1,13 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -35,7 +36,7 @@ func TestChannel_CreateAndDescribe(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "my-channel", resp["ChannelName"]) assert.Contains(t, resp["Arn"], "arn:aws:mediatailor:us-east-1:000000000000:channel/my-channel") assert.Equal(t, "STOPPED", resp["ChannelState"]) @@ -80,7 +81,7 @@ func TestChannel_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/channel/test-channel", nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-channel", descResp["ChannelName"]) assert.Equal(t, "STOPPED", descResp["ChannelState"]) @@ -92,14 +93,14 @@ func TestChannel_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Len(t, updateResp["Outputs"], 1) // List rec = doRequest(t, h, http.MethodGet, "/channels", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Items"], 1) // Delete @@ -126,7 +127,7 @@ func TestChannel_StartStop(t *testing.T) { // Verify running rec = doRequest(t, h, http.MethodGet, "/channel/ch1", nil) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "RUNNING", descResp["ChannelState"]) // Start again is idempotent @@ -139,7 +140,7 @@ func TestChannel_StartStop(t *testing.T) { // Verify stopped rec = doRequest(t, h, http.MethodGet, "/channel/ch1", nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "STOPPED", descResp["ChannelState"]) // Stop again is idempotent @@ -201,7 +202,7 @@ func TestChannel_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Items"]) } @@ -227,7 +228,7 @@ func TestListChannels_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } @@ -252,7 +253,7 @@ func TestChannelOutput_WithHLS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) outputs, _ := resp["Outputs"].([]any) require.Len(t, outputs, 1) out, _ := outputs[0].(map[string]any) @@ -308,7 +309,7 @@ func TestCreateChannel_ReturnsTier(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BASIC", resp["Tier"], "Tier must default to BASIC") } @@ -362,7 +363,7 @@ func TestCreateChannel_FillerSlate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) fs, ok := resp["FillerSlate"].(map[string]any) require.True(t, ok, "FillerSlate must be present in response") @@ -387,7 +388,7 @@ func TestCreateChannel_TagsSurviveDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) createdTags, _ := created["tags"].(map[string]any) assert.Equal(t, "prod", createdTags["env"], "tags must be present in the create response") @@ -395,7 +396,7 @@ func TestCreateChannel_TagsSurviveDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described, jsonx.V1())) describedTags, _ := described["tags"].(map[string]any) assert.Equal(t, "prod", describedTags["env"], "tags set at creation must survive to DescribeChannel") } @@ -417,7 +418,7 @@ func TestChannel_AudiencesAndTimeShift(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) audiences, _ := resp["Audiences"].([]any) assert.ElementsMatch(t, []any{"aud1", "aud2"}, audiences) ts, ok := resp["TimeShiftConfiguration"].(map[string]any) @@ -430,7 +431,7 @@ func TestChannel_AudiencesAndTimeShift(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) updateResp := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) audiences, _ = updateResp["Audiences"].([]any) assert.ElementsMatch(t, []any{"aud3"}, audiences) assert.Nil(t, updateResp["TimeShiftConfiguration"], "UpdateChannel omitting TimeShiftConfiguration must clear it") @@ -466,7 +467,7 @@ func TestCreateChannel_Tier(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantTier, resp["Tier"]) } }) diff --git a/services/mediatailor/handler_functions_test.go b/services/mediatailor/handler_functions_test.go index ce1ab0b21c..b025edbb52 100644 --- a/services/mediatailor/handler_functions_test.go +++ b/services/mediatailor/handler_functions_test.go @@ -1,13 +1,15 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestFunction_CRUD's subtests share one Handler and depend on running in @@ -39,7 +41,7 @@ func TestFunction_CRUD(t *testing.T) { //nolint:tparallel // subtests are order- t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "fn1", resp["FunctionId"]) assert.Equal(t, "CUSTOM_OUTPUT", resp["FunctionType"]) assert.Contains(t, resp["Arn"], ":mediatailor:") @@ -55,7 +57,7 @@ func TestFunction_CRUD(t *testing.T) { //nolint:tparallel // subtests are order- t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "fn1", resp["FunctionId"]) assert.Equal(t, "my function", resp["Description"]) }, @@ -69,7 +71,7 @@ func TestFunction_CRUD(t *testing.T) { //nolint:tparallel // subtests are order- t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1) }, @@ -161,7 +163,7 @@ func TestListFunctions_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } diff --git a/services/mediatailor/handler_live_sources_test.go b/services/mediatailor/handler_live_sources_test.go index b3a6897e9f..d067ff9d53 100644 --- a/services/mediatailor/handler_live_sources_test.go +++ b/services/mediatailor/handler_live_sources_test.go @@ -1,7 +1,7 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -40,7 +41,7 @@ func TestLiveSource_Create(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ls1", resp["LiveSourceName"]) assert.Equal(t, "sl1", resp["SourceLocationName"]) assert.Contains(t, resp["Arn"], ":mediatailor:") @@ -105,7 +106,7 @@ func TestLiveSource_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ls1", resp["LiveSourceName"]) // update @@ -117,7 +118,7 @@ func TestLiveSource_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) cfgs, _ := updated["HttpPackageConfigurations"].([]any) require.Len(t, cfgs, 1) cfg0, _ := cfgs[0].(map[string]any) @@ -128,7 +129,7 @@ func TestLiveSource_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["Items"].([]any) assert.Len(t, items, 1) @@ -202,7 +203,7 @@ func TestListLiveSources_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } diff --git a/services/mediatailor/handler_logs_test.go b/services/mediatailor/handler_logs_test.go index f479e61136..ca94cd4063 100644 --- a/services/mediatailor/handler_logs_test.go +++ b/services/mediatailor/handler_logs_test.go @@ -1,13 +1,15 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestConfigureLogsForChannel(t *testing.T) { @@ -30,7 +32,7 @@ func TestConfigureLogsForChannel(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ch1", resp["ChannelName"]) logTypes, _ := resp["LogTypes"].([]any) assert.Len(t, logTypes, 1) @@ -83,7 +85,7 @@ func TestConfigureLogsForPlaybackConfiguration(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "pc1", resp["PlaybackConfigurationName"]) assert.InDelta(t, float64(60), resp["PercentEnabled"], 0.0001) }, @@ -196,7 +198,7 @@ func TestConfigureLogsForChannel_PersistsToDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) logCfg, ok := resp["LogConfiguration"].(map[string]any) require.True(t, ok, "DescribeChannel must include LogConfiguration") @@ -225,7 +227,7 @@ func TestConfigureLogsForPlaybackConfiguration_PersistsToGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) logCfg, ok := resp["LogConfiguration"].(map[string]any) require.True(t, ok, "GetPlaybackConfiguration must include LogConfiguration") @@ -328,7 +330,7 @@ func TestConfigureLogsForPlaybackConfiguration_SurvivesRePut(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) logCfg, ok := resp["LogConfiguration"].(map[string]any) require.True(t, ok) diff --git a/services/mediatailor/handler_playback_configurations_test.go b/services/mediatailor/handler_playback_configurations_test.go index 816ce67550..c909bf0406 100644 --- a/services/mediatailor/handler_playback_configurations_test.go +++ b/services/mediatailor/handler_playback_configurations_test.go @@ -1,13 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -32,7 +33,7 @@ func TestPlaybackConfiguration_PutAndGet(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "my-config", resp["Name"]) assert.Contains( t, @@ -83,7 +84,7 @@ func TestPlaybackConfiguration_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/playbackConfiguration/test-config", nil) assert.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "test-config", getResp["Name"]) assert.Equal(t, "https://ads.example.com", getResp["AdDecisionServerUrl"]) @@ -91,7 +92,7 @@ func TestPlaybackConfiguration_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/playbackConfigurations", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Items"], 1) // Delete @@ -124,7 +125,7 @@ func TestPlaybackConfiguration_PutIdempotent(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/playbackConfiguration/cfg1", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "https://new-ads.example.com", resp["AdDecisionServerUrl"]) } @@ -165,7 +166,7 @@ func TestPlaybackConfiguration_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Items"]) } @@ -187,7 +188,7 @@ func TestListPlaybackConfigurations_WithNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 3) } @@ -280,7 +281,7 @@ func TestPutPlaybackConfiguration_TagsReplacedOnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["tags"].(map[string]any) assert.Contains(t, tags, "new", "new tag must be present after update") @@ -301,7 +302,7 @@ func TestPutPlaybackConfiguration_HlsConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hlsCfg, ok := resp["HlsConfiguration"].(map[string]any) require.True(t, ok, "HlsConfiguration must be present in response") @@ -366,14 +367,14 @@ func TestPutPlaybackConfiguration_ExtraConfigRoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assertPlaybackConfigExtras(t, putResp) rec = doRequest(t, h, http.MethodGet, "/playbackConfiguration/cfg1", nil) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assertPlaybackConfigExtras(t, getResp) } @@ -447,7 +448,7 @@ func TestPutPlaybackConfiguration_AdsPersonalization(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) tt.check(t, putResp) name, _ := tt.body["Name"].(string) @@ -455,7 +456,7 @@ func TestPutPlaybackConfiguration_AdsPersonalization(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) tt.check(t, getResp) }) } @@ -476,13 +477,13 @@ func TestPutPlaybackConfiguration_DualStackFieldsAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) rec = doRequest(t, h, http.MethodGet, "/playbackConfiguration/cfg-dualstack", nil) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) for _, resp := range []map[string]any{putResp, getResp} { _, ok := resp["DualStackPlaybackEndpointPrefix"] diff --git a/services/mediatailor/handler_prefetch_schedules_test.go b/services/mediatailor/handler_prefetch_schedules_test.go index 45cb29f107..c15ecf4792 100644 --- a/services/mediatailor/handler_prefetch_schedules_test.go +++ b/services/mediatailor/handler_prefetch_schedules_test.go @@ -1,13 +1,15 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPrefetchSchedule_CRUD(t *testing.T) { @@ -28,7 +30,7 @@ func TestPrefetchSchedule_CRUD(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "sched1", resp["Name"]) assert.Equal(t, "pc1", resp["PlaybackConfigurationName"]) assert.Contains(t, resp["Arn"], ":mediatailor:") @@ -58,7 +60,7 @@ func TestPrefetchSchedule_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "sched1", resp["Name"]) // list @@ -66,7 +68,7 @@ func TestPrefetchSchedule_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["Items"].([]any) assert.Len(t, items, 1) @@ -136,7 +138,7 @@ func TestListPrefetchSchedules_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } @@ -167,7 +169,7 @@ func TestListPrefetchSchedules_PostWithBodyPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1, "body MaxResults must limit the page size") assert.NotEmpty(t, resp["NextToken"], "a NextToken must be returned when more pages remain") @@ -204,7 +206,7 @@ func TestCreatePrefetchSchedule_RetrievalAndConsumption(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) retrieval, ok := resp["Retrieval"].(map[string]any) require.True(t, ok, "Retrieval must be present in response") @@ -246,7 +248,7 @@ func TestListPrefetchSchedules_FiltersByScheduleTypeAndStreamID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2, "ScheduleType=SINGLE must exclude the RECURRING schedule") @@ -255,7 +257,7 @@ func TestListPrefetchSchedules_FiltersByScheduleTypeAndStreamID(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ = resp["Items"].([]any) assert.Len(t, items, 2, "StreamId=stream-1 must match single-a and recurring-a only") } @@ -278,7 +280,7 @@ func TestPrefetchSchedule_TagsAndScheduleTypeRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "RECURRING", resp["ScheduleType"]) assert.Equal(t, "stream-9", resp["StreamId"]) tags, _ := resp["tags"].(map[string]any) diff --git a/services/mediatailor/handler_programs_test.go b/services/mediatailor/handler_programs_test.go index 013bac219c..c200aa7f66 100644 --- a/services/mediatailor/handler_programs_test.go +++ b/services/mediatailor/handler_programs_test.go @@ -1,12 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestProgram_CRUD(t *testing.T) { @@ -24,7 +26,7 @@ func TestProgram_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, "prog1", created["ProgramName"]) assert.Equal(t, "ch1", created["ChannelName"]) assert.Contains(t, created["Arn"], ":mediatailor:") @@ -34,7 +36,7 @@ func TestProgram_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var described map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &described, jsonx.V1())) assert.Equal(t, "prog1", described["ProgramName"]) assert.Equal(t, "sl1", described["SourceLocationName"]) @@ -49,7 +51,7 @@ func TestProgram_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var schedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp, jsonx.V1())) items, _ := schedResp["Items"].([]any) assert.Len(t, items, 1) item0, _ := items[0].(map[string]any) @@ -151,7 +153,7 @@ func TestHandleGetChannelSchedule_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var schedResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schedResp, jsonx.V1())) items, _ := schedResp["Items"].([]any) require.Len(t, items, 1) } diff --git a/services/mediatailor/handler_source_locations_test.go b/services/mediatailor/handler_source_locations_test.go index 1d2a862431..4783fbb050 100644 --- a/services/mediatailor/handler_source_locations_test.go +++ b/services/mediatailor/handler_source_locations_test.go @@ -1,13 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -32,7 +33,7 @@ func TestSourceLocation_CreateAndDescribe(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "my-location", resp["SourceLocationName"]) assert.Contains(t, resp["Arn"], "arn:aws:mediatailor:us-east-1:000000000000:sourceLocation/my-location") httpCfg, _ := resp["HttpConfiguration"].(map[string]any) @@ -76,7 +77,7 @@ func TestSourceLocation_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/sourceLocation/test-sl", nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "test-sl", descResp["SourceLocationName"]) // Update @@ -85,7 +86,7 @@ func TestSourceLocation_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) httpCfg, _ := updateResp["HttpConfiguration"].(map[string]any) assert.Equal(t, "https://updated.example.com", httpCfg["BaseUrl"]) @@ -93,7 +94,7 @@ func TestSourceLocation_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/sourceLocations", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Items"], 1) // Delete @@ -152,7 +153,7 @@ func TestSourceLocation_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Items"]) } @@ -172,7 +173,7 @@ func TestListSourceLocations_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } @@ -267,7 +268,7 @@ func TestCreateSourceLocation_TagsSurviveDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["tags"].(map[string]any) assert.Equal(t, "video", tags["team"], "tags set at creation must survive to DescribeSourceLocation") } @@ -285,7 +286,7 @@ func TestSourceLocation_Timestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) require.NotNil(t, created["CreationTime"], "CreationTime must be populated") require.NotNil(t, created["LastModifiedTime"], "LastModifiedTime must be populated") @@ -295,7 +296,7 @@ func TestSourceLocation_Timestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updated map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updated, jsonx.V1())) assert.Equal(t, created["CreationTime"], updated["CreationTime"], "CreationTime must not change on update") require.NotNil(t, updated["LastModifiedTime"]) } diff --git a/services/mediatailor/handler_tags_test.go b/services/mediatailor/handler_tags_test.go index 2817fabed1..2892d5c111 100644 --- a/services/mediatailor/handler_tags_test.go +++ b/services/mediatailor/handler_tags_test.go @@ -1,7 +1,7 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTags_FullCycle(t *testing.T) { @@ -25,7 +27,7 @@ func TestTags_FullCycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) resourceARN, _ := putResp["PlaybackConfigurationArn"].(string) // TagResource @@ -38,7 +40,7 @@ func TestTags_FullCycle(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/tags/"+resourceARN, nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "media", tags["team"]) @@ -53,7 +55,7 @@ func TestTags_FullCycle(t *testing.T) { // Verify tag removed rec = doRequest(t, h, http.MethodGet, "/tags/"+resourceARN, nil) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ = listResp["tags"].(map[string]any) assert.NotContains(t, tags, "env") assert.Equal(t, "media", tags["team"]) @@ -94,7 +96,7 @@ func TestTagResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ = resp["PlaybackConfigurationArn"].(string) } else { arn = "arn:aws:mediatailor:us-east-1:000000000000:playbackConfiguration/nonexistent" @@ -132,7 +134,7 @@ func TestListTagsForResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ = resp["PlaybackConfigurationArn"].(string) } else { arn = "arn:aws:mediatailor:us-east-1:000000000000:playbackConfiguration/nonexistent" @@ -170,7 +172,7 @@ func TestUntagResource(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ = resp["PlaybackConfigurationArn"].(string) // Actually tag it first diff --git a/services/mediatailor/handler_test.go b/services/mediatailor/handler_test.go index 010eb8ad3d..df6f724bc5 100644 --- a/services/mediatailor/handler_test.go +++ b/services/mediatailor/handler_test.go @@ -2,7 +2,7 @@ package mediatailor_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -32,7 +33,7 @@ func doRequest(t *testing.T, h *mediatailor.Handler, method, path string, body a if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -528,7 +529,7 @@ func TestHandler_TagsWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // A PascalCase "Tags" key must NOT be how tags are surfaced. _, wrongKeyPresent := resp["Tags"] @@ -550,7 +551,7 @@ func TestHandler_TagsWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok, "response must carry tags under lowercase 'tags' key") @@ -568,7 +569,7 @@ func TestHandler_TagsWireKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["tags"].(map[string]any) require.True(t, ok, "response must carry tags under lowercase 'tags' key") @@ -591,7 +592,7 @@ func TestHandler_ChannelTimestamps_EpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) creationTime, ok := resp["CreationTime"].(float64) require.True(t, ok, "CreationTime must decode as a JSON number, got %T", resp["CreationTime"]) @@ -625,7 +626,7 @@ func TestHandler_PaginationQueryParamCasing(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1, "lowercase maxResults query param must limit the page size") assert.NotEmpty(t, resp["NextToken"], "a NextToken must be returned when more pages remain") @@ -643,7 +644,7 @@ func TestHandler_PaginationQueryParamCasing(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1, "PascalCase MaxResults query param must limit the page size") assert.NotEmpty(t, resp["NextToken"], "a NextToken must be returned when more pages remain") @@ -661,7 +662,7 @@ func TestHandler_PaginationQueryParamCasing(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1, "ListFunctions must actually paginate, not return every function") assert.NotEmpty(t, resp["NextToken"], "a NextToken must be returned when more pages remain") @@ -695,7 +696,7 @@ func TestHandler_PaginationQueryParamCasing(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 1, "GetChannelSchedule must actually paginate, not return every program") assert.NotEmpty(t, resp["NextToken"], "a NextToken must be returned when more pages remain") diff --git a/services/mediatailor/handler_vod_sources_test.go b/services/mediatailor/handler_vod_sources_test.go index 6b99e2ab46..fa2488bf60 100644 --- a/services/mediatailor/handler_vod_sources_test.go +++ b/services/mediatailor/handler_vod_sources_test.go @@ -1,13 +1,14 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -37,7 +38,7 @@ func TestVodSource_CreateAndDescribe(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "my-vod", resp["VodSourceName"]) assert.Equal(t, "sl1", resp["SourceLocationName"]) assert.Contains( @@ -87,7 +88,7 @@ func TestVodSource_CRUD(t *testing.T) { rec = doRequest(t, h, http.MethodGet, "/sourceLocation/sl1/vodSource/vod1", nil) assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "vod1", descResp["VodSourceName"]) // Update @@ -99,14 +100,14 @@ func TestVodSource_CRUD(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Len(t, updateResp["HttpPackageConfigurations"], 2) // List rec = doRequest(t, h, http.MethodGet, "/sourceLocation/sl1/vodSources", nil) assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Items"], 1) // Delete @@ -154,7 +155,7 @@ func TestVodSource_ListEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Items"]) } @@ -177,7 +178,7 @@ func TestListVodSources_WithItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Items"].([]any) assert.Len(t, items, 2) } @@ -238,7 +239,7 @@ func TestCreateVodSource_TagsSurviveDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["tags"].(map[string]any) assert.Equal(t, "video", tags["team"], "tags set at creation must survive to DescribeVodSource") } @@ -260,7 +261,7 @@ func TestVodSource_Timestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) require.NotNil(t, created["CreationTime"]) require.NotNil(t, created["LastModifiedTime"]) } diff --git a/services/mediatailor/multi_region_test.go b/services/mediatailor/multi_region_test.go index 48215c9fd1..b5a6bab9d8 100644 --- a/services/mediatailor/multi_region_test.go +++ b/services/mediatailor/multi_region_test.go @@ -2,7 +2,8 @@ package mediatailor_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -180,8 +182,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/mediatailor/persistence.go b/services/mediatailor/persistence.go index 2e5840fc82..e0771067ec 100644 --- a/services/mediatailor/persistence.go +++ b/services/mediatailor/persistence.go @@ -2,7 +2,7 @@ package mediatailor import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -32,7 +32,7 @@ const mediatailorSnapshotVersion = 1 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/mediatailor/wire_field_fixes_test.go b/services/mediatailor/wire_field_fixes_test.go index 733124b622..ffc4bba2d2 100644 --- a/services/mediatailor/wire_field_fixes_test.go +++ b/services/mediatailor/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package mediatailor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mediatailor" ) @@ -153,7 +154,7 @@ func TestGetPrefetchSchedule_NoCreationTimeOnWire(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains( t, resp, "CreationTime", "GetPrefetchScheduleOutput has no CreationTime member on the real API; emitting one is a fabricated field", @@ -224,7 +225,7 @@ func TestCreateChannel_NoLogConfigurationOnWire(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotContains( t, createResp, "LogConfiguration", "CreateChannelOutput has no LogConfiguration member on the real API", @@ -236,7 +237,7 @@ func TestCreateChannel_NoLogConfigurationOnWire(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code, updateRec.Body.String()) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotContains( t, updateResp, "LogConfiguration", "UpdateChannelOutput has no LogConfiguration member on the real API", @@ -246,7 +247,7 @@ func TestCreateChannel_NoLogConfigurationOnWire(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code, describeRec.Body.String()) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Contains( t, describeResp, "LogConfiguration", "DescribeChannelOutput.LogConfiguration IS required on the real API and must stay present", diff --git a/services/memorydb/acls_test.go b/services/memorydb/acls_test.go index ec99afe7ea..cfdcef82a3 100644 --- a/services/memorydb/acls_test.go +++ b/services/memorydb/acls_test.go @@ -2,13 +2,14 @@ package memorydb_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -119,7 +120,7 @@ func TestUpdateACLSliceNoAlias(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) acls := resp["ACLs"].([]any) acl := acls[0].(map[string]any) users := acl["UserNames"].([]any) diff --git a/services/memorydb/dataplane_test.go b/services/memorydb/dataplane_test.go index 18144e2916..e5087b49dd 100644 --- a/services/memorydb/dataplane_test.go +++ b/services/memorydb/dataplane_test.go @@ -3,7 +3,7 @@ package memorydb_test import ( "bufio" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net" "strconv" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -114,7 +115,7 @@ func describeClusterObj(t *testing.T, h *memorydb.Handler, name string) map[stri var resp struct { Clusters []map[string]any `json:"Clusters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Clusters, 1) return resp.Clusters[0] diff --git a/services/memorydb/errcode_test.go b/services/memorydb/errcode_test.go index 4306b8c722..ff94b90ac2 100644 --- a/services/memorydb/errcode_test.go +++ b/services/memorydb/errcode_test.go @@ -1,12 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // -- Error __type wire-shape regression tests ------------------------------- @@ -22,7 +24,7 @@ func responseType(t *testing.T, body []byte) string { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) typ, _ := resp["__type"].(string) @@ -105,7 +107,7 @@ func TestDeleteUser_CascadesFromACL(t *testing.T) { UserNames []string `json:"UserNames"` } `json:"ACLs"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ACLs, 1) assert.NotContains(t, resp.ACLs[0].UserNames, "in-use-user") } diff --git a/services/memorydb/events.go b/services/memorydb/events.go index f533898f7c..ae5c0faaf0 100644 --- a/services/memorydb/events.go +++ b/services/memorydb/events.go @@ -2,10 +2,11 @@ package memorydb import ( "context" - "encoding/json" "fmt" "sort" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AddEvent appends an event to the backend event log (used internally for seeding). @@ -75,7 +76,7 @@ func (b *InMemoryBackend) DescribeEvents(ctx context.Context, req *describeEvent // field. Real aws-sdk-go-v2 clients serialize these TStamp shapes as a JSON // number of epoch seconds (awsjson1.1), so n arrives as json.Number, not an // RFC3339 string. Returns nil, nil when n is the empty (unset) value. -func parseEpochRequestTime(n json.Number) (*time.Time, error) { +func parseEpochRequestTime(n jsonx.Number) (*time.Time, error) { if n == "" { return nil, nil //nolint:nilnil // absent filter is a valid, distinct state from "parse failed" } diff --git a/services/memorydb/events_test.go b/services/memorydb/events_test.go index e11be642f3..89dbec1982 100644 --- a/services/memorydb/events_test.go +++ b/services/memorydb/events_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -83,7 +84,7 @@ func TestDescribeEvents_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Events []map[string]any `json:"Events"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first, jsonx.V1())) require.Len(t, first.Events, 1, "first page must truncate to MaxResults") require.NotEmpty(t, first.NextToken, "NextToken must be set when more events remain") @@ -94,7 +95,7 @@ func TestDescribeEvents_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Events []map[string]any `json:"Events"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &second)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &second, jsonx.V1())) require.Len(t, second.Events, 1, "second page must return the next event") assert.NotEqual(t, first.Events[0]["Message"], second.Events[0]["Message"], "no event must repeat across pages") } diff --git a/services/memorydb/handler.go b/services/memorydb/handler.go index c4e703f486..5802b14970 100644 --- a/services/memorydb/handler.go +++ b/services/memorydb/handler.go @@ -2,7 +2,7 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -150,7 +151,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } diff --git a/services/memorydb/handler_acls.go b/services/memorydb/handler_acls.go index 1e76accc6c..1093fb6b9f 100644 --- a/services/memorydb/handler_acls.go +++ b/services/memorydb/handler_acls.go @@ -2,16 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateACL(ctx context.Context, c *echo.Context, body []byte) error { var req createACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -34,7 +36,7 @@ func (h *Handler) handleCreateACL(ctx context.Context, c *echo.Context, body []b func (h *Handler) handleDescribeACLs(ctx context.Context, c *echo.Context, body []byte) error { var req describeACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -61,7 +63,7 @@ func (h *Handler) handleDescribeACLs(ctx context.Context, c *echo.Context, body func (h *Handler) handleDeleteACL(ctx context.Context, c *echo.Context, body []byte) error { var req deleteACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -80,7 +82,7 @@ func (h *Handler) handleDeleteACL(ctx context.Context, c *echo.Context, body []b func (h *Handler) handleUpdateACL(ctx context.Context, c *echo.Context, body []byte) error { var req updateACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_acls_test.go b/services/memorydb/handler_acls_test.go index fe9ff8d87b..9de290f055 100644 --- a/services/memorydb/handler_acls_test.go +++ b/services/memorydb/handler_acls_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -105,7 +106,7 @@ func TestHandler_ACL_Tags(t *testing.T) { // Get ACL ARN var aclResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp, jsonx.V1())) aclMap := aclResp["ACL"].(map[string]any) aclARN := aclMap["ARN"].(string) @@ -290,7 +291,7 @@ func TestHandler_Pagination_NextToken(t *testing.T) { rec1 := doRequest(t, h, "DescribeACLs", map[string]any{"MaxResults": 2}) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) acls1, _ := resp1["ACLs"].([]any) assert.Len(t, acls1, 2) nextToken, _ := resp1["NextToken"].(string) @@ -303,7 +304,7 @@ func TestHandler_Pagination_NextToken(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) acls2, _ := resp2["ACLs"].([]any) assert.NotEmpty(t, acls2) diff --git a/services/memorydb/handler_clusters.go b/services/memorydb/handler_clusters.go index 102346888b..7aba7437d3 100644 --- a/services/memorydb/handler_clusters.go +++ b/services/memorydb/handler_clusters.go @@ -2,7 +2,7 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "time" @@ -10,12 +10,13 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateCluster(ctx context.Context, c *echo.Context, body []byte) error { var req createClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -42,7 +43,7 @@ func (h *Handler) handleCreateCluster(ctx context.Context, c *echo.Context, body func (h *Handler) handleDescribeClusters(ctx context.Context, c *echo.Context, body []byte) error { var req describeClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -71,7 +72,7 @@ func (h *Handler) handleDescribeClusters(ctx context.Context, c *echo.Context, b func (h *Handler) handleDeleteCluster(ctx context.Context, c *echo.Context, body []byte) error { var req deleteClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -100,7 +101,7 @@ func (h *Handler) handleDeleteCluster(ctx context.Context, c *echo.Context, body func (h *Handler) handleUpdateCluster(ctx context.Context, c *echo.Context, body []byte) error { var req updateClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -121,7 +122,7 @@ func (h *Handler) handleUpdateCluster(ctx context.Context, c *echo.Context, body func (h *Handler) handleBatchUpdateCluster(ctx context.Context, c *echo.Context, body []byte) error { var req batchUpdateClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -165,7 +166,7 @@ func (h *Handler) handleBatchUpdateCluster(ctx context.Context, c *echo.Context, func (h *Handler) handleFailoverShard(ctx context.Context, c *echo.Context, body []byte) error { var req failoverShardRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -184,7 +185,7 @@ func (h *Handler) handleFailoverShard(ctx context.Context, c *echo.Context, body func (h *Handler) handleListAllowedNodeTypeUpdates(ctx context.Context, c *echo.Context, body []byte) error { var req listAllowedNodeTypeUpdatesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_clusters_lifecycle_test.go b/services/memorydb/handler_clusters_lifecycle_test.go index cb96d1b0d9..a87af2b427 100644 --- a/services/memorydb/handler_clusters_lifecycle_test.go +++ b/services/memorydb/handler_clusters_lifecycle_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -46,7 +47,7 @@ func TestHandler_DescribeClusters_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) assert.Len(t, clusters, tt.wantCount) @@ -88,7 +89,7 @@ func TestHandler_DescribeClusters_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1, jsonx.V1())) // NextToken may be empty or non-empty depending on implementation nextToken, _ := resp1["NextToken"].(string) @@ -98,7 +99,7 @@ func TestHandler_DescribeClusters_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) clusters := resp2["Clusters"].([]any) // At minimum should return a valid response @@ -137,7 +138,7 @@ func TestHandler_DescribeClusters_ShowShards(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) require.Len(t, clusters, 1) @@ -574,7 +575,7 @@ func TestHandler_BatchUpdateCluster_Mixed(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) processed := resp["ProcessedClusters"].([]any) unprocessed := resp["UnprocessedClusters"].([]any) @@ -681,7 +682,7 @@ func TestHandler_Engine_Valkey(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl := resp["Cluster"].(map[string]any) assert.Equal(t, tt.wantEngine, cl["Engine"]) assert.Equal(t, tt.wantVersion, cl["EngineVersion"]) @@ -880,7 +881,7 @@ func TestHandler_UpdateCluster_AutoMinorVersionUpgradeNotSettable(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl := resp["Cluster"].(map[string]any) assert.Equal(t, true, cl["AutoMinorVersionUpgrade"], "no real client can change this via UpdateCluster") } @@ -1076,7 +1077,7 @@ func TestHandler_CreateCluster_RestoreFromSnapshot(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl := resp["Cluster"].(map[string]any) assert.Equal(t, tt.wantEngineVersion, cl["EngineVersion"]) assert.Equal(t, "restored-cluster", cl["Name"]) @@ -1215,7 +1216,7 @@ func TestHandler_BatchUpdateCluster_MissingNames(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) processed := resp["ProcessedClusters"].([]any) unprocessed := resp["UnprocessedClusters"].([]any) assert.Len(t, processed, tt.wantProcessed) diff --git a/services/memorydb/handler_clusters_test.go b/services/memorydb/handler_clusters_test.go index de76d5addd..f716d12c63 100644 --- a/services/memorydb/handler_clusters_test.go +++ b/services/memorydb/handler_clusters_test.go @@ -2,7 +2,7 @@ package memorydb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -58,7 +59,7 @@ func TestHandler_CreateCluster(t *testing.T) { if tt.name == "missing target header" { e := echo.New() - bodyBytes, _ := json.Marshal(tt.body) + bodyBytes, _ := json.Marshal(tt.body, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) rec := httptest.NewRecorder() c := e.NewContext(req, rec) @@ -74,7 +75,7 @@ func TestHandler_CreateCluster(t *testing.T) { if tt.wantCluster { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusterVal, ok := resp["Cluster"] require.True(t, ok, "response should contain Cluster field") @@ -137,7 +138,7 @@ func TestHandler_DescribeClusters(t *testing.T) { if tt.wantCount > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters, ok := resp["Clusters"].([]any) require.True(t, ok) assert.Len(t, clusters, tt.wantCount) @@ -207,7 +208,7 @@ func TestHandler_ClusterResponse_ParameterGroupStatus(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) clusters, _ := resp["Clusters"].([]any) require.Len(t, clusters, 1) cl, _ := clusters[0].(map[string]any) @@ -228,7 +229,7 @@ func TestHandler_ClusterResponse_MultiRegionClusterName(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cl, _ := createResp["Cluster"].(map[string]any) // Field must be present (empty string is fine for a cluster not in multi-region). @@ -250,7 +251,7 @@ func TestHandler_ClusterParameterGroupStatus_InSync(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp, jsonx.V1())) cl, _ := resp["Cluster"].(map[string]any) assert.Equal(t, "in-sync", cl["ParameterGroupStatus"], "default ParameterGroupStatus must be in-sync") @@ -343,7 +344,7 @@ func TestHandler_BatchUpdateCluster(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) processed := resp["ProcessedClusters"].([]any) assert.Len(t, processed, tt.wantProcessed) @@ -403,7 +404,7 @@ func TestHandler_UpdateCluster(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cluster := resp["Cluster"].(map[string]any) assert.Equal(t, "updated", cluster["Description"]) } @@ -562,7 +563,7 @@ func TestHandler_SnsTopicStatusStoredOnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) cluster, _ := updateResp["Cluster"].(map[string]any) gotStatus, _ := cluster["SnsTopicStatus"].(string) @@ -733,7 +734,7 @@ func TestHandler_DeleteCluster_WithSnapshot_EmitsEvent(t *testing.T) { require.Equal(t, http.StatusOK, evRec.Code) var evResp map[string]any - require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &evResp)) + require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &evResp, jsonx.V1())) events, _ := evResp["Events"].([]any) // At least create + delete events. assert.GreaterOrEqual(t, len(events), 2, "expected create and delete events") @@ -789,7 +790,7 @@ func TestHandler_BatchUpdateCluster_Extended(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) processed, _ := resp["ProcessedClusters"].([]any) unprocessed, _ := resp["UnprocessedClusters"].([]any) assert.Len(t, processed, tt.wantProcessed) @@ -873,7 +874,7 @@ func TestHandler_EnginePatchVersion_AllFamilies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl, _ := resp["Cluster"].(map[string]any) assert.Equal(t, tt.wantPatchVersion, cl["EnginePatchVersion"], diff --git a/services/memorydb/handler_engine_versions.go b/services/memorydb/handler_engine_versions.go index 911a9182e0..d82b2f2cb5 100644 --- a/services/memorydb/handler_engine_versions.go +++ b/services/memorydb/handler_engine_versions.go @@ -2,16 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeEngineVersions(ctx context.Context, c *echo.Context, body []byte) error { var req describeEngineVersionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_engine_versions_test.go b/services/memorydb/handler_engine_versions_test.go index ad7258ac70..7a93978f6b 100644 --- a/services/memorydb/handler_engine_versions_test.go +++ b/services/memorydb/handler_engine_versions_test.go @@ -1,12 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DescribeEngineVersions(t *testing.T) { @@ -51,7 +53,7 @@ func TestHandler_DescribeEngineVersions(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions := resp["EngineVersions"].([]any) assert.GreaterOrEqual(t, len(versions), tt.wantMinCount) }) @@ -86,7 +88,7 @@ func TestHandler_DescribeEngineVersions_Filtering(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, _ := resp["EngineVersions"].([]any) assert.GreaterOrEqual(t, len(versions), tt.wantMinCount) @@ -148,7 +150,7 @@ func TestHandler_DescribeEngineVersions_Filtered(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions := resp["EngineVersions"].([]any) assert.GreaterOrEqual(t, len(versions), tt.wantMin) @@ -194,7 +196,7 @@ func TestHandler_DescribeEngineVersions_IncludesValkey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions := resp["EngineVersions"].([]any) assert.GreaterOrEqual(t, len(versions), tt.wantMinCount) @@ -225,7 +227,7 @@ func TestHandler_DescribeEngineVersions_EachHasEngine(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, v := range resp["EngineVersions"].([]any) { ev := v.(map[string]any) diff --git a/services/memorydb/handler_events.go b/services/memorydb/handler_events.go index b10d74a016..5fd0b4822f 100644 --- a/services/memorydb/handler_events.go +++ b/services/memorydb/handler_events.go @@ -2,12 +2,13 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -20,7 +21,7 @@ const defaultDescribeEventsLimit = 100 func (h *Handler) handleDescribeEvents(ctx context.Context, c *echo.Context, body []byte) error { var req describeEventsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_events_test.go b/services/memorydb/handler_events_test.go index c1a8093bb0..aee2c59225 100644 --- a/services/memorydb/handler_events_test.go +++ b/services/memorydb/handler_events_test.go @@ -2,7 +2,7 @@ package memorydb_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -98,7 +99,7 @@ func TestWireEpoch_DescribeEvents_DateIsNumber(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, _ := resp["Events"].([]any) require.NotEmpty(t, events, "expected at least one event for the created cluster") @@ -130,7 +131,7 @@ func TestWireEpoch_DescribeEvents_StartTimeAcceptsEpochNumber(t *testing.T) { "DescribeEvents must accept an epoch-seconds StartTime the way a real SDK client sends it") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, _ := resp["Events"].([]any) assert.NotEmpty(t, events, "StartTime=0 (epoch start) should not filter out the cluster-creation event") @@ -143,7 +144,7 @@ func TestWireEpoch_DescribeEvents_StartTimeAcceptsEpochNumber(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) resp = nil - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, _ = resp["Events"].([]any) assert.Empty(t, events, "a future StartTime should filter out all past events") } @@ -183,7 +184,7 @@ func TestHandler_DescribeEvents(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events := resp["Events"].([]any) assert.Len(t, events, tt.wantCount) }) @@ -242,7 +243,7 @@ func TestHandler_Events_Generated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, _ := resp["Events"].([]any) assert.NotEmpty(t, events, "expected events for %s", tt.wantSrcName) }) @@ -308,7 +309,7 @@ func TestHandler_Events_SourceTypeFiltering(t *testing.T) { require.Equal(t, http.StatusOK, evRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &resp, jsonx.V1())) events, _ := resp["Events"].([]any) assert.GreaterOrEqual(t, len(events), tt.wantEventCount) }) @@ -435,7 +436,7 @@ func TestHandler_Events_AutoPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events := resp["Events"].([]any) require.NotEmpty(t, events, "expected at least one event for source %q type %q", tt.wantSrc, tt.wantType) @@ -501,7 +502,7 @@ func TestHandler_DescribeEvents_Duration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events := resp["Events"].([]any) assert.GreaterOrEqual(t, len(events), tt.wantMinCount) }) diff --git a/services/memorydb/handler_multi_region_cluster_membership_test.go b/services/memorydb/handler_multi_region_cluster_membership_test.go index 46e7c2d026..b8e561cc01 100644 --- a/services/memorydb/handler_multi_region_cluster_membership_test.go +++ b/services/memorydb/handler_multi_region_cluster_membership_test.go @@ -1,12 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_MultiRegionCluster_TLSEnabled verifies TLSEnabled defaults to @@ -60,7 +62,7 @@ func TestHandler_MultiRegionCluster_TLSEnabled(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mrc, _ := resp["MultiRegionCluster"].(map[string]any) require.NotNil(t, mrc) assert.Equal(t, tt.wantTLS, mrc["TLSEnabled"]) @@ -86,7 +88,7 @@ func TestHandler_DescribeMultiRegionClusters_ShowClusterDetails(t *testing.T) { require.Equal(t, http.StatusOK, createMRC.Code) var mrcResp map[string]any - require.NoError(t, json.Unmarshal(createMRC.Body.Bytes(), &mrcResp)) + require.NoError(t, json.Unmarshal(createMRC.Body.Bytes(), &mrcResp, jsonx.V1())) mrc, _ := mrcResp["MultiRegionCluster"].(map[string]any) mrcName, _ := mrc["MultiRegionClusterName"].(string) require.NotEmpty(t, mrcName) @@ -108,7 +110,7 @@ func TestHandler_DescribeMultiRegionClusters_ShowClusterDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mrcs, _ := resp["MultiRegionClusters"].([]any) require.Len(t, mrcs, 1) got, _ := mrcs[0].(map[string]any) @@ -125,7 +127,7 @@ func TestHandler_DescribeMultiRegionClusters_ShowClusterDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mrcs, _ := resp["MultiRegionClusters"].([]any) require.Len(t, mrcs, 1) got, _ := mrcs[0].(map[string]any) @@ -160,7 +162,7 @@ func TestHandler_CreateCluster_MultiRegionClusterName_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["__type"], "MultiRegionClusterNotFoundFault") } @@ -180,7 +182,7 @@ func TestHandler_CreateCluster_MultiRegionClusterName_Linked(t *testing.T) { require.Equal(t, http.StatusOK, createMRC.Code) var mrcResp map[string]any - require.NoError(t, json.Unmarshal(createMRC.Body.Bytes(), &mrcResp)) + require.NoError(t, json.Unmarshal(createMRC.Body.Bytes(), &mrcResp, jsonx.V1())) mrc, _ := mrcResp["MultiRegionCluster"].(map[string]any) mrcName, _ := mrc["MultiRegionClusterName"].(string) require.NotEmpty(t, mrcName) @@ -194,7 +196,7 @@ func TestHandler_CreateCluster_MultiRegionClusterName_Linked(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl, _ := resp["Cluster"].(map[string]any) assert.Equal(t, mrcName, cl["MultiRegionClusterName"]) } diff --git a/services/memorydb/handler_multi_region_clusters.go b/services/memorydb/handler_multi_region_clusters.go index 0f26dea002..bd796ec767 100644 --- a/services/memorydb/handler_multi_region_clusters.go +++ b/services/memorydb/handler_multi_region_clusters.go @@ -2,17 +2,19 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateMultiRegionCluster(ctx context.Context, c *echo.Context, body []byte) error { var req createMultiRegionClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -42,7 +44,7 @@ func (h *Handler) handleCreateMultiRegionCluster(ctx context.Context, c *echo.Co func (h *Handler) handleDeleteMultiRegionCluster(ctx context.Context, c *echo.Context, body []byte) error { var req deleteMultiRegionClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -71,7 +73,7 @@ func (h *Handler) handleDeleteMultiRegionCluster(ctx context.Context, c *echo.Co func (h *Handler) handleDescribeMultiRegionClusters(ctx context.Context, c *echo.Context, body []byte) error { var req describeMultiRegionClustersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -108,7 +110,7 @@ func (h *Handler) handleDescribeMultiRegionClusters(ctx context.Context, c *echo func (h *Handler) handleDescribeMultiRegionParameterGroups(ctx context.Context, c *echo.Context, body []byte) error { var req describeMultiRegionParameterGroupsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -143,7 +145,7 @@ func (h *Handler) handleDescribeMultiRegionParameterGroups(ctx context.Context, func (h *Handler) handleListAllowedMultiRegionClusterUpdates(ctx context.Context, c *echo.Context, body []byte) error { var req listAllowedMultiRegionClusterUpdatesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -170,7 +172,7 @@ func (h *Handler) handleListAllowedMultiRegionClusterUpdates(ctx context.Context func (h *Handler) handleUpdateMultiRegionCluster(ctx context.Context, c *echo.Context, body []byte) error { var req updateMultiRegionClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -196,7 +198,7 @@ func (h *Handler) handleUpdateMultiRegionCluster(ctx context.Context, c *echo.Co func (h *Handler) handleDescribeMultiRegionParameters(ctx context.Context, c *echo.Context, body []byte) error { var req describeMultiRegionParametersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_multi_region_clusters_test.go b/services/memorydb/handler_multi_region_clusters_test.go index b12e81e4c7..03736590fd 100644 --- a/services/memorydb/handler_multi_region_clusters_test.go +++ b/services/memorydb/handler_multi_region_clusters_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -59,7 +60,7 @@ func TestHandler_DescribeMultiRegionParameters_WithGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["MultiRegionParameters"]) } @@ -75,7 +76,7 @@ func TestHandler_ListAllowedMultiRegionClusterUpdates_OK(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var mrcOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mrcOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mrcOut, jsonx.V1())) mrc := mrcOut["MultiRegionCluster"].(map[string]any) mrcName := mrc["MultiRegionClusterName"].(string) @@ -126,7 +127,7 @@ func TestHandler_UpdateMultiRegionCluster_OK(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var mrcOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mrcOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mrcOut, jsonx.V1())) mrc := mrcOut["MultiRegionCluster"].(map[string]any) mrcName := mrc["MultiRegionClusterName"].(string) @@ -137,7 +138,7 @@ func TestHandler_UpdateMultiRegionCluster_OK(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) updated := out["MultiRegionCluster"].(map[string]any) assert.Equal(t, "updated description", updated["Description"]) } @@ -224,7 +225,7 @@ func TestHandler_CreateMultiRegionCluster(t *testing.T) { if tt.wantMRC { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) mrc, ok := resp["MultiRegionCluster"] require.True(t, ok) mrcMap := mrc.(map[string]any) @@ -272,7 +273,7 @@ func TestHandler_DeleteMultiRegionCluster(t *testing.T) { var createResp map[string]any createRec := doRequest(t, h, "DescribeMultiRegionClusters", map[string]any{}) - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusters := createResp["MultiRegionClusters"].([]any) require.Len(t, clusters, 1) clusterName := clusters[0].(map[string]any)["MultiRegionClusterName"].(string) @@ -342,7 +343,7 @@ func TestHandler_DescribeMultiRegionClusters(t *testing.T) { if tt.wantCount > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["MultiRegionClusters"].([]any) assert.Len(t, clusters, tt.wantCount) } @@ -382,7 +383,7 @@ func TestHandler_DescribeMultiRegionParameterGroups(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["MultiRegionParameterGroups"].([]any) assert.Len(t, groups, tt.wantCount) } @@ -414,7 +415,7 @@ func TestHandler_MultiRegionParameterGroups_DefaultSeeded(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, _ := resp["MultiRegionParameterGroups"].([]any) assert.NotEmpty(t, groups, "multi-region parameter group %q should be seeded", tt.pgName) }) @@ -430,7 +431,7 @@ func TestHandler_MultiRegionParameterGroups_DefaultCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, _ := resp["MultiRegionParameterGroups"].([]any) assert.GreaterOrEqual(t, len(groups), 4, "at least 4 default multi-region parameter groups expected") } @@ -457,7 +458,7 @@ func TestHandler_MultiRegionParameters_DefaultNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) params, _ := resp["MultiRegionParameters"].([]any) assert.NotEmpty(t, params, "multi-region parameters should be non-empty for %q", tt.pgName) }) @@ -481,7 +482,7 @@ func TestHandler_MultiRegionParameters_HaveSourceField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) params, _ := resp["MultiRegionParameters"].([]any) require.NotEmpty(t, params) @@ -593,7 +594,7 @@ func TestHandler_DescribeMultiRegionParameterGroups_FilteredAndNotFound(t *testi if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["MultiRegionParameterGroups"].([]any) assert.Len(t, groups, tt.wantCount) } diff --git a/services/memorydb/handler_parameter_groups.go b/services/memorydb/handler_parameter_groups.go index 00c088e52f..f5982753b2 100644 --- a/services/memorydb/handler_parameter_groups.go +++ b/services/memorydb/handler_parameter_groups.go @@ -2,17 +2,19 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateParameterGroup(ctx context.Context, c *echo.Context, body []byte) error { var req createParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -32,10 +34,15 @@ func (h *Handler) handleCreateParameterGroup(ctx context.Context, c *echo.Contex return c.JSON(http.StatusOK, createParameterGroupResponse{ParameterGroup: toParameterGroupObject(pg)}) } -func (h *Handler) handleDescribeParameterGroups(ctx context.Context, c *echo.Context, body []byte) error { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleDescribeParameterGroups( + ctx context.Context, + c *echo.Context, + body []byte, +) error { var req describeParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -63,7 +70,7 @@ func (h *Handler) handleDescribeParameterGroups(ctx context.Context, c *echo.Con func (h *Handler) handleDeleteParameterGroup(ctx context.Context, c *echo.Context, body []byte) error { var req deleteParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -82,7 +89,7 @@ func (h *Handler) handleDeleteParameterGroup(ctx context.Context, c *echo.Contex func (h *Handler) handleUpdateParameterGroup(ctx context.Context, c *echo.Context, body []byte) error { var req updateParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -103,7 +110,7 @@ func (h *Handler) handleUpdateParameterGroup(ctx context.Context, c *echo.Contex func (h *Handler) handleDescribeParameters(ctx context.Context, c *echo.Context, body []byte) error { var req describeParametersRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -135,7 +142,7 @@ func (h *Handler) handleDescribeParameters(ctx context.Context, c *echo.Context, func (h *Handler) handleResetParameterGroup(ctx context.Context, c *echo.Context, body []byte) error { var req resetParameterGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_parameter_groups_test.go b/services/memorydb/handler_parameter_groups_test.go index a661f38af9..a07cc72396 100644 --- a/services/memorydb/handler_parameter_groups_test.go +++ b/services/memorydb/handler_parameter_groups_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -67,7 +68,7 @@ func TestHandler_DescribeParameters_OK(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["Parameters"]) } @@ -595,7 +596,7 @@ func TestHandler_Pagination_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) pgs, _ := resp["ParameterGroups"].([]any) assert.LessOrEqual(t, len(pgs), tt.maxResults) if tt.maxResults >= 5 { @@ -866,7 +867,7 @@ func TestDescribeParameters_Pagination(t *testing.T) { var allParams struct { Parameters []map[string]any `json:"Parameters"` } - require.NoError(t, json.Unmarshal(all.Body.Bytes(), &allParams)) + require.NoError(t, json.Unmarshal(all.Body.Bytes(), &allParams, jsonx.V1())) require.NotEmpty(t, allParams.Parameters) pageSize := len(allParams.Parameters) - 1 @@ -882,7 +883,7 @@ func TestDescribeParameters_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Parameters []map[string]any `json:"Parameters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &first, jsonx.V1())) require.Len(t, first.Parameters, pageSize, "first page must truncate to MaxResults") require.NotEmpty(t, first.NextToken, "NextToken must be set when more parameters remain") @@ -897,7 +898,7 @@ func TestDescribeParameters_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Parameters []map[string]any `json:"Parameters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &second)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &second, jsonx.V1())) require.Len(t, second.Parameters, len(allParams.Parameters)-pageSize, "second page must return the remainder") assert.NotEqual( t, first.Parameters[0]["Name"], second.Parameters[0]["Name"], "no parameter must repeat across pages", diff --git a/services/memorydb/handler_reserved_nodes.go b/services/memorydb/handler_reserved_nodes.go index 0e3e84006e..0c224a4fd0 100644 --- a/services/memorydb/handler_reserved_nodes.go +++ b/services/memorydb/handler_reserved_nodes.go @@ -2,16 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeReservedNodes(ctx context.Context, c *echo.Context, body []byte) error { var req describeReservedNodesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -33,7 +35,7 @@ func (h *Handler) handleDescribeReservedNodes(ctx context.Context, c *echo.Conte func (h *Handler) handleDescribeReservedNodesOfferings(ctx context.Context, c *echo.Context, body []byte) error { var req describeReservedNodesOfferingsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -59,7 +61,7 @@ func (h *Handler) handleDescribeReservedNodesOfferings(ctx context.Context, c *e func (h *Handler) handlePurchaseReservedNodesOffering(ctx context.Context, c *echo.Context, body []byte) error { var req purchaseReservedNodesOfferingRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_reserved_nodes_test.go b/services/memorydb/handler_reserved_nodes_test.go index 13bd235941..faafa8aca4 100644 --- a/services/memorydb/handler_reserved_nodes_test.go +++ b/services/memorydb/handler_reserved_nodes_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -45,7 +46,7 @@ func TestHandler_DescribeReservedNodes(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nodes := resp["ReservedNodes"].([]any) assert.Len(t, nodes, tt.wantCount) @@ -93,7 +94,7 @@ func TestHandler_DescribeReservedNodesOfferings(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) offerings := resp["ReservedNodesOfferings"].([]any) assert.GreaterOrEqual(t, len(offerings), tt.wantMin) @@ -150,7 +151,7 @@ func TestHandler_PurchaseReservedNodesOffering(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rn, ok := resp["ReservedNode"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, rn["NodeType"]) @@ -179,7 +180,7 @@ func TestHandler_PurchaseReservedNodesOffering_ThenDescribe(t *testing.T) { require.Equal(t, http.StatusOK, recDescribe.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &resp, jsonx.V1())) nodes := resp["ReservedNodes"].([]any) assert.Len(t, nodes, 1) @@ -196,7 +197,7 @@ func TestWireEpoch_ReservedNode_StartTimeIsNumber(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rn, _ := resp["ReservedNode"].(map[string]any) require.NotNil(t, rn) @@ -238,7 +239,7 @@ func TestHandler_ReservedNodes_PurchaseAndDescribe(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rn, _ := resp["ReservedNode"].(map[string]any) assert.NotNil(t, rn) assert.NotEmpty(t, rn["ReservationId"]) @@ -257,7 +258,7 @@ func TestHandler_ReservedNodesOfferings_Seeded(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) offerings, _ := resp["ReservedNodesOfferings"].([]any) assert.GreaterOrEqual(t, len(offerings), 3, "at least 3 reserved node offerings should be seeded") } @@ -281,7 +282,7 @@ func TestHandler_ReservedNodes_DescribeAfterPurchase(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) nodes, _ := resp["ReservedNodes"].([]any) assert.Len(t, nodes, 1) @@ -345,7 +346,7 @@ func TestHandler_ReservedNodes_WithPurchase(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nodes := resp["ReservedNodes"].([]any) assert.Len(t, nodes, tt.wantCount) @@ -384,7 +385,7 @@ func TestHandler_ReservedNodesOfferings_Filtered(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) offerings := resp["ReservedNodesOfferings"].([]any) assert.Len(t, offerings, tt.wantCount) @@ -441,7 +442,7 @@ func TestHandler_DescribeReservedNodesOfferings_DurationFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) offerings := resp["ReservedNodesOfferings"].([]any) assert.GreaterOrEqual(t, len(offerings), tt.wantMinCount) diff --git a/services/memorydb/handler_service_updates.go b/services/memorydb/handler_service_updates.go index 5daadc40fb..b758a93d36 100644 --- a/services/memorydb/handler_service_updates.go +++ b/services/memorydb/handler_service_updates.go @@ -2,17 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeServiceUpdates(ctx context.Context, c *echo.Context, body []byte) error { var req describeServiceUpdatesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } updates, err := h.Backend.DescribeServiceUpdates(ctx, &req) diff --git a/services/memorydb/handler_service_updates_test.go b/services/memorydb/handler_service_updates_test.go index 4131cfcbfe..ae16d54868 100644 --- a/services/memorydb/handler_service_updates_test.go +++ b/services/memorydb/handler_service_updates_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -34,7 +35,7 @@ func TestWireEpoch_DescribeServiceUpdates_DatesAreNumbers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates, _ := resp["ServiceUpdates"].([]any) require.NotEmpty(t, updates) @@ -62,7 +63,7 @@ func TestHandler_DescribeServiceUpdates_Basic(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["ServiceUpdates"]) } @@ -125,7 +126,7 @@ func TestHandler_DescribeServiceUpdates_Filtering(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates, _ := resp["ServiceUpdates"].([]any) assert.GreaterOrEqual(t, len(updates), tt.wantMinCount) assert.LessOrEqual(t, len(updates), tt.wantMaxCount) @@ -172,7 +173,7 @@ func TestHandler_DescribeServiceUpdates_Filtered(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates := resp["ServiceUpdates"].([]any) assert.Len(t, updates, tt.wantCount) @@ -227,7 +228,7 @@ func TestHandler_DescribeServiceUpdates_SeededFixtures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates := resp["ServiceUpdates"].([]any) assert.GreaterOrEqual(t, len(updates), tt.wantMinCount) }) @@ -244,7 +245,7 @@ func TestHandler_DescribeServiceUpdates_FieldsPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates := resp["ServiceUpdates"].([]any) require.NotEmpty(t, updates) @@ -316,7 +317,7 @@ func TestHandler_DescribeServiceUpdates_ClusterNamesFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates := resp["ServiceUpdates"].([]any) require.Len(t, updates, tt.wantCount) @@ -352,7 +353,7 @@ func TestHandler_BatchUpdateCluster_ThenDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) updates := resp["ServiceUpdates"].([]any) require.Len(t, updates, 1) assert.Equal(t, "complete", updates[0].(map[string]any)["Status"]) diff --git a/services/memorydb/handler_shards_test.go b/services/memorydb/handler_shards_test.go index 91f44d8580..e6d1f3b424 100644 --- a/services/memorydb/handler_shards_test.go +++ b/services/memorydb/handler_shards_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -69,7 +70,7 @@ func TestHandler_ListAllowedNodeTypeUpdates_OK(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out["ScaleUpNodeTypes"]) assert.NotNil(t, out["ScaleDownNodeTypes"]) } @@ -114,7 +115,7 @@ func TestHandler_FailoverShard_EmitsEvent_ViaMinimalBody(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "failover: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl, _ := resp["Cluster"].(map[string]any) assert.Equal(t, "failover-cluster", cl["Name"]) @@ -124,7 +125,7 @@ func TestHandler_FailoverShard_EmitsEvent_ViaMinimalBody(t *testing.T) { }) require.Equal(t, http.StatusOK, evRec.Code) var evResp map[string]any - require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &evResp)) + require.NoError(t, json.Unmarshal(evRec.Body.Bytes(), &evResp, jsonx.V1())) events, _ := evResp["Events"].([]any) assert.GreaterOrEqual(t, len(events), 2, "create + failover events expected") } @@ -277,7 +278,7 @@ func TestHandler_UpdateCluster_ShardConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl := resp["Cluster"].(map[string]any) assert.InDelta(t, tt.wantShards, cl["NumberOfShards"], 0) assert.InDelta(t, tt.wantReplicas, firstShardReplicaCount(t, cl), 0) @@ -335,7 +336,7 @@ func TestHandler_DescribeClusters_ShowShardDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) require.Len(t, clusters, 1) cl := clusters[0].(map[string]any) @@ -398,7 +399,7 @@ func TestHandler_ShardNodes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) clusters := resp["Clusters"].([]any) cl := clusters[0].(map[string]any) @@ -489,7 +490,7 @@ func TestHandler_FailoverShard(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Cluster"]) } }) @@ -520,7 +521,7 @@ func TestHandler_FailoverShard_EmitsEvent(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) events := resp["Events"].([]any) found := false @@ -584,7 +585,7 @@ func TestHandler_ListAllowedNodeTypeUpdates(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["ScaleUpNodeTypes"]) assert.NotNil(t, resp["ScaleDownNodeTypes"]) } diff --git a/services/memorydb/handler_snapshots.go b/services/memorydb/handler_snapshots.go index 577ddc606c..08df00abf6 100644 --- a/services/memorydb/handler_snapshots.go +++ b/services/memorydb/handler_snapshots.go @@ -2,16 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var req createSnapshotRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -38,7 +40,7 @@ func (h *Handler) handleCreateSnapshot(ctx context.Context, c *echo.Context, bod func (h *Handler) handleCopySnapshot(ctx context.Context, c *echo.Context, body []byte) error { var req copySnapshotRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -70,7 +72,7 @@ func (h *Handler) handleCopySnapshot(ctx context.Context, c *echo.Context, body func (h *Handler) handleDeleteSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var req deleteSnapshotRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -89,7 +91,7 @@ func (h *Handler) handleDeleteSnapshot(ctx context.Context, c *echo.Context, bod func (h *Handler) handleDescribeSnapshots(ctx context.Context, c *echo.Context, body []byte) error { var req describeSnapshotRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -124,7 +126,7 @@ func (h *Handler) handleDescribeSnapshots(ctx context.Context, c *echo.Context, func (h *Handler) handleExportSnapshot(ctx context.Context, c *echo.Context, body []byte) error { var req exportSnapshotRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_snapshots_test.go b/services/memorydb/handler_snapshots_test.go index 4e4b8b8273..d0a7f1a662 100644 --- a/services/memorydb/handler_snapshots_test.go +++ b/services/memorydb/handler_snapshots_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "sync" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -41,7 +42,7 @@ func TestWireShape_Snapshot_NoTopLevelCreationTime_HasDataTiering(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snap, _ := resp["Snapshot"].(map[string]any) require.NotNil(t, snap) @@ -146,7 +147,7 @@ func TestHandler_ExportSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "export snapshot: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snap, _ := resp["Snapshot"].(map[string]any) assert.Equal(t, "export-snap", snap["Name"], "Snapshot.Name must match") assert.NotEmpty(t, snap["ARN"], "Snapshot.ARN must be present") @@ -236,7 +237,7 @@ func TestHandler_CreateSnapshot(t *testing.T) { if tt.wantSnapshot { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snap, ok := resp["Snapshot"] require.True(t, ok) snapMap := snap.(map[string]any) @@ -309,7 +310,7 @@ func TestHandler_CopySnapshot(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snap := resp["Snapshot"].(map[string]any) assert.Equal(t, tt.wantName, snap["Name"]) } @@ -523,7 +524,7 @@ func TestHandler_Snapshot_FilterBySource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps, _ := resp["Snapshots"].([]any) assert.GreaterOrEqual(t, len(snaps), tt.wantMinResults, "filter=%q: expected >= %d snapshots", tt.filterSource, tt.wantMinResults) @@ -627,7 +628,7 @@ func TestHandler_DescribeSnapshots_Filtered(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snapshots := resp["Snapshots"].([]any) assert.Len(t, snapshots, tt.wantCount) @@ -711,7 +712,7 @@ func TestHandler_AutomatedSnapshot_OnCreate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) if tt.wantAutoSnap { @@ -785,7 +786,7 @@ func TestHandler_DescribeSnapshots_SourceFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) assert.GreaterOrEqual(t, len(snaps), tt.wantMinCount) @@ -999,7 +1000,7 @@ func TestHandler_DescribeSnapshots_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) assert.Len(t, snaps, 2) assert.NotEmpty(t, resp["NextToken"]) diff --git a/services/memorydb/handler_subnet_groups.go b/services/memorydb/handler_subnet_groups.go index 424fc4e25a..2bd198102e 100644 --- a/services/memorydb/handler_subnet_groups.go +++ b/services/memorydb/handler_subnet_groups.go @@ -2,19 +2,20 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateSubnetGroup(ctx context.Context, c *echo.Context, body []byte) error { var req createSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -34,10 +35,15 @@ func (h *Handler) handleCreateSubnetGroup(ctx context.Context, c *echo.Context, return c.JSON(http.StatusOK, createSubnetGroupResponse{SubnetGroup: toSubnetGroupObject(sg)}) } -func (h *Handler) handleDescribeSubnetGroups(ctx context.Context, c *echo.Context, body []byte) error { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleDescribeSubnetGroups( + ctx context.Context, + c *echo.Context, + body []byte, +) error { var req describeSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -60,7 +66,7 @@ func (h *Handler) handleDescribeSubnetGroups(ctx context.Context, c *echo.Contex func (h *Handler) handleDeleteSubnetGroup(ctx context.Context, c *echo.Context, body []byte) error { var req deleteSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -79,7 +85,7 @@ func (h *Handler) handleDeleteSubnetGroup(ctx context.Context, c *echo.Context, func (h *Handler) handleUpdateSubnetGroup(ctx context.Context, c *echo.Context, body []byte) error { var req updateSubnetGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_subnet_groups_test.go b/services/memorydb/handler_subnet_groups_test.go index e61d074d47..490e3ccc97 100644 --- a/services/memorydb/handler_subnet_groups_test.go +++ b/services/memorydb/handler_subnet_groups_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -283,7 +284,7 @@ func TestHandler_SubnetGroup_SupportedNetworkTypesAndAvailabilityZone(t *testing require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sg, _ := resp["SubnetGroup"].(map[string]any) require.NotNil(t, sg) diff --git a/services/memorydb/handler_tags.go b/services/memorydb/handler_tags.go index fe75c677d4..70160154c8 100644 --- a/services/memorydb/handler_tags.go +++ b/services/memorydb/handler_tags.go @@ -2,16 +2,18 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTags(ctx context.Context, c *echo.Context, body []byte) error { var req listTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -30,7 +32,7 @@ func (h *Handler) handleListTags(ctx context.Context, c *echo.Context, body []by func (h *Handler) handleTagResource(ctx context.Context, c *echo.Context, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -60,7 +62,7 @@ func (h *Handler) handleTagResource(ctx context.Context, c *echo.Context, body [ func (h *Handler) handleUntagResource(ctx context.Context, c *echo.Context, body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_tags_test.go b/services/memorydb/handler_tags_test.go index 9c185347d6..018ded1354 100644 --- a/services/memorydb/handler_tags_test.go +++ b/services/memorydb/handler_tags_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -42,7 +43,7 @@ func TestHandler_Tags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) clusterMap := createResp["Cluster"].(map[string]any) clusterARN := clusterMap["ARN"].(string) @@ -52,7 +53,7 @@ func TestHandler_Tags(t *testing.T) { if tt.wantStatus == http.StatusOK { var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) tagList := tagsResp["TagList"].([]any) require.NotEmpty(t, tagList) } @@ -470,7 +471,7 @@ func TestHandler_CreateCluster_TagsStored(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tagList, _ := listResp["TagList"].([]any) require.Len(t, tagList, 2) @@ -504,7 +505,7 @@ func TestHandler_Tags_AllResources(t *testing.T) { "Tags": []map[string]any{{"Key": "Env", "Value": "prod"}}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["SubnetGroup"].(map[string]any)["ARN"].(string) }, @@ -523,7 +524,7 @@ func TestHandler_Tags_AllResources(t *testing.T) { "Tags": []map[string]any{{"Key": "Env", "Value": "prod"}}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["User"].(map[string]any)["ARN"].(string) }, @@ -538,7 +539,7 @@ func TestHandler_Tags_AllResources(t *testing.T) { "Tags": []map[string]any{{"Key": "Env", "Value": "prod"}}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ParameterGroup"].(map[string]any)["ARN"].(string) }, @@ -559,7 +560,7 @@ func TestHandler_Tags_AllResources(t *testing.T) { "Tags": []map[string]any{{"Key": "Env", "Value": "prod"}}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Snapshot"].(map[string]any)["ARN"].(string) }, @@ -578,7 +579,7 @@ func TestHandler_Tags_AllResources(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagList := resp["TagList"].([]any) assert.NotEmpty(t, tagList) @@ -664,7 +665,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "create: %s", rec.Body) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) resourceARN := tt.getARN(createResp) require.NotEmpty(t, resourceARN) @@ -683,7 +684,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagList, _ := listResp["TagList"].([]any) assert.Len(t, tagList, 2) @@ -697,7 +698,7 @@ func TestHandler_TagOperations(t *testing.T) { // Verify only 1 tag remains. listRec2 := doRequest(t, h, "ListTags", map[string]any{"ResourceArn": resourceARN}) require.Equal(t, http.StatusOK, listRec2.Code) - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp, jsonx.V1())) tagList2, _ := listResp["TagList"].([]any) assert.Len(t, tagList2, 1) }) @@ -794,7 +795,7 @@ func TestHandler_TagResource_Limit(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clusterMap := createResp["Cluster"].(map[string]any) arn := clusterMap["ARN"].(string) @@ -890,7 +891,7 @@ func TestHandler_Tags_Extended(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagList := resp["TagList"].([]any) tags := make(map[string]string) for _, t := range tagList { @@ -931,7 +932,7 @@ func TestHandler_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tagList := resp["TagList"].([]any) assert.Len(t, tagList, 1) tag := tagList[0].(map[string]any) diff --git a/services/memorydb/handler_test.go b/services/memorydb/handler_test.go index 6d99adc725..d367ccffd6 100644 --- a/services/memorydb/handler_test.go +++ b/services/memorydb/handler_test.go @@ -2,7 +2,7 @@ package memorydb_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -23,7 +24,7 @@ func doCreateCluster(t *testing.T, h *memorydb.Handler, body map[string]any) (ma t.Helper() rec := doRequest(t, h, "CreateCluster", body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp, rec.Code } @@ -37,7 +38,7 @@ func doDescribeUsers(t *testing.T, h *memorydb.Handler, userName string) []any { rec := doRequest(t, h, "DescribeUsers", body) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) users, _ := resp["Users"].([]any) return users @@ -52,7 +53,7 @@ func doDescribeACLs(t *testing.T, h *memorydb.Handler, aclName string) []any { rec := doRequest(t, h, "DescribeACLs", body) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) acls, _ := resp["ACLs"].([]any) return acls @@ -67,7 +68,7 @@ func doDescribeSnapshots(t *testing.T, h *memorydb.Handler, snapshotName string) rec := doRequest(t, h, "DescribeSnapshots", body) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps, _ := resp["Snapshots"].([]any) return snaps @@ -82,7 +83,7 @@ func doDescribeParameterGroups(t *testing.T, h *memorydb.Handler, pgName string) rec := doRequest(t, h, "DescribeParameterGroups", body) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pgs, _ := resp["ParameterGroups"].([]any) return pgs @@ -93,7 +94,7 @@ func doDescribeParameters(t *testing.T, h *memorydb.Handler, pgName string) []an rec := doRequest(t, h, "DescribeParameters", map[string]any{"ParameterGroupName": pgName}) require.Equal(t, http.StatusOK, rec.Code, "DescribeParameters failed: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) params, _ := resp["Parameters"].([]any) return params @@ -130,7 +131,7 @@ func doRequest(t *testing.T, h *memorydb.Handler, op string, body any) *httptest if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -238,7 +239,7 @@ func TestHandler_Infrastructure(t *testing.T) { // callers use this instead and assert on the result back in the main // goroutine. func doRequestAsync(h *memorydb.Handler, op string, body any) (*httptest.ResponseRecorder, error) { - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) if err != nil { return nil, err } @@ -280,7 +281,7 @@ func collectSnapshotARNs(t *testing.T, h *memorydb.Handler) []string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps, _ := resp["Snapshots"].([]any) @@ -383,7 +384,7 @@ func TestExtractResourceSnapshotName(t *testing.T) { func doRequestNoTarget(t *testing.T, h *memorydb.Handler, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -409,7 +410,7 @@ func doRequestWithServiceHeader( //nolint:unused // existing issue. ) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -554,7 +555,7 @@ func TestHandler_ExtractResource(t *testing.T) { t.Parallel() h := newTestHandler(t) - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -741,7 +742,7 @@ func createCluster(t *testing.T, h *memorydb.Handler, body map[string]any) map[s require.Equal(t, http.StatusOK, rec.Code, "create cluster failed: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -818,7 +819,7 @@ func TestPaginationAcrossResourceTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Find the list field. var items []any diff --git a/services/memorydb/handler_users.go b/services/memorydb/handler_users.go index 3217145680..d469649a1d 100644 --- a/services/memorydb/handler_users.go +++ b/services/memorydb/handler_users.go @@ -2,18 +2,20 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/v2" "math" "net/http" "slices" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateUser(ctx context.Context, c *echo.Context, body []byte) error { var req createUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -36,7 +38,7 @@ func (h *Handler) handleCreateUser(ctx context.Context, c *echo.Context, body [] func (h *Handler) handleDescribeUsers(ctx context.Context, c *echo.Context, body []byte) error { var req describeUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -92,7 +94,7 @@ func filterUsersByName(users []*User, filters []userFilterReq) []*User { func (h *Handler) handleDeleteUser(ctx context.Context, c *echo.Context, body []byte) error { var req deleteUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -111,7 +113,7 @@ func (h *Handler) handleDeleteUser(ctx context.Context, c *echo.Context, body [] func (h *Handler) handleUpdateUser(ctx context.Context, c *echo.Context, body []byte) error { var req updateUserRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/memorydb/handler_users_test.go b/services/memorydb/handler_users_test.go index 7448593fe0..91b3d0c270 100644 --- a/services/memorydb/handler_users_test.go +++ b/services/memorydb/handler_users_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -42,7 +43,7 @@ func TestWireAuthType_DefaultUserIsNoPassword(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) user, _ := resp["User"].(map[string]any) auth, _ := user["Authentication"].(map[string]any) @@ -86,7 +87,7 @@ func TestWireAuthType_UpdateUserValidatesType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) user, _ := resp["User"].(map[string]any) auth, _ := user["Authentication"].(map[string]any) assert.Equal(t, "no-password", auth["Type"]) @@ -172,7 +173,7 @@ func TestHandler_UserResponse_NoEngineField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) u, _ := resp["User"].(map[string]any) _, hasEngine := u["Engine"] assert.False(t, hasEngine, "Engine must not appear in the User response (not part of the real SDK type)") @@ -192,7 +193,7 @@ func TestHandler_UserResponse_ACLNames_CreateReturnsEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) u, _ := resp["User"].(map[string]any) aclNames, hasField := u["ACLNames"] diff --git a/services/memorydb/lifecycle_test.go b/services/memorydb/lifecycle_test.go index 5cd2491ff2..161e50989a 100644 --- a/services/memorydb/lifecycle_test.go +++ b/services/memorydb/lifecycle_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -29,7 +30,7 @@ func TestCluster_LifecycleDelay_Default(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cl, _ := resp["Cluster"].(map[string]any) assert.Equal(t, "available", cl["Status"]) } @@ -63,7 +64,7 @@ func TestCluster_LifecycleDelay_CreatingThenAvailable(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, "body: %s", createRec.Body) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cl, _ := createResp["Cluster"].(map[string]any) assert.Equal(t, "creating", cl["Status"], "CreateCluster response must show creating while pending") @@ -72,7 +73,7 @@ func TestCluster_LifecycleDelay_CreatingThenAvailable(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) clusters, _ := descResp["Clusters"].([]any) require.Len(t, clusters, 1) got, _ := clusters[0].(map[string]any) @@ -85,7 +86,7 @@ func TestCluster_LifecycleDelay_CreatingThenAvailable(t *testing.T) { require.Equal(t, http.StatusOK, afterRec.Code) var afterResp map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterResp)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterResp, jsonx.V1())) afterClusters, _ := afterResp["Clusters"].([]any) require.Len(t, afterClusters, 1) afterGot, _ := afterClusters[0].(map[string]any) @@ -123,7 +124,7 @@ func TestCluster_LifecycleDelay_UpdateAndUnrelatedOpsObserveOverlay(t *testing.T require.Equal(t, http.StatusOK, updateRec.Code, "body: %s", updateRec.Body) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) cl, _ := updateResp["Cluster"].(map[string]any) assert.Equal(t, "creating", cl["Status"], "UpdateCluster response must still observe the pending status") assert.Equal(t, "updated while creating", cl["Description"]) diff --git a/services/memorydb/models_events.go b/services/memorydb/models_events.go index c6a1a00d64..4c73580c72 100644 --- a/services/memorydb/models_events.go +++ b/services/memorydb/models_events.go @@ -1,8 +1,9 @@ package memorydb import ( - "encoding/json" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Event represents a MemoryDB event. @@ -19,13 +20,13 @@ type Event struct { // requires a quoted RFC3339 string -- decoding a real client's request would // fail with a SerializationException. See parseEpochRequestTime (backend.go). type describeEventsRequest struct { - MaxResults *int32 `json:"MaxResults,omitempty"` - Duration *int32 `json:"Duration,omitempty"` - StartTime json.Number `json:"StartTime,omitempty"` - EndTime json.Number `json:"EndTime,omitempty"` - SourceName string `json:"SourceName,omitempty"` - SourceType string `json:"SourceType,omitempty"` - NextToken string `json:"NextToken,omitempty"` + MaxResults *int32 `json:"MaxResults,omitempty"` + Duration *int32 `json:"Duration,omitempty"` + StartTime jsonx.Number `json:"StartTime,omitempty"` + EndTime jsonx.Number `json:"EndTime,omitempty"` + SourceName string `json:"SourceName,omitempty"` + SourceType string `json:"SourceType,omitempty"` + NextToken string `json:"NextToken,omitempty"` } // eventObject.Date is epoch seconds (float64), matching the real diff --git a/services/memorydb/parameter_groups_test.go b/services/memorydb/parameter_groups_test.go index de0b417a5f..37a4b30e06 100644 --- a/services/memorydb/parameter_groups_test.go +++ b/services/memorydb/parameter_groups_test.go @@ -2,13 +2,14 @@ package memorydb_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -118,7 +119,7 @@ func TestDescribeParameters_Sorted(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) params, _ := out["Parameters"].([]any) for i := 1; i < len(params); i++ { diff --git a/services/memorydb/persistence.go b/services/memorydb/persistence.go index 3820a8d821..2f70cfe712 100644 --- a/services/memorydb/persistence.go +++ b/services/memorydb/persistence.go @@ -2,8 +2,10 @@ package memorydb import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -32,7 +34,7 @@ const memorydbSnapshotVersion = 1 // [store.Table.Snapshot] slice. type backendSnapshot struct { ParameterGroups map[string][]*ParameterGroup `json:"parameterGroups"` - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Clusters map[string][]*Cluster `json:"clusters"` ACLs map[string][]*ACL `json:"acls"` SubnetGroups map[string][]*SubnetGroup `json:"subnetGroups"` @@ -89,7 +91,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { DefaultRegion: b.defaultRegion, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "memorydb: failed to marshal snapshot", "error", err) diff --git a/services/memorydb/persistence_test.go b/services/memorydb/persistence_test.go index 22f8e93a07..99d3ce0a01 100644 --- a/services/memorydb/persistence_test.go +++ b/services/memorydb/persistence_test.go @@ -1,13 +1,14 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -259,10 +260,10 @@ func TestInMemoryBackend_Restore_IncompatibleVersion(t *testing.T) { // current memorydbSnapshotVersion constant. var raw map[string]any - require.NoError(t, json.Unmarshal(original.Snapshot(ctx), &raw)) + require.NoError(t, json.Unmarshal(original.Snapshot(ctx), &raw, jsonx.V1())) raw["version"] = -1 - badSnap, err := json.Marshal(raw) + badSnap, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) fresh := memorydb.NewInMemoryBackend("123456789012", "us-east-1") diff --git a/services/memorydb/shard_detail_test.go b/services/memorydb/shard_detail_test.go index ef4d7fe011..18dcee75b9 100644 --- a/services/memorydb/shard_detail_test.go +++ b/services/memorydb/shard_detail_test.go @@ -1,7 +1,7 @@ package memorydb_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -16,7 +17,7 @@ func decodeBody(t *testing.T, body []byte) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out } diff --git a/services/memorydb/snapshots_test.go b/services/memorydb/snapshots_test.go index fadb512861..aa93a4e590 100644 --- a/services/memorydb/snapshots_test.go +++ b/services/memorydb/snapshots_test.go @@ -2,13 +2,14 @@ package memorydb_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -56,7 +57,7 @@ func TestDescribeSnapshots(t *testing.T) { if tt.wantCount > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) assert.Len(t, snaps, tt.wantCount) } @@ -99,7 +100,7 @@ func TestDescribeSnapshotNoTopLevelCreationTimeField(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) snaps := resp["Snapshots"].([]any) require.Len(t, snaps, 1) diff --git a/services/memorydb/tags_test.go b/services/memorydb/tags_test.go index 334cd811da..068783db24 100644 --- a/services/memorydb/tags_test.go +++ b/services/memorydb/tags_test.go @@ -2,13 +2,14 @@ package memorydb_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/memorydb" ) @@ -108,7 +109,7 @@ func TestTagResourceReturnsTagList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagList, ok := resp["TagList"].([]any) require.True(t, ok) @@ -138,7 +139,7 @@ func TestUntagResourceReturnsTagList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagList := resp["TagList"].([]any) assert.Len(t, tagList, 1) diff --git a/services/mgn/handler.go b/services/mgn/handler.go index 65fd2ddf8d..a653293bd2 100644 --- a/services/mgn/handler.go +++ b/services/mgn/handler.go @@ -2,7 +2,7 @@ package mgn import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -239,7 +240,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { addErrorFields(body, apiErr) } - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) @@ -385,14 +386,14 @@ func decodeJSONBody(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validationError("malformed request body: " + err.Error()) } var pg struct { NextToken string `json:"nextToken"` } - if json.Unmarshal(body, &pg) == nil && page.ValidateToken(pg.NextToken) != nil { + if json.Unmarshal(body, &pg, jsonx.V1()) == nil && page.ValidateToken(pg.NextToken) != nil { return validationError("Invalid nextToken") } @@ -402,7 +403,7 @@ func decodeJSONBody(body []byte, v any) error { // marshalResponse marshals v, wrapping any error as an internal failure // (marshaling our own well-typed wire structs should never fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, internalServerError("mgn: marshal response: " + err.Error()) } diff --git a/services/mgn/multi_region_test.go b/services/mgn/multi_region_test.go index 467dace111..bb52c77f2f 100644 --- a/services/mgn/multi_region_test.go +++ b/services/mgn/multi_region_test.go @@ -2,7 +2,8 @@ package mgn_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/mgn" ) @@ -183,8 +185,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/mgn/persistence.go b/services/mgn/persistence.go index 1df02b9f57..c3115392e1 100644 --- a/services/mgn/persistence.go +++ b/services/mgn/persistence.go @@ -2,7 +2,7 @@ package mgn import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -19,11 +19,11 @@ const mgnSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the MGN backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` - ServiceInitialized bool `json:"serviceInitialized"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` + ServiceInitialized bool `json:"serviceInitialized"` } // Snapshot serialises the backend state to JSON. diff --git a/services/mgn/s3import_launch.go b/services/mgn/s3import_launch.go index f731522c9c..e081a81957 100644 --- a/services/mgn/s3import_launch.go +++ b/services/mgn/s3import_launch.go @@ -2,13 +2,16 @@ package mgn import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -388,10 +391,9 @@ func parseActionParameters(raw string) (map[string]string, error) { ExternalParameters map[string]string `json:"externalParameters"` } - dec := json.NewDecoder(bytes.NewReader([]byte(raw))) - dec.DisallowUnknownFields() + dec := jsontext.NewDecoder(bytes.NewReader([]byte(raw))) - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), json.RejectUnknownMembers(true)); err != nil { return nil, fmt.Errorf("%w: %w", errImportBadParameters, err) } diff --git a/services/mgn/wire_field_fixes_test.go b/services/mgn/wire_field_fixes_test.go index 924cf8026c..29405e5ab1 100644 --- a/services/mgn/wire_field_fixes_test.go +++ b/services/mgn/wire_field_fixes_test.go @@ -2,7 +2,8 @@ package mgn_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/mgn" @@ -37,7 +39,7 @@ func newRawServer(t *testing.T, h *mgn.Handler) string { func rawPost(t *testing.T, url string, body map[string]any) map[string]any { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, url, bytes.NewReader(raw)) @@ -50,7 +52,7 @@ func rawPost(t *testing.T, url string, body map[string]any) map[string]any { t.Cleanup(func() { _ = resp.Body.Close() }) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) require.Equal(t, http.StatusOK, resp.StatusCode, "response: %v", out) return out diff --git a/services/mq/broker_pagination_test.go b/services/mq/broker_pagination_test.go index 99fd89e905..8af5a254dc 100644 --- a/services/mq/broker_pagination_test.go +++ b/services/mq/broker_pagination_test.go @@ -4,7 +4,8 @@ package mq_test // continuation across pages, maxResults limits, and default listing. import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mq" ) @@ -54,11 +56,11 @@ func TestListBrokers_PaginationOpaqueToken(t *testing.T) { rec := doRequest(t, h, http.MethodGet, path, nil) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var brokers []any - require.NoError(t, json.Unmarshal(out["brokerSummaries"], &brokers)) + require.NoError(t, json.Unmarshal(out["brokerSummaries"], &brokers, jsonx.V1())) assert.Len(t, brokers, tt.wantCount) rawToken, hasToken := out["nextToken"] @@ -66,7 +68,7 @@ func TestListBrokers_PaginationOpaqueToken(t *testing.T) { require.True(t, hasToken, "nextToken must be present when more pages exist") var tok string - require.NoError(t, json.Unmarshal(rawToken, &tok)) + require.NoError(t, json.Unmarshal(rawToken, &tok, jsonx.V1())) assert.NotEmpty(t, tok) // Token must be opaque base64 integer, not a broker name. @@ -74,7 +76,7 @@ func TestListBrokers_PaginationOpaqueToken(t *testing.T) { assert.Equal(t, tt.wantCount, offset, "token encodes offset = items returned so far") } else if hasToken { var tok string - require.NoError(t, json.Unmarshal(rawToken, &tok)) + require.NoError(t, json.Unmarshal(rawToken, &tok, jsonx.V1())) assert.Empty(t, tok, "nextToken must be empty string when no more pages") } }) @@ -92,27 +94,27 @@ func TestListBrokers_PaginationContinuation(t *testing.T) { rec1 := doRequest(t, h, http.MethodGet, "/v1/brokers?maxResults=2", nil) require.Equal(t, http.StatusOK, rec1.Code) - var page1 map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + var page1 map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) var tok string - require.NoError(t, json.Unmarshal(page1["nextToken"], &tok)) + require.NoError(t, json.Unmarshal(page1["nextToken"], &tok, jsonx.V1())) require.NotEmpty(t, tok) rec2 := doRequest(t, h, http.MethodGet, "/v1/brokers?maxResults=2&nextToken="+tok, nil) require.Equal(t, http.StatusOK, rec2.Code) - var page2 map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + var page2 map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) var brokers2 []any - require.NoError(t, json.Unmarshal(page2["brokerSummaries"], &brokers2)) + require.NoError(t, json.Unmarshal(page2["brokerSummaries"], &brokers2, jsonx.V1())) assert.Len(t, brokers2, 1, "page 2 should contain the remaining broker") // No further pages. if rawTok2, ok := page2["nextToken"]; ok { var tok2 string - require.NoError(t, json.Unmarshal(rawTok2, &tok2)) + require.NoError(t, json.Unmarshal(rawTok2, &tok2, jsonx.V1())) assert.Empty(t, tok2, "no nextToken on final page") } } diff --git a/services/mq/configurations_test.go b/services/mq/configurations_test.go index fe6441e74c..88d2eff153 100644 --- a/services/mq/configurations_test.go +++ b/services/mq/configurations_test.go @@ -2,7 +2,8 @@ package mq_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mq" ) @@ -79,7 +81,7 @@ func TestUpdateConfiguration(t *testing.T) { updateBody, err := json.Marshal(map[string]any{ "description": "updated description", "data": "...", - }) + }, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -331,11 +333,11 @@ func TestListConfigurations_PaginationOpaqueToken(t *testing.T) { rec := doRequest(t, h, http.MethodGet, path, nil) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var cfgs []any - require.NoError(t, json.Unmarshal(out["configurations"], &cfgs)) + require.NoError(t, json.Unmarshal(out["configurations"], &cfgs, jsonx.V1())) assert.Len(t, cfgs, tt.wantCount) rawToken, hasToken := out["nextToken"] @@ -343,13 +345,13 @@ func TestListConfigurations_PaginationOpaqueToken(t *testing.T) { require.True(t, hasToken) var tok string - require.NoError(t, json.Unmarshal(rawToken, &tok)) + require.NoError(t, json.Unmarshal(rawToken, &tok, jsonx.V1())) offset := decodeOpaqueToken(t, tok) assert.Equal(t, tt.wantCount, offset) } else if hasToken { var tok string - require.NoError(t, json.Unmarshal(rawToken, &tok)) + require.NoError(t, json.Unmarshal(rawToken, &tok, jsonx.V1())) assert.Empty(t, tok) } }) diff --git a/services/mq/handler_brokers.go b/services/mq/handler_brokers.go index 22394e9104..83f38e9eaf 100644 --- a/services/mq/handler_brokers.go +++ b/services/mq/handler_brokers.go @@ -1,7 +1,7 @@ package mq import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "sort" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -39,7 +40,7 @@ type createBrokerInput struct { func (h *Handler) handleCreateBroker(c *echo.Context, body []byte) error { var in createBrokerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -233,7 +234,7 @@ type updateBrokerResponse struct { func (h *Handler) handleUpdateBroker(c *echo.Context, brokerID string, body []byte) error { var in updateBrokerInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -550,7 +551,7 @@ type promoteInput struct { func (h *Handler) handlePromote(c *echo.Context, brokerID string, body []byte) error { var in promoteInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mq/handler_configurations.go b/services/mq/handler_configurations.go index 32a8c2702e..4c082a8c13 100644 --- a/services/mq/handler_configurations.go +++ b/services/mq/handler_configurations.go @@ -1,7 +1,7 @@ package mq import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -25,7 +26,7 @@ type createConfigurationInput struct { func (h *Handler) handleCreateConfiguration(c *echo.Context, body []byte) error { var in createConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body"), @@ -122,7 +123,7 @@ type updateConfigurationInput struct { func (h *Handler) handleUpdateConfiguration(c *echo.Context, configID string, body []byte) error { var in updateConfigurationInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON( http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body"), diff --git a/services/mq/handler_tags.go b/services/mq/handler_tags.go index e2fd087cce..f9a87f9b49 100644 --- a/services/mq/handler_tags.go +++ b/services/mq/handler_tags.go @@ -1,10 +1,12 @@ package mq import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTags(c *echo.Context, resourceARN string) error { @@ -22,7 +24,7 @@ type createTagsInput struct { func (h *Handler) handleCreateTags(c *echo.Context, resourceARN string, body []byte) error { var in createTagsInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mq/handler_test.go b/services/mq/handler_test.go index 8d5443f2eb..b84bfcb03a 100644 --- a/services/mq/handler_test.go +++ b/services/mq/handler_test.go @@ -3,7 +3,7 @@ package mq_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mq" ) @@ -47,7 +48,7 @@ func doRequest(t *testing.T, h *mq.Handler, method, path string, body any) *http var reqBody *bytes.Reader if body != nil { - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(bodyBytes) } else { @@ -92,7 +93,7 @@ func parseResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/mq/handler_users.go b/services/mq/handler_users.go index 4802132281..fbe69684dc 100644 --- a/services/mq/handler_users.go +++ b/services/mq/handler_users.go @@ -1,13 +1,14 @@ package mq import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -27,7 +28,7 @@ type createUserBody struct { func (h *Handler) handleCreateUser(c *echo.Context, brokerID, username string, body []byte) error { var in createUserBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } @@ -83,7 +84,7 @@ type updateUserBody struct { func (h *Handler) handleUpdateUser(c *echo.Context, brokerID, username string, body []byte) error { var in updateUserBody - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return c.JSON(http.StatusBadRequest, errorResponse("BadRequestException", "invalid request body")) } diff --git a/services/mq/models.go b/services/mq/models.go index a3e02c9d10..eeab71ccef 100644 --- a/services/mq/models.go +++ b/services/mq/models.go @@ -1,8 +1,10 @@ package mq import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -245,7 +247,7 @@ func (l LdapServerMetadata) MarshalJSON() ([]byte, error) { a := alias(l) a.ServiceAccountPassword = "" - b, err := json.Marshal(a) + b, err := json.Marshal(a, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal ldap server metadata: %w", err) } diff --git a/services/mq/multi_region_test.go b/services/mq/multi_region_test.go index 296bdd746d..65b4fca182 100644 --- a/services/mq/multi_region_test.go +++ b/services/mq/multi_region_test.go @@ -1,7 +1,8 @@ package mq_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mq" ) @@ -38,7 +40,7 @@ func regionCall(t *testing.T, h *mq.Handler, region, method, path, body string) out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out @@ -129,9 +131,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/mq/persistence.go b/services/mq/persistence.go index 0adc89f782..1d07f21127 100644 --- a/services/mq/persistence.go +++ b/services/mq/persistence.go @@ -2,9 +2,11 @@ package mq import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -29,7 +31,7 @@ const mqSnapshotVersion = 1 // store.Table-backed, see store_setup.go) but was persisted before this // refactor and remains so. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -56,7 +58,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "mq: failed to marshal snapshot", "error", err) diff --git a/services/mq/user_summary_wire_test.go b/services/mq/user_summary_wire_test.go index 8bdc3389ce..5243f9e988 100644 --- a/services/mq/user_summary_wire_test.go +++ b/services/mq/user_summary_wire_test.go @@ -1,13 +1,14 @@ package mq_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mq" ) @@ -40,7 +41,7 @@ func TestUserSummary_NoFabricatedConsoleAccess(t *testing.T) { "ListUsers' UserSummary items must not carry a fabricated consoleAccess key") broker := describeTestBroker(t, h, brokerID) - usersRaw, err := json.Marshal(broker["users"]) + usersRaw, err := json.Marshal(broker["users"], jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(usersRaw), "consoleAccess", "DescribeBrokerOutput.Users (UserSummary) must not carry a fabricated consoleAccess key") diff --git a/services/mwaa/airflow_access.go b/services/mwaa/airflow_access.go index b1466338f0..4a49869c44 100644 --- a/services/mwaa/airflow_access.go +++ b/services/mwaa/airflow_access.go @@ -5,7 +5,7 @@ import ( "context" "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -13,6 +13,8 @@ import ( "strings" "time" "unicode" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -71,7 +73,7 @@ func (e *airflowEngine) doWith( } if out != nil && len(res.body) > 0 { - _ = json.Unmarshal(res.body, out) + _ = json.Unmarshal(res.body, out, jsonx.V1()) } return res.status, res.body, nil @@ -127,7 +129,7 @@ func (e *airflowEngine) newToken(key, envName, kind string) string { _, _ = rand.Read(sig) enc := base64.RawURLEncoding - payload, _ := json.Marshal(map[string]any{"env": envName, "type": kind, "exp": exp.Unix()}) + payload, _ := json.Marshal(map[string]any{"env": envName, "type": kind, "exp": exp.Unix()}, jsonx.V1()) tok := enc.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + "." + enc.EncodeToString(payload) + "." + enc.EncodeToString(sig) @@ -276,7 +278,7 @@ func (e *airflowEngine) proxyRest( if req.Body != nil { var err error - if body, err = json.Marshal(req.Body); err != nil { + if body, err = json.Marshal(req.Body, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: Body is not JSON: %w", ErrInvalidParameter, err) } } @@ -297,7 +299,7 @@ func (e *airflowEngine) proxyRest( doc := any(map[string]any{}) if len(raw) > 0 { - if jerr := json.Unmarshal(raw, &doc); jerr != nil { + if jerr := json.Unmarshal(raw, &doc, jsonx.V1()); jerr != nil { doc = string(raw) } } diff --git a/services/mwaa/airflow_auth.go b/services/mwaa/airflow_auth.go index 72457c870b..5e68c3fd42 100644 --- a/services/mwaa/airflow_auth.go +++ b/services/mwaa/airflow_auth.go @@ -4,13 +4,15 @@ import ( "context" "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" "net/url" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -115,7 +117,7 @@ func (e *airflowEngine) loginV3(ctx context.Context, t airflowTarget) ([]*http.C } func (e *airflowEngine) v3Token(ctx context.Context, t airflowTarget) (string, error) { - body, _ := json.Marshal(map[string]string{"username": airflowAdminUser, "password": t.pass}) + body, _ := json.Marshal(map[string]string{"username": airflowAdminUser, "password": t.pass}, jsonx.V1()) res, err := e.request(ctx, http.MethodPost, t.base+"/auth/token", body, http.Header{ headerContentType: {"application/json"}, @@ -128,7 +130,8 @@ func (e *airflowEngine) v3Token(ctx context.Context, t airflowTarget) (string, e Token string `json:"access_token"` } - if res.status >= http.StatusMultipleChoices || json.Unmarshal(res.body, &out) != nil || out.Token == "" { + if res.status >= http.StatusMultipleChoices || json.Unmarshal(res.body, &out, jsonx.V1()) != nil || + out.Token == "" { return "", fmt.Errorf("%w: status %d", errAirflowLogin, res.status) } diff --git a/services/mwaa/airflow_auth_test.go b/services/mwaa/airflow_auth_test.go index 5507348082..1b8700dc6d 100644 --- a/services/mwaa/airflow_auth_test.go +++ b/services/mwaa/airflow_auth_test.go @@ -1,7 +1,7 @@ package mwaa_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "os" "path/filepath" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const startupBody = "export MY_VAR=from-startup\necho hello-startup\n" @@ -196,7 +197,7 @@ func TestAirflowAuth_SSOLoginAndRest(t *testing.T) { desc, err := rig.b.GetEnvironment(t.Context(), "auth") require.NoError(t, err) - raw, _ := json.Marshal(desc) + raw, _ := json.Marshal(desc, jsonx.V1()) assert.NotContains(t, string(raw), pass) rec := doMWAARequest(t, rig.h, http.MethodPost, "/webtoken/auth", nil) @@ -204,7 +205,7 @@ func TestAirflowAuth_SSOLoginAndRest(t *testing.T) { var tok map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok, jsonx.V1())) sso := rawRequest(t, rig.h, http.MethodGet, "/aws_mwaa/aws-console-sso?login=true&token="+tok["WebToken"], "", nil) require.Equal(t, http.StatusFound, sso.Code) @@ -235,7 +236,7 @@ func TestAirflowAuth_BrowserTarget(t *testing.T) { var tok map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok, jsonx.V1())) env, err := rig.b.GetEnvironment(t.Context(), "host") require.NoError(t, err) diff --git a/services/mwaa/airflow_engine_test.go b/services/mwaa/airflow_engine_test.go index 4be1b516ed..2571834f15 100644 --- a/services/mwaa/airflow_engine_test.go +++ b/services/mwaa/airflow_engine_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "maps" "net/http" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -414,7 +415,7 @@ func TestAirflowEngine_CLIAndTokens(t *testing.T) { var tok map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok, jsonx.V1())) assert.NotEmpty(t, tok["CliToken"]) tests := []struct { @@ -442,7 +443,7 @@ func TestAirflowEngine_CLIAndTokens(t *testing.T) { var resp map[string]string - require.NoError(t, json.Unmarshal(out.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(out.Body.Bytes(), &resp, jsonx.V1())) stdout, err := base64.StdEncoding.DecodeString(resp["stdout"]) require.NoError(t, err) stderr, err := base64.StdEncoding.DecodeString(resp["stderr"]) @@ -466,7 +467,7 @@ func TestAirflowEngine_WebLoginAndRest(t *testing.T) { var tok map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tok, jsonx.V1())) env, err := rig.b.GetEnvironment(t.Context(), "web") require.NoError(t, err) diff --git a/services/mwaa/airflow_sync.go b/services/mwaa/airflow_sync.go index a763753543..6828e1f177 100644 --- a/services/mwaa/airflow_sync.go +++ b/services/mwaa/airflow_sync.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -18,6 +18,7 @@ import ( awss3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" s3backend "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -200,7 +201,7 @@ func (e *airflowEngine) stageStatic(ctx context.Context, dir string, in airflowL _ = os.Chmod(filepath.Join(dir, "state"), airflowDirMode) - passwords, _ := json.Marshal(map[string]string{airflowAdminUser: in.adminPass}) + passwords, _ := json.Marshal(map[string]string{airflowAdminUser: in.adminPass}, jsonx.V1()) for name, data := range map[string][]byte{ "requirements.txt": reqs, diff --git a/services/mwaa/environments_validation_test.go b/services/mwaa/environments_validation_test.go index 31af2efa1c..e12fdde6ba 100644 --- a/services/mwaa/environments_validation_test.go +++ b/services/mwaa/environments_validation_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -441,7 +442,7 @@ func TestWorkerReplacementStrategy_AbsentFromCreate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "an unknown/invented field on Create must not fail validation") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasField := resp["WorkerReplacementStrategy"] assert.False(t, hasField, "CreateEnvironment response must never echo WorkerReplacementStrategy") } diff --git a/services/mwaa/export_test.go b/services/mwaa/export_test.go index b4ab003473..551925057d 100644 --- a/services/mwaa/export_test.go +++ b/services/mwaa/export_test.go @@ -1,9 +1,11 @@ package mwaa import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // EnvironmentCount returns the total number of environments in the backend across @@ -68,7 +70,7 @@ func NewInvokeRequest(method, path string) *invokeRestAPIRequest { func ResponseJSON(t *testing.T, r *InvokeRestAPIResponse) string { t.Helper() - raw, err := json.Marshal(r.RestAPIResponse) + raw, err := json.Marshal(r.RestAPIResponse, jsonx.V1()) if err != nil { t.Fatal(err) } diff --git a/services/mwaa/handler.go b/services/mwaa/handler.go index 76d590e740..1cf8259daa 100644 --- a/services/mwaa/handler.go +++ b/services/mwaa/handler.go @@ -2,7 +2,7 @@ package mwaa import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -328,7 +329,7 @@ func decodeJSONBody(c *echo.Context, target any) bool { return false } - if jsonErr := json.Unmarshal(body, target); jsonErr != nil { + if jsonErr := json.Unmarshal(body, target, jsonx.V1()); jsonErr != nil { _ = writeErrorResponse(c, http.StatusBadRequest, "ValidationException", "invalid request body") return false diff --git a/services/mwaa/handler_cli_token_test.go b/services/mwaa/handler_cli_token_test.go index 6812d4491f..0cf2aef90f 100644 --- a/services/mwaa/handler_cli_token_test.go +++ b/services/mwaa/handler_cli_token_test.go @@ -1,13 +1,16 @@ package mwaa_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateCliToken_HTTP_NonAvailable_Returns404(t *testing.T) { @@ -58,7 +61,7 @@ func TestHandler_CliToken_HappyPath(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["CliToken"]) } @@ -95,7 +98,7 @@ func TestHandler_CreateCliToken(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["CliToken"]) } }) @@ -118,7 +121,7 @@ func TestCliToken_HTTP_ResponseStructure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["CliToken"]) assert.NotEmpty(t, resp["WebServerHostname"]) @@ -156,7 +159,7 @@ func TestHTTP_TokensIncludeWebServerHostname(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantKey]) assert.NotEmpty(t, resp["WebServerHostname"]) assert.Contains(t, resp["WebServerHostname"], "amazonaws.com") diff --git a/services/mwaa/handler_environments_test.go b/services/mwaa/handler_environments_test.go index 9ffc310db6..c6a79201a3 100644 --- a/services/mwaa/handler_environments_test.go +++ b/services/mwaa/handler_environments_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -71,7 +72,7 @@ func TestHandler_CreateEnvironment(t *testing.T) { if tt.wantArn { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) } }) @@ -122,7 +123,7 @@ func TestHandler_GetEnvironment(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Environment"]) } }) @@ -168,7 +169,7 @@ func TestHandler_ListEnvironments(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) envs, ok := resp["Environments"].([]any) require.True(t, ok) @@ -317,7 +318,7 @@ func TestGetEnvironment_DerivedFields(t *testing.T) { Environment mwaa.Environment `json:"Environment"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) env := resp.Environment assert.Equal(t, int32(2), env.Schedulers) @@ -393,7 +394,7 @@ func TestListEnvironments_Pagination(t *testing.T) { Environments []string `json:"Environments"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantNames, resp.Environments) assert.Equal(t, tt.wantNext, resp.NextToken) }) @@ -481,7 +482,7 @@ func TestListEnvironments_HTTP_NoMaxResults_UsesDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["Environments"] assert.True(t, ok, "response must have Environments key") } @@ -510,7 +511,7 @@ func TestListEnvironments_HTTP_NextToken_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) envs1 := resp1["Environments"].([]any) assert.Len(t, envs1, 3) @@ -525,7 +526,7 @@ func TestListEnvironments_HTTP_NextToken_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) envs2 := resp2["Environments"].([]any) assert.Len(t, envs2, 2, "second page should have the remaining 2 environments") @@ -555,7 +556,7 @@ func TestHTTP_GetEnvironment_DefaultsPresent(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) env := resp["Environment"].(map[string]any) assert.InEpsilon(t, float64(10), env["MaxWorkers"], 0.001, "default MaxWorkers=10 should be visible via HTTP") @@ -1014,7 +1015,7 @@ func TestLifecycle_HTTP_CreateResponseDoesNotExposeStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) assert.Nil(t, resp["Status"], "create response must not expose Status") } @@ -1040,7 +1041,7 @@ func TestLifecycle_HTTP_GetEnvShowsCreatingThenAvailable(t *testing.T) { Status string `json:"Status"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(get1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(get1.Body.Bytes(), &resp1, jsonx.V1())) assert.Equal(t, "CREATING", resp1.Environment.Status) // Second GET: AVAILABLE @@ -1052,7 +1053,7 @@ func TestLifecycle_HTTP_GetEnvShowsCreatingThenAvailable(t *testing.T) { Status string `json:"Status"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(get2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(get2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, "AVAILABLE", resp2.Environment.Status) } @@ -1210,7 +1211,7 @@ func TestHTTP_FullCRUDLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] assert.NotEmpty(t, envARN) @@ -1224,7 +1225,7 @@ func TestHTTP_FullCRUDLifecycle(t *testing.T) { EnvironmentClass string `json:"EnvironmentClass"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(get1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(get1.Body.Bytes(), &resp1, jsonx.V1())) assert.Equal(t, "CREATING", resp1.Environment.Status) assert.Equal(t, "2.9.2", resp1.Environment.AirflowVersion) assert.Equal(t, "mw1.medium", resp1.Environment.EnvironmentClass) @@ -1237,7 +1238,7 @@ func TestHTTP_FullCRUDLifecycle(t *testing.T) { Status string `json:"Status"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(get2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(get2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, "AVAILABLE", resp2.Environment.Status) // Update. @@ -1255,7 +1256,7 @@ func TestHTTP_FullCRUDLifecycle(t *testing.T) { EnvironmentClass string `json:"EnvironmentClass"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(get3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(get3.Body.Bytes(), &resp3, jsonx.V1())) assert.Equal(t, "UPDATING", resp3.Environment.Status) assert.Equal(t, "mw1.large", resp3.Environment.EnvironmentClass) @@ -1304,7 +1305,7 @@ func TestHTTP_LoggingConfig_AllModules(t *testing.T) { LoggingConfiguration *mwaa.LoggingConfiguration `json:"LoggingConfiguration"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.Environment.LoggingConfiguration) require.NotNil(t, resp.Environment.LoggingConfiguration.DagProcessingLogs) assert.Equal(t, "INFO", resp.Environment.LoggingConfiguration.DagProcessingLogs.LogLevel) @@ -1345,7 +1346,7 @@ func TestHTTP_LoggingConfig_CloudWatchLogGroupArn_NotEchoedFromRequest(t *testin LoggingConfiguration *mwaa.LoggingConfiguration `json:"LoggingConfiguration"` } `json:"Environment"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) require.NotNil(t, resp.Environment.LoggingConfiguration) require.NotNil(t, resp.Environment.LoggingConfiguration.DagProcessingLogs) assert.Equal(t, "INFO", resp.Environment.LoggingConfiguration.DagProcessingLogs.LogLevel) diff --git a/services/mwaa/handler_metrics.go b/services/mwaa/handler_metrics.go index cac93f0911..a369a1ce3e 100644 --- a/services/mwaa/handler_metrics.go +++ b/services/mwaa/handler_metrics.go @@ -1,7 +1,7 @@ package mwaa import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handlePublishMetrics(c *echo.Context, name string) error { @@ -19,7 +20,7 @@ func (h *Handler) handlePublishMetrics(c *echo.Context, name string) error { var req publishMetricsRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/mwaa/handler_metrics_test.go b/services/mwaa/handler_metrics_test.go index 8bada07a86..e1abccd3e0 100644 --- a/services/mwaa/handler_metrics_test.go +++ b/services/mwaa/handler_metrics_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PublishMetrics(t *testing.T) { @@ -120,7 +122,7 @@ func TestHandler_PublishMetrics_NotFound_ErrorType(t *testing.T) { Message string `json:"message"` Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body.Type) } diff --git a/services/mwaa/handler_rest_api.go b/services/mwaa/handler_rest_api.go index 8b0f149334..063e810c8d 100644 --- a/services/mwaa/handler_rest_api.go +++ b/services/mwaa/handler_rest_api.go @@ -1,7 +1,7 @@ package mwaa import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleInvokeRestAPI(c *echo.Context, name string) error { @@ -19,7 +20,7 @@ func (h *Handler) handleInvokeRestAPI(c *echo.Context, name string) error { var req invokeRestAPIRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/mwaa/handler_rest_api_test.go b/services/mwaa/handler_rest_api_test.go index d52b7aed8d..6eb983ff8c 100644 --- a/services/mwaa/handler_rest_api_test.go +++ b/services/mwaa/handler_rest_api_test.go @@ -1,7 +1,7 @@ package mwaa_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_InvokeRestApi(t *testing.T) { @@ -126,7 +128,7 @@ func TestHandler_InvokeRestApi(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InDelta(t, float64(200), resp["RestApiStatusCode"], 0) } }) diff --git a/services/mwaa/handler_tags.go b/services/mwaa/handler_tags.go index 55208bd07f..b1c1d54d95 100644 --- a/services/mwaa/handler_tags.go +++ b/services/mwaa/handler_tags.go @@ -1,7 +1,7 @@ package mwaa import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTagsForResource(c *echo.Context, resourceARN string) error { @@ -42,7 +43,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string) error { Tags map[string]string `json:"Tags"` } - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/mwaa/handler_tags_test.go b/services/mwaa/handler_tags_test.go index 6eae6720be..79c03fbeae 100644 --- a/services/mwaa/handler_tags_test.go +++ b/services/mwaa/handler_tags_test.go @@ -1,13 +1,15 @@ package mwaa_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagsFlow(t *testing.T) { @@ -39,7 +41,7 @@ func TestHandler_TagsFlow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] require.NotEmpty(t, envARN) @@ -54,7 +56,7 @@ func TestHandler_TagsFlow(t *testing.T) { assert.Equal(t, http.StatusOK, listTagRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listTagRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listTagRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags, ok := tagsResp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "test", tags["env"]) @@ -104,7 +106,7 @@ func TestHandler_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] require.NotEmpty(t, envARN) @@ -129,7 +131,7 @@ func TestHandler_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].(map[string]any) assert.Len(t, tags, tt.wantTagsLen) }) @@ -158,14 +160,14 @@ func TestListTagsForResource_HTTP_EmptyTagsShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] rec := doMWAARequest(t, h, http.MethodGet, "/tags/"+envARN, nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["Tags"] assert.True(t, ok, "response must have Tags key even when empty") } @@ -187,7 +189,7 @@ func TestTagLimit_HTTP_TagResource_Exceeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] // Build 51 tags. @@ -257,7 +259,7 @@ func TestTags_HTTP_CreateWithTagsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) envARN := createResp["Arn"] // ListTagsForResource returns the creation tags. @@ -267,7 +269,7 @@ func TestTags_HTTP_CreateWithTagsRoundTrip(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagsResp, jsonx.V1())) assert.Equal(t, "airflow", tagsResp.Tags["service"]) assert.Equal(t, "production", tagsResp.Tags["tier"]) } diff --git a/services/mwaa/handler_test.go b/services/mwaa/handler_test.go index 931a1a0360..daf2fbff23 100644 --- a/services/mwaa/handler_test.go +++ b/services/mwaa/handler_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -37,7 +38,7 @@ func doMWAARequest(t *testing.T, h *mwaa.Handler, method, path string, body any) if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/mwaa/handler_web_login_token_test.go b/services/mwaa/handler_web_login_token_test.go index df79cda172..48a6370ea6 100644 --- a/services/mwaa/handler_web_login_token_test.go +++ b/services/mwaa/handler_web_login_token_test.go @@ -1,13 +1,16 @@ package mwaa_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateWebLoginToken_HTTP_NonAvailable_Returns404(t *testing.T) { @@ -57,7 +60,7 @@ func TestHandler_WebToken_HappyPath(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WebToken"]) } @@ -94,7 +97,7 @@ func TestHandler_CreateWebLoginToken(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WebToken"]) } }) @@ -117,7 +120,7 @@ func TestWebLoginToken_HTTP_ResponseStructure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WebToken"]) assert.NotEmpty(t, resp["WebServerHostname"]) diff --git a/services/mwaa/persistence.go b/services/mwaa/persistence.go index 87e7877f6f..0487a5eaae 100644 --- a/services/mwaa/persistence.go +++ b/services/mwaa/persistence.go @@ -2,7 +2,7 @@ package mwaa import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -73,7 +73,7 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Metrics map[string]map[string][]MetricDatum `json:"metrics"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -173,7 +173,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds the environments store.Table from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, envDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/mwaa/tags_test.go b/services/mwaa/tags_test.go index 6a92630053..880fd4b324 100644 --- a/services/mwaa/tags_test.go +++ b/services/mwaa/tags_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -333,7 +334,7 @@ func TestBackend_ARNIndex(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) envARN = resp["Arn"] } diff --git a/services/mwaa/web_login_identity_test.go b/services/mwaa/web_login_identity_test.go index bf89dc60dd..447ef94342 100644 --- a/services/mwaa/web_login_identity_test.go +++ b/services/mwaa/web_login_identity_test.go @@ -2,7 +2,7 @@ package mwaa_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/mwaa" ) @@ -52,7 +53,7 @@ func TestCreateWebLoginTokenIamIdentity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.want, out["IamIdentity"]) }) } diff --git a/services/neptune/engine_probe.go b/services/neptune/engine_probe.go index ebc2449e50..bf006a76d8 100644 --- a/services/neptune/engine_probe.go +++ b/services/neptune/engine_probe.go @@ -7,7 +7,7 @@ import ( "crypto/sha1" //nolint:gosec // RFC 6455 mandates SHA-1 for the handshake accept key "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -17,6 +17,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -98,7 +100,7 @@ func ProbeGremlin(ctx context.Context, addr string) error { } var resp gremlinResponse - if err = json.Unmarshal(reply, &resp); err != nil { + if err = json.Unmarshal(reply, &resp, jsonx.V1()); err != nil { return fmt.Errorf("decode gremlin response: %w", err) } @@ -114,7 +116,7 @@ func gremlinPayload() ([]byte, error) { RequestID: uuid.NewString(), Op: "eval", Args: map[string]any{"gremlin": gremlinProbeQ, "language": "gremlin-groovy"}, - }) + }, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode gremlin request: %w", err) } diff --git a/services/neptune/gremlin/frame.go b/services/neptune/gremlin/frame.go index 5f789a44e5..4ede1d092a 100644 --- a/services/neptune/gremlin/frame.go +++ b/services/neptune/gremlin/frame.go @@ -2,11 +2,14 @@ package gremlin import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "github.com/google/uuid" "github.com/gorilla/websocket" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const uuidBytes = 16 @@ -79,7 +82,8 @@ func recoverFrame(messageType int, data []byte) FrameRequest { RequestID string `json:"requestId"` } - if json.NewDecoder(bytes.NewReader(body)).Decode(&raw) == nil && raw.RequestID != "" { + if json.UnmarshalDecode(jsontext.NewDecoder(bytes.NewReader(body)), &raw, jsonx.V1()) == nil && + raw.RequestID != "" { req.ID = raw.RequestID } diff --git a/services/neptune/gremlin/graphson.go b/services/neptune/gremlin/graphson.go index f859ea5711..a2a981569c 100644 --- a/services/neptune/gremlin/graphson.go +++ b/services/neptune/gremlin/graphson.go @@ -2,9 +2,12 @@ package gremlin import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "math" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -190,10 +193,9 @@ func (w graphsonWriter) emptyMap() any { func marshalNoEscape(v any) ([]byte, error) { var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) + enc := jsontext.NewEncoder(&buf, jsonx.V1(), jsontext.EscapeForHTML(false)) - if err := enc.Encode(v); err != nil { + if err := json.MarshalEncode(enc, v); err != nil { return nil, err //nolint:wrapcheck // plain encode error } @@ -203,7 +205,7 @@ func marshalNoEscape(v any) ([]byte, error) { // decodeGraphSON turns a decoded JSON request value (numbers as json.Number) into engine values. func decodeGraphSON(v any) (any, error) { switch t := v.(type) { - case json.Number: + case jsonx.Number: return numberFromJSON(t), nil case []any: return decodeGraphSONList(t) @@ -219,7 +221,7 @@ func decodeGraphSON(v any) (any, error) { return v, nil } -func numberFromJSON(n json.Number) any { +func numberFromJSON(n jsonx.Number) any { if i, err := n.Int64(); err == nil { return i } @@ -295,7 +297,7 @@ func decodeTyped(typ string, val any) (any, error) { func decodeNumber(val any) (any, error) { switch t := val.(type) { - case json.Number: + case jsonx.Number: return numberFromJSON(t), nil case string: f, err := strconv.ParseFloat(t, 64) diff --git a/services/neptune/gremlin/server.go b/services/neptune/gremlin/server.go index fe995d2fd9..c872ef47a8 100644 --- a/services/neptune/gremlin/server.go +++ b/services/neptune/gremlin/server.go @@ -3,7 +3,8 @@ package gremlin import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" ) @@ -151,10 +153,9 @@ func parseJSONRequest(body []byte, c codec) (*message, error) { Processor string `json:"processor"` } - dec := json.NewDecoder(bytes.NewReader(body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(body)) - if err := dec.Decode(&raw); err != nil { + if err := json.UnmarshalDecode(dec, &raw, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, invalidArg("Malformed message: %v", err) } @@ -422,7 +423,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { func toJSONNumber(v any) any { if f, ok := v.(float64); ok { - return json.Number(formatFloat(f)) + return jsonx.Number(formatFloat(f)) } return v @@ -437,7 +438,12 @@ func readHTTPRequest(w http.ResponseWriter, r *http.Request) (*httpRequest, erro var req httpRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil && !errors.Is(err, io.EOF) { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(r.Body), + &req, + jsonx.V1(), + ); err != nil && + !errors.Is(err, io.EOF) { return nil, invalidArg("Malformed request body: %v", err) } @@ -459,5 +465,8 @@ func writeHTTPError(w http.ResponseWriter, e *Error, id string) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(map[string]string{jsonReqID: id, jsonCode: e.Code, "detailedMessage": e.Detail}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]string{jsonReqID: id, jsonCode: e.Code, "detailedMessage": e.Detail}, + ) } diff --git a/services/neptune/gremlin/server_test.go b/services/neptune/gremlin/server_test.go index 70ac09fe0c..c7b5f289bf 100644 --- a/services/neptune/gremlin/server_test.go +++ b/services/neptune/gremlin/server_test.go @@ -2,7 +2,8 @@ package gremlin_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/neptune/gremlin" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" @@ -55,7 +57,7 @@ type wsResponse struct { Code int `json:"code"` } `json:"status"` Result struct { - Data json.RawMessage `json:"data"` + Data jsontext.Value `json:"data"` } `json:"result"` } @@ -75,7 +77,7 @@ func call(t *testing.T, c *websocket.Conn, mt int, payload []byte) []wsResponse require.NoError(t, err) var r wsResponse - require.NoError(t, json.Unmarshal(data, &r), string(data)) + require.NoError(t, json.Unmarshal(data, &r, jsonx.V1()), string(data)) out = append(out, r) @@ -89,7 +91,7 @@ const evalFrame = `{"requestId":"11111111-1111-1111-1111-111111111111","op":"eva `"args":{"gremlin":%q,"language":"gremlin-groovy","aliases":{"g":"g"}%s}}` func evalBody(script, extra string) string { - b, _ := json.Marshal(script) + b, _ := json.Marshal(script, jsonx.V1()) return strings.Replace(strings.Replace(evalFrame, "%q", string(b), 1), "%s", extra, 1) } diff --git a/services/neptune/gremlin_ws_proxy_test.go b/services/neptune/gremlin_ws_proxy_test.go index 7e43c9c833..0916fad851 100644 --- a/services/neptune/gremlin_ws_proxy_test.go +++ b/services/neptune/gremlin_ws_proxy_test.go @@ -5,7 +5,7 @@ import ( "crypto/sha256" "encoding/binary" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "net" "net/http" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -136,7 +137,7 @@ func wsFreePort(t *testing.T) int { func wsEval(script string) []byte { b, _ := json.Marshal(map[string]any{ "requestId": wsTestReqID, "op": "eval", "processor": "", "args": map[string]any{"gremlin": script}, - }) + }, jsonx.V1()) return b } @@ -290,7 +291,7 @@ func TestGremlinProxyFrameAuthorization(t *testing.T) { return } - require.NoError(t, json.Unmarshal(raw, &out), string(raw)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1()), string(raw)) assert.InDelta(t, tt.wantCode, out.Status.Code, 0) assert.Contains(t, out.Status.Message, tt.wantMsg) diff --git a/services/neptune/handler_test.go b/services/neptune/handler_test.go index 6ca880a42b..b8aa07df99 100644 --- a/services/neptune/handler_test.go +++ b/services/neptune/handler_test.go @@ -1,7 +1,7 @@ package neptune_test import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "io" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/neptune" ) @@ -552,7 +553,7 @@ func TestPersistenceRoundTrip(t *testing.T) { // Verify it's valid JSON var raw map[string]any - require.NoError(t, json.Unmarshal(data, &raw)) + require.NoError(t, json.Unmarshal(data, &raw, jsonx.V1())) restored := neptune.NewInMemoryBackend("000000000000", "us-east-1") require.NoError(t, restored.Restore(t.Context(), data)) diff --git a/services/neptune/iam_auth.go b/services/neptune/iam_auth.go index 519d2bddfa..bb562a9181 100644 --- a/services/neptune/iam_auth.go +++ b/services/neptune/iam_auth.go @@ -4,7 +4,7 @@ import ( "bytes" "cmp" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/bolt" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" "github.com/blackbirdworks/gopherstack/services/neptune/gremlin" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" @@ -289,7 +290,7 @@ func (ca clusterAuth) authenticateBolt(auth map[string]any, now time.Time) (*iam } var tok map[string]string - if err := json.Unmarshal([]byte(raw), &tok); err != nil { + if err := json.Unmarshal([]byte(raw), &tok, jsonx.V1()); err != nil { return nil, errIAMBadBoltToken } @@ -435,7 +436,7 @@ func authorizeProxied(w http.ResponseWriter, r *http.Request) bool { Gremlin string `json:"gremlin"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) script = cmp.Or(req.Gremlin, script) } diff --git a/services/neptune/iam_dataplane_test.go b/services/neptune/iam_dataplane_test.go index 76c2aee13b..ab8212a7c9 100644 --- a/services/neptune/iam_dataplane_test.go +++ b/services/neptune/iam_dataplane_test.go @@ -3,7 +3,7 @@ package neptune_test import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "io" "net" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/bolt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/iam" "github.com/blackbirdworks/gopherstack/services/neptune" @@ -233,7 +234,7 @@ func dpGremlinWS(t *testing.T, c neptune.DBCluster, accessKey, script string) (m frame, err := json.Marshal(map[string]any{ "requestId": "7f1a8f4c-0b0e-4a8a-9d3a-1c2d3e4f5a6b", "op": "eval", "processor": "", "args": map[string]any{"gremlin": script}, - }) + }, jsonx.V1()) require.NoError(t, err) require.NoError(t, conn.WriteMessage(websocket.TextMessage, frame)) @@ -241,7 +242,7 @@ func dpGremlinWS(t *testing.T, c neptune.DBCluster, accessKey, script string) (m require.NoError(t, err) var out map[string]any - require.NoError(t, json.Unmarshal(raw, &out)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1())) return out, nil } @@ -294,7 +295,7 @@ func dpBoltToken(t *testing.T, c neptune.DBCluster, accessKey, secret, service s raw, err := json.Marshal(map[string]string{ "Authorization": req.Header.Get("Authorization"), "HttpMethod": http.MethodGet, "X-Amz-Date": req.Header.Get("X-Amz-Date"), "Host": req.Host, - }) + }, jsonx.V1()) require.NoError(t, err) return string(raw) diff --git a/services/neptune/opencypher/graph_test.go b/services/neptune/opencypher/graph_test.go index 758876f8ba..594970b179 100644 --- a/services/neptune/opencypher/graph_test.go +++ b/services/neptune/opencypher/graph_test.go @@ -1,12 +1,13 @@ package opencypher_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" ) @@ -22,7 +23,7 @@ func run(t *testing.T, g *opencypher.Graph, q string, params map[string]any) (st return "", err } - b, err := json.Marshal(res) + b, err := json.Marshal(res, jsonx.V1()) require.NoError(t, err) return string(b), nil @@ -149,7 +150,7 @@ func TestNodeAndRelationshipShapes(t *testing.T) { var rows []map[string]map[string]any - require.NoError(t, json.Unmarshal([]byte(got), &rows)) + require.NoError(t, json.Unmarshal([]byte(got), &rows, jsonx.V1())) require.Len(t, rows, 1) a, r := rows[0]["a"], rows[0]["r"] diff --git a/services/neptune/opencypher/persist.go b/services/neptune/opencypher/persist.go index c9ddacf56a..fc0fea1a5e 100644 --- a/services/neptune/opencypher/persist.go +++ b/services/neptune/opencypher/persist.go @@ -3,11 +3,14 @@ package opencypher import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "sort" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -63,7 +66,7 @@ func (g *Graph) Export(ctx context.Context) ([]byte, error) { }) } - b, err := json.Marshal(d) + b, err := json.Marshal(d, jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode graph: %w", err) } @@ -105,10 +108,9 @@ func (g *Graph) Import(ctx context.Context, data []byte) error { } func decodeUseNumber(data []byte, v any) error { - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(data)) - return dec.Decode(v) + return json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()) } func encodeProps(p map[string]any) map[string]any { @@ -149,7 +151,7 @@ func decodeProps(p map[string]any) map[string]any { func decodeValue(v any) any { switch t := v.(type) { - case json.Number: + case jsonx.Number: if i, err := t.Int64(); err == nil { return i } diff --git a/services/neptune/opencypher/value.go b/services/neptune/opencypher/value.go index 39e5ddc023..b5f1a3a1b3 100644 --- a/services/neptune/opencypher/value.go +++ b/services/neptune/opencypher/value.go @@ -3,12 +3,15 @@ package opencypher import ( "bytes" "cmp" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Result is the table produced by RETURN; a query with no RETURN has no columns. @@ -36,8 +39,8 @@ func (r *Result) MarshalJSON() ([]byte, error) { buf.WriteByte(',') } - k, _ := json.Marshal(col) - v, err := json.Marshal(jsonValue(row[j])) + k, _ := json.Marshal(col, jsonx.V1()) + v, err := json.Marshal(jsonValue(row[j]), jsonx.V1()) if err != nil { return nil, fmt.Errorf("encode %s: %w", col, err) } @@ -112,11 +115,10 @@ func ParseParameters(raw string) (map[string]any, error) { return map[string]any{}, nil } - dec := json.NewDecoder(strings.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(strings.NewReader(raw)) var m map[string]any - if err := dec.Decode(&m); err != nil { + if err := json.UnmarshalDecode(dec, &m, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, &Error{Code: CodeInvalidParameter, Detail: "parameters must be a JSON object: " + err.Error()} } @@ -129,7 +131,7 @@ func ParseParameters(raw string) (map[string]any, error) { func normalize(v any) any { switch t := v.(type) { - case json.Number: + case jsonx.Number: if i, err := t.Int64(); err == nil { return i } diff --git a/services/neptune/opencypher_front.go b/services/neptune/opencypher_front.go index 1a864f5095..ce60f4072f 100644 --- a/services/neptune/opencypher_front.go +++ b/services/neptune/opencypher_front.go @@ -2,7 +2,8 @@ package neptune import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -20,6 +21,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/bolt" "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sgfilter" "github.com/blackbirdworks/gopherstack/services/neptune/gremlin" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" @@ -169,7 +171,7 @@ type cypherCall struct { func writeCypherJSON(w http.ResponseWriter, status int, body any) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.WriteHeader(status) - _ = json.NewEncoder(w).Encode(body) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), body) } func writeCypherError(w http.ResponseWriter, status int, code, detail string) { @@ -207,7 +209,7 @@ var errMethodNotAllowed = errors.New("method not allowed") func readCypherJSON(body io.Reader) (cypherCall, error) { var req cypherRequest - if err := json.NewDecoder(body).Decode(&req); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(body), &req, jsonx.V1()); err != nil { return cypherCall{}, fmt.Errorf("decode request: %w", err) } @@ -225,7 +227,7 @@ func readCypherJSON(body io.Reader) (cypherCall, error) { case string: call.params = p default: - raw, _ := json.Marshal(p) + raw, _ := json.Marshal(p, jsonx.V1()) call.params = string(raw) } @@ -291,7 +293,7 @@ func (f *cypherFront) runQuery(ctx context.Context, w http.ResponseWriter, query return } - writeCypherJSON(w, http.StatusOK, map[string]json.RawMessage{"results": rowsOrEmpty(rows)}) + writeCypherJSON(w, http.StatusOK, map[string]jsontext.Value{"results": rowsOrEmpty(rows)}) } func (f *cypherFront) serveExplain(ctx context.Context, w http.ResponseWriter, call cypherCall, params map[string]any) { @@ -318,9 +320,9 @@ func (f *cypherFront) serveExplain(ctx context.Context, w http.ResponseWriter, c _, _ = io.WriteString(w, plan) } -func rowsOrEmpty(rows []byte) json.RawMessage { +func rowsOrEmpty(rows []byte) jsontext.Value { if len(rows) == 0 { - return json.RawMessage("[]") + return jsontext.Value("[]") } return rows diff --git a/services/neptune/opencypher_front_test.go b/services/neptune/opencypher_front_test.go index 2574392dcc..3792a13275 100644 --- a/services/neptune/opencypher_front_test.go +++ b/services/neptune/opencypher_front_test.go @@ -2,7 +2,7 @@ package neptune_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/neptune" ) @@ -72,7 +73,7 @@ func cypherDo(t *testing.T, method, target, contentType, body string) (int, map[ require.NoError(t, err) var out map[string]any - require.NoError(t, json.Unmarshal(raw, &out), string(raw)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1()), string(raw)) return resp.StatusCode, out } diff --git a/services/neptune/opencypher_queries_internal_test.go b/services/neptune/opencypher_queries_internal_test.go index b9bc663d6b..1e3d6f9cd4 100644 --- a/services/neptune/opencypher_queries_internal_test.go +++ b/services/neptune/opencypher_queries_internal_test.go @@ -2,7 +2,7 @@ package neptune import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/neptune/opencypher" ) @@ -22,7 +23,7 @@ func statusDo(t *testing.T, f *cypherFront, method, target string) (int, map[str f.serveQueryStatus(rec, req) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return rec.Code, out } diff --git a/services/neptune/persistence.go b/services/neptune/persistence.go index 7b5c12c5c4..fc4c2f4a2f 100644 --- a/services/neptune/persistence.go +++ b/services/neptune/persistence.go @@ -2,7 +2,7 @@ package neptune import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -54,14 +54,14 @@ func regionalDTOKeyFn[V any](d *regionalDTO[V]) string { return regionKey(d.Regi // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ClusterRoles map[string]map[string][]string `json:"clusterRoles"` Tags map[string]map[string][]Tag `json:"tags"` ParameterOverrides map[string]map[string]ParameterValue `json:"parameterOverrides"` ClusterParameterOverrides map[string]map[string]ParameterValue `json:"clusterParameterOverrides"` PendingMaintenanceActions map[string]map[string]PendingMaintenanceAction `json:"pendingMaintenanceActions"` EventsLog map[string][]Event `json:"eventsLog"` - OpenCypherGraphs map[string]json.RawMessage `json:"openCypherGraphs,omitempty"` + OpenCypherGraphs map[string]jsontext.Value `json:"openCypherGraphs,omitempty"` AccountID string `json:"accountID"` Region string `json:"region"` Version int `json:"version"` @@ -289,7 +289,7 @@ func unwrapRegionalDTOs[V any](dtos *store.Table[regionalDTO[V]], setRegion func // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, clusterDTOs, instanceDTOs, subnetGroupDTOs, clusterParameterGroupDTOs, clusterSnapshotDTOs, parameterGroupDTOs, clusterEndpointDTOs, eventSubscriptionDTOs, globalClusterDTOs := buildPersistenceDTORegistry() @@ -342,12 +342,12 @@ func (b *InMemoryBackend) graphLocked(key string) *opencypher.Graph { return g } -func (b *InMemoryBackend) exportGraphsLocked(ctx context.Context) map[string]json.RawMessage { +func (b *InMemoryBackend) exportGraphsLocked(ctx context.Context) map[string]jsontext.Value { if len(b.graphs) == 0 { return nil } - out := make(map[string]json.RawMessage, len(b.graphs)) + out := make(map[string]jsontext.Value, len(b.graphs)) for key, g := range b.graphs { raw, err := g.Export(ctx) @@ -363,7 +363,7 @@ func (b *InMemoryBackend) exportGraphsLocked(ctx context.Context) map[string]jso return out } -func (b *InMemoryBackend) importGraphsLocked(ctx context.Context, dumps map[string]json.RawMessage) { +func (b *InMemoryBackend) importGraphsLocked(ctx context.Context, dumps map[string]jsontext.Value) { b.graphs = make(map[string]*opencypher.Graph, len(dumps)) for key, raw := range dumps { diff --git a/services/networkfirewall/handler.go b/services/networkfirewall/handler.go index 721cad7a10..418abf4e0d 100644 --- a/services/networkfirewall/handler.go +++ b/services/networkfirewall/handler.go @@ -2,8 +2,7 @@ package networkfirewall import ( "context" - "encoding/json" - "errors" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -12,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -85,7 +85,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if json.Unmarshal(body, &data) != nil { + if json.Unmarshal(body, &data, jsonx.V1()) != nil { return "" } @@ -127,12 +127,12 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - _, isSyntax := errors.AsType[*json.SyntaxError](err) - _, isType := errors.AsType[*json.UnmarshalTypeError](err) + isSyntax := jsonx.IsSyntaxError(err) + isType := jsonx.IsTypeError(err) if isSyntax || isType { err = invalid("The request body is not valid: %v", err) diff --git a/services/networkfirewall/ordering_test.go b/services/networkfirewall/ordering_test.go index eaeab0fdc3..90dfa2acd7 100644 --- a/services/networkfirewall/ordering_test.go +++ b/services/networkfirewall/ordering_test.go @@ -1,7 +1,8 @@ package networkfirewall_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "testing" @@ -10,23 +11,25 @@ import ( "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // retime rewrites timeField on every record that has idField, giving the i-th id (sorted) times[i]. func retime(t *testing.T, snap []byte, timeField string, times []string) []byte { t.Helper() - var root map[string]json.RawMessage + var root map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &root)) + require.NoError(t, json.Unmarshal(snap, &root, jsonx.V1())) - var tables map[string]json.RawMessage + var tables map[string]jsontext.Value - require.NoError(t, json.Unmarshal(root["tables"], &tables)) + require.NoError(t, json.Unmarshal(root["tables"], &tables, jsonx.V1())) for name, raw := range tables { var rows []map[string]any - if json.Unmarshal(raw, &rows) != nil { + if json.Unmarshal(raw, &rows, jsonx.V1()) != nil { continue } @@ -50,18 +53,18 @@ func retime(t *testing.T, snap []byte, timeField string, times []string) []byte } } - b, err := json.Marshal(rows) + b, err := json.Marshal(rows, jsonx.V1()) require.NoError(t, err) tables[name] = b } - tb, err := json.Marshal(tables) + tb, err := json.Marshal(tables, jsonx.V1()) require.NoError(t, err) root["tables"] = tb - out, err := json.Marshal(root) + out, err := json.Marshal(root, jsonx.V1()) require.NoError(t, err) return out diff --git a/services/networkfirewall/persistence.go b/services/networkfirewall/persistence.go index 727b6973df..2cf39f7ea0 100644 --- a/services/networkfirewall/persistence.go +++ b/services/networkfirewall/persistence.go @@ -2,7 +2,7 @@ package networkfirewall import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -13,8 +13,8 @@ const networkFirewallSnapshotVersion = 1 // backendSnapshot is the on-disk shape: one JSON array per registered table. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes backend state to JSON. diff --git a/services/networkfirewall/resource_policy.go b/services/networkfirewall/resource_policy.go index 73ae4c26a5..551b14ec16 100644 --- a/services/networkfirewall/resource_policy.go +++ b/services/networkfirewall/resource_policy.go @@ -2,7 +2,11 @@ package networkfirewall import ( "context" - "encoding/json" + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutResourcePolicyInput is the PutResourcePolicy request. @@ -29,10 +33,10 @@ type DeleteResourcePolicyInput = DescribeResourcePolicyInput func validatePolicyDocument(doc string) error { var parsed struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if err := json.Unmarshal([]byte(doc), &parsed); err != nil || len(parsed.Statement) == 0 { + if err := json.Unmarshal([]byte(doc), &parsed, jsonx.V1()); err != nil || len(parsed.Statement) == 0 { return invalidResourcePolicy("The policy must be a JSON document with a Statement") } diff --git a/services/networkmanager/corenetworkpolicydiff.go b/services/networkmanager/corenetworkpolicydiff.go index e2ddc6a36f..65e364880b 100644 --- a/services/networkmanager/corenetworkpolicydiff.go +++ b/services/networkmanager/corenetworkpolicydiff.go @@ -1,8 +1,11 @@ package networkmanager import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "reflect" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // This file implements the real ADD/MODIFY/REMOVE structural diff @@ -28,17 +31,17 @@ import ( type namedSection struct { key string - raw json.RawMessage + raw jsontext.Value } // policyDocSections is the subset of the real Cloud WAN policy-document // schema's top-level keys this diff engine reads. type policyDocSections struct { - CoreNetworkConfiguration json.RawMessage `json:"core-network-configuration"` - Segments []json.RawMessage `json:"segments"` - NetworkFunctionGroups []json.RawMessage `json:"network-function-groups"` - SegmentActions []json.RawMessage `json:"segment-actions"` - AttachmentPolicies []json.RawMessage `json:"attachment-policies"` + CoreNetworkConfiguration jsontext.Value `json:"core-network-configuration"` + Segments []jsontext.Value `json:"segments"` + NetworkFunctionGroups []jsontext.Value `json:"network-function-groups"` + SegmentActions []jsontext.Value `json:"segment-actions"` + AttachmentPolicies []jsontext.Value `json:"attachment-policies"` } func parsePolicyDocSections(doc string) policyDocSections { @@ -51,22 +54,22 @@ func parsePolicyDocSections(doc string) policyDocSections { // CreateCoreNetwork) -- an error here means an empty/absent document, // which decodes to the zero value, itself a valid "nothing to diff" // input to diffNamedSections. - _ = json.Unmarshal([]byte(doc), &s) + _ = json.Unmarshal([]byte(doc), &s, jsonx.V1()) return s } -func namedByKey(items []json.RawMessage, keyField string) map[string]namedSection { +func namedByKey(items []jsontext.Value, keyField string) map[string]namedSection { out := make(map[string]namedSection, len(items)) for _, item := range items { - var fields map[string]json.RawMessage - if err := json.Unmarshal(item, &fields); err != nil { + var fields map[string]jsontext.Value + if err := json.Unmarshal(item, &fields, jsonx.V1()); err != nil { continue } var key string - if err := json.Unmarshal(fields[keyField], &key); err != nil { + if err := json.Unmarshal(fields[keyField], &key, jsonx.V1()); err != nil { continue } @@ -82,7 +85,7 @@ func namedByKey(items []json.RawMessage, keyField string) map[string]namedSectio // key present in either oldItems or newItems, keyed by keyField (e.g. // "name", "rule-number"). func diffSectionEntries( - oldItems, newItems []json.RawMessage, + oldItems, newItems []jsontext.Value, keyField, changeType, pathPrefix string, values func(key string) *CoreNetworkChangeValues, ) []CoreNetworkChange { @@ -139,9 +142,9 @@ func withRawJSON(v *CoreNetworkChangeValues, raw string) *CoreNetworkChangeValue return v } -func jsonEqual(a, b json.RawMessage) bool { +func jsonEqual(a, b jsontext.Value) bool { var av, bv any - if json.Unmarshal(a, &av) != nil || json.Unmarshal(b, &bv) != nil { + if json.Unmarshal(a, &av, jsonx.V1()) != nil || json.Unmarshal(b, &bv, jsonx.V1()) != nil { return string(a) == string(b) } @@ -159,7 +162,7 @@ const ( // diffCoreNetworkConfiguration compares the single "core-network-configuration" // object, which has no natural key -- a single MODIFY/ADD/REMOVE covers it. -func diffCoreNetworkConfiguration(oldRaw, newRaw json.RawMessage) []CoreNetworkChange { +func diffCoreNetworkConfiguration(oldRaw, newRaw jsontext.Value) []CoreNetworkChange { base := CoreNetworkChange{ Identifier: coreNetworkConfigurationIdentifier, IdentifierPath: coreNetworkConfigurationChangeType, Type: coreNetworkConfigurationChangeType, diff --git a/services/networkmanager/corenetworks.go b/services/networkmanager/corenetworks.go index f9355f8fda..3bd265fad4 100644 --- a/services/networkmanager/corenetworks.go +++ b/services/networkmanager/corenetworks.go @@ -1,10 +1,11 @@ package networkmanager import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -42,7 +43,7 @@ func (b *InMemoryBackend) CreateCoreNetwork( return nil, validationError(fmt.Sprintf("%s %s not found", resourceGlobalNetwork, globalNetworkID)) } - if policyDocument != "" && !json.Valid([]byte(policyDocument)) { + if policyDocument != "" && !jsontext.Value([]byte(policyDocument)).IsValid(jsonx.V1()) { return nil, coreNetworkPolicyError("PolicyDocument is not valid JSON", []CoreNetworkPolicyError{ {ErrorCode: "InvalidPolicyDocument", Message: "PolicyDocument must be valid JSON", Path: "/"}, }) @@ -170,7 +171,7 @@ func (b *InMemoryBackend) PutCoreNetworkPolicy( return nil, nil, notFoundError(resourceCoreNetwork, coreNetworkID) } - if !json.Valid([]byte(policyDocument)) { + if !jsontext.Value([]byte(policyDocument)).IsValid(jsonx.V1()) { return nil, nil, coreNetworkPolicyError("PolicyDocument is not valid JSON", []CoreNetworkPolicyError{ {ErrorCode: "InvalidPolicyDocument", Message: "PolicyDocument must be valid JSON", Path: "/"}, }) diff --git a/services/networkmanager/handler.go b/services/networkmanager/handler.go index 40d7a603c5..6ab7053341 100644 --- a/services/networkmanager/handler.go +++ b/services/networkmanager/handler.go @@ -2,7 +2,7 @@ package networkmanager import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -245,7 +246,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { addErrorFields(body, apiErr) } - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) @@ -378,7 +379,7 @@ func decodeJSONBody(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validationError("malformed request body: " + err.Error()) } @@ -388,7 +389,7 @@ func decodeJSONBody(body []byte, v any) error { // marshalResponse marshals v, wrapping any error as an internal failure // (marshaling our own well-typed wire structs should never fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, internalServerError("networkmanager: marshal response: " + err.Error()) } diff --git a/services/networkmanager/introspection.go b/services/networkmanager/introspection.go index ffceea9890..f0d2c763c7 100644 --- a/services/networkmanager/introspection.go +++ b/services/networkmanager/introspection.go @@ -1,9 +1,10 @@ package networkmanager import ( - "encoding/json" + "encoding/json/v2" "sort" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -201,7 +202,7 @@ func (b *InMemoryBackend) coreNetworkScopedResourceItems( } func mustJSON(v any) string { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return "{}" } diff --git a/services/networkmanager/persistence.go b/services/networkmanager/persistence.go index cdb47f4c19..a996c5daa4 100644 --- a/services/networkmanager/persistence.go +++ b/services/networkmanager/persistence.go @@ -2,7 +2,7 @@ package networkmanager import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -20,11 +20,11 @@ const networkManagerSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Network Manager // backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - OrgStatus *organizationStatus `json:"orgStatus"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + OrgStatus *organizationStatus `json:"orgStatus"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/networkmanager/resourcepolicy.go b/services/networkmanager/resourcepolicy.go index b784652e07..d97230c78e 100644 --- a/services/networkmanager/resourcepolicy.go +++ b/services/networkmanager/resourcepolicy.go @@ -1,6 +1,10 @@ package networkmanager -import "encoding/json" +import ( + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" +) // This file implements PARITY.md family W: a resource-based IAM-style JSON // policy document attached to a NetworkManager resource ARN (used for @@ -16,7 +20,7 @@ func (b *InMemoryBackend) PutResourcePolicy(resourceArn, policyDocument string) b.mu.Lock("PutResourcePolicy") defer b.mu.Unlock() - if !json.Valid([]byte(policyDocument)) { + if !jsontext.Value([]byte(policyDocument)).IsValid(jsonx.V1()) { return validationError("PolicyDocument is not valid JSON") } diff --git a/services/networkmanager/validate.go b/services/networkmanager/validate.go index 0f640f3cb0..d52a4ebd95 100644 --- a/services/networkmanager/validate.go +++ b/services/networkmanager/validate.go @@ -1,13 +1,14 @@ package networkmanager import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" "strconv" "unicode/utf8" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -93,7 +94,7 @@ func decodeObject(body []byte) (map[string]any, bool) { } var m map[string]any - if err := json.Unmarshal(body, &m); err != nil { + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { return nil, false } diff --git a/services/networkmonitor/handler.go b/services/networkmonitor/handler.go index b1fa05d524..5d9b874387 100644 --- a/services/networkmonitor/handler.go +++ b/services/networkmonitor/handler.go @@ -2,7 +2,7 @@ package networkmonitor import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -293,9 +294,6 @@ func (h *Handler) dispatch( } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var code string var status int @@ -313,8 +311,8 @@ func (h *Handler) handleError(c *echo.Context, err error) error { status = http.StatusBadRequest code = "ValidationException" case errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): status = http.StatusBadRequest code = "ValidationException" default: @@ -402,7 +400,7 @@ func extractTagResourceARN(path string) string { func (h *Handler) handleCreateMonitor(ctx context.Context, body []byte) ([]byte, error) { var req createMonitorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -430,7 +428,7 @@ func (h *Handler) handleCreateMonitor(ctx context.Context, body []byte) ([]byte, Tags: m.Tags, } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleDeleteMonitor(ctx context.Context, path string) ([]byte, error) { @@ -473,7 +471,7 @@ func (h *Handler) handleGetMonitor(ctx context.Context, path string) ([]byte, er ModifiedAt: epochSeconds(m.ModifiedAt), } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleUpdateMonitor( @@ -487,7 +485,7 @@ func (h *Handler) handleUpdateMonitor( } var req updateMonitorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -505,7 +503,7 @@ func (h *Handler) handleUpdateMonitor( Tags: m.Tags, } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleListMonitors(ctx context.Context, query url.Values) ([]byte, error) { @@ -529,7 +527,7 @@ func (h *Handler) handleListMonitors(ctx context.Context, query url.Values) ([]b NextToken: outToken, } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleCreateProbe(ctx context.Context, path string, body []byte) ([]byte, error) { @@ -539,7 +537,7 @@ func (h *Handler) handleCreateProbe(ctx context.Context, path string, body []byt } var req createProbeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -552,7 +550,7 @@ func (h *Handler) handleCreateProbe(ctx context.Context, path string, body []byt return nil, err } - return json.Marshal(toProbeWire(probe)) + return json.Marshal(toProbeWire(probe), jsonx.V1()) } func (h *Handler) handleDeleteProbe(ctx context.Context, path string) ([]byte, error) { @@ -591,7 +589,7 @@ func (h *Handler) handleGetProbe(ctx context.Context, path string) ([]byte, erro return nil, err } - return json.Marshal(toProbeWire(probe)) + return json.Marshal(toProbeWire(probe), jsonx.V1()) } func (h *Handler) handleUpdateProbe(ctx context.Context, path string, body []byte) ([]byte, error) { @@ -607,7 +605,7 @@ func (h *Handler) handleUpdateProbe(ctx context.Context, path string, body []byt } var req updateProbeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -616,7 +614,7 @@ func (h *Handler) handleUpdateProbe(ctx context.Context, path string, body []byt return nil, err } - return json.Marshal(toProbeWire(probe)) + return json.Marshal(toProbeWire(probe), jsonx.V1()) } func (h *Handler) handleListTagsForResource(ctx context.Context, path string) ([]byte, error) { @@ -634,7 +632,7 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, path string) ([ tags = map[string]string{} } - return json.Marshal(listTagsForResourceResponse{Tags: tags}) + return json.Marshal(listTagsForResourceResponse{Tags: tags}, jsonx.V1()) } func (h *Handler) handleTagResource(ctx context.Context, path string, body []byte) ([]byte, error) { @@ -644,7 +642,7 @@ func (h *Handler) handleTagResource(ctx context.Context, path string, body []byt } var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/networkmonitor/handler_monitors_test.go b/services/networkmonitor/handler_monitors_test.go index 1414a279a4..61ba10624f 100644 --- a/services/networkmonitor/handler_monitors_test.go +++ b/services/networkmonitor/handler_monitors_test.go @@ -1,13 +1,16 @@ package networkmonitor_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerCreateMonitor(t *testing.T) { @@ -185,7 +188,7 @@ func TestHandlerListMonitors(t *testing.T) { } var resp map[string]any - if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { + if err := json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -271,9 +274,9 @@ func TestHandlerGetMonitorProbeTimestampsEpochFloat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out struct { - Probes []map[string]json.RawMessage `json:"probes"` + Probes []map[string]jsontext.Value `json:"probes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Probes, 1, "expected one probe in GetMonitor response") createdAtRaw, hasCreatedAt := out.Probes[0]["createdAt"] @@ -281,7 +284,7 @@ func TestHandlerGetMonitorProbeTimestampsEpochFloat(t *testing.T) { if hasCreatedAt { var asFloat float64 - require.NoError(t, json.Unmarshal(createdAtRaw, &asFloat), + require.NoError(t, json.Unmarshal(createdAtRaw, &asFloat, jsonx.V1()), "probe createdAt in GetMonitor must be JSON number, got: %s", string(createdAtRaw)) assert.Greater(t, asFloat, float64(0)) } @@ -310,15 +313,15 @@ func TestHandlerGetMonitorTimestampsEpochFloat(t *testing.T) { rec := doNMRequest(t, h, http.MethodGet, "/monitors/epoch-mon", nil) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) fieldRaw, ok := raw[tt.field] assert.True(t, ok, "%s must be present", tt.field) if ok { var asFloat float64 - require.NoError(t, json.Unmarshal(fieldRaw, &asFloat), + require.NoError(t, json.Unmarshal(fieldRaw, &asFloat, jsonx.V1()), "%s must be JSON number, got: %s", tt.field, string(fieldRaw)) assert.Greater(t, asFloat, float64(0)) } @@ -382,7 +385,7 @@ func TestHandlerListMonitorsPagination(t *testing.T) { NextToken string `json:"nextToken"` Monitors []any `json:"monitors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) pageCounts = append(pageCounts, len(out.Monitors)) token = out.NextToken @@ -412,7 +415,7 @@ func TestHandlerListMonitorsTokenPastEnd(t *testing.T) { NextToken string `json:"nextToken"` Monitors []any `json:"monitors"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.Monitors, "token past last item must return empty list, not first page") assert.Empty(t, out.NextToken) diff --git a/services/networkmonitor/handler_probes_test.go b/services/networkmonitor/handler_probes_test.go index 31d3610f5b..5d857395b4 100644 --- a/services/networkmonitor/handler_probes_test.go +++ b/services/networkmonitor/handler_probes_test.go @@ -1,12 +1,15 @@ package networkmonitor_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerProbeLifecycle(t *testing.T) { @@ -38,7 +41,7 @@ func TestHandlerProbeLifecycle(t *testing.T) { } var probeResp map[string]any - if err := json.Unmarshal(rr.Body.Bytes(), &probeResp); err != nil { + if err := json.Unmarshal(rr.Body.Bytes(), &probeResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal probe: %v", err) } @@ -96,15 +99,15 @@ func TestHandlerProbeTimestampsEpochFloat(t *testing.T) { rec := doNMRequest(t, h, http.MethodGet, "/monitors/ts-mon/probes/"+probeID, nil) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) createdAtRaw, hasCreatedAt := raw["createdAt"] assert.True(t, hasCreatedAt, "createdAt must be present in GetProbe response") if hasCreatedAt { var asFloat float64 - require.NoError(t, json.Unmarshal(createdAtRaw, &asFloat), + require.NoError(t, json.Unmarshal(createdAtRaw, &asFloat, jsonx.V1()), "createdAt must be a JSON number (epoch seconds), got: %s", string(createdAtRaw)) assert.Greater(t, asFloat, float64(0), "createdAt epoch value must be positive") } @@ -145,7 +148,7 @@ func TestHandlerProbeAddressFamilyAutoDetect(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantFamily, out["addressFamily"], "addressFamily for %s", tt.destination) }) } @@ -169,6 +172,6 @@ func TestHandlerUpdateProbeStatePending(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "PENDING", out["state"], "probe state after UpdateProbe with state=PENDING") } diff --git a/services/networkmonitor/handler_tags_test.go b/services/networkmonitor/handler_tags_test.go index b5a07e40e9..3d37da5bc6 100644 --- a/services/networkmonitor/handler_tags_test.go +++ b/services/networkmonitor/handler_tags_test.go @@ -1,9 +1,11 @@ package networkmonitor_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerTags(t *testing.T) { @@ -21,7 +23,7 @@ func TestHandlerTags(t *testing.T) { } var mon map[string]any - if err := json.Unmarshal(rr.Body.Bytes(), &mon); err != nil { + if err := json.Unmarshal(rr.Body.Bytes(), &mon, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -36,7 +38,7 @@ func TestHandlerTags(t *testing.T) { } var tagResp map[string]any - if err := json.Unmarshal(rr.Body.Bytes(), &tagResp); err != nil { + if err := json.Unmarshal(rr.Body.Bytes(), &tagResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal tags: %v", err) } @@ -58,7 +60,7 @@ func TestHandlerTags(t *testing.T) { } rr = doNMRequest(t, h, http.MethodGet, "/tags/"+monARN, nil) - if err := json.Unmarshal(rr.Body.Bytes(), &tagResp); err != nil { + if err := json.Unmarshal(rr.Body.Bytes(), &tagResp, jsonx.V1()); err != nil { t.Fatalf("unmarshal: %v", err) } diff --git a/services/networkmonitor/handler_test.go b/services/networkmonitor/handler_test.go index d0771c314c..671ad94a6a 100644 --- a/services/networkmonitor/handler_test.go +++ b/services/networkmonitor/handler_test.go @@ -2,7 +2,7 @@ package networkmonitor_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/networkmonitor" ) @@ -36,7 +37,7 @@ func doNMRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) if err != nil { t.Fatalf("marshal body: %v", err) } @@ -69,7 +70,7 @@ func createMonitorP(t *testing.T, h *networkmonitor.Handler, name string) string require.Equal(t, http.StatusOK, rec.Code, "create monitor: %s", rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arn, _ := out["monitorArn"].(string) require.NotEmpty(t, arn) @@ -90,7 +91,7 @@ func createProbeP(t *testing.T, h *networkmonitor.Handler, monitorName, destinat require.Equal(t, http.StatusOK, rec.Code, "create probe: %s", rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) id, _ := out["probeId"].(string) require.NotEmpty(t, id) diff --git a/services/networkmonitor/persistence.go b/services/networkmonitor/persistence.go index 4128694355..cdef74dee7 100644 --- a/services/networkmonitor/persistence.go +++ b/services/networkmonitor/persistence.go @@ -2,7 +2,7 @@ package networkmonitor import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -25,9 +25,9 @@ const networkmonitorSnapshotVersion = 1 // backend. Tables holds one JSON-encoded array per registered store.Table // (just "monitors"), produced by [store.Registry.SnapshotAll]. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - NextProbeSeq int64 `json:"next_probe_seq"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + NextProbeSeq int64 `json:"next_probe_seq"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/oam/persistence.go b/services/oam/persistence.go index ee310d30a2..e7b2cbe8e1 100644 --- a/services/oam/persistence.go +++ b/services/oam/persistence.go @@ -2,7 +2,7 @@ package oam import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,11 +12,11 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Seq int64 `json:"seq"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Seq int64 `json:"seq"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/oam/policy.go b/services/oam/policy.go index c38d3f1445..27c497cfac 100644 --- a/services/oam/policy.go +++ b/services/oam/policy.go @@ -1,11 +1,13 @@ package oam import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -15,24 +17,24 @@ const ( ) type rawPolicy struct { - Version string `json:"Version,omitempty"` - Statement json.RawMessage `json:"Statement"` + Version string `json:"Version,omitempty"` + Statement jsontext.Value `json:"Statement"` } -func decodeStatements(raw json.RawMessage) ([]map[string]any, bool) { +func decodeStatements(raw jsontext.Value) ([]map[string]any, bool) { trimmed := strings.TrimSpace(string(raw)) switch { case strings.HasPrefix(trimmed, "{"): var one map[string]any - if json.Unmarshal(raw, &one) != nil { + if json.Unmarshal(raw, &one, jsonx.V1()) != nil { return nil, false } return []map[string]any{one}, true case strings.HasPrefix(trimmed, "["): var many []map[string]any - if json.Unmarshal(raw, &many) != nil { + if json.Unmarshal(raw, &many, jsonx.V1()) != nil { return nil, false } @@ -63,7 +65,7 @@ func stringList(v any) []string { func validateSinkPolicy(policy string) error { var p rawPolicy - if err := json.Unmarshal([]byte(policy), &p); err != nil { + if err := json.Unmarshal([]byte(policy), &p, jsonx.V1()); err != nil { return fail(ErrInvalidParameter, "Policy is not valid JSON") } @@ -120,7 +122,7 @@ func (b *InMemoryBackend) sinkPolicyAllowsLocked( } var p rawPolicy - if json.Unmarshal([]byte(sink.Policy), &p) != nil { + if json.Unmarshal([]byte(sink.Policy), &p, jsonx.V1()) != nil { return false } @@ -141,7 +143,7 @@ func (b *InMemoryBackend) sinkPolicyAllowsLocked( return false } - doc, err := json.Marshal(map[string]any{"Version": p.Version, "Statement": applicable}) + doc, err := json.Marshal(map[string]any{"Version": p.Version, "Statement": applicable}, jsonx.V1()) if err != nil { return false } diff --git a/services/oam/realclient_links_test.go b/services/oam/realclient_links_test.go index 96f2ef845c..b6b01c7600 100644 --- a/services/oam/realclient_links_test.go +++ b/services/oam/realclient_links_test.go @@ -1,7 +1,7 @@ package oam_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/oam" ) @@ -23,11 +24,11 @@ func allowPolicy(account, condition string) string { if condition != "" { var c any - _ = json.Unmarshal([]byte(condition), &c) + _ = json.Unmarshal([]byte(condition), &c, jsonx.V1()) stmt["Condition"] = c } - b, _ := json.Marshal(map[string]any{"Version": "2012-10-17", "Statement": []any{stmt}}) + b, _ := json.Marshal(map[string]any{"Version": "2012-10-17", "Statement": []any{stmt}}, jsonx.V1()) return string(b) } diff --git a/services/ociapigateway/auth.go b/services/ociapigateway/auth.go index 7228d56e18..41f9a74700 100644 --- a/services/ociapigateway/auth.go +++ b/services/ociapigateway/auth.go @@ -7,7 +7,8 @@ import ( "crypto/rsa" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "errors" "fmt" @@ -20,6 +21,7 @@ import ( "github.com/golang-jwt/jwt/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -170,7 +172,11 @@ func (b *InMemoryBackend) remoteKeys(ctx context.Context, uri string, hours *int } `json:"keys"` } - if err = json.NewDecoder(io.LimitReader(resp.Body, jwksMaxBytes)).Decode(&doc); err != nil { + if err = json.UnmarshalDecode( + jsontext.NewDecoder(io.LimitReader(resp.Body, jwksMaxBytes)), + &doc, + jsonx.V1(), + ); err != nil { return nil, err } @@ -460,7 +466,7 @@ func (b *InMemoryBackend) customAuth(ctx context.Context, a *authDoc, token stri payload = map[string]any{"type": "USER_DEFINED", "data": data} } - body, _ := json.Marshal(payload) //nolint:errchkjson // static shape + body, _ := json.Marshal(payload, jsonx.V1()) //nolint:errchkjson // static shape out, err := inv.InvokeFunction(ctx, a.FunctionID, FunctionRequest{ Method: http.MethodPost, Header: http.Header{"Content-Type": {"application/json"}}, Body: body, @@ -475,7 +481,7 @@ func (b *InMemoryBackend) customAuth(ctx context.Context, a *authDoc, token stri Active bool `json:"active"` } - if json.Unmarshal(out.Body, &verdict) != nil || !verdict.Active { + if json.Unmarshal(out.Body, &verdict, jsonx.V1()) != nil || !verdict.Active { return authResult{wwwAuth: verdict.WWWAuth} } diff --git a/services/ociapigateway/auth_test.go b/services/ociapigateway/auth_test.go index 390b485b5f..866d961d0a 100644 --- a/services/ociapigateway/auth_test.go +++ b/services/ociapigateway/auth_test.go @@ -5,7 +5,8 @@ import ( "crypto/rsa" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "math/big" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ociapigateway "github.com/blackbirdworks/gopherstack/services/ociapigateway" ) @@ -51,7 +53,7 @@ func pemOf(t *testing.T, k *rsa.PrivateKey, kid string) string { b, err := json.Marshal(map[string]string{ "format": "PEM", "kid": kid, "key": string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})), - }) + }, jsonx.V1()) require.NoError(t, err) return string(b) @@ -262,9 +264,9 @@ func TestJWTRemoteJWKS(t *testing.T) { fetches.Add(1) var key map[string]any - _ = json.Unmarshal([]byte(jwkOf(k, "r1")), &key) + _ = json.Unmarshal([]byte(jwkOf(k, "r1")), &key, jsonx.V1()) - _ = json.NewEncoder(w).Encode(map[string]any{"keys": []any{key}}) + _ = json.MarshalEncode(jsontext.NewEncoder(w, jsonx.V1()), map[string]any{"keys": []any{key}}) })) t.Cleanup(jwks.Close) @@ -356,7 +358,7 @@ func TestCustomAuthenticator(t *testing.T) { Token string `json:"token"` } - _ = json.Unmarshal(in.Body, &p) + _ = json.Unmarshal(in.Body, &p, jsonx.V1()) verdict := map[string]any{"active": false, "wwwAuthenticate": `Bearer realm="orders"`} @@ -367,7 +369,7 @@ func TestCustomAuthenticator(t *testing.T) { verdict = map[string]any{"active": true, "scope": []string{"orders.write"}} } - out, _ := json.Marshal(verdict) + out, _ := json.Marshal(verdict, jsonx.V1()) return ociapigateway.FunctionResponse{Status: 200, Body: out}, nil }} diff --git a/services/ociapigateway/certificates.go b/services/ociapigateway/certificates.go index 4e776742ba..e15a4e1795 100644 --- a/services/ociapigateway/certificates.go +++ b/services/ociapigateway/certificates.go @@ -3,7 +3,7 @@ package ociapigateway import ( "crypto/tls" "crypto/x509" - "encoding/json" + "encoding/json/jsontext" "encoding/pem" "time" @@ -13,12 +13,12 @@ import ( type createCertificateIn struct { Tags - CompartmentID string `json:"compartmentId"` - DisplayName string `json:"displayName"` - PrivateKey string `json:"privateKey"` - Certificate string `json:"certificate"` - Intermediate string `json:"intermediateCertificates"` - Locks json.RawMessage `json:"locks"` + CompartmentID string `json:"compartmentId"` + DisplayName string `json:"displayName"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + Intermediate string `json:"intermediateCertificates"` + Locks jsontext.Value `json:"locks"` } type updateCertificateIn struct { @@ -33,7 +33,7 @@ type CertificateWire struct { TimeNotValidAfter time.Time `json:"timeNotValidAfter"` DefinedTags map[string]map[string]any `json:"definedTags,omitempty"` FreeformTags map[string]string `json:"freeformTags,omitempty"` - Locks json.RawMessage `json:"locks,omitempty"` + Locks jsontext.Value `json:"locks,omitempty"` ID string `json:"id"` CompartmentID string `json:"compartmentId"` DisplayName string `json:"displayName"` diff --git a/services/ociapigateway/dataplane.go b/services/ociapigateway/dataplane.go index 24794a4f73..77916d1246 100644 --- a/services/ociapigateway/dataplane.go +++ b/services/ociapigateway/dataplane.go @@ -1,7 +1,7 @@ package ociapigateway import ( - "encoding/json" + "encoding/json/v2" "io" "maps" "net/http" @@ -9,6 +9,7 @@ import ( "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -26,7 +27,10 @@ func writeGWError(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - out, _ := json.Marshal(map[string]any{"code": status, "message": msg}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + map[string]any{"code": status, "message": msg}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = w.Write(out) } diff --git a/services/ociapigateway/dataplane_test.go b/services/ociapigateway/dataplane_test.go index fef97d428b..b22b60c78f 100644 --- a/services/ociapigateway/dataplane_test.go +++ b/services/ociapigateway/dataplane_test.go @@ -2,7 +2,7 @@ package ociapigateway_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ociapigateway "github.com/blackbirdworks/gopherstack/services/ociapigateway" ) @@ -452,7 +453,7 @@ func TestSpecRoundTrip(t *testing.T) { } `json:"specification"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, "STOCK_RESPONSE_BACKEND", out.Specification.Routes[0].Backend.Type) assert.NotContains(t, body, "StubResponse") } diff --git a/services/ociapigateway/deployments.go b/services/ociapigateway/deployments.go index e0b3aa781f..f67d68bd15 100644 --- a/services/ociapigateway/deployments.go +++ b/services/ociapigateway/deployments.go @@ -1,7 +1,7 @@ package ociapigateway import ( - "encoding/json" + "encoding/json/jsontext" "time" "github.com/blackbirdworks/gopherstack/pkgs/page" @@ -10,18 +10,18 @@ import ( type createDeploymentIn struct { Tags - GatewayID string `json:"gatewayId"` - CompartmentID string `json:"compartmentId"` - PathPrefix string `json:"pathPrefix"` - DisplayName string `json:"displayName"` - Locks json.RawMessage `json:"locks"` - Specification json.RawMessage `json:"specification"` + GatewayID string `json:"gatewayId"` + CompartmentID string `json:"compartmentId"` + PathPrefix string `json:"pathPrefix"` + DisplayName string `json:"displayName"` + Locks jsontext.Value `json:"locks"` + Specification jsontext.Value `json:"specification"` } type updateDeploymentIn struct { Tags - DisplayName *string `json:"displayName"` - Specification json.RawMessage `json:"specification"` + DisplayName *string `json:"displayName"` + Specification jsontext.Value `json:"specification"` } // DeploymentWire is the wire form of a deployment. @@ -37,8 +37,8 @@ type DeploymentWire struct { Endpoint string `json:"endpoint"` DisplayName string `json:"displayName"` LifecycleState string `json:"lifecycleState"` - Locks json.RawMessage `json:"locks,omitempty"` - Specification json.RawMessage `json:"specification,omitempty"` + Locks jsontext.Value `json:"locks,omitempty"` + Specification jsontext.Value `json:"specification,omitempty"` } func (b *InMemoryBackend) deploymentView(d *Deployment, full bool) DeploymentWire { diff --git a/services/ociapigateway/gateways.go b/services/ociapigateway/gateways.go index 91ae8de001..7ce31e0923 100644 --- a/services/ociapigateway/gateways.go +++ b/services/ociapigateway/gateways.go @@ -1,36 +1,38 @@ package ociapigateway import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "slices" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) type createGatewayIn struct { Tags - Locks json.RawMessage `json:"locks"` - CaBundles json.RawMessage `json:"caBundles"` - ResponseCache json.RawMessage `json:"responseCacheDetails"` - DisplayName string `json:"displayName"` - CompartmentID string `json:"compartmentId"` - EndpointType string `json:"endpointType"` - SubnetID string `json:"subnetId"` - CertificateID string `json:"certificateId"` - IPMode string `json:"ipMode"` - NSGs []string `json:"networkSecurityGroupIds"` + Locks jsontext.Value `json:"locks"` + CaBundles jsontext.Value `json:"caBundles"` + ResponseCache jsontext.Value `json:"responseCacheDetails"` + DisplayName string `json:"displayName"` + CompartmentID string `json:"compartmentId"` + EndpointType string `json:"endpointType"` + SubnetID string `json:"subnetId"` + CertificateID string `json:"certificateId"` + IPMode string `json:"ipMode"` + NSGs []string `json:"networkSecurityGroupIds"` } type updateGatewayIn struct { Tags - CaBundles json.RawMessage `json:"caBundles"` - ResponseCache json.RawMessage `json:"responseCacheDetails"` - DisplayName *string `json:"displayName"` - CertificateID *string `json:"certificateId"` - NSGs []string `json:"networkSecurityGroupIds"` + CaBundles jsontext.Value `json:"caBundles"` + ResponseCache jsontext.Value `json:"responseCacheDetails"` + DisplayName *string `json:"displayName"` + CertificateID *string `json:"certificateId"` + NSGs []string `json:"networkSecurityGroupIds"` } type changeCompartmentIn struct { @@ -43,9 +45,9 @@ type GatewayWire struct { TimeUpdated time.Time `json:"timeUpdated"` DefinedTags map[string]map[string]any `json:"definedTags,omitempty"` FreeformTags map[string]string `json:"freeformTags,omitempty"` - Locks json.RawMessage `json:"locks,omitempty"` - CaBundles json.RawMessage `json:"caBundles,omitempty"` - ResponseCache json.RawMessage `json:"responseCacheDetails,omitempty"` + Locks jsontext.Value `json:"locks,omitempty"` + CaBundles jsontext.Value `json:"caBundles,omitempty"` + ResponseCache jsontext.Value `json:"responseCacheDetails,omitempty"` ID string `json:"id"` CompartmentID string `json:"compartmentId"` EndpointType string `json:"endpointType"` @@ -117,12 +119,12 @@ func (b *InMemoryBackend) validateGateway(in *createGatewayIn) *APIError { return b.checkCertificateRef(in.CertificateID) } -func jsonIsNoCache(raw json.RawMessage) bool { +func jsonIsNoCache(raw jsontext.Value) bool { var d struct { Type string `json:"type"` } - return json.Unmarshal(raw, &d) == nil && d.Type == "NONE" + return json.Unmarshal(raw, &d, jsonx.V1()) == nil && d.Type == "NONE" } func (b *InMemoryBackend) gatewayNameTaken(compartment, name, except string) bool { @@ -169,7 +171,7 @@ func (b *InMemoryBackend) CreateGateway(in createGatewayIn) (GatewayWire, string return gatewayView(g), wr, g.Etag, nil } -func nullRaw(r json.RawMessage) json.RawMessage { +func nullRaw(r jsontext.Value) jsontext.Value { if string(r) == jsonNull { return nil } diff --git a/services/ociapigateway/handler.go b/services/ociapigateway/handler.go index 714b9cd74f..e98844c165 100644 --- a/services/ociapigateway/handler.go +++ b/services/ociapigateway/handler.go @@ -3,7 +3,8 @@ package ociapigateway import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +119,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociapigateway: encode response: %w", err) } @@ -125,9 +128,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -162,10 +163,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -187,7 +187,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -262,7 +265,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociapigateway/helpers_test.go b/services/ociapigateway/helpers_test.go index e791ca8a95..810f0ec7f2 100644 --- a/services/ociapigateway/helpers_test.go +++ b/services/ociapigateway/helpers_test.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "io" @@ -19,6 +19,7 @@ import ( "github.com/oracle/oci-go-sdk/v65/common" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ociapigateway "github.com/blackbirdworks/gopherstack/services/ociapigateway" @@ -190,7 +191,7 @@ func specFromJSON(t *testing.T, s string) *apigateway.ApiSpecification { t.Helper() var spec apigateway.ApiSpecification - require.NoError(t, json.Unmarshal([]byte(s), &spec)) + require.NoError(t, json.Unmarshal([]byte(s), &spec, jsonx.V1())) return &spec } diff --git a/services/ociapigateway/models.go b/services/ociapigateway/models.go index 6158ff5b8a..d03c5e2ef9 100644 --- a/services/ociapigateway/models.go +++ b/services/ociapigateway/models.go @@ -1,7 +1,7 @@ package ociapigateway import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -38,59 +38,59 @@ func (t *Tags) merge(in Tags) { // Gateway is an API gateway. type Gateway struct { - TimeCreated time.Time `json:"timeCreated"` - TimeUpdated time.Time `json:"timeUpdated"` - Tags Tags `json:"tags"` - TransitionAt *time.Time `json:"transitionAt,omitempty"` - Locks json.RawMessage `json:"locks,omitempty"` - CaBundles json.RawMessage `json:"caBundles,omitempty"` - ResponseCache json.RawMessage `json:"responseCache,omitempty"` - ID string `json:"id"` - CompartmentID string `json:"compartmentId"` - EndpointType string `json:"endpointType"` - DisplayName string `json:"displayName"` - SubnetID string `json:"subnetId"` - Hostname string `json:"hostname"` - CertificateID string `json:"certificateId,omitempty"` - IPMode string `json:"ipMode,omitempty"` - State string `json:"state"` - Etag string `json:"etag"` - NSGs []string `json:"networkSecurityGroupIds,omitempty"` + TimeCreated time.Time `json:"timeCreated"` + TimeUpdated time.Time `json:"timeUpdated"` + Tags Tags `json:"tags"` + TransitionAt *time.Time `json:"transitionAt,omitempty"` + Locks jsontext.Value `json:"locks,omitempty"` + CaBundles jsontext.Value `json:"caBundles,omitempty"` + ResponseCache jsontext.Value `json:"responseCache,omitempty"` + ID string `json:"id"` + CompartmentID string `json:"compartmentId"` + EndpointType string `json:"endpointType"` + DisplayName string `json:"displayName"` + SubnetID string `json:"subnetId"` + Hostname string `json:"hostname"` + CertificateID string `json:"certificateId,omitempty"` + IPMode string `json:"ipMode,omitempty"` + State string `json:"state"` + Etag string `json:"etag"` + NSGs []string `json:"networkSecurityGroupIds,omitempty"` } // Deployment is an API deployment on a gateway. type Deployment struct { - TimeCreated time.Time `json:"timeCreated"` - TimeUpdated time.Time `json:"timeUpdated"` - Tags Tags `json:"tags"` - TransitionAt *time.Time `json:"transitionAt,omitempty"` - ID string `json:"id"` - GatewayID string `json:"gatewayId"` - CompartmentID string `json:"compartmentId"` - PathPrefix string `json:"pathPrefix"` - DisplayName string `json:"displayName"` - State string `json:"state"` - Etag string `json:"etag"` - Locks json.RawMessage `json:"locks,omitempty"` - Spec json.RawMessage `json:"specification"` + TimeCreated time.Time `json:"timeCreated"` + TimeUpdated time.Time `json:"timeUpdated"` + Tags Tags `json:"tags"` + TransitionAt *time.Time `json:"transitionAt,omitempty"` + ID string `json:"id"` + GatewayID string `json:"gatewayId"` + CompartmentID string `json:"compartmentId"` + PathPrefix string `json:"pathPrefix"` + DisplayName string `json:"displayName"` + State string `json:"state"` + Etag string `json:"etag"` + Locks jsontext.Value `json:"locks,omitempty"` + Spec jsontext.Value `json:"specification"` } // Certificate is a TLS certificate usable by a gateway. type Certificate struct { - TimeCreated time.Time `json:"timeCreated"` - TimeUpdated time.Time `json:"timeUpdated"` - TimeNotValidAfter time.Time `json:"timeNotValidAfter"` - Tags Tags `json:"tags"` - TransitionAt *time.Time `json:"transitionAt,omitempty"` - Locks json.RawMessage `json:"locks,omitempty"` - ID string `json:"id"` - CompartmentID string `json:"compartmentId"` - DisplayName string `json:"displayName"` - CertPEM string `json:"certificate"` - Intermediate string `json:"intermediateCertificates,omitempty"` - State string `json:"state"` - Etag string `json:"etag"` - SubjectNames []string `json:"subjectNames"` + TimeCreated time.Time `json:"timeCreated"` + TimeUpdated time.Time `json:"timeUpdated"` + TimeNotValidAfter time.Time `json:"timeNotValidAfter"` + Tags Tags `json:"tags"` + TransitionAt *time.Time `json:"transitionAt,omitempty"` + Locks jsontext.Value `json:"locks,omitempty"` + ID string `json:"id"` + CompartmentID string `json:"compartmentId"` + DisplayName string `json:"displayName"` + CertPEM string `json:"certificate"` + Intermediate string `json:"intermediateCertificates,omitempty"` + State string `json:"state"` + Etag string `json:"etag"` + SubjectNames []string `json:"subjectNames"` } // WorkRequest records an asynchronous operation. diff --git a/services/ociapigateway/spec.go b/services/ociapigateway/spec.go index 331f768915..63fdb134e0 100644 --- a/services/ociapigateway/spec.go +++ b/services/ociapigateway/spec.go @@ -2,12 +2,15 @@ package ociapigateway import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/url" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Backend, authentication and policy discriminators. @@ -144,12 +147,12 @@ type authDoc struct { } type requestPolicies struct { - Authentication *authDoc `json:"authentication"` - RateLimiting *rateDoc `json:"rateLimiting"` - Cors *corsDoc `json:"cors"` - MutualTLS json.RawMessage `json:"mutualTls"` - UsagePlans json.RawMessage `json:"usagePlans"` - DynamicAuthentication json.RawMessage `json:"dynamicAuthentication"` + Authentication *authDoc `json:"authentication"` + RateLimiting *rateDoc `json:"rateLimiting"` + Cors *corsDoc `json:"cors"` + MutualTLS jsontext.Value `json:"mutualTls"` + UsagePlans jsontext.Value `json:"usagePlans"` + DynamicAuthentication jsontext.Value `json:"dynamicAuthentication"` } type setItem struct { @@ -196,19 +199,19 @@ type authzDoc struct { } type routeRequestPolicies struct { - Authorization *authzDoc `json:"authorization"` - Cors *corsDoc `json:"cors"` - HeaderTransform *transformDoc `json:"headerTransformations"` - QueryTransform *transformDoc `json:"queryParameterTransformations"` - QueryValidations json.RawMessage `json:"queryParameterValidations"` - HeaderValidations json.RawMessage `json:"headerValidations"` - BodyValidation json.RawMessage `json:"bodyValidation"` - ResponseCacheLookup json.RawMessage `json:"responseCacheLookup"` + Authorization *authzDoc `json:"authorization"` + Cors *corsDoc `json:"cors"` + HeaderTransform *transformDoc `json:"headerTransformations"` + QueryTransform *transformDoc `json:"queryParameterTransformations"` + QueryValidations jsontext.Value `json:"queryParameterValidations"` + HeaderValidations jsontext.Value `json:"headerValidations"` + BodyValidation jsontext.Value `json:"bodyValidation"` + ResponseCacheLookup jsontext.Value `json:"responseCacheLookup"` } type routeResponsePolicies struct { - HeaderTransform *transformDoc `json:"headerTransformations"` - ResponseCacheStore json.RawMessage `json:"responseCacheStore"` + HeaderTransform *transformDoc `json:"headerTransformations"` + ResponseCacheStore jsontext.Value `json:"responseCacheStore"` } type routeDoc struct { @@ -221,7 +224,7 @@ type routeDoc struct { type specDoc struct { RequestPolicies *requestPolicies `json:"requestPolicies"` - LoggingPolicies json.RawMessage `json:"loggingPolicies"` + LoggingPolicies jsontext.Value `json:"loggingPolicies"` Routes []routeDoc `json:"routes"` } @@ -231,7 +234,7 @@ var ( prefixCharRe = regexp.MustCompile(`^[A-Za-z0-9$\-_.+!*'(),%;:@&=]+$`) ) -func isPresent(raw json.RawMessage) bool { +func isPresent(raw jsontext.Value) bool { return len(raw) > 0 && !bytes.Equal(raw, []byte(jsonNull)) } @@ -265,13 +268,13 @@ func validatePathPrefix(p string) *APIError { } // compileSpec decodes and validates a deployment specification. -func compileSpec(raw json.RawMessage) (*compiledSpec, *APIError) { +func compileSpec(raw jsontext.Value) (*compiledSpec, *APIError) { if !isPresent(raw) { return nil, errMissing("specification is required") } var doc specDoc - if err := json.Unmarshal(raw, &doc); err != nil { + if err := json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return nil, errParam("specification is not valid: " + err.Error()) } @@ -312,7 +315,7 @@ func validateRequestPolicies(p *requestPolicies) *APIError { return nil } - for name, raw := range map[string]json.RawMessage{ + for name, raw := range map[string]jsontext.Value{ "mutualTls": p.MutualTLS, "usagePlans": p.UsagePlans, "dynamicAuthentication": p.DynamicAuthentication, } { if isPresent(raw) { @@ -525,7 +528,7 @@ func validateRouteRequestPolicies(p *routeRequestPolicies) *APIError { return nil } - for name, raw := range map[string]json.RawMessage{ + for name, raw := range map[string]jsontext.Value{ "queryParameterValidations": p.QueryValidations, "headerValidations": p.HeaderValidations, "bodyValidation": p.BodyValidation, "responseCacheLookup": p.ResponseCacheLookup, } { diff --git a/services/ociartifacts/dataplane.go b/services/ociartifacts/dataplane.go index 3772030349..042339cd55 100644 --- a/services/ociartifacts/dataplane.go +++ b/services/ociartifacts/dataplane.go @@ -4,12 +4,13 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry" ) @@ -111,7 +112,7 @@ func (b *InMemoryBackend) IssueToken(user string, anon bool, scopes []Scope) (st b.mu.RUnlock() body, _ := json.Marshal( - tokenClaims{Sub: user, Anon: anon, Scopes: raw, Exp: exp}, + tokenClaims{Sub: user, Anon: anon, Scopes: raw, Exp: exp}, jsonx.V1(), ) //nolint:errchkjson // static shape payload := base64.RawURLEncoding.EncodeToString(body) @@ -131,7 +132,7 @@ func (b *InMemoryBackend) ParseToken(token string) (tokenClaims, bool) { } var c tokenClaims - if json.Unmarshal(raw, &c) != nil { + if json.Unmarshal(raw, &c, jsonx.V1()) != nil { return tokenClaims{}, false } @@ -274,7 +275,7 @@ func (b *InMemoryBackend) RecordPush(display, ref, digest, user string, body []b var doc manifestDoc - _ = json.Unmarshal(body, &doc) + _ = json.Unmarshal(body, &doc, jsonx.V1()) b.mu.Lock("RecordPush") defer b.mu.Unlock() diff --git a/services/ociartifacts/handler.go b/services/ociartifacts/handler.go index 69e6ba2dd5..efcd5df436 100644 --- a/services/ociartifacts/handler.go +++ b/services/ociartifacts/handler.go @@ -7,7 +7,8 @@ package ociartifacts import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -20,6 +21,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -118,7 +121,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociartifacts: encode response: %w", err) } @@ -127,9 +130,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -172,10 +173,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr(http.StatusBadRequest, "CannotParseRequest", "The request body is not valid JSON for this operation") } @@ -194,7 +194,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -266,7 +269,7 @@ func (h *Handler) admin(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociartifacts/helpers_test.go b/services/ociartifacts/helpers_test.go index 21b98b682d..40bee2da4e 100644 --- a/services/ociartifacts/helpers_test.go +++ b/services/ociartifacts/helpers_test.go @@ -7,7 +7,7 @@ import ( "crypto/rsa" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "io" @@ -22,6 +22,7 @@ import ( "github.com/oracle/oci-go-sdk/v65/genericartifactscontent" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -254,7 +255,7 @@ func (e *env) upload( func jsonBody(t *testing.T, v any) string { t.Helper() - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(out) diff --git a/services/ociartifacts/registry.go b/services/ociartifacts/registry.go index 4dc292baf2..9140ad9173 100644 --- a/services/ociartifacts/registry.go +++ b/services/ociartifacts/registry.go @@ -5,11 +5,12 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry" ) @@ -28,7 +29,7 @@ type regErrorEntry struct { } func writeJSON(w http.ResponseWriter, status int, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { status, raw = http.StatusInternalServerError, []byte(`{"errors":[{"code":"UNKNOWN","message":"encode"}]}`) } diff --git a/services/ociartifacts/registry_test.go b/services/ociartifacts/registry_test.go index 5dbbf1fb00..28c43b61ee 100644 --- a/services/ociartifacts/registry_test.go +++ b/services/ociartifacts/registry_test.go @@ -1,7 +1,7 @@ package ociartifacts_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociregistry/ocitest" "github.com/blackbirdworks/gopherstack/services/ociartifacts" ) @@ -36,7 +37,7 @@ func (e *env) token(t *testing.T, authz, scope string) (int, tokenReply) { var out tokenReply if resp.Status == http.StatusOK { - require.NoError(t, json.Unmarshal(resp.Body, &out)) + require.NoError(t, json.Unmarshal(resp.Body, &out, jsonx.V1())) } return resp.Status, out @@ -845,7 +846,7 @@ func manifestFor(t *testing.T, cfgDigest, layerDigest string, layerSize, cfgSize "layers": []any{map[string]any{ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": layerSize, "digest": layerDigest, }}, - }) + }, jsonx.V1()) require.NoError(t, err) return body diff --git a/services/ocicontainerengine/handler.go b/services/ocicontainerengine/handler.go index 82ccbad374..0dbcf22207 100644 --- a/services/ocicontainerengine/handler.go +++ b/services/ocicontainerengine/handler.go @@ -2,7 +2,7 @@ package ocicontainerengine import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -15,6 +15,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +117,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocicontainerengine: encode response: %w", err) } @@ -124,9 +126,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -163,7 +163,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -185,7 +185,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -254,7 +257,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocicontainerengine/helpers_test.go b/services/ocicontainerengine/helpers_test.go index 5d56c80776..b242888e8a 100644 --- a/services/ocicontainerengine/helpers_test.go +++ b/services/ocicontainerengine/helpers_test.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "io" @@ -19,6 +19,7 @@ import ( ke "github.com/oracle/oci-go-sdk/v65/containerengine" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -345,7 +346,7 @@ func (e *env) mkPool( } func quote(s string) string { - b, _ := json.Marshal(s) + b, _ := json.Marshal(s, jsonx.V1()) return string(b) } diff --git a/services/ocicontainerengine/models.go b/services/ocicontainerengine/models.go index 2790480923..65e599f08e 100644 --- a/services/ocicontainerengine/models.go +++ b/services/ocicontainerengine/models.go @@ -1,7 +1,7 @@ package ocicontainerengine import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -80,15 +80,15 @@ type TagCfg struct { // ClusterOptions is the cluster create and update options block. type ClusterOptions struct { - K8sNetwork *NetworkConfig `json:"kubernetesNetworkConfig,omitempty"` - AddOns *AddOns `json:"addOns,omitempty"` - Admission *Admission `json:"admissionControllerOptions,omitempty"` - PersistentVolume *TagCfg `json:"persistentVolumeConfig,omitempty"` - ServiceLb *TagCfg `json:"serviceLbConfig,omitempty"` - OIDCAuth json.RawMessage `json:"openIdConnectTokenAuthenticationConfig,omitempty"` - OIDCDiscovery json.RawMessage `json:"openIdConnectDiscovery,omitempty"` - ServiceLbSubnetID []string `json:"serviceLbSubnetIds,omitempty"` - IPFamilies []string `json:"ipFamilies,omitempty"` + K8sNetwork *NetworkConfig `json:"kubernetesNetworkConfig,omitempty"` + AddOns *AddOns `json:"addOns,omitempty"` + Admission *Admission `json:"admissionControllerOptions,omitempty"` + PersistentVolume *TagCfg `json:"persistentVolumeConfig,omitempty"` + ServiceLb *TagCfg `json:"serviceLbConfig,omitempty"` + OIDCAuth jsontext.Value `json:"openIdConnectTokenAuthenticationConfig,omitempty"` + OIDCDiscovery jsontext.Value `json:"openIdConnectDiscovery,omitempty"` + ServiceLbSubnetID []string `json:"serviceLbSubnetIds,omitempty"` + IPFamilies []string `json:"ipFamilies,omitempty"` } // KeyDetail references an image signing key. @@ -144,11 +144,11 @@ type Cluster struct { // Placement is one nodeConfigDetails placement configuration. type Placement struct { - PreemptibleConfig json.RawMessage `json:"preemptibleNodeConfig,omitempty"` - AvailabilityDom string `json:"availabilityDomain"` - SubnetID string `json:"subnetId"` - CapacityReserveID string `json:"capacityReservationId,omitempty"` - FaultDomains []string `json:"faultDomains,omitempty"` + PreemptibleConfig jsontext.Value `json:"preemptibleNodeConfig,omitempty"` + AvailabilityDom string `json:"availabilityDomain"` + SubnetID string `json:"subnetId"` + CapacityReserveID string `json:"capacityReservationId,omitempty"` + FaultDomains []string `json:"faultDomains,omitempty"` } // NodeConfig is nodeConfigDetails. @@ -205,8 +205,8 @@ type NodePool struct { QuantityPerSub *int `json:"quantityPerSubnet,omitempty"` Tags Tags `json:"tags"` NodeMetadata map[string]string `json:"nodeMetadata,omitempty"` - Eviction json.RawMessage `json:"eviction,omitempty"` - Cycling json.RawMessage `json:"cycling,omitempty"` + Eviction jsontext.Value `json:"eviction,omitempty"` + Cycling jsontext.Value `json:"cycling,omitempty"` ID string `json:"id"` ClusterID string `json:"clusterId"` CompartmentID string `json:"compartmentId"` diff --git a/services/ocicontainerengine/nodepools.go b/services/ocicontainerengine/nodepools.go index 6915f697c7..7a95f7dd1a 100644 --- a/services/ocicontainerengine/nodepools.go +++ b/services/ocicontainerengine/nodepools.go @@ -2,7 +2,7 @@ package ocicontainerengine import ( "encoding/binary" - "encoding/json" + "encoding/json/jsontext" "math" "net/netip" "regexp" @@ -46,10 +46,10 @@ type CreateNodePoolIn struct { Quantity *int `json:"quantityPerSubnet"` NodeConfig *NodeConfigIn `json:"nodeConfigDetails"` NodeMetadata map[string]string `json:"nodeMetadata"` - Eviction json.RawMessage `json:"nodeEvictionNodePoolSettings"` - Cycling json.RawMessage `json:"nodePoolCyclingDetails"` - SecondaryVnics json.RawMessage `json:"secondaryVnics"` - PrimaryVnic json.RawMessage `json:"primaryVnic"` + Eviction jsontext.Value `json:"nodeEvictionNodePoolSettings"` + Cycling jsontext.Value `json:"nodePoolCyclingDetails"` + SecondaryVnics jsontext.Value `json:"secondaryVnics"` + PrimaryVnic jsontext.Value `json:"primaryVnic"` Compartment string `json:"compartmentId"` ClusterID string `json:"clusterId"` Name string `json:"name"` @@ -71,10 +71,10 @@ type UpdateNodePoolIn struct { SSHKey *string `json:"sshPublicKey"` Shape *string `json:"nodeShape"` NodeMetadata map[string]string `json:"nodeMetadata"` - Eviction json.RawMessage `json:"nodeEvictionNodePoolSettings"` - Cycling json.RawMessage `json:"nodePoolCyclingDetails"` - SecondaryVnics json.RawMessage `json:"secondaryVnics"` - PrimaryVnic json.RawMessage `json:"primaryVnic"` + Eviction jsontext.Value `json:"nodeEvictionNodePoolSettings"` + Cycling jsontext.Value `json:"nodePoolCyclingDetails"` + SecondaryVnics jsontext.Value `json:"secondaryVnics"` + PrimaryVnic jsontext.Value `json:"primaryVnic"` LaunchType string `json:"networkLaunchType"` Labels []KeyValue `json:"initialNodeLabels"` SubnetIDs []string `json:"subnetIds"` @@ -105,8 +105,8 @@ type NodePoolWire struct { NodeSourceDetails *NodeSource `json:"nodeSourceDetails,omitempty"` QuantityPerSubnet *int `json:"quantityPerSubnet,omitempty"` NodeConfigDetails *NodeConfig `json:"nodeConfigDetails,omitempty"` - Eviction json.RawMessage `json:"nodeEvictionNodePoolSettings,omitempty"` - Cycling json.RawMessage `json:"nodePoolCyclingDetails,omitempty"` + Eviction jsontext.Value `json:"nodeEvictionNodePoolSettings,omitempty"` + Cycling jsontext.Value `json:"nodePoolCyclingDetails,omitempty"` Nodes []NodeWire `json:"nodes,omitempty"` ID string `json:"id"` ClusterID string `json:"clusterId"` @@ -171,7 +171,7 @@ func nodePoolView(np *NodePool, summary bool) NodePoolWire { return w } -func rejectUnsupported(secondary, primary json.RawMessage, launch string) *APIError { +func rejectUnsupported(secondary, primary jsontext.Value, launch string) *APIError { if len(secondary) > 0 && string(secondary) != "null" && string(secondary) != "[]" { return errParam("secondaryVnics are not supported by this emulator") } diff --git a/services/ocicontainerinstances/handler.go b/services/ocicontainerinstances/handler.go index e20e9b677a..2954bf5269 100644 --- a/services/ocicontainerinstances/handler.go +++ b/services/ocicontainerinstances/handler.go @@ -3,7 +3,8 @@ package ocicontainerinstances import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +119,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocicontainerinstances: encode response: %w", err) } @@ -125,9 +128,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -168,10 +169,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -193,7 +193,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -262,7 +265,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocicontainerinstances/instances.go b/services/ocicontainerinstances/instances.go index 00f1c5a148..46210b2fb8 100644 --- a/services/ocicontainerinstances/instances.go +++ b/services/ocicontainerinstances/instances.go @@ -1,11 +1,12 @@ package ocicontainerinstances import ( - "encoding/json" + "encoding/json/v2" "slices" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -23,13 +24,13 @@ const ( ) func (in *Instance) clone() *Instance { - raw, err := json.Marshal(in) + raw, err := json.Marshal(in, jsonx.V1()) if err != nil { return in } out := &Instance{} - if json.Unmarshal(raw, out) != nil { + if json.Unmarshal(raw, out, jsonx.V1()) != nil { return in } @@ -172,7 +173,7 @@ func (b *InMemoryBackend) resolveCreds(secrets []PullSecret) ([]Credential, erro Password string `json:"password"` } - if json.Unmarshal([]byte(raw), &auth) != nil || auth.Username == "" || auth.Password == "" { + if json.Unmarshal([]byte(raw), &auth, jsonx.V1()) != nil || auth.Username == "" || auth.Password == "" { return nil, errVaultSecretShape } diff --git a/services/ocicontainerinstances/models.go b/services/ocicontainerinstances/models.go index 979f7b29e4..927876d227 100644 --- a/services/ocicontainerinstances/models.go +++ b/services/ocicontainerinstances/models.go @@ -1,7 +1,7 @@ package ocicontainerinstances import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -110,7 +110,7 @@ type Container struct { State string `json:"lifecycleState"` Details string `json:"lifecycleDetails,omitempty"` WorkingDir string `json:"workingDirectory,omitempty"` - Security json.RawMessage `json:"securityContext,omitempty"` + Security jsontext.Value `json:"securityContext,omitempty"` Command []string `json:"command,omitempty"` Args []string `json:"arguments,omitempty"` Mounts []VolumeMount `json:"volumeMounts,omitempty"` @@ -170,7 +170,7 @@ type Instance struct { Restart string `json:"containerRestartPolicy"` Etag string `json:"etag"` Target string `json:"target,omitempty"` - Security json.RawMessage `json:"securityContext,omitempty"` + Security jsontext.Value `json:"securityContext,omitempty"` Containers []*Container `json:"containers"` Volumes []Volume `json:"volumes,omitempty"` Vnics []Vnic `json:"vnics"` diff --git a/services/ocicontainerinstances/validate.go b/services/ocicontainerinstances/validate.go index 4189af447c..cd086133d0 100644 --- a/services/ocicontainerinstances/validate.go +++ b/services/ocicontainerinstances/validate.go @@ -3,13 +3,15 @@ package ocicontainerinstances import ( "cmp" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/netip" "path" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -152,7 +154,7 @@ func (b *InMemoryBackend) buildVnics(inst *Instance, vnics []createVnicIn) *APIE return nil } -func checkSecurity(raw json.RawMessage) *APIError { +func checkSecurity(raw jsontext.Value) *APIError { if len(raw) == 0 || string(raw) == "null" { return nil } @@ -161,7 +163,7 @@ func checkSecurity(raw json.RawMessage) *APIError { Type string `json:"securityContextType"` } - if err := json.Unmarshal(raw, &sc); err != nil || sc.Type != "LINUX" { + if err := json.Unmarshal(raw, &sc, jsonx.V1()); err != nil || sc.Type != "LINUX" { return errParam("securityContext.securityContextType must be LINUX") } diff --git a/services/ocicontainerinstances/volumes.go b/services/ocicontainerinstances/volumes.go index cd44c26fc7..de23dfb81f 100644 --- a/services/ocicontainerinstances/volumes.go +++ b/services/ocicontainerinstances/volumes.go @@ -2,13 +2,16 @@ package ocicontainerinstances import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "os" "path" "path/filepath" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -166,13 +169,13 @@ func (s *scratch) binds(mounts []VolumeMount) []string { } // securityUser maps a LINUX security context to docker's uid[:gid] user and the fsGroup supplementary group. -func securityUser(raw json.RawMessage) string { +func securityUser(raw jsontext.Value) string { var sc struct { User *int `json:"runAsUser"` Group *int `json:"runAsGroup"` } - if len(raw) == 0 || json.Unmarshal(raw, &sc) != nil || sc.User == nil { + if len(raw) == 0 || json.Unmarshal(raw, &sc, jsonx.V1()) != nil || sc.User == nil { return "" } diff --git a/services/ocicontainerinstances/wire.go b/services/ocicontainerinstances/wire.go index ef59865aee..2a3ba70a9c 100644 --- a/services/ocicontainerinstances/wire.go +++ b/services/ocicontainerinstances/wire.go @@ -1,7 +1,7 @@ package ocicontainerinstances import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -49,7 +49,7 @@ type ( ImageURL string `json:"imageUrl"` DisplayName string `json:"displayName"` WorkingDir string `json:"workingDirectory"` - SecurityCtx json.RawMessage `json:"securityContext"` + SecurityCtx jsontext.Value `json:"securityContext"` Command []string `json:"command"` Arguments []string `json:"arguments"` VolumeMounts []VolumeMount `json:"volumeMounts"` @@ -61,7 +61,7 @@ type ( Graceful *int64 `json:"gracefulShutdownTimeoutInSeconds"` Freeform map[string]string `json:"freeformTags"` Defined map[string]any `json:"definedTags"` - SecurityCtx json.RawMessage `json:"securityContext"` + SecurityCtx jsontext.Value `json:"securityContext"` Compartment string `json:"compartmentId"` AD string `json:"availabilityDomain"` Shape string `json:"shape"` @@ -114,7 +114,7 @@ type instanceSummaryWire struct { Details string `json:"lifecycleDetails,omitempty"` Shape string `json:"shape"` Restart string `json:"containerRestartPolicy"` - Security json.RawMessage `json:"securityContext,omitempty"` + Security jsontext.Value `json:"securityContext,omitempty"` ShapeConfig shapeConfigWire `json:"shapeConfig"` Count int `json:"containerCount"` VolumeCount int `json:"volumeCount"` @@ -218,7 +218,7 @@ type containerSummaryWire struct { Details string `json:"lifecycleDetails,omitempty"` InstanceID string `json:"containerInstanceId"` ImageURL string `json:"imageUrl"` - Security json.RawMessage `json:"securityContext,omitempty"` + Security jsontext.Value `json:"securityContext,omitempty"` NoResource bool `json:"isResourcePrincipalDisabled"` } diff --git a/services/ocicore/handler.go b/services/ocicore/handler.go index 8810659825..9dbc45d914 100644 --- a/services/ocicore/handler.go +++ b/services/ocicore/handler.go @@ -2,7 +2,7 @@ package ocicore import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +14,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -114,7 +116,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocicore: encode response: %w", err) } @@ -123,9 +125,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -157,7 +157,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -179,7 +179,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -248,7 +251,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocicore/instance.go b/services/ocicore/instance.go index ca5e1d1c4d..e0bbb8e2b3 100644 --- a/services/ocicore/instance.go +++ b/services/ocicore/instance.go @@ -2,10 +2,13 @@ package ocicore import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -39,7 +42,7 @@ type LaunchInstanceIn struct { ImageID string `json:"imageId"` Shape string `json:"shape"` SubnetID string `json:"subnetId"` - LaunchVolumeAttachments []json.RawMessage `json:"launchVolumeAttachments"` + LaunchVolumeAttachments []jsontext.Value `json:"launchVolumeAttachments"` } func checkMetadata(md map[string]string, ext map[string]any) *APIError { @@ -50,7 +53,7 @@ func checkMetadata(md map[string]string, ext map[string]any) *APIError { } for k, v := range ext { - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) size += len(k) + len(raw) } diff --git a/services/ocievents/condition.go b/services/ocievents/condition.go index 3082564441..f0dec8bc81 100644 --- a/services/ocievents/condition.go +++ b/services/ocievents/condition.go @@ -2,11 +2,14 @@ package ocievents import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseCondition decodes a rule condition: a JSON object whose leaves are strings, numbers, booleans or @@ -16,15 +19,15 @@ func parseCondition(s string) (map[string]any, *APIError) { return nil, errMissing("condition is required") } - dec := json.NewDecoder(bytes.NewReader([]byte(s))) - dec.UseNumber() + data := []byte(s) + dec := jsontext.NewDecoder(bytes.NewReader(data)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, errParam("condition is not valid JSON: " + err.Error()) } - if dec.More() { + if rest := bytes.TrimLeft(data[dec.InputOffset():], " \t\r\n"); len(rest) > 0 && rest[0] != ']' && rest[0] != '}' { return nil, errParam("condition must be a single JSON object") } @@ -64,7 +67,7 @@ func validateNode(path string, v any) *APIError { return errParam(path + ": array elements must be strings, numbers or booleans") } } - case string, json.Number, bool: + case string, jsonx.Number, bool: default: return errParam(path + ": null is not a valid filter value") } @@ -74,7 +77,7 @@ func validateNode(path string, v any) *APIError { func isScalar(v any) bool { switch v.(type) { - case string, json.Number, float64, bool: + case string, jsonx.Number, float64, bool: return true default: return false @@ -85,7 +88,7 @@ func scalarString(v any) string { switch t := v.(type) { case string: return t - case json.Number: + case jsonx.Number: return t.String() case float64: return strconv.FormatFloat(t, 'f', -1, 64) diff --git a/services/ocievents/delivery_test.go b/services/ocievents/delivery_test.go index e6c6617746..0078c7be85 100644 --- a/services/ocievents/delivery_test.go +++ b/services/ocievents/delivery_test.go @@ -1,7 +1,7 @@ package ocievents_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ocievents "github.com/blackbirdworks/gopherstack/services/ocievents" ocinotifications "github.com/blackbirdworks/gopherstack/services/ocinotifications" ociobjectstorage "github.com/blackbirdworks/gopherstack/services/ociobjectstorage" @@ -167,7 +168,7 @@ func TestFilterMatching(t *testing.T) { require.Len(t, got, 1) var env map[string]any - require.NoError(t, json.Unmarshal([]byte(got[0]), &env)) + require.NoError(t, json.Unmarshal([]byte(got[0]), &env, jsonx.V1())) assert.Equal(t, tt.event.EventType, env["eventType"]) assert.Equal(t, "0.1", env["cloudEventsVersion"]) assert.Equal(t, "2.0", env["eventTypeVersion"]) @@ -379,7 +380,7 @@ func (p *pipeline) streamed(t *testing.T) []map[string]any { for _, m := range msgs { var env map[string]any - require.NoError(t, json.Unmarshal(m.Value, &env)) + require.NoError(t, json.Unmarshal(m.Value, &env, jsonx.V1())) out = append(out, env) } @@ -438,7 +439,7 @@ func TestObjectEventsDelivered(t *testing.T) { assert.Equal(t, ociobjectstorage.DefaultNamespace, details["namespace"]) var fromFn map[string]any - require.NoError(t, json.Unmarshal([]byte(invoked[i]), &fromFn)) + require.NoError(t, json.Unmarshal([]byte(invoked[i]), &fromFn, jsonx.V1())) assert.Equal(t, streamed[i]["eventID"], fromFn["eventID"]) } diff --git a/services/ocievents/emit.go b/services/ocievents/emit.go index b126f77024..7f9d10526e 100644 --- a/services/ocievents/emit.go +++ b/services/ocievents/emit.go @@ -2,11 +2,13 @@ package ocievents import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Delivery statuses. @@ -96,13 +98,13 @@ func buildEnvelope(ev Event, id string) ([]byte, map[string]any, error) { CompartmentID: ev.CompartmentID, ResourceName: ev.ResourceName, ResourceID: ev.ResourceID, AvailabilityDomain: ad, AdditionalDetails: details, }, - }) + }, jsonx.V1()) if err != nil { return nil, nil, err //nolint:wrapcheck // plain marshal failure } var generic map[string]any - if err = json.Unmarshal(raw, &generic); err != nil { + if err = json.Unmarshal(raw, &generic, jsonx.V1()); err != nil { return nil, nil, err //nolint:wrapcheck // plain unmarshal failure } diff --git a/services/ocievents/handler.go b/services/ocievents/handler.go index 80c8e93608..93bbce8cbb 100644 --- a/services/ocievents/handler.go +++ b/services/ocievents/handler.go @@ -3,7 +3,8 @@ package ocievents import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +118,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocievents: encode response: %w", err) } @@ -124,9 +127,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -154,10 +155,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -179,7 +179,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -257,7 +260,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocievents/rules_test.go b/services/ocievents/rules_test.go index d9e05e0808..77911aed05 100644 --- a/services/ocievents/rules_test.go +++ b/services/ocievents/rules_test.go @@ -1,7 +1,7 @@ package ocievents_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,8 @@ import ( "github.com/oracle/oci-go-sdk/v65/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRuleLifecycle(t *testing.T) { @@ -247,7 +249,7 @@ func TestCreateRuleValidation(t *testing.T) { m := validRuleBody() tt.mutate(m) - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, jsonx.V1()) require.NoError(t, err) status, body := rawDo(t, http.MethodPost, e.base+"/20181201/rules", string(raw)) diff --git a/services/ocifunctions/docker_test.go b/services/ocifunctions/docker_test.go index 28f4b9b572..1bcbdcde93 100644 --- a/services/ocifunctions/docker_test.go +++ b/services/ocifunctions/docker_test.go @@ -1,7 +1,7 @@ package ocifunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "os" "os/exec" @@ -16,6 +16,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsid" "github.com/blackbirdworks/gopherstack/pkgs/gcpserve" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" oci "github.com/blackbirdworks/gopherstack/services/ocifunctions" ) @@ -148,7 +149,7 @@ func TestDocker_FDKImage(t *testing.T) { got := map[string]string{} if ierr == nil && out != "" && resp.RawResponse.StatusCode == http.StatusOK { - require.NoError(t, json.Unmarshal([]byte(out), &got), out) + require.NoError(t, json.Unmarshal([]byte(out), &got, jsonx.V1()), out) } return resp.RawResponse, got, ierr diff --git a/services/ocifunctions/fake_runtime_test.go b/services/ocifunctions/fake_runtime_test.go index d6259d71e2..8a7630c45d 100644 --- a/services/ocifunctions/fake_runtime_test.go +++ b/services/ocifunctions/fake_runtime_test.go @@ -2,7 +2,8 @@ package ocifunctions_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,7 @@ import ( "sync/atomic" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errNoSocketDir = errors.New("no socket directory mounted at /tmp/iofs") @@ -99,8 +101,10 @@ func (f *fakeRuntime) handler(id string, spec container.Spec) http.Handler { w.Header().Set("Content-Type", "application/json") w.Header().Set("Fn-Http-Status", "200") w.Header().Set("Fn-Http-H-X-Echo", "1") - _ = json.NewEncoder(w). - Encode(map[string]any{"body": string(body), "env": specEnv(spec), "path": r.URL.Path}) + _ = json.MarshalEncode( + jsontext.NewEncoder(w, jsonx.V1()), + map[string]any{"body": string(body), "env": specEnv(spec), "path": r.URL.Path}, + ) } }) } diff --git a/services/ocifunctions/funcs.go b/services/ocifunctions/funcs.go index ba0f50f5c0..f48ea409f0 100644 --- a/services/ocifunctions/funcs.go +++ b/services/ocifunctions/funcs.go @@ -1,7 +1,7 @@ package ocifunctions import ( - "encoding/json" + "encoding/json/jsontext" "maps" "strings" "time" @@ -39,8 +39,8 @@ type fnIn struct { Source *sourceIn `json:"sourceDetails"` Name string `json:"displayName"` AppID string `json:"applicationId"` - Failure json.RawMessage `json:"failureDestination"` - Success json.RawMessage `json:"successDestination"` + Failure jsontext.Value `json:"failureDestination"` + Success jsontext.Value `json:"successDestination"` } type fnWire struct { @@ -60,8 +60,8 @@ type fnWire struct { LifecycleState string `json:"lifecycleState"` Shape string `json:"shape,omitempty"` InvokeEndpoint string `json:"invokeEndpoint"` - Success json.RawMessage `json:"successDestination,omitempty"` - Failure json.RawMessage `json:"failureDestination,omitempty"` + Success jsontext.Value `json:"successDestination,omitempty"` + Failure jsontext.Value `json:"failureDestination,omitempty"` Memory int64 `json:"memoryInMBs"` Timeout int `json:"timeoutInSeconds"` } diff --git a/services/ocifunctions/handler.go b/services/ocifunctions/handler.go index dad9738a7d..989087c535 100644 --- a/services/ocifunctions/handler.go +++ b/services/ocifunctions/handler.go @@ -2,7 +2,7 @@ package ocifunctions import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +118,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocifunctions: encode response: %w", err) } @@ -125,9 +127,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -174,7 +174,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -196,7 +196,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -258,7 +261,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocifunctions/invoke_test.go b/services/ocifunctions/invoke_test.go index 18435e46e8..ff2803a089 100644 --- a/services/ocifunctions/invoke_test.go +++ b/services/ocifunctions/invoke_test.go @@ -2,7 +2,7 @@ package ocifunctions_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" oci "github.com/blackbirdworks/gopherstack/services/ocifunctions" ) @@ -116,7 +117,7 @@ func TestInvoke_FDKContract(t *testing.T) { require.NoError(t, err) var got echo - require.NoError(t, json.Unmarshal([]byte(out), &got)) + require.NoError(t, json.Unmarshal([]byte(out), &got, jsonx.V1())) assert.Equal(t, "hello", got.Body) assert.Equal(t, "/call", got.Path) assert.Equal(t, "1", resp.RawResponse.Header.Get("Fn-Http-H-X-Echo")) diff --git a/services/ocifunctions/models.go b/services/ocifunctions/models.go index 0babf8141f..25495d094f 100644 --- a/services/ocifunctions/models.go +++ b/services/ocifunctions/models.go @@ -1,7 +1,7 @@ package ocifunctions import ( - "encoding/json" + "encoding/json/jsontext" "maps" "time" ) @@ -104,8 +104,8 @@ type Function struct { Image string `json:"image"` ImageDigest string `json:"imageDigest,omitempty"` Etag string `json:"etag"` - Success json.RawMessage `json:"successDestination,omitempty"` - Failure json.RawMessage `json:"failureDestination,omitempty"` + Success jsontext.Value `json:"successDestination,omitempty"` + Failure jsontext.Value `json:"failureDestination,omitempty"` Memory int64 `json:"memoryInMBs"` Seq int64 `json:"seq"` Timeout int `json:"timeoutInSeconds"` diff --git a/services/ocifunctions/persistence_test.go b/services/ocifunctions/persistence_test.go index 01acc87b0c..69682631da 100644 --- a/services/ocifunctions/persistence_test.go +++ b/services/ocifunctions/persistence_test.go @@ -1,7 +1,7 @@ package ocifunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" oci "github.com/blackbirdworks/gopherstack/services/ocifunctions" ) @@ -84,10 +85,10 @@ func TestRestoreRejects(t *testing.T) { app := src.mkApp(t, "app", nil) body := map[string]any{} - require.NoError(t, json.Unmarshal(src.handler.Snapshot(t.Context()), &body)) + require.NoError(t, json.Unmarshal(src.handler.Snapshot(t.Context()), &body, jsonx.V1())) tt.mutate(body) - out, err := json.Marshal(body) + out, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) dst := newEnv(t, envOpts{}) diff --git a/services/ociidentity/handler.go b/services/ociidentity/handler.go index fd731fe458..3c00c18ed6 100644 --- a/services/ociidentity/handler.go +++ b/services/ociidentity/handler.go @@ -2,7 +2,7 @@ package ociidentity import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +14,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -113,7 +115,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociidentity: encode response: %w", err) } @@ -122,9 +124,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -156,7 +156,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -178,7 +178,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -240,7 +243,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociidentity/handler_test.go b/services/ociidentity/handler_test.go index 154320a3f2..c4f01984e1 100644 --- a/services/ociidentity/handler_test.go +++ b/services/ociidentity/handler_test.go @@ -3,7 +3,7 @@ package ociidentity_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" oci "github.com/blackbirdworks/gopherstack/services/ociidentity" ) @@ -71,7 +72,7 @@ func TestRawErrorEnvelopes(t *testing.T) { Code string `json:"code"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &eb)) + require.NoError(t, json.Unmarshal(body, &eb, jsonx.V1())) assert.Equal(t, tt.wantCode, eb.Code) assert.NotEmpty(t, eb.Message) }) @@ -87,7 +88,12 @@ func TestEnforcedAuthAndAdminEndpoint(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, svcErr(t, err).GetHTTPStatusCode()) body, err := json.Marshal( - map[string]string{"tenancy": testTenancy, "user": "ocid1.user.oc1..bootstrap", "publicKey": e.kp.pubPEM}, + map[string]string{ + "tenancy": testTenancy, + "user": "ocid1.user.oc1..bootstrap", + "publicKey": e.kp.pubPEM, + }, + jsonx.V1(), ) require.NoError(t, err) diff --git a/services/ociloadbalancer/handler.go b/services/ociloadbalancer/handler.go index 92581c3156..942fbce6c3 100644 --- a/services/ociloadbalancer/handler.go +++ b/services/ociloadbalancer/handler.go @@ -3,7 +3,8 @@ package ociloadbalancer import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +119,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociloadbalancer: encode response: %w", err) } @@ -125,9 +128,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -162,10 +163,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -187,7 +187,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -262,7 +265,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociloadbalancer/store.go b/services/ociloadbalancer/store.go index baa3fdb44d..9a8889bf26 100644 --- a/services/ociloadbalancer/store.go +++ b/services/ociloadbalancer/store.go @@ -4,7 +4,7 @@ import ( "cmp" "crypto/tls" "encoding/binary" - "encoding/json" + "encoding/json/v2" "net/netip" "slices" "strings" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" "github.com/blackbirdworks/gopherstack/pkgs/page" @@ -164,13 +165,13 @@ func (b *InMemoryBackend) transitionAt() *time.Time { // clone deep-copies a load balancer; stored load balancers are immutable once published. func (lb *LoadBalancer) clone() *LoadBalancer { - raw, err := json.Marshal(lb) + raw, err := json.Marshal(lb, jsonx.V1()) if err != nil { return lb } out := &LoadBalancer{} - if json.Unmarshal(raw, out) != nil { + if json.Unmarshal(raw, out, jsonx.V1()) != nil { return lb } diff --git a/services/ocilogging/agents.go b/services/ocilogging/agents.go index 20744d8a4e..4d44664b5a 100644 --- a/services/ocilogging/agents.go +++ b/services/ocilogging/agents.go @@ -2,10 +2,12 @@ package ocilogging import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -28,7 +30,7 @@ type CreateAgentConfigIn struct { GroupAssoc *groupAssociationWire `json:"groupAssociation"` Name string `json:"displayName"` Compartment string `json:"compartmentId"` - ServiceConfig json.RawMessage `json:"serviceConfiguration"` + ServiceConfig jsontext.Value `json:"serviceConfiguration"` } // UpdateAgentConfigIn is the UpdateUnifiedAgentConfiguration request body. @@ -38,7 +40,7 @@ type UpdateAgentConfigIn struct { Description *string `json:"description"` Name *string `json:"displayName"` GroupAssoc *groupAssociationWire `json:"groupAssociation"` - ServiceConfig json.RawMessage `json:"serviceConfiguration"` + ServiceConfig jsontext.Value `json:"serviceConfiguration"` } // AgentConfigWire is the GetUnifiedAgentConfiguration response. @@ -54,7 +56,7 @@ type AgentConfigWire struct { LifecycleState string `json:"lifecycleState"` ConfigurationState string `json:"configurationState"` GroupAssociation groupAssociationWire `json:"groupAssociation"` - ServiceConfig json.RawMessage `json:"serviceConfiguration"` + ServiceConfig jsontext.Value `json:"serviceConfiguration"` Enabled bool `json:"isEnabled"` } @@ -88,7 +90,7 @@ func agentView(a *AgentConfig) AgentConfigWire { } // validateServiceConfig checks the polymorphic serviceConfiguration and returns its configurationType. -func (b *InMemoryBackend) validateServiceConfig(raw json.RawMessage) (string, *APIError) { +func (b *InMemoryBackend) validateServiceConfig(raw jsontext.Value) (string, *APIError) { if len(raw) == 0 || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { return "", errMissing("serviceConfiguration is required") } @@ -104,7 +106,7 @@ func (b *InMemoryBackend) validateServiceConfig(raw json.RawMessage) (string, *A } `json:"sources"` } - if err := json.Unmarshal(raw, &c); err != nil { + if err := json.Unmarshal(raw, &c, jsonx.V1()); err != nil { return "", errParam("serviceConfiguration is not a valid configuration object") } diff --git a/services/ocilogging/handler.go b/services/ocilogging/handler.go index 897686c2de..4777977912 100644 --- a/services/ocilogging/handler.go +++ b/services/ocilogging/handler.go @@ -2,7 +2,7 @@ package ocilogging import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -15,6 +15,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +117,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocilogging: encode response: %w", err) } @@ -124,9 +126,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -161,7 +161,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -183,7 +183,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -252,7 +255,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocilogging/ingest.go b/services/ocilogging/ingest.go index d7a9b7b04a..9a0012d10b 100644 --- a/services/ocilogging/ingest.go +++ b/services/ocilogging/ingest.go @@ -1,12 +1,14 @@ package ocilogging import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "unicode/utf8" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -140,7 +142,7 @@ type ServiceEvent struct { // EmitServiceLog stores the event in every ACTIVE SERVICE log whose source matches; it returns how many logs took it. func (b *InMemoryBackend) EmitServiceLog(service, resource, category string, ev ServiceEvent) int { - data, err := json.Marshal(ev.Data) + data, err := json.Marshal(ev.Data, jsonx.V1()) if err != nil { return 0 } diff --git a/services/ocilogging/models.go b/services/ocilogging/models.go index 4021b7813e..c30863401c 100644 --- a/services/ocilogging/models.go +++ b/services/ocilogging/models.go @@ -1,7 +1,7 @@ package ocilogging import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -87,21 +87,21 @@ type Log struct { // AgentConfig is a stored unified agent configuration. type AgentConfig struct { - TimeCreated time.Time `json:"timeCreated"` - TimeModified time.Time `json:"timeModified"` - TransitionAt *time.Time `json:"transitionAt,omitempty"` - ServiceConfig json.RawMessage `json:"serviceConfiguration"` - Tags Tags `json:"tags"` - ID string `json:"id"` - CompartmentID string `json:"compartmentId"` - Name string `json:"displayName"` - Description string `json:"description,omitempty"` - State string `json:"state"` - ConfigType string `json:"configurationType"` - Etag string `json:"etag"` - Groups []string `json:"groupList,omitempty"` - Seq int64 `json:"seq"` - Enabled bool `json:"isEnabled"` + TimeCreated time.Time `json:"timeCreated"` + TimeModified time.Time `json:"timeModified"` + TransitionAt *time.Time `json:"transitionAt,omitempty"` + ServiceConfig jsontext.Value `json:"serviceConfiguration"` + Tags Tags `json:"tags"` + ID string `json:"id"` + CompartmentID string `json:"compartmentId"` + Name string `json:"displayName"` + Description string `json:"description,omitempty"` + State string `json:"state"` + ConfigType string `json:"configurationType"` + Etag string `json:"etag"` + Groups []string `json:"groupList,omitempty"` + Seq int64 `json:"seq"` + Enabled bool `json:"isEnabled"` } // Entry is one ingested log record. diff --git a/services/ocilogging/persistence_test.go b/services/ocilogging/persistence_test.go index 7162513235..00de9b90b7 100644 --- a/services/ocilogging/persistence_test.go +++ b/services/ocilogging/persistence_test.go @@ -1,7 +1,7 @@ package ocilogging_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" oci "github.com/blackbirdworks/gopherstack/services/ocilogging" ) @@ -220,7 +221,7 @@ func TestAdminKeyEndpoint(t *testing.T) { t.Parallel() e := newEnv(t, envOpts{}) - pub, err := json.Marshal(e.kp.pubPEM) + pub, err := json.Marshal(e.kp.pubPEM, jsonx.V1()) require.NoError(t, err) status, _ := rawDo(t, http.MethodPost, e.base+"/_gopherstack/oci/keys", diff --git a/services/ocilogging/query.go b/services/ocilogging/query.go index 2f086be207..c46621e28e 100644 --- a/services/ocilogging/query.go +++ b/services/ocilogging/query.go @@ -2,7 +2,7 @@ package ocilogging import ( "cmp" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" @@ -10,6 +10,7 @@ import ( "strings" "unicode" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -624,7 +625,7 @@ func scalarString(v any) string { return "" } - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return string(b) } diff --git a/services/ocilogging/search.go b/services/ocilogging/search.go index 3c5c8e6b6a..9c8c500d3d 100644 --- a/services/ocilogging/search.go +++ b/services/ocilogging/search.go @@ -1,13 +1,14 @@ package ocilogging import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -106,7 +107,7 @@ func (b *InMemoryBackend) buildRow(l *Log, e *Entry) *row { var data any = e.Data var parsed any - if json.Unmarshal([]byte(e.Data), &parsed) == nil { + if json.Unmarshal([]byte(e.Data), &parsed, jsonx.V1()) == nil { if _, isMap := parsed.(map[string]any); isMap { data = parsed } diff --git a/services/ocimonitoring/alarms.go b/services/ocimonitoring/alarms.go index b3ba39da3a..bb4105bf01 100644 --- a/services/ocimonitoring/alarms.go +++ b/services/ocimonitoring/alarms.go @@ -1,7 +1,7 @@ package ocimonitoring import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "regexp" "slices" @@ -63,57 +63,57 @@ type SuppressionWire struct { // CreateAlarmIn is the CreateAlarm request body. type CreateAlarmIn struct { Tags - InSubtree *bool `json:"metricCompartmentIdInSubtree"` - PerDimension *bool `json:"isNotificationsPerMetricDimensionEnabled"` - Enabled *bool `json:"isEnabled"` - Suppression *SuppressionWire `json:"suppression"` - ResourceGroup *string `json:"resourceGroup"` - Resolution *string `json:"resolution"` - Pending *string `json:"pendingDuration"` - Body *string `json:"body"` - MessageFormat *string `json:"messageFormat"` - Repeat *string `json:"repeatNotificationDuration"` - RuleName *string `json:"ruleName"` - NotifVersion *string `json:"notificationVersion"` - NotifTitle *string `json:"notificationTitle"` - EvalSlack *string `json:"evaluationSlackDuration"` - AlarmSummary *string `json:"alarmSummary"` - Name string `json:"displayName"` - Compartment string `json:"compartmentId"` - MetricComp string `json:"metricCompartmentId"` - Namespace string `json:"namespace"` - Query string `json:"query"` - Severity string `json:"severity"` - Destinations []string `json:"destinations"` - Overrides []json.RawMessage `json:"overrides"` + InSubtree *bool `json:"metricCompartmentIdInSubtree"` + PerDimension *bool `json:"isNotificationsPerMetricDimensionEnabled"` + Enabled *bool `json:"isEnabled"` + Suppression *SuppressionWire `json:"suppression"` + ResourceGroup *string `json:"resourceGroup"` + Resolution *string `json:"resolution"` + Pending *string `json:"pendingDuration"` + Body *string `json:"body"` + MessageFormat *string `json:"messageFormat"` + Repeat *string `json:"repeatNotificationDuration"` + RuleName *string `json:"ruleName"` + NotifVersion *string `json:"notificationVersion"` + NotifTitle *string `json:"notificationTitle"` + EvalSlack *string `json:"evaluationSlackDuration"` + AlarmSummary *string `json:"alarmSummary"` + Name string `json:"displayName"` + Compartment string `json:"compartmentId"` + MetricComp string `json:"metricCompartmentId"` + Namespace string `json:"namespace"` + Query string `json:"query"` + Severity string `json:"severity"` + Destinations []string `json:"destinations"` + Overrides []jsontext.Value `json:"overrides"` } // UpdateAlarmIn is the UpdateAlarm request body. type UpdateAlarmIn struct { Tags - Name *string `json:"displayName"` - Compartment *string `json:"compartmentId"` - MetricComp *string `json:"metricCompartmentId"` - InSubtree *bool `json:"metricCompartmentIdInSubtree"` - Namespace *string `json:"namespace"` - ResourceGroup *string `json:"resourceGroup"` - Query *string `json:"query"` - Resolution *string `json:"resolution"` - Pending *string `json:"pendingDuration"` - Severity *string `json:"severity"` - Body *string `json:"body"` - PerDimension *bool `json:"isNotificationsPerMetricDimensionEnabled"` - MessageFormat *string `json:"messageFormat"` - Destinations []string `json:"destinations"` - Repeat *string `json:"repeatNotificationDuration"` - Suppression *SuppressionWire `json:"suppression"` - Enabled *bool `json:"isEnabled"` - RuleName *string `json:"ruleName"` - NotifVersion *string `json:"notificationVersion"` - NotifTitle *string `json:"notificationTitle"` - EvalSlack *string `json:"evaluationSlackDuration"` - AlarmSummary *string `json:"alarmSummary"` - Overrides []json.RawMessage `json:"overrides"` + Name *string `json:"displayName"` + Compartment *string `json:"compartmentId"` + MetricComp *string `json:"metricCompartmentId"` + InSubtree *bool `json:"metricCompartmentIdInSubtree"` + Namespace *string `json:"namespace"` + ResourceGroup *string `json:"resourceGroup"` + Query *string `json:"query"` + Resolution *string `json:"resolution"` + Pending *string `json:"pendingDuration"` + Severity *string `json:"severity"` + Body *string `json:"body"` + PerDimension *bool `json:"isNotificationsPerMetricDimensionEnabled"` + MessageFormat *string `json:"messageFormat"` + Destinations []string `json:"destinations"` + Repeat *string `json:"repeatNotificationDuration"` + Suppression *SuppressionWire `json:"suppression"` + Enabled *bool `json:"isEnabled"` + RuleName *string `json:"ruleName"` + NotifVersion *string `json:"notificationVersion"` + NotifTitle *string `json:"notificationTitle"` + EvalSlack *string `json:"evaluationSlackDuration"` + AlarmSummary *string `json:"alarmSummary"` + Overrides []jsontext.Value `json:"overrides"` } // AlarmWire is the Alarm response shape. @@ -145,7 +145,7 @@ type AlarmWire struct { Repeat string `json:"repeatNotificationDuration,omitempty"` LifecycleState string `json:"lifecycleState"` Destinations []string `json:"destinations"` - Overrides []json.RawMessage `json:"overrides,omitempty"` + Overrides []jsontext.Value `json:"overrides,omitempty"` Enabled bool `json:"isEnabled"` } diff --git a/services/ocimonitoring/alarms_test.go b/services/ocimonitoring/alarms_test.go index 0d7d64e76c..6525e9b923 100644 --- a/services/ocimonitoring/alarms_test.go +++ b/services/ocimonitoring/alarms_test.go @@ -1,7 +1,7 @@ package ocimonitoring_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,8 @@ import ( "github.com/oracle/oci-go-sdk/v65/monitoring" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (e *env) alarmDetails(topic string, mod func(*monitoring.CreateAlarmDetails)) monitoring.CreateAlarmDetails { @@ -305,7 +307,7 @@ func TestAlarmFiringDeliversToNotifications(t *testing.T) { } `json:"alarmMetaData"` } - require.NoError(t, json.Unmarshal([]byte(msg.body), &n)) + require.NoError(t, json.Unmarshal([]byte(msg.body), &n, jsonx.V1())) assert.Equal(t, "OK_TO_FIRING", n.Type) assert.Equal(t, "CRITICAL", n.Severity) assert.Equal(t, "cpu-high", n.Title) @@ -327,7 +329,7 @@ func TestAlarmFiringDeliversToNotifications(t *testing.T) { Type string `json:"type"` } - require.NoError(t, json.Unmarshal([]byte(o.sink.next(t).body), &ok)) + require.NoError(t, json.Unmarshal([]byte(o.sink.next(t).body), &ok, jsonx.V1())) assert.Equal(t, "FIRING_TO_OK", ok.Type) hist, err := e.mon.GetAlarmHistory(t.Context(), monitoring.GetAlarmHistoryRequest{AlarmId: a.Id}) @@ -509,7 +511,7 @@ func TestAlarmGroupedThreshold(t *testing.T) { } `json:"alarmMetaData"` } - require.NoError(t, json.Unmarshal([]byte(o.sink.next(t).body), &n)) + require.NoError(t, json.Unmarshal([]byte(o.sink.next(t).body), &n, jsonx.V1())) assert.Equal(t, 1, n.AlarmMetaData[0].TotalMetricsFiring, "only web2 breaches; db1 is filtered out") assert.Equal(t, []map[string]string{{"host": "web2"}}, n.AlarmMetaData[0].Dimensions) } diff --git a/services/ocimonitoring/eval.go b/services/ocimonitoring/eval.go index dc2b5915c1..19b8134724 100644 --- a/services/ocimonitoring/eval.go +++ b/services/ocimonitoring/eval.go @@ -2,12 +2,14 @@ package ocimonitoring import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -231,9 +233,9 @@ func renderNotice(a *Alarm, kind string, now time.Time, hits []firing) (string, ) if a.MessageFormat == formatPretty { - out, err = json.MarshalIndent(w, "", " ") + out, err = json.Marshal(w, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) } else { - out, err = json.Marshal(w) + out, err = json.Marshal(w, jsonx.V1()) } if err != nil { diff --git a/services/ocimonitoring/handler.go b/services/ocimonitoring/handler.go index 0ee1af6e2f..a760cd4499 100644 --- a/services/ocimonitoring/handler.go +++ b/services/ocimonitoring/handler.go @@ -2,7 +2,7 @@ package ocimonitoring import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -15,6 +15,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +117,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocimonitoring: encode response: %w", err) } @@ -124,9 +126,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -154,7 +154,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -176,7 +176,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -245,7 +248,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocimonitoring/models.go b/services/ocimonitoring/models.go index fed338bd5f..f3b51796e0 100644 --- a/services/ocimonitoring/models.go +++ b/services/ocimonitoring/models.go @@ -1,7 +1,7 @@ package ocimonitoring import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -75,7 +75,7 @@ type Alarm struct { PerDimension *bool `json:"isNotificationsPerMetricDimensionEnabled,omitempty"` Tags Tags `json:"tags"` Extra map[string]string `json:"extra,omitempty"` - Overrides []json.RawMessage `json:"overrides,omitempty"` + Overrides []jsontext.Value `json:"overrides,omitempty"` ID string `json:"id"` Name string `json:"displayName"` CompartmentID string `json:"compartmentId"` diff --git a/services/ocimonitoring/persistence_test.go b/services/ocimonitoring/persistence_test.go index 264b0d9624..1f2ec0b94c 100644 --- a/services/ocimonitoring/persistence_test.go +++ b/services/ocimonitoring/persistence_test.go @@ -1,7 +1,7 @@ package ocimonitoring_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" oci "github.com/blackbirdworks/gopherstack/services/ocimonitoring" ) @@ -150,7 +151,7 @@ func TestAdminKeyEndpoint(t *testing.T) { t.Parallel() e := newEnv(t, envOpts{}) - pub, err := json.Marshal(e.kp.pubPEM) + pub, err := json.Marshal(e.kp.pubPEM, jsonx.V1()) require.NoError(t, err) status, _ := rawDo(t, http.MethodPost, e.base+"/_gopherstack/oci/keys", diff --git a/services/ocimysql/channels.go b/services/ocimysql/channels.go index 5652f02f85..2f79c13220 100644 --- a/services/ocimysql/channels.go +++ b/services/ocimysql/channels.go @@ -1,7 +1,7 @@ package ocimysql import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "strings" @@ -28,13 +28,13 @@ type ChannelSourceIn struct { // ChannelTargetIn is the channel target details. type ChannelTargetIn struct { - DelayInSeconds *int `json:"delayInSeconds"` - TargetType string `json:"targetType"` - DBSystemID string `json:"dbSystemId"` - ChannelName string `json:"channelName"` - ApplierUsername string `json:"applierUsername"` - TablesWithoutPrimaryKeyHandling string `json:"tablesWithoutPrimaryKeyHandling"` - Filters json.RawMessage `json:"filters"` + DelayInSeconds *int `json:"delayInSeconds"` + TargetType string `json:"targetType"` + DBSystemID string `json:"dbSystemId"` + ChannelName string `json:"channelName"` + ApplierUsername string `json:"applierUsername"` + TablesWithoutPrimaryKeyHandling string `json:"tablesWithoutPrimaryKeyHandling"` + Filters jsontext.Value `json:"filters"` } // CreateChannelIn is the CreateChannel body. diff --git a/services/ocimysql/dbsystems.go b/services/ocimysql/dbsystems.go index 2ff6784d18..8afb68f2a3 100644 --- a/services/ocimysql/dbsystems.go +++ b/services/ocimysql/dbsystems.go @@ -1,7 +1,8 @@ package ocimysql import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "regexp" @@ -9,6 +10,7 @@ import ( "time" "unicode" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -64,7 +66,7 @@ type BackupPolicyIn struct { WindowStartTime *string `json:"windowStartTime"` RetentionInDays *int `json:"retentionInDays"` SoftDelete string `json:"softDelete"` - CopyPolicies json.RawMessage `json:"copyPolicies"` + CopyPolicies jsontext.Value `json:"copyPolicies"` } // DeletionPolicyIn is the create/update deletion policy. @@ -89,33 +91,33 @@ type SourceIn struct { // CreateDBSystemIn is the CreateDBSystem body. type CreateDBSystemIn struct { Tags - Extra map[string]json.RawMessage `json:"-"` - IsHighlyAvailable *bool `json:"isHighlyAvailable"` - DataStorageGBs *int `json:"dataStorageSizeInGBs"` - Port *int `json:"port"` - PortX *int `json:"portX"` - DataStorage *DataStorageIn `json:"dataStorage"` - BackupPolicy *BackupPolicyIn `json:"backupPolicy"` - Source *SourceIn `json:"source"` - DeletionPolicy *DeletionPolicyIn `json:"deletionPolicy"` - Maintenance map[string]any `json:"maintenance"` - Description string `json:"description"` - AdminPassword string `json:"adminPassword"` - DisplayName string `json:"displayName"` - ShapeName string `json:"shapeName"` - AvailabilityDom string `json:"availabilityDomain"` - FaultDomain string `json:"faultDomain"` - ConfigurationID string `json:"configurationId"` - MysqlVersion string `json:"mysqlVersion"` - AdminUsername string `json:"adminUsername"` - SubnetID string `json:"subnetId"` - HostnameLabel string `json:"hostnameLabel"` - IPAddress string `json:"ipAddress"` - CrashRecovery string `json:"crashRecovery"` - DBManagement string `json:"databaseManagement"` - DatabaseMode string `json:"databaseMode"` - AccessMode string `json:"accessMode"` - CompartmentID string `json:"compartmentId"` + Extra map[string]jsontext.Value `json:"-"` + IsHighlyAvailable *bool `json:"isHighlyAvailable"` + DataStorageGBs *int `json:"dataStorageSizeInGBs"` + Port *int `json:"port"` + PortX *int `json:"portX"` + DataStorage *DataStorageIn `json:"dataStorage"` + BackupPolicy *BackupPolicyIn `json:"backupPolicy"` + Source *SourceIn `json:"source"` + DeletionPolicy *DeletionPolicyIn `json:"deletionPolicy"` + Maintenance map[string]any `json:"maintenance"` + Description string `json:"description"` + AdminPassword string `json:"adminPassword"` + DisplayName string `json:"displayName"` + ShapeName string `json:"shapeName"` + AvailabilityDom string `json:"availabilityDomain"` + FaultDomain string `json:"faultDomain"` + ConfigurationID string `json:"configurationId"` + MysqlVersion string `json:"mysqlVersion"` + AdminUsername string `json:"adminUsername"` + SubnetID string `json:"subnetId"` + HostnameLabel string `json:"hostnameLabel"` + IPAddress string `json:"ipAddress"` + CrashRecovery string `json:"crashRecovery"` + DBManagement string `json:"databaseManagement"` + DatabaseMode string `json:"databaseMode"` + AccessMode string `json:"accessMode"` + CompartmentID string `json:"compartmentId"` } func validPassword(p string) bool { @@ -906,13 +908,13 @@ func (h *Handler) createDBSystem(q *req) error { return q.json(http.StatusOK, d, etag) } -func extras(body []byte) map[string]json.RawMessage { - var all map[string]json.RawMessage - if json.Unmarshal(body, &all) != nil { +func extras(body []byte) map[string]jsontext.Value { + var all map[string]jsontext.Value + if json.Unmarshal(body, &all, jsonx.V1()) != nil { return nil } - out := map[string]json.RawMessage{} + out := map[string]jsontext.Value{} for _, k := range extraKeys { if v, ok := all[k]; ok && string(v) != "null" { diff --git a/services/ocimysql/handler.go b/services/ocimysql/handler.go index f81ca587b9..8e1d93291c 100644 --- a/services/ocimysql/handler.go +++ b/services/ocimysql/handler.go @@ -2,7 +2,7 @@ package ocimysql import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -15,6 +15,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +117,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocimysql: encode response: %w", err) } @@ -124,9 +126,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -163,7 +163,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -185,7 +185,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -254,7 +257,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocimysql/lifecycle.go b/services/ocimysql/lifecycle.go index b851c375d2..c5db7d2af0 100644 --- a/services/ocimysql/lifecycle.go +++ b/services/ocimysql/lifecycle.go @@ -2,7 +2,7 @@ package ocimysql import ( "context" - "encoding/json" + "encoding/json/jsontext" "net/http" "net/netip" "slices" @@ -159,29 +159,29 @@ func (b *InMemoryBackend) mutable(id, ifMatch string, states ...string) (*DBSyst // UpdateDBSystemIn is the UpdateDBSystem body. type UpdateDBSystemIn struct { Tags - DataStorageGBs *int `json:"dataStorageSizeInGBs"` - HostnameLabel *string `json:"hostnameLabel"` - SubnetID *string `json:"subnetId"` - ShapeName *string `json:"shapeName"` - MysqlVersion *string `json:"mysqlVersion"` - ConfigID *string `json:"configurationId"` - AdminUsername *string `json:"adminUsername"` - Port *int `json:"port"` - Extra map[string]json.RawMessage `json:"-"` - IPAddress *string `json:"ipAddress"` - Description *string `json:"description"` - HA *bool `json:"isHighlyAvailable"` - AdminPassword *string `json:"adminPassword"` - PortX *int `json:"portX"` - DataStorage *DataStorageIn `json:"dataStorage"` - BackupPolicy *BackupPolicyIn `json:"backupPolicy"` - Deletion *DeletionPolicyIn `json:"deletionPolicy"` - Maintenance map[string]any `json:"maintenance"` - DisplayName *string `json:"displayName"` - DBManagement string `json:"databaseManagement"` - DatabaseMode string `json:"databaseMode"` - AccessMode string `json:"accessMode"` - CrashRecovery string `json:"crashRecovery"` + DataStorageGBs *int `json:"dataStorageSizeInGBs"` + HostnameLabel *string `json:"hostnameLabel"` + SubnetID *string `json:"subnetId"` + ShapeName *string `json:"shapeName"` + MysqlVersion *string `json:"mysqlVersion"` + ConfigID *string `json:"configurationId"` + AdminUsername *string `json:"adminUsername"` + Port *int `json:"port"` + Extra map[string]jsontext.Value `json:"-"` + IPAddress *string `json:"ipAddress"` + Description *string `json:"description"` + HA *bool `json:"isHighlyAvailable"` + AdminPassword *string `json:"adminPassword"` + PortX *int `json:"portX"` + DataStorage *DataStorageIn `json:"dataStorage"` + BackupPolicy *BackupPolicyIn `json:"backupPolicy"` + Deletion *DeletionPolicyIn `json:"deletionPolicy"` + Maintenance map[string]any `json:"maintenance"` + DisplayName *string `json:"displayName"` + DBManagement string `json:"databaseManagement"` + DatabaseMode string `json:"databaseMode"` + AccessMode string `json:"accessMode"` + CrashRecovery string `json:"crashRecovery"` } type updateEffect struct { @@ -228,7 +228,7 @@ func (b *InMemoryBackend) applyUpdate(n *DBSystem, in UpdateDBSystemIn) (updateE for k, v := range in.Extra { if n.Extra == nil { - n.Extra = map[string]json.RawMessage{} + n.Extra = map[string]jsontext.Value{} } n.Extra[k] = v diff --git a/services/ocimysql/models.go b/services/ocimysql/models.go index 10049fd971..400ad67502 100644 --- a/services/ocimysql/models.go +++ b/services/ocimysql/models.go @@ -1,7 +1,7 @@ package ocimysql import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -40,13 +40,13 @@ func (t *Tags) merge(in Tags) { // BackupPolicy is the automatic backup policy of a DB system. type BackupPolicy struct { - Tags Tags `json:"tags"` - WindowStartTime string `json:"windowStartTime"` - SoftDelete string `json:"softDelete"` - CopyPolicies json.RawMessage `json:"copyPolicies,omitempty"` - RetentionInDays int `json:"retentionInDays"` - IsEnabled bool `json:"isEnabled"` - PitrEnabled bool `json:"pitrEnabled"` + Tags Tags `json:"tags"` + WindowStartTime string `json:"windowStartTime"` + SoftDelete string `json:"softDelete"` + CopyPolicies jsontext.Value `json:"copyPolicies,omitempty"` + RetentionInDays int `json:"retentionInDays"` + IsEnabled bool `json:"isEnabled"` + PitrEnabled bool `json:"pitrEnabled"` } // DeletionPolicy controls what DeleteDBSystem does. @@ -64,48 +64,48 @@ type Source struct { // DBSystem is a MySQL DB system. AdminPassword and RootSecret are never returned by the API. type DBSystem struct { - TimeCreated time.Time `json:"timeCreated"` - TimeUpdated time.Time `json:"timeUpdated"` - LastAutoBackup time.Time `json:"lastAutoBackup"` - Tags Tags `json:"tags"` - TransitionAt *time.Time `json:"transitionAt,omitempty"` - Source *Source `json:"source,omitempty"` - Extra map[string]json.RawMessage `json:"extra,omitempty"` - Maintenance map[string]any `json:"maintenance,omitempty"` - FD string `json:"faultDomain"` - AdminUser string `json:"adminUsername"` - ID string `json:"id"` - CompartmentID string `json:"compartmentId"` - Name string `json:"displayName"` - Description string `json:"description,omitempty"` - SubnetID string `json:"subnetId"` - Shape string `json:"shapeName"` - Version string `json:"mysqlVersion"` - ConfigID string `json:"configurationId"` - Hostname string `json:"hostnameLabel,omitempty"` - AD string `json:"availabilityDomain"` - EngineHost string `json:"engineHost,omitempty"` - State string `json:"state"` - After string `json:"after,omitempty"` - Details string `json:"details,omitempty"` - Etag string `json:"etag"` - IP string `json:"ipAddress"` - AdminPass string `json:"adminPassword,omitempty"` - RootSecret string `json:"rootSecret,omitempty"` - DatabaseMode string `json:"databaseMode"` - AccessMode string `json:"accessMode"` - CrashRecovery string `json:"crashRecovery"` - DBManagement string `json:"databaseManagement"` - Deletion DeletionPolicy `json:"deletionPolicy"` - BackupPolicy BackupPolicy `json:"backupPolicy"` - DataGB int `json:"dataStorageSizeInGBs"` - MaxGB int `json:"maxStorageSizeInGBs,omitempty"` - Port int `json:"port"` - PortX int `json:"portX"` - EnginePort int `json:"enginePort,omitempty"` - Seq int64 `json:"seq"` - AutoExpand bool `json:"isAutoExpandStorageEnabled"` - HA bool `json:"isHighlyAvailable"` + TimeCreated time.Time `json:"timeCreated"` + TimeUpdated time.Time `json:"timeUpdated"` + LastAutoBackup time.Time `json:"lastAutoBackup"` + Tags Tags `json:"tags"` + TransitionAt *time.Time `json:"transitionAt,omitempty"` + Source *Source `json:"source,omitempty"` + Extra map[string]jsontext.Value `json:"extra,omitempty"` + Maintenance map[string]any `json:"maintenance,omitempty"` + FD string `json:"faultDomain"` + AdminUser string `json:"adminUsername"` + ID string `json:"id"` + CompartmentID string `json:"compartmentId"` + Name string `json:"displayName"` + Description string `json:"description,omitempty"` + SubnetID string `json:"subnetId"` + Shape string `json:"shapeName"` + Version string `json:"mysqlVersion"` + ConfigID string `json:"configurationId"` + Hostname string `json:"hostnameLabel,omitempty"` + AD string `json:"availabilityDomain"` + EngineHost string `json:"engineHost,omitempty"` + State string `json:"state"` + After string `json:"after,omitempty"` + Details string `json:"details,omitempty"` + Etag string `json:"etag"` + IP string `json:"ipAddress"` + AdminPass string `json:"adminPassword,omitempty"` + RootSecret string `json:"rootSecret,omitempty"` + DatabaseMode string `json:"databaseMode"` + AccessMode string `json:"accessMode"` + CrashRecovery string `json:"crashRecovery"` + DBManagement string `json:"databaseManagement"` + Deletion DeletionPolicy `json:"deletionPolicy"` + BackupPolicy BackupPolicy `json:"backupPolicy"` + DataGB int `json:"dataStorageSizeInGBs"` + MaxGB int `json:"maxStorageSizeInGBs,omitempty"` + Port int `json:"port"` + PortX int `json:"portX"` + EnginePort int `json:"enginePort,omitempty"` + Seq int64 `json:"seq"` + AutoExpand bool `json:"isAutoExpandStorageEnabled"` + HA bool `json:"isHighlyAvailable"` } // Backup is a DB system backup. Dump holds the logical dump when the docker engine took it. @@ -134,24 +134,24 @@ type Backup struct { // ChannelSource is the replication source of a channel; the password is never stored. type ChannelSource struct { - Extra map[string]json.RawMessage `json:"extra,omitempty"` - SourceType string `json:"sourceType"` - Hostname string `json:"hostname"` - Username string `json:"username"` - SslMode string `json:"sslMode"` - Port int `json:"port"` - HasPassword bool `json:"hasPassword"` + Extra map[string]jsontext.Value `json:"extra,omitempty"` + SourceType string `json:"sourceType"` + Hostname string `json:"hostname"` + Username string `json:"username"` + SslMode string `json:"sslMode"` + Port int `json:"port"` + HasPassword bool `json:"hasPassword"` } // ChannelTarget is the DB system a channel replicates into. type ChannelTarget struct { - TargetType string `json:"targetType"` - DBSystemID string `json:"dbSystemId"` - ChannelName string `json:"channelName"` - ApplierUsername string `json:"applierUsername"` - TablesWithoutPrimaryKeyHandling string `json:"tablesWithoutPrimaryKeyHandling"` - Filters json.RawMessage `json:"filters,omitempty"` - DelayInSeconds int `json:"delayInSeconds"` + TargetType string `json:"targetType"` + DBSystemID string `json:"dbSystemId"` + ChannelName string `json:"channelName"` + ApplierUsername string `json:"applierUsername"` + TablesWithoutPrimaryKeyHandling string `json:"tablesWithoutPrimaryKeyHandling"` + Filters jsontext.Value `json:"filters,omitempty"` + DelayInSeconds int `json:"delayInSeconds"` } // Channel is a replication channel. It records configuration and lifecycle only; no data is replicated. diff --git a/services/ocinosql/alter.go b/services/ocinosql/alter.go index f1be37a408..2fc0c31509 100644 --- a/services/ocinosql/alter.go +++ b/services/ocinosql/alter.go @@ -1,10 +1,12 @@ package ocinosql import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -121,7 +123,7 @@ func checkAddColumn(cols []Column, c Column) *APIError { return nil } -func coerceDefault(ty *dtype, raw json.RawMessage) (any, string) { +func coerceDefault(ty *dtype, raw jsontext.Value) (any, string) { v, err := decodeJSON(raw) if err != nil { return nil, "is not a valid literal" @@ -154,7 +156,7 @@ func reshapeRows(t *Table, removed []string, added []Column) { } } - r.Value, _ = json.Marshal(m) + r.Value, _ = json.Marshal(m, jsonx.V1()) } } @@ -216,9 +218,9 @@ func renderIdentity(id *Identity) string { return " GENERATED BY DEFAULT AS IDENTITY" } -func renderDefault(raw json.RawMessage) string { +func renderDefault(raw jsontext.Value) string { var s string - if json.Unmarshal(raw, &s) == nil { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil { return "'" + strings.ReplaceAll(s, "'", "''") + "'" } diff --git a/services/ocinosql/ddl.go b/services/ocinosql/ddl.go index b48777d1f8..c8efdde147 100644 --- a/services/ocinosql/ddl.go +++ b/services/ocinosql/ddl.go @@ -1,11 +1,13 @@ package ocinosql import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "strconv" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -20,12 +22,12 @@ var identRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`) // Column is one table column. type Column struct { - Name string `json:"name"` - Type string `json:"type"` - Default json.RawMessage `json:"default,omitempty"` - NotNull bool `json:"notNull,omitempty"` - AsUUID bool `json:"asUuid,omitempty"` - Gen bool `json:"generated,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Default jsontext.Value `json:"default,omitempty"` + NotNull bool `json:"notNull,omitempty"` + AsUUID bool `json:"asUuid,omitempty"` + Gen bool `json:"generated,omitempty"` } // Identity is the table's identity column spec and its counter. @@ -318,21 +320,21 @@ func (p *parser) parseColumnOption(col *Column, t *dtype, id *Identity) (bool, * return true, id, nil } -func (p *parser) defaultLiteral() (json.RawMessage, *APIError) { +func (p *parser) defaultLiteral() (jsontext.Value, *APIError) { t := p.next() switch { case t.kind == tkNumber: - return json.RawMessage(t.text), nil + return jsontext.Value(t.text), nil case t.kind == tkString: - b, _ := json.Marshal(t.text) + b, _ := json.Marshal(t.text, jsonx.V1()) return b, nil case t.up == kwTrue || t.up == kwFalse: - return json.RawMessage(strings.ToLower(t.text)), nil + return jsontext.Value(strings.ToLower(t.text)), nil case t.up == kwNull: - return json.RawMessage("null"), nil + return jsontext.Value("null"), nil case t.text == "-" && p.peek().kind == tkNumber: - return json.RawMessage("-" + p.next().text), nil + return jsontext.Value("-" + p.next().text), nil } return nil, errParam("syntax error: DEFAULT must be a number, string, boolean or NULL literal") diff --git a/services/ocinosql/handler.go b/services/ocinosql/handler.go index 4f9a1016e1..ee846e70e9 100644 --- a/services/ocinosql/handler.go +++ b/services/ocinosql/handler.go @@ -3,7 +3,8 @@ package ocinosql import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +119,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocinosql: encode response: %w", err) } @@ -125,9 +128,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -164,10 +165,9 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - dec := json.NewDecoder(bytes.NewReader(q.body)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(q.body)) - if err := dec.Decode(v); err != nil { + if err := json.UnmarshalDecode(dec, v, jsonx.V1(), jsonx.UseNumber()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -189,7 +189,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -258,7 +261,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocinosql/limits.go b/services/ocinosql/limits.go index 5ba7ab51f7..8869fe6d6c 100644 --- a/services/ocinosql/limits.go +++ b/services/ocinosql/limits.go @@ -1,8 +1,9 @@ package ocinosql import ( - "encoding/json" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Service limits from Oracle's "Oracle NoSQL Database Cloud Service Limits" @@ -18,7 +19,7 @@ func valueBytes(v any) int { switch x := v.(type) { case string: return len(x) - case json.Number: + case jsonx.Number: return len(x.String()) case map[string]any: n := 0 diff --git a/services/ocinosql/models.go b/services/ocinosql/models.go index 0c45a77e8d..7fb5196c81 100644 --- a/services/ocinosql/models.go +++ b/services/ocinosql/models.go @@ -1,7 +1,7 @@ package ocinosql import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -46,9 +46,9 @@ type Limits struct { // Row is one stored table row. type Row struct { - Expire *time.Time `json:"expire,omitempty"` - Version string `json:"version"` - Value json.RawMessage `json:"value"` + Expire *time.Time `json:"expire,omitempty"` + Version string `json:"version"` + Value jsontext.Value `json:"value"` } // IndexKey is one key of a secondary index. diff --git a/services/ocinosql/rows.go b/services/ocinosql/rows.go index ef5c06e37f..6be2183f69 100644 --- a/services/ocinosql/rows.go +++ b/services/ocinosql/rows.go @@ -3,11 +3,13 @@ package ocinosql import ( "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -35,26 +37,26 @@ type usageWire struct { // UpdateRowResult is the UpdateRow response body. type UpdateRowResult struct { - Version *string `json:"version"` - ExistingVersion *string `json:"existingVersion,omitempty"` - GeneratedValue *string `json:"generatedValue,omitempty"` - ExistingValue json.RawMessage `json:"existingValue,omitempty"` - Usage usageWire `json:"usage"` + Version *string `json:"version"` + ExistingVersion *string `json:"existingVersion,omitempty"` + GeneratedValue *string `json:"generatedValue,omitempty"` + ExistingValue jsontext.Value `json:"existingValue,omitempty"` + Usage usageWire `json:"usage"` } // RowResult is the GetRow response body. type RowResult struct { - TimeOfExpiration *time.Time `json:"timeOfExpiration,omitempty"` - Value json.RawMessage `json:"value"` - Usage usageWire `json:"usage"` + TimeOfExpiration *time.Time `json:"timeOfExpiration,omitempty"` + Value jsontext.Value `json:"value"` + Usage usageWire `json:"usage"` } // DeleteRowResult is the DeleteRow response body. type DeleteRowResult struct { - ExistingVersion *string `json:"existingVersion,omitempty"` - ExistingValue json.RawMessage `json:"existingValue,omitempty"` - IsSuccess bool `json:"isSuccess"` - Usage usageWire `json:"usage"` + ExistingVersion *string `json:"existingVersion,omitempty"` + ExistingValue jsontext.Value `json:"existingValue,omitempty"` + IsSuccess bool `json:"isSuccess"` + Usage usageWire `json:"usage"` } func unitsFor(size int) int { return max(1, (size+bytesPerUnit-1)/bytesPerUnit) } @@ -189,7 +191,7 @@ func (t *Table) generateIdentity(col string, out map[string]any, given map[strin v := strconv.FormatInt(id.Next, 10) id.Next += id.Increment - out[col], given[col] = json.Number(v), true + out[col], given[col] = jsonx.Number(v), true return &v, nil } @@ -293,7 +295,7 @@ func (b *InMemoryBackend) UpdateRow(ref, ifMatch string, in updateRowIn) (Update return b.refusedPut(t, existing, in, now), nil } - raw, _ := json.Marshal(row) + raw, _ := json.Marshal(row, jsonx.V1()) t.Rows[key] = &Row{Value: raw, Version: t.nextVersion(), Expire: expire} w := unitsFor(len(raw)) t.record(now, 0, w) @@ -346,7 +348,7 @@ func parseKey(t *Table, parts []string) (map[string]any, *APIError) { var raw any = val if sv.types[i].isNumeric() { - raw = json.Number(val) + raw = jsonx.Number(val) } n, msg := coerce(sv.types[i], raw) diff --git a/services/ocinosql/sqlexec.go b/services/ocinosql/sqlexec.go index a2479f3f5f..849b559911 100644 --- a/services/ocinosql/sqlexec.go +++ b/services/ocinosql/sqlexec.go @@ -1,12 +1,12 @@ package ocinosql import ( - "encoding/json" "slices" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -373,7 +373,7 @@ func (pl *plan) sortEntries(es []entry) { func (pl *plan) project(es []entry) []map[string]any { st := pl.stmt if st.count { - return []map[string]any{{"Column_1": json.Number(strconv.Itoa(len(es)))}} + return []map[string]any{{"Column_1": jsonx.Number(strconv.Itoa(len(es)))}} } out := make([]map[string]any, 0, len(es)) diff --git a/services/ocinosql/sqlparse.go b/services/ocinosql/sqlparse.go index 0ce8cc9d24..bff8b6e844 100644 --- a/services/ocinosql/sqlparse.go +++ b/services/ocinosql/sqlparse.go @@ -1,9 +1,10 @@ package ocinosql import ( - "encoding/json" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type step struct { @@ -493,7 +494,7 @@ func (p *parser) parseOperand() (expr, *APIError) { return nil, p.fail("a number") } - return &litExpr{v: json.Number("-" + n.text)}, nil + return &litExpr{v: jsonx.Number("-" + n.text)}, nil case t.kind == tkIdent && !isLiteralKw(t.up): steps, err := p.parsePath() if err != nil { @@ -518,7 +519,7 @@ func isLiteralKw(up string) bool { return up == kwTrue || up == kwFalse || up == func literalOf(t tok) (expr, bool) { switch { case t.kind == tkNumber: - return &litExpr{v: json.Number(t.text)}, true + return &litExpr{v: jsonx.Number(t.text)}, true case t.kind == tkString: return &litExpr{v: t.text}, true case t.kind == tkVar: diff --git a/services/ocinosql/tables.go b/services/ocinosql/tables.go index 35b6d094a0..83c4d024c3 100644 --- a/services/ocinosql/tables.go +++ b/services/ocinosql/tables.go @@ -1,13 +1,14 @@ package ocinosql import ( - "encoding/json" + "encoding/json/v2" "net/http" "slices" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -369,7 +370,7 @@ func schemaView2(t *Table) *schemaWire { if len(c.Default) > 0 { var s string - if json.Unmarshal(c.Default, &s) != nil { + if json.Unmarshal(c.Default, &s, jsonx.V1()) != nil { s = string(c.Default) } diff --git a/services/ocinosql/values.go b/services/ocinosql/values.go index 646010f0f0..4d780f2aa5 100644 --- a/services/ocinosql/values.go +++ b/services/ocinosql/values.go @@ -3,7 +3,8 @@ package ocinosql import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "math" "math/big" @@ -13,6 +14,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/strs" ) @@ -46,18 +48,17 @@ const ( ) func decodeJSON(raw []byte) (any, error) { - dec := json.NewDecoder(bytes.NewReader(raw)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(raw)) var v any - if err := dec.Decode(&v); err != nil { + if err := json.UnmarshalDecode(dec, &v, jsonx.V1(), jsonx.UseNumber()); err != nil { return nil, fmt.Errorf("decode json: %w", err) } return v, nil } -func decodeRow(raw json.RawMessage) map[string]any { +func decodeRow(raw jsontext.Value) map[string]any { v, err := decodeJSON(raw) if err != nil { return map[string]any{} @@ -69,7 +70,7 @@ func decodeRow(raw json.RawMessage) map[string]any { } func coerceInt(v any, lo, hi int64, kind string) (any, string) { - n, ok := v.(json.Number) + n, ok := v.(jsonx.Number) if !ok { return nil, "is not a valid " + kind } @@ -93,7 +94,7 @@ func coerceInt(v any, lo, hi int64, kind string) (any, string) { return nil, "is out of range for " + kind } - return json.Number(strconv.FormatInt(i, 10)), "" + return jsonx.Number(strconv.FormatInt(i, 10)), "" } func parseTimestamp(v any) (time.Time, bool) { @@ -106,7 +107,7 @@ func parseTimestamp(v any) (time.Time, bool) { return t.UTC(), true } } - case json.Number: + case jsonx.Number: if ms, err := strconv.ParseInt(x.String(), 10, 64); err == nil { return time.UnixMilli(ms).UTC(), true } @@ -149,7 +150,7 @@ func coerce(t *dtype, v any) (any, string) { func coerceScalar(t *dtype, v any) (any, string) { switch t.kind { case kNumber: - n, ok := v.(json.Number) + n, ok := v.(jsonx.Number) if _, rok := new(big.Rat).SetString(n.String()); !ok || !rok { return nil, "is not a valid NUMBER" } @@ -263,7 +264,7 @@ func coerceRecord(t *dtype, v any) (any, string) { return out, "" } -func cmpNum(a, b json.Number) (int, bool) { +func cmpNum(a, b jsonx.Number) (int, bool) { ra, ok1 := new(big.Rat).SetString(a.String()) rb, ok2 := new(big.Rat).SetString(b.String()) @@ -277,8 +278,8 @@ func cmpNum(a, b json.Number) (int, bool) { // compareVals orders two scalar values of the same family; ok is false when they are not comparable. func compareVals(a, b any) (int, bool) { switch x := a.(type) { - case json.Number: - if y, ok := b.(json.Number); ok { + case jsonx.Number: + if y, ok := b.(jsonx.Number); ok { return cmpNum(x, y) } case string: @@ -302,7 +303,7 @@ func compareVals(a, b any) (int, bool) { } func keyPart(v any) string { - if n, ok := v.(json.Number); ok { + if n, ok := v.(jsonx.Number); ok { if r, rok := new(big.Rat).SetString(n.String()); rok { return r.RatString() } @@ -326,7 +327,7 @@ func rowKey(pk []string, row map[string]any) (string, *APIError) { parts[i] = keyPart(v) } - b, _ := json.Marshal(parts) //nolint:errchkjson // []string cannot fail + b, _ := json.Marshal(parts, jsonx.V1()) //nolint:errchkjson // []string cannot fail return string(b), nil } @@ -340,7 +341,7 @@ func validUUID(s string) bool { } func coerceFloat(kind string, v any) (any, string) { - n, ok := v.(json.Number) + n, ok := v.(jsonx.Number) if !ok { return nil, "is not a valid " + kind } @@ -350,5 +351,5 @@ func coerceFloat(kind string, v any) (any, string) { return nil, "is not a valid " + kind } - return json.Number(strconv.FormatFloat(f, 'g', -1, 64)), "" + return jsonx.Number(strconv.FormatFloat(f, 'g', -1, 64)), "" } diff --git a/services/ocinotifications/handler.go b/services/ocinotifications/handler.go index 9e9d2dc97c..713514fdd5 100644 --- a/services/ocinotifications/handler.go +++ b/services/ocinotifications/handler.go @@ -2,7 +2,7 @@ package ocinotifications import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -117,7 +119,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocinotifications: encode response: %w", err) } @@ -126,9 +128,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -168,7 +168,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -190,7 +190,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -278,7 +281,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocinotifications/publish.go b/services/ocinotifications/publish.go index 9418c1535e..87b4cec7ef 100644 --- a/services/ocinotifications/publish.go +++ b/services/ocinotifications/publish.go @@ -3,7 +3,8 @@ package ocinotifications import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -116,7 +119,7 @@ func (b *InMemoryBackend) queueConfirmation(s *Subscription, t *Topic) { "SubscriptionId": s.ID, "ConfirmationURL": d.ConfirmationURL, "Message": "You have chosen to subscribe to the topic " + t.Name + ". To confirm the subscription, visit the ConfirmationURL included in this message.", - }) + }, jsonx.V1()) d.Body = string(body) d.Title = "Confirm your subscription to " + t.Name b.route(d) @@ -154,7 +157,7 @@ func (b *InMemoryBackend) PublishMessage(topicID, messageType string, in Publish return PublishWire{}, errParam("a message is limited to 64 KB") } - if messageType == msgJSON && !json.Valid([]byte(*in.Body)) { + if messageType == msgJSON && !jsontext.Value([]byte(*in.Body)).IsValid(jsonx.V1()) { return PublishWire{}, errParam("body must be valid JSON when messageType is JSON") } diff --git a/services/ocinotifications/subs.go b/services/ocinotifications/subs.go index de6576d716..e12d9fbf5d 100644 --- a/services/ocinotifications/subs.go +++ b/services/ocinotifications/subs.go @@ -1,7 +1,7 @@ package ocinotifications import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/mail" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -104,7 +105,7 @@ func policyOf(s *Subscription) deliveryPolicyWire { } func subView(s *Subscription) SubWire { - pol, _ := json.Marshal(policyOf(s)) //nolint:errchkjson // static shape + pol, _ := json.Marshal(policyOf(s), jsonx.V1()) //nolint:errchkjson // static shape return SubWire{ ID: s.ID, diff --git a/services/ociobjectstorage/auth_test.go b/services/ociobjectstorage/auth_test.go index 94450b9ab6..94c243c587 100644 --- a/services/ociobjectstorage/auth_test.go +++ b/services/ociobjectstorage/auth_test.go @@ -1,7 +1,7 @@ package ociobjectstorage_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -114,14 +115,17 @@ func TestAdminKeyRegistration(t *testing.T) { _, err := e.client.GetNamespace(t.Context(), objectstorage.GetNamespaceRequest{}) assert.Equal(t, 401, svcErr(t, err).GetHTTPStatusCode()) - payload, _ := json.Marshal(map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.keys.pubPEM}) + payload, _ := json.Marshal( + map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.keys.pubPEM}, + jsonx.V1(), + ) code, body := rawDo(t, http.MethodPost, e.baseURL+"/_gopherstack/oci/keys", string(payload)) require.Equal(t, http.StatusCreated, code, body) var out struct { KeyID string `json:"keyId"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, testTenancy+"/"+testUser+"/"+e.keys.fprint, out.KeyID) resp, err := e.client.GetNamespace(t.Context(), objectstorage.GetNamespaceRequest{}) diff --git a/services/ociobjectstorage/handler.go b/services/ociobjectstorage/handler.go index a7a60b6e8f..0861f616f8 100644 --- a/services/ociobjectstorage/handler.go +++ b/services/ociobjectstorage/handler.go @@ -2,7 +2,7 @@ package ociobjectstorage import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -13,6 +13,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -112,18 +114,17 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociobjectstorage: encode response: %w", err) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - - if q.r.Method != http.MethodHead { - _, _ = q.w.Write(out) + if q.r.Method == http.MethodHead { + out = nil } + httputils.WriteJSONBytes(q.w, status, out) + return nil } @@ -151,7 +152,10 @@ func (h *Handler) fail(q *req, err error) { q.w.WriteHeader(ae.Status) if q.r.Method != http.MethodHead { - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } } @@ -268,7 +272,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociobjectstorage/ops.go b/services/ociobjectstorage/ops.go index 7243b1315d..7659a5f2b6 100644 --- a/services/ociobjectstorage/ops.go +++ b/services/ociobjectstorage/ops.go @@ -1,11 +1,13 @@ package ociobjectstorage import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func buildOps() map[string]opFunc { @@ -54,7 +56,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", diff --git a/services/ociqueue/handler.go b/services/ociqueue/handler.go index 62a3e9dcfc..a97c4c777f 100644 --- a/services/ociqueue/handler.go +++ b/services/ociqueue/handler.go @@ -2,7 +2,7 @@ package ociqueue import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +118,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ociqueue: encode response: %w", err) } @@ -125,9 +127,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -174,7 +174,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -196,7 +196,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -265,7 +268,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ociqueue/helpers_test.go b/services/ociqueue/helpers_test.go index bde7f5c137..60b9f673b3 100644 --- a/services/ociqueue/helpers_test.go +++ b/services/ociqueue/helpers_test.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" - "encoding/json" + "encoding/json/v2" "encoding/pem" "fmt" "io" @@ -19,6 +19,7 @@ import ( "github.com/oracle/oci-go-sdk/v65/queue" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -223,7 +224,7 @@ func get(t *testing.T, c queue.QueueClient, q queue.Queue, mod func(*queue.GetMe } func quote(s string) string { - b, _ := json.Marshal(s) + b, _ := json.Marshal(s, jsonx.V1()) return string(b) } diff --git a/services/ociqueue/queues.go b/services/ociqueue/queues.go index 99019f4c11..95b81da2e2 100644 --- a/services/ociqueue/queues.go +++ b/services/ociqueue/queues.go @@ -1,7 +1,7 @@ package ociqueue import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "slices" "strconv" @@ -27,27 +27,27 @@ const ( // CreateQueueIn is the CreateQueue request body. type CreateQueueIn struct { Tags - Retention *int `json:"retentionInSeconds"` - Visibility *int `json:"visibilityInSeconds"` - Timeout *int `json:"timeoutInSeconds"` - ChannelLimit *int `json:"channelConsumptionLimit"` - DLQCount *int `json:"deadLetterQueueDeliveryCount"` - KMSKeyID *string `json:"customEncryptionKeyId"` - Name string `json:"displayName"` - Compartment string `json:"compartmentId"` - Capabilities []json.RawMessage `json:"capabilities"` + Retention *int `json:"retentionInSeconds"` + Visibility *int `json:"visibilityInSeconds"` + Timeout *int `json:"timeoutInSeconds"` + ChannelLimit *int `json:"channelConsumptionLimit"` + DLQCount *int `json:"deadLetterQueueDeliveryCount"` + KMSKeyID *string `json:"customEncryptionKeyId"` + Name string `json:"displayName"` + Compartment string `json:"compartmentId"` + Capabilities []jsontext.Value `json:"capabilities"` } // UpdateQueueIn is the UpdateQueue request body. type UpdateQueueIn struct { Tags - Name *string `json:"displayName"` - Visibility *int `json:"visibilityInSeconds"` - Timeout *int `json:"timeoutInSeconds"` - ChannelLimit *int `json:"channelConsumptionLimit"` - DLQCount *int `json:"deadLetterQueueDeliveryCount"` - KMSKeyID *string `json:"customEncryptionKeyId"` - Capabilities []json.RawMessage `json:"capabilities"` + Name *string `json:"displayName"` + Visibility *int `json:"visibilityInSeconds"` + Timeout *int `json:"timeoutInSeconds"` + ChannelLimit *int `json:"channelConsumptionLimit"` + DLQCount *int `json:"deadLetterQueueDeliveryCount"` + KMSKeyID *string `json:"customEncryptionKeyId"` + Capabilities []jsontext.Value `json:"capabilities"` } // PurgeIn is the PurgeQueue request body. @@ -131,7 +131,7 @@ func firstErr(errs ...*APIError) *APIError { return nil } -func checkCapabilities(c []json.RawMessage) *APIError { +func checkCapabilities(c []jsontext.Value) *APIError { if len(c) > 0 { return errParam("capabilities are not supported by this emulator") } diff --git a/services/ocistreaming/cursors.go b/services/ocistreaming/cursors.go index 8398e57860..4e39d60a55 100644 --- a/services/ocistreaming/cursors.go +++ b/services/ocistreaming/cursors.go @@ -2,7 +2,7 @@ package ocistreaming import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strconv" @@ -10,6 +10,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -39,7 +41,7 @@ type cursorTok struct { } func (c cursorTok) encode() string { - raw, _ := json.Marshal(c) //nolint:errchkjson // static shape + raw, _ := json.Marshal(c, jsonx.V1()) //nolint:errchkjson // static shape return base64.RawURLEncoding.EncodeToString(raw) } @@ -48,7 +50,7 @@ func decodeCursor(streamID, v string) (cursorTok, error) { var c cursorTok raw, err := base64.RawURLEncoding.DecodeString(v) - if err != nil || json.Unmarshal(raw, &c) != nil || c.Stream != streamID { + if err != nil || json.Unmarshal(raw, &c, jsonx.V1()) != nil || c.Stream != streamID { return c, errParam("The cursor is invalid or does not belong to this stream") } diff --git a/services/ocistreaming/handler.go b/services/ocistreaming/handler.go index ad3f717cba..16e9a24e15 100644 --- a/services/ocistreaming/handler.go +++ b/services/ocistreaming/handler.go @@ -2,7 +2,7 @@ package ocistreaming import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -116,7 +118,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocistreaming: encode response: %w", err) } @@ -125,9 +127,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -174,7 +174,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -196,7 +196,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -258,7 +261,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocistreaming/service_test.go b/services/ocistreaming/service_test.go index 935e5ecdef..052cc03a6d 100644 --- a/services/ocistreaming/service_test.go +++ b/services/ocistreaming/service_test.go @@ -2,7 +2,7 @@ package ocistreaming_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -95,7 +96,10 @@ func TestAdminKeyRegistration(t *testing.T) { _, err := e.admin.ListStreamPools(t.Context(), req) assert.Equal(t, http.StatusUnauthorized, svcErr(t, err).GetHTTPStatusCode()) - payload, _ := json.Marshal(map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.kp.pubPEM}) + payload, _ := json.Marshal( + map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.kp.pubPEM}, + jsonx.V1(), + ) code, body := rawDo(t, http.MethodPost, e.base+"/_gopherstack/oci/keys", string(payload)) require.Equal(t, http.StatusCreated, code, body) diff --git a/services/ocivault/handler.go b/services/ocivault/handler.go index 8523e4ebdd..0689cff9a4 100644 --- a/services/ocivault/handler.go +++ b/services/ocivault/handler.go @@ -2,7 +2,7 @@ package ocivault import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/ociauth" @@ -115,7 +117,7 @@ type errorBody struct { func (q *req) header(k, v string) { q.w.Header().Set(k, v) } func (q *req) json(status int, v any, etag string) error { - out, err := json.Marshal(v) + out, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("ocivault: encode response: %w", err) } @@ -124,9 +126,7 @@ func (q *req) json(status int, v any, etag string) error { q.header("etag", etag) } - q.header("Content-Type", "application/json") - q.w.WriteHeader(status) - _, _ = q.w.Write(out) + httputils.WriteJSONBytes(q.w, status, out) return nil } @@ -173,7 +173,7 @@ func (q *req) decode(v any) error { return errMissing("A request body is required") } - if err := json.Unmarshal(q.body, v); err != nil { + if err := json.Unmarshal(q.body, v, jsonx.V1()); err != nil { return newErr( http.StatusBadRequest, "CannotParseRequest", @@ -195,7 +195,10 @@ func (h *Handler) fail(q *req, err error) { q.header("Content-Type", "application/json") q.w.WriteHeader(ae.Status) - out, _ := json.Marshal(errorBody{Code: ae.Code, Message: ae.Message}) //nolint:errchkjson // static shape + out, _ := json.Marshal( + errorBody{Code: ae.Code, Message: ae.Message}, + jsonx.V1(), + ) //nolint:errchkjson // static shape _, _ = q.w.Write(out) } @@ -257,7 +260,7 @@ func (h *Handler) adminKeys(q *req) { var in adminKeyReq body, _ := io.ReadAll(io.LimitReader(q.r.Body, adminBodyLimit)) - if err := json.Unmarshal(body, &in); err != nil || in.Tenancy == "" || in.User == "" { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil || in.Tenancy == "" || in.User == "" { h.fail(q, errParam("tenancy, user and publicKey are required")) return diff --git a/services/ocivault/handler_test.go b/services/ocivault/handler_test.go index 4e5db40ae3..ad23de5ad4 100644 --- a/services/ocivault/handler_test.go +++ b/services/ocivault/handler_test.go @@ -2,7 +2,7 @@ package ocivault_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -112,7 +113,7 @@ func TestPathVaultRouting(t *testing.T) { VaultID string `json:"vaultId"` } - require.NoError(t, json.Unmarshal([]byte(out), &k)) + require.NoError(t, json.Unmarshal([]byte(out), &k, jsonx.V1())) assert.Equal(t, *v.Id, k.VaultID) code, out = rawDo(t, http.MethodGet, e.base+"/_vault/"+*v.Id+"/20180608/keys?compartmentId="+testCompartment, "") @@ -161,7 +162,7 @@ func TestErrorBodyShapeAndUnknownRoutes(t *testing.T) { Message string `json:"message"` } - require.NoError(t, json.Unmarshal([]byte(out), &eb)) + require.NoError(t, json.Unmarshal([]byte(out), &eb, jsonx.V1())) assert.Equal(t, tt.wantCode, eb.Code) assert.NotEmpty(t, eb.Message) assert.NotEqual(t, eb.Code, eb.Message) @@ -234,7 +235,10 @@ func TestAdminKeyRegistration(t *testing.T) { _, err := e.vaults.ListVaults(t.Context(), listReq) assert.Equal(t, http.StatusUnauthorized, svcErr(t, err).GetHTTPStatusCode()) - payload, _ := json.Marshal(map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.kp.pubPEM}) + payload, _ := json.Marshal( + map[string]string{"tenancy": testTenancy, "user": testUser, "publicKey": e.kp.pubPEM}, + jsonx.V1(), + ) code, body := rawDo(t, http.MethodPost, e.base+"/_gopherstack/oci/keys", string(payload)) require.Equal(t, http.StatusCreated, code, body) diff --git a/services/ocivault/models.go b/services/ocivault/models.go index 6fcbc68a31..60af20ab9d 100644 --- a/services/ocivault/models.go +++ b/services/ocivault/models.go @@ -1,7 +1,7 @@ package ocivault import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -106,7 +106,7 @@ type Secret struct { Description string `json:"description,omitempty"` State string `json:"state"` Etag string `json:"etag"` - Rules []json.RawMessage `json:"rules,omitempty"` + Rules []jsontext.Value `json:"rules,omitempty"` Versions []*SecretVersion `json:"versions"` Seq int64 `json:"seq"` Current int64 `json:"current"` diff --git a/services/ocivault/seal.go b/services/ocivault/seal.go index c8851b7ec5..fb6cf32bee 100644 --- a/services/ocivault/seal.go +++ b/services/ocivault/seal.go @@ -12,8 +12,10 @@ import ( _ "crypto/sha512" // registers SHA-384/512 for crypto.Hash.New "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "hash" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -51,7 +53,7 @@ func aadBytes(aad map[string]string) []byte { return nil } - b, _ := json.Marshal(aad) //nolint:errchkjson // map[string]string always marshals; keys are sorted + b, _ := json.Marshal(aad, jsonx.V1()) //nolint:errchkjson // map[string]string always marshals; keys are sorted return b } diff --git a/services/ocivault/secrets.go b/services/ocivault/secrets.go index fce0df635b..3c55f64207 100644 --- a/services/ocivault/secrets.go +++ b/services/ocivault/secrets.go @@ -3,13 +3,15 @@ package ocivault import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "slices" "strconv" "time" "github.com/blackbirdworks/gopherstack/pkgs/iso8601" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -37,16 +39,16 @@ type CreateSecretIn struct { DefinedTags map[string]map[string]any `json:"definedTags"` Metadata map[string]any `json:"metadata"` SecretContent *SecretContent `json:"secretContent"` - ReplicationConfig json.RawMessage `json:"replicationConfig"` - RotationConfig json.RawMessage `json:"rotationConfig"` - SecretGenerationContext json.RawMessage `json:"secretGenerationContext"` + ReplicationConfig jsontext.Value `json:"replicationConfig"` + RotationConfig jsontext.Value `json:"rotationConfig"` + SecretGenerationContext jsontext.Value `json:"secretGenerationContext"` EnableAutoGeneration *bool `json:"enableAutoGeneration"` CompartmentID string `json:"compartmentId"` KeyID string `json:"keyId"` SecretName string `json:"secretName"` VaultID string `json:"vaultId"` Description string `json:"description"` - SecretRules []json.RawMessage `json:"secretRules"` + SecretRules []jsontext.Value `json:"secretRules"` } // UpdateSecretIn is the UpdateSecret request body. @@ -57,11 +59,11 @@ type UpdateSecretIn struct { DefinedTags map[string]map[string]any `json:"definedTags"` Metadata map[string]any `json:"metadata"` SecretContent *SecretContent `json:"secretContent"` - ReplicationConfig json.RawMessage `json:"replicationConfig"` - RotationConfig json.RawMessage `json:"rotationConfig"` - SecretGenerationContext json.RawMessage `json:"secretGenerationContext"` + ReplicationConfig jsontext.Value `json:"replicationConfig"` + RotationConfig jsontext.Value `json:"rotationConfig"` + SecretGenerationContext jsontext.Value `json:"secretGenerationContext"` EnableAutoGeneration *bool `json:"enableAutoGeneration"` - SecretRules []json.RawMessage `json:"secretRules"` + SecretRules []jsontext.Value `json:"secretRules"` } type expiryRule struct { @@ -70,7 +72,7 @@ type expiryRule struct { Interval string `json:"secretVersionExpiryInterval"` } -func unsupported(raw json.RawMessage, auto *bool, what string) *APIError { +func unsupported(raw jsontext.Value, auto *bool, what string) *APIError { if len(bytes.TrimSpace(raw)) > 0 && string(bytes.TrimSpace(raw)) != "null" || auto != nil && *auto { return errParam(what + " is not supported by this emulator") } @@ -78,10 +80,10 @@ func unsupported(raw json.RawMessage, auto *bool, what string) *APIError { return nil } -func rejectUnsupported(repl, rot, gen json.RawMessage, auto *bool) *APIError { +func rejectUnsupported(repl, rot, gen jsontext.Value, auto *bool) *APIError { for _, c := range []struct { what string - raw json.RawMessage + raw jsontext.Value }{ {what: "replicationConfig", raw: repl}, {what: "rotationConfig", raw: rot}, @@ -95,13 +97,18 @@ func rejectUnsupported(repl, rot, gen json.RawMessage, auto *bool) *APIError { return unsupported(nil, auto, "enableAutoGeneration") } -func validateRules(rules []json.RawMessage) *APIError { +func validateRules(rules []jsontext.Value) *APIError { for _, r := range rules { var head struct { RuleType string `json:"ruleType"` } - if err := json.Unmarshal(r, &head); err != nil || (head.RuleType != ruleExpiry && head.RuleType != ruleReuse) { + if err := json.Unmarshal( + r, + &head, + jsonx.V1(), + ); err != nil || + (head.RuleType != ruleExpiry && head.RuleType != ruleReuse) { return errParam("secretRules[].ruleType must be SECRET_EXPIRY_RULE or SECRET_REUSE_RULE") } @@ -115,9 +122,9 @@ func validateRules(rules []json.RawMessage) *APIError { return nil } -func parseExpiry(raw json.RawMessage) (expiryRule, *APIError) { +func parseExpiry(raw jsontext.Value) (expiryRule, *APIError) { var e expiryRule - if err := json.Unmarshal(raw, &e); err != nil { + if err := json.Unmarshal(raw, &e, jsonx.V1()); err != nil { return e, errParam("invalid SECRET_EXPIRY_RULE") } @@ -130,13 +137,13 @@ func parseExpiry(raw json.RawMessage) (expiryRule, *APIError) { return e, nil } -func ruleOfType(rules []json.RawMessage, t string) json.RawMessage { +func ruleOfType(rules []jsontext.Value, t string) jsontext.Value { for _, r := range rules { var head struct { RuleType string `json:"ruleType"` } - if json.Unmarshal(r, &head) == nil && head.RuleType == t { + if json.Unmarshal(r, &head, jsonx.V1()) == nil && head.RuleType == t { return r } } @@ -144,7 +151,7 @@ func ruleOfType(rules []json.RawMessage, t string) json.RawMessage { return nil } -func (b *InMemoryBackend) versionExpiry(rules []json.RawMessage) *time.Time { +func (b *InMemoryBackend) versionExpiry(rules []jsontext.Value) *time.Time { raw := ruleOfType(rules, ruleExpiry) if raw == nil { return nil diff --git a/services/ocivault/secrets_test.go b/services/ocivault/secrets_test.go index 4246998146..379b082494 100644 --- a/services/ocivault/secrets_test.go +++ b/services/ocivault/secrets_test.go @@ -1,7 +1,7 @@ package ocivault_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" oci "github.com/blackbirdworks/gopherstack/services/ocivault" ) @@ -644,7 +645,7 @@ func TestSecretIfMatchAndCompartmentMove(t *testing.T) { require.NoError(t, err) assert.Equal(t, "ocid1.compartment.oc1..moved", *moved.CompartmentId) - raw, err := json.Marshal(moved.Secret) + raw, err := json.Marshal(moved.Secret, jsonx.V1()) require.NoError(t, err) assert.Contains(t, string(raw), "currentVersionNumber") } diff --git a/services/ocivault/secretwire.go b/services/ocivault/secretwire.go index 61f6c10afa..3d0e484fd4 100644 --- a/services/ocivault/secretwire.go +++ b/services/ocivault/secretwire.go @@ -1,7 +1,7 @@ package ocivault import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -20,7 +20,7 @@ type secretWire struct { VaultID string `json:"vaultId"` KeyID string `json:"keyId"` Description string `json:"description,omitempty"` - SecretRules []json.RawMessage `json:"secretRules,omitempty"` + SecretRules []jsontext.Value `json:"secretRules,omitempty"` } type secretSummaryWire struct { diff --git a/services/omics/delete_precondition_test.go b/services/omics/delete_precondition_test.go index d913d06d06..ae40065d2c 100644 --- a/services/omics/delete_precondition_test.go +++ b/services/omics/delete_precondition_test.go @@ -1,12 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDeleteRun_RequiresTerminalState verifies real AWS DeleteRun semantics @@ -26,7 +28,7 @@ func TestDeleteRun_RequiresTerminalState(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) runID := startResp["id"].(string) delRec := doRequest(t, h, http.MethodDelete, "/run/"+runID, nil) @@ -55,7 +57,7 @@ func TestDeleteReferenceStore_RequiresEmpty(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+storeID+"/importjob", map[string]any{ @@ -68,7 +70,7 @@ func TestDeleteReferenceStore_RequiresEmpty(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) refs, ok := listResp["references"].([]any) require.True(t, ok) require.Len(t, refs, 1) @@ -100,7 +102,7 @@ func TestDeleteSequenceStore_RequiresEmpty(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/importjob", map[string]any{ @@ -113,7 +115,7 @@ func TestDeleteSequenceStore_RequiresEmpty(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) readSets, ok := listResp["readSets"].([]any) require.True(t, ok) require.Len(t, readSets, 1) @@ -147,7 +149,7 @@ func TestDeleteReference_RequiresNoAssociatedReadSet(t *testing.T) { require.Equal(t, http.StatusCreated, refStoreRec.Code) var refStore map[string]any - require.NoError(t, json.Unmarshal(refStoreRec.Body.Bytes(), &refStore)) + require.NoError(t, json.Unmarshal(refStoreRec.Body.Bytes(), &refStore, jsonx.V1())) refStoreID := refStore["id"].(string) refJobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+refStoreID+"/importjob", map[string]any{ @@ -160,7 +162,7 @@ func TestDeleteReference_RequiresNoAssociatedReadSet(t *testing.T) { require.Equal(t, http.StatusOK, refListRec.Code) var refListResp map[string]any - require.NoError(t, json.Unmarshal(refListRec.Body.Bytes(), &refListResp)) + require.NoError(t, json.Unmarshal(refListRec.Body.Bytes(), &refListResp, jsonx.V1())) refs, ok := refListResp["references"].([]any) require.True(t, ok) require.Len(t, refs, 1) @@ -173,7 +175,7 @@ func TestDeleteReference_RequiresNoAssociatedReadSet(t *testing.T) { require.Equal(t, http.StatusCreated, seqStoreRec.Code) var seqStore map[string]any - require.NoError(t, json.Unmarshal(seqStoreRec.Body.Bytes(), &seqStore)) + require.NoError(t, json.Unmarshal(seqStoreRec.Body.Bytes(), &seqStore, jsonx.V1())) seqStoreID := seqStore["id"].(string) readSetJobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+seqStoreID+"/importjob", map[string]any{ @@ -190,7 +192,7 @@ func TestDeleteReference_RequiresNoAssociatedReadSet(t *testing.T) { require.Equal(t, http.StatusOK, readSetListRec.Code) var readSetListResp map[string]any - require.NoError(t, json.Unmarshal(readSetListRec.Body.Bytes(), &readSetListResp)) + require.NoError(t, json.Unmarshal(readSetListRec.Body.Bytes(), &readSetListResp, jsonx.V1())) readSets, ok := readSetListResp["readSets"].([]any) require.True(t, ok) require.Len(t, readSets, 1) diff --git a/services/omics/handler.go b/services/omics/handler.go index cb633586ce..0cdc04ccf8 100644 --- a/services/omics/handler.go +++ b/services/omics/handler.go @@ -1,7 +1,7 @@ package omics import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -782,7 +783,7 @@ func readJSON(c *echo.Context, v any) error { return nil } - if jsonErr := json.Unmarshal(body, v); jsonErr != nil { + if jsonErr := json.Unmarshal(body, v, jsonx.V1()); jsonErr != nil { if writeErr := c.JSON(http.StatusBadRequest, errResp(errValidation, "invalid JSON")); writeErr != nil { return writeErr } diff --git a/services/omics/handler_annotation_stores_test.go b/services/omics/handler_annotation_stores_test.go index eece97ac5b..8128a5d787 100644 --- a/services/omics/handler_annotation_stores_test.go +++ b/services/omics/handler_annotation_stores_test.go @@ -1,13 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -31,7 +32,7 @@ func TestOmics_AnnotationStore(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) // Real CreateAnnotationStoreOutput is narrower than // GetAnnotationStoreOutput -- no storeArn/reference member // (api_op_CreateAnnotationStore.go:68-95); storeArn is @@ -98,14 +99,14 @@ func TestCreateAnnotationStoreStoresReference(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Equal(t, "CREATING", createResp["status"]) getRec := doRequest(t, h, http.MethodGet, "/annotationStore/ann-store-ref", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotNil(t, getResp["reference"]) // GetAnnotationStore advances CREATING->ACTIVE on its first poll (the // waiter-hang fix -- see PARITY.md), so the immediate Get after Create @@ -130,7 +131,7 @@ func TestCreateAnnotationStore_ResponseShape(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "v1", resp["versionName"]) assert.NotContains(t, resp, "storeArn") assert.NotContains(t, resp, "reference") @@ -157,7 +158,7 @@ func TestListAnnotationImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusCreated, jobRec.Code) var job map[string]any - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &job, jsonx.V1())) jobID := job["jobId"].(string) // storeName filter: no job targets "other-store". @@ -166,7 +167,7 @@ func TestListAnnotationImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs, ok := resp["annotationImportJobs"].([]any) require.True(t, ok) assert.Empty(t, jobs) @@ -177,7 +178,7 @@ func TestListAnnotationImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) jobs2, ok := resp2["annotationImportJobs"].([]any) require.True(t, ok) require.Len(t, jobs2, 1) @@ -188,7 +189,7 @@ func TestListAnnotationImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) jobs3, ok := resp3["annotationImportJobs"].([]any) require.True(t, ok) assert.Empty(t, jobs3) @@ -197,7 +198,7 @@ func TestListAnnotationImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var resp4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4, jsonx.V1())) jobs4, ok := resp4["annotationImportJobs"].([]any) require.True(t, ok) require.Len(t, jobs4, 1) @@ -218,7 +219,7 @@ func createAnnotationStorePair(t *testing.T, h *omics.Handler) map[string]string require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids[name], _ = resp["id"].(string) } @@ -307,7 +308,7 @@ func TestListAnnotationStores_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.ElementsMatch(t, tc.wantNames, annotationStoreNames(t, resp["annotationStores"])) }) } @@ -348,7 +349,7 @@ func TestListAnnotationStoreVersions_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["annotationStoreVersions"].([]any) require.True(t, ok) assert.Len(t, versions, tc.wantCount) diff --git a/services/omics/handler_configurations_test.go b/services/omics/handler_configurations_test.go index 25cca88b2e..31b52066cb 100644 --- a/services/omics/handler_configurations_test.go +++ b/services/omics/handler_configurations_test.go @@ -1,12 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestOmics_Configuration exercises the Configuration CRUD family. @@ -39,7 +41,7 @@ func TestOmics_Configuration(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "cfg1", resp["name"]) assert.Equal(t, "ACTIVE", resp["status"]) assert.NotEmpty(t, resp["arn"]) @@ -101,14 +103,14 @@ func TestS3AccessPolicy_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) assert.Equal(t, apArn, putResp["s3AccessPointArn"]) getRec := doRequest(t, h, http.MethodGet, "/s3accesspolicy/"+apArn, nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) policy, _ := getResp["s3AccessPolicy"].(string) assert.JSONEq(t, `{"Version":"2012-10-17"}`, policy, "real wire key is s3AccessPolicy") assert.Nil(t, getResp["policy"], "policy is not a real wire key") diff --git a/services/omics/handler_read_sets_test.go b/services/omics/handler_read_sets_test.go index 7f3934320d..a5a80de901 100644 --- a/services/omics/handler_read_sets_test.go +++ b/services/omics/handler_read_sets_test.go @@ -3,7 +3,7 @@ package omics_test import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -31,7 +32,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/sequencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequestRaw(t, h, http.MethodPut, @@ -49,7 +50,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/sequencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequestRaw(t, h, http.MethodPut, @@ -67,7 +68,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/sequencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequest(t, h, http.MethodPost, @@ -76,7 +77,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { ) require.Equal(t, http.StatusCreated, rec2.Code) var uploadResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &uploadResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &uploadResp, jsonx.V1())) uploadID := uploadResp["uploadId"].(string) payload := []byte("ACGT sequence data") @@ -90,7 +91,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { ) require.Equal(t, http.StatusOK, rec3.Code) var partResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &partResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &partResp, jsonx.V1())) assert.Equal(t, expectedChecksum, partResp["checksum"]) assert.Equal(t, "SHA256", partResp["checksumAlgorithm"]) }, @@ -102,7 +103,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/sequencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequest(t, h, http.MethodPost, @@ -111,7 +112,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { ) require.Equal(t, http.StatusCreated, rec2.Code) var uploadResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &uploadResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &uploadResp, jsonx.V1())) uploadID := uploadResp["uploadId"].(string) payload := []byte("hello omics") @@ -128,7 +129,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { ) require.Equal(t, http.StatusOK, rec4.Code) var rsResp map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &rsResp)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &rsResp, jsonx.V1())) rsID := rsResp["readSetId"].(string) rec5 := doRequestRaw(t, h, http.MethodGet, @@ -146,7 +147,7 @@ func TestOmics_UploadReadSetPart_And_GetReadSet(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/sequencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequestRaw(t, h, http.MethodGet, @@ -179,7 +180,7 @@ func TestCreateMultipartReadSetUpload_WireShape(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) rec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/upload", map[string]any{ @@ -191,7 +192,7 @@ func TestCreateMultipartReadSetUpload_WireShape(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "FASTQ", resp["sourceFileType"]) assert.Equal(t, "sample-1", resp["sampleId"]) assert.Equal(t, "subject-1", resp["subjectId"]) @@ -211,7 +212,7 @@ func TestReadSetMetadata_FileTypeField(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/importjob", map[string]any{ @@ -224,7 +225,7 @@ func TestReadSetMetadata_FileTypeField(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) readSets, ok := listResp["readSets"].([]any) require.True(t, ok) require.Len(t, readSets, 1) @@ -280,7 +281,7 @@ func TestReadSetMetadata_FilesField_ImportJob(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/importjob", map[string]any{ @@ -293,7 +294,7 @@ func TestReadSetMetadata_FilesField_ImportJob(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) readSets, ok := listResp["readSets"].([]any) require.True(t, ok) require.Len(t, readSets, 1) @@ -309,7 +310,7 @@ func TestReadSetMetadata_FilesField_ImportJob(t *testing.T) { require.Equal(t, http.StatusOK, metaRec.Code) var meta map[string]any - require.NoError(t, json.Unmarshal(metaRec.Body.Bytes(), &meta)) + require.NoError(t, json.Unmarshal(metaRec.Body.Bytes(), &meta, jsonx.V1())) files, ok := meta["files"].(map[string]any) require.True(t, ok, "files sub-object must be present") assert.Contains(t, files, "source1") @@ -357,7 +358,7 @@ func TestReadSetMetadata_FilesField_MultipartUpload(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) uploadRec := doRequest(t, h, http.MethodPost, @@ -367,7 +368,7 @@ func TestReadSetMetadata_FilesField_MultipartUpload(t *testing.T) { require.Equal(t, http.StatusCreated, uploadRec.Code) var uploadResp map[string]any - require.NoError(t, json.Unmarshal(uploadRec.Body.Bytes(), &uploadResp)) + require.NoError(t, json.Unmarshal(uploadRec.Body.Bytes(), &uploadResp, jsonx.V1())) uploadID := uploadResp["uploadId"].(string) part1Rec := doRequestRaw(t, h, http.MethodPut, @@ -396,7 +397,7 @@ func TestReadSetMetadata_FilesField_MultipartUpload(t *testing.T) { // "readSetId" -- the files sub-object only ever appears on // GetReadSetMetadata, so fetch it there. var completeResp map[string]any - require.NoError(t, json.Unmarshal(completeRec.Body.Bytes(), &completeResp)) + require.NoError(t, json.Unmarshal(completeRec.Body.Bytes(), &completeResp, jsonx.V1())) readSetID, ok := completeResp["readSetId"].(string) require.True(t, ok, "readSetId must be present") require.NotEmpty(t, readSetID) @@ -408,7 +409,7 @@ func TestReadSetMetadata_FilesField_MultipartUpload(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var rs map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &rs)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &rs, jsonx.V1())) files, ok := rs["files"].(map[string]any) require.True(t, ok, "files sub-object must be present") @@ -476,7 +477,7 @@ func TestUploadReadSetPart_BodyReadError_InternalServerException(t *testing.T) { require.Equal(t, http.StatusInternalServerError, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InternalServerException", resp["__type"]) }) } diff --git a/services/omics/handler_readjson_test.go b/services/omics/handler_readjson_test.go index 138e1727d2..d13e3fad6c 100644 --- a/services/omics/handler_readjson_test.go +++ b/services/omics/handler_readjson_test.go @@ -2,7 +2,8 @@ package omics_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +12,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestReadJSONMalformedWritesExactlyOneDocument guards against the same @@ -32,14 +35,14 @@ func TestReadJSONMalformedWritesExactlyOneDocument(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) - dec := json.NewDecoder(rec.Body) + dec := jsontext.NewDecoder(rec.Body) var first map[string]any - require.NoError(t, dec.Decode(&first)) + require.NoError(t, json.UnmarshalDecode(dec, &first, jsonx.V1())) require.Contains(t, first, "message") var second map[string]any - err := dec.Decode(&second) + err := json.UnmarshalDecode(dec, &second, jsonx.V1()) require.ErrorIs( t, err, diff --git a/services/omics/handler_reference_stores_test.go b/services/omics/handler_reference_stores_test.go index 1f94d9367e..30de0ab053 100644 --- a/services/omics/handler_reference_stores_test.go +++ b/services/omics/handler_reference_stores_test.go @@ -1,7 +1,7 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -32,7 +33,7 @@ func TestOmics_ReferenceStore(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:omics:") assert.Equal(t, "test-store", resp["name"]) }, @@ -66,7 +67,7 @@ func TestOmics_ReferenceStore(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rec2 := doRequest(t, h, http.MethodGet, "/referencestore/"+resp["id"].(string), nil) assert.Equal(t, http.StatusOK, rec2.Code) @@ -80,7 +81,7 @@ func TestOmics_ReferenceStore(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rec2 := doRequest(t, h, http.MethodDelete, "/referencestore/"+resp["id"].(string), nil) assert.Equal(t, http.StatusOK, rec2.Code) @@ -98,7 +99,7 @@ func TestOmics_ReferenceStore(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores := resp["referenceStores"].([]any) assert.Len(t, stores, 2) }) @@ -144,7 +145,7 @@ func TestOmics_GetReference_Binary(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return fmt.Sprintf("/referencestore/%s/reference/doesnotexist", resp["id"]) }, @@ -157,7 +158,7 @@ func TestOmics_GetReference_Binary(t *testing.T) { rec := doRequest(t, h, http.MethodPost, "/referencestore", map[string]any{"name": "s"}) require.Equal(t, http.StatusCreated, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["id"].(string) rec2 := doRequest(t, h, http.MethodPost, @@ -176,7 +177,7 @@ func TestOmics_GetReference_Binary(t *testing.T) { ) require.Equal(t, http.StatusOK, rec3.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &listResp, jsonx.V1())) refs := listResp["references"].([]any) require.Len(t, refs, 1) refID := refs[0].(map[string]any)["id"].(string) @@ -210,14 +211,14 @@ func TestDeleteReferenceStoreReturnsID(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) storeID := createResp["id"].(string) delRec := doRequest(t, h, http.MethodDelete, "/referencestore/"+storeID, nil) require.Equal(t, http.StatusOK, delRec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp, jsonx.V1())) assert.Equal(t, storeID, delResp["id"]) } @@ -233,7 +234,7 @@ func TestListReferenceImportJobs_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+storeID+"/importjob", map[string]any{ @@ -247,7 +248,7 @@ func TestListReferenceImportJobs_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs, ok := resp["importJobs"].([]any) require.True(t, ok) assert.Empty(t, jobs, "no import job has failed") @@ -257,7 +258,7 @@ func TestListReferenceImportJobs_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) jobs2, ok := resp2["importJobs"].([]any) require.True(t, ok) assert.Len(t, jobs2, 1) @@ -274,7 +275,7 @@ func TestGetReferenceMetadata_FilesField(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+storeID+"/importjob", map[string]any{ @@ -287,7 +288,7 @@ func TestGetReferenceMetadata_FilesField(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) refs, ok := listResp["references"].([]any) require.True(t, ok) require.Len(t, refs, 1) @@ -300,7 +301,7 @@ func TestGetReferenceMetadata_FilesField(t *testing.T) { require.Equal(t, http.StatusOK, metaRec.Code) var meta map[string]any - require.NoError(t, json.Unmarshal(metaRec.Body.Bytes(), &meta)) + require.NoError(t, json.Unmarshal(metaRec.Body.Bytes(), &meta, jsonx.V1())) files, ok := meta["files"].(map[string]any) require.True(t, ok, "files sub-object must be present") source, ok := files["source"].(map[string]any) diff --git a/services/omics/handler_runs_test.go b/services/omics/handler_runs_test.go index 733fcd424f..074f90c72b 100644 --- a/services/omics/handler_runs_test.go +++ b/services/omics/handler_runs_test.go @@ -1,13 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -31,7 +32,7 @@ func TestOmics_RunGroup(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:omics:") }, }, @@ -43,7 +44,7 @@ func TestOmics_RunGroup(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["items"]) }, }, @@ -100,7 +101,7 @@ func TestOmics_Run(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:omics:") }, }, @@ -153,7 +154,7 @@ func TestRunStartsAsPending(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PENDING", resp["status"]) } @@ -171,7 +172,7 @@ func TestStartRunReturnsPartialResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"]) assert.NotEmpty(t, resp["id"]) assert.Equal(t, "PENDING", resp["status"]) @@ -192,7 +193,7 @@ func TestUpdateRunCachePersistsDescription(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cacheID := createResp["id"].(string) updateRec := doRequest(t, h, http.MethodPost, "/runCache/"+cacheID, map[string]any{ @@ -204,7 +205,7 @@ func TestUpdateRunCachePersistsDescription(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "updated description", getResp["description"]) } @@ -222,7 +223,7 @@ func TestListRunsInBatchReturnsAssociatedRuns(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp, jsonx.V1())) batchID := batchResp["id"].(string) // Start a second run explicitly associated with this batch via the @@ -242,7 +243,7 @@ func TestListRunsInBatchReturnsAssociatedRuns(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) runs, ok := listResp["runs"].([]any) require.True(t, ok) assert.Len(t, runs, 2) @@ -265,11 +266,11 @@ func TestListRunsInBatchEmptyForOtherBatch(t *testing.T) { require.Equal(t, http.StatusCreated, batch2Rec.Code) var b1 map[string]any - require.NoError(t, json.Unmarshal(batch1Rec.Body.Bytes(), &b1)) + require.NoError(t, json.Unmarshal(batch1Rec.Body.Bytes(), &b1, jsonx.V1())) batchID1 := b1["id"].(string) var b2 map[string]any - require.NoError(t, json.Unmarshal(batch2Rec.Body.Bytes(), &b2)) + require.NoError(t, json.Unmarshal(batch2Rec.Body.Bytes(), &b2, jsonx.V1())) batchID2 := b2["id"].(string) // Associate a run with batch 1. @@ -286,7 +287,7 @@ func TestListRunsInBatchEmptyForOtherBatch(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) runs, ok := listResp["runs"].([]any) require.True(t, ok) assert.Len(t, runs, 1) @@ -317,7 +318,7 @@ func TestCancelRunGuardsTerminalState(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) runID := startResp["id"].(string) // Cancel once. @@ -349,7 +350,7 @@ func TestStartRun_ResponseIncludesOptionalFields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["uuid"], "StartRunOutput.uuid must be populated") assert.Equal(t, "VPC", resp["networkingMode"]) assert.Equal(t, "s3://bucket/output", resp["runOutputUri"]) @@ -367,7 +368,7 @@ func TestRun_BatchIDSerializedAsBatchIdKey(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) runRec := doRequest(t, h, http.MethodPost, "/run", map[string]any{ @@ -379,14 +380,14 @@ func TestRun_BatchIDSerializedAsBatchIdKey(t *testing.T) { require.Equal(t, http.StatusCreated, runRec.Code) var run map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run, jsonx.V1())) runID := run["id"].(string) getRec := doRequest(t, h, http.MethodGet, "/run/"+runID, nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, batchID, getResp["batchId"], "real wire key is batchId") assert.Nil(t, getResp["runBatchId"], "runBatchId is not a real wire key") } @@ -412,7 +413,7 @@ func TestListRuns_FiltersByNameRunGroupBatchStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) runs, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, runs, 1) @@ -422,7 +423,7 @@ func TestListRuns_FiltersByNameRunGroupBatchStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) runs2, ok := resp2["items"].([]any) require.True(t, ok) require.Len(t, runs2, 1) @@ -432,7 +433,7 @@ func TestListRuns_FiltersByNameRunGroupBatchStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) runs3, ok := resp3["items"].([]any) require.True(t, ok) assert.Empty(t, runs3, "no run has been cancelled yet") @@ -450,7 +451,7 @@ func TestListRunTasks_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusCreated, runRec.Code) var run map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run, jsonx.V1())) runID := run["id"].(string) // The auto-created task starts PENDING; filtering for RUNNING must exclude it. @@ -458,7 +459,7 @@ func TestListRunTasks_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tasks, ok := resp["items"].([]any) require.True(t, ok) assert.Empty(t, tasks) @@ -467,7 +468,7 @@ func TestListRunTasks_FiltersByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) tasks2, ok := resp2["items"].([]any) require.True(t, ok) assert.Len(t, tasks2, 1) @@ -486,7 +487,7 @@ func TestListRunGroups_FiltersByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, groups, 1) @@ -508,7 +509,7 @@ func TestListRunBatches_FiltersByNameAndStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) batches, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, batches, 1) @@ -520,7 +521,7 @@ func TestListRunBatches_FiltersByNameAndStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) batches2, ok := resp2["items"].([]any) require.True(t, ok) assert.Len(t, batches2, 2) @@ -537,7 +538,7 @@ func TestListRunsInBatch_FiltersByRunID(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) runRec := doRequest(t, h, http.MethodPost, "/run", map[string]any{ @@ -547,14 +548,14 @@ func TestListRunsInBatch_FiltersByRunID(t *testing.T) { require.Equal(t, http.StatusCreated, runRec.Code) var run map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run, jsonx.V1())) runID := run["id"].(string) rec := doRequest(t, h, http.MethodGet, "/runBatch/"+batchID+"/run?runId=does-not-exist", nil) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) runs, ok := resp["runs"].([]any) require.True(t, ok) assert.Empty(t, runs, "runId filter must exclude non-matching runs") @@ -563,7 +564,7 @@ func TestListRunsInBatch_FiltersByRunID(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) runs2, ok := resp2["runs"].([]any) require.True(t, ok) require.Len(t, runs2, 1) @@ -587,7 +588,7 @@ func TestDeleteBatch_RequiresTerminalState(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) omics.SetRunBatchStatusForTest(backend, batchID, "SUBMITTING") diff --git a/services/omics/handler_sequence_stores_test.go b/services/omics/handler_sequence_stores_test.go index ba22dcc24c..cd1fcceee6 100644 --- a/services/omics/handler_sequence_stores_test.go +++ b/services/omics/handler_sequence_stores_test.go @@ -1,12 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOmics_SequenceStore(t *testing.T) { @@ -29,7 +31,7 @@ func TestOmics_SequenceStore(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:omics:") assert.Equal(t, "seq-store", resp["name"]) }, @@ -55,7 +57,7 @@ func TestOmics_SequenceStore(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) stores := resp["sequenceStores"].([]any) assert.Empty(t, stores) }, @@ -89,7 +91,7 @@ func TestSequenceStoreHasStatusAndUpdateTime(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, "ACTIVE", created["status"]) id, ok := created["id"].(string) @@ -99,7 +101,7 @@ func TestSequenceStoreHasStatusAndUpdateTime(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACTIVE", resp["status"]) assert.NotEmpty(t, resp["updateTime"]) } @@ -122,7 +124,7 @@ func TestCreateSequenceStoreETagAlgorithmAndS3AccessConfig(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SHA256up", resp["eTagAlgorithmFamily"]) assert.NotContains(t, resp, "eTagAlgorithm") @@ -143,13 +145,13 @@ func TestDeleteSequenceStoreReturnsID(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) storeID := createResp["id"].(string) delRec := doRequest(t, h, http.MethodDelete, "/sequencestore/"+storeID, nil) require.Equal(t, http.StatusOK, delRec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp, jsonx.V1())) assert.Equal(t, storeID, delResp["id"]) } diff --git a/services/omics/handler_shares_test.go b/services/omics/handler_shares_test.go index 4c8b50ba37..3d03699125 100644 --- a/services/omics/handler_shares_test.go +++ b/services/omics/handler_shares_test.go @@ -1,7 +1,7 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -78,7 +79,7 @@ func createShare(t *testing.T, h *omics.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, _ := resp["shareId"].(string) @@ -158,7 +159,7 @@ func TestOmics_Share_ResponseShape(t *testing.T) { require.Less(t, rec.Code, http.StatusBadRequest) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) keys := make([]string, 0, len(resp)) for k := range resp { @@ -183,7 +184,7 @@ func TestOmics_GetShare_FullObjectFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) share, ok := resp["share"].(map[string]any) require.True(t, ok) @@ -265,7 +266,7 @@ func createSharePair(t *testing.T, h *omics.Handler) map[string]string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids[key], _ = resp["shareId"].(string) } @@ -355,7 +356,7 @@ func TestListShares_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) var want []string if tc.wantKey != "" { diff --git a/services/omics/handler_tags_test.go b/services/omics/handler_tags_test.go index a17493f3ba..45bb68244f 100644 --- a/services/omics/handler_tags_test.go +++ b/services/omics/handler_tags_test.go @@ -1,7 +1,7 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOmics_Tags(t *testing.T) { @@ -21,7 +23,7 @@ func TestOmics_Tags(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var wfResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wfResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wfResp, jsonx.V1())) wfARN := wfResp["arn"].(string) // TagResource @@ -33,7 +35,7 @@ func TestOmics_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["tags"].(map[string]any) assert.Equal(t, "test", tags["env"]) } diff --git a/services/omics/handler_test.go b/services/omics/handler_test.go index f63508583f..dda411a38d 100644 --- a/services/omics/handler_test.go +++ b/services/omics/handler_test.go @@ -2,13 +2,14 @@ package omics_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http/httptest" "testing" "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -48,7 +49,7 @@ func doRequest(t *testing.T, h *omics.Handler, method, path string, body any) *h if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/omics/handler_variant_stores_test.go b/services/omics/handler_variant_stores_test.go index 9bfee1679d..27b8409fe9 100644 --- a/services/omics/handler_variant_stores_test.go +++ b/services/omics/handler_variant_stores_test.go @@ -1,13 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -31,7 +32,7 @@ func TestOmics_VariantStore(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "var-store", resp["name"]) assert.NotEmpty(t, resp["id"]) assert.NotContains(t, resp, "storeArn") @@ -82,7 +83,7 @@ func TestCreateVariantStoreStoresReference(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["reference"]) assert.Equal(t, "CREATING", resp["status"]) } @@ -106,7 +107,7 @@ func TestListVariantImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusCreated, jobRec.Code) var job map[string]any - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &job)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &job, jsonx.V1())) jobID := job["jobId"].(string) rec := doRequest(t, h, http.MethodPost, "/import/variants", @@ -114,7 +115,7 @@ func TestListVariantImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs, ok := resp["variantImportJobs"].([]any) require.True(t, ok) assert.Empty(t, jobs) @@ -123,7 +124,7 @@ func TestListVariantImportJobs_FiltersByStatusStoreNameAndIds(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) jobs2, ok := resp2["variantImportJobs"].([]any) require.True(t, ok) require.Len(t, jobs2, 1) @@ -142,7 +143,7 @@ func createVariantStorePair(t *testing.T, h *omics.Handler) map[string]string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids[name], _ = resp["id"].(string) } @@ -231,7 +232,7 @@ func TestListVariantStores_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.ElementsMatch(t, tc.wantNames, variantStoreNames(t, resp["variantStores"])) }) } diff --git a/services/omics/handler_workflows_test.go b/services/omics/handler_workflows_test.go index 315ed6ac10..838abd5f4b 100644 --- a/services/omics/handler_workflows_test.go +++ b/services/omics/handler_workflows_test.go @@ -1,12 +1,14 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOmics_Workflow(t *testing.T) { @@ -29,7 +31,7 @@ func TestOmics_Workflow(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["arn"], "arn:aws:omics:") assert.NotEmpty(t, resp["id"]) assert.Equal(t, "CREATING", resp["status"]) @@ -50,7 +52,7 @@ func TestOmics_Workflow(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["items"]) }, }, @@ -90,7 +92,7 @@ func TestWorkflowStartsAsCreating(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CREATING", resp["status"]) } @@ -107,7 +109,7 @@ func TestCreateWorkflowReturnsPartialResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["arn"]) assert.NotEmpty(t, resp["id"]) assert.Nil(t, resp["name"], "full workflow object must not be returned") @@ -127,14 +129,14 @@ func TestWorkflowHasTypeField(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) wfID := createResp["id"].(string) getRec := doRequest(t, h, http.MethodGet, "/workflow/"+wfID, nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "PRIVATE", getResp["type"]) } @@ -151,7 +153,7 @@ func TestUpdateWorkflowReturnsWorkflow(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) wfID := createResp["id"].(string) updateRec := doRequest(t, h, http.MethodPost, "/workflow/"+wfID, map[string]any{ @@ -161,7 +163,7 @@ func TestUpdateWorkflowReturnsWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated-name", updateResp["name"]) assert.Equal(t, "new description", updateResp["description"]) } @@ -191,7 +193,7 @@ func TestCreateWorkflow_ResponseIncludesUUID(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["uuid"], "CreateWorkflowOutput.uuid must be populated") } @@ -208,7 +210,7 @@ func TestListWorkflows_FiltersByNameAndType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) workflows, ok := resp["items"].([]any) require.True(t, ok) require.Len(t, workflows, 1) @@ -220,7 +222,7 @@ func TestListWorkflows_FiltersByNameAndType(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) workflows2, ok := resp2["items"].([]any) require.True(t, ok) assert.Empty(t, workflows2) @@ -237,7 +239,7 @@ func TestListWorkflowVersions_FiltersByType(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) wfID := wf["id"].(string) verRec := doRequest(t, h, http.MethodPost, "/workflow/"+wfID+"/version", map[string]any{"versionName": "v1"}) @@ -249,7 +251,7 @@ func TestListWorkflowVersions_FiltersByType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["items"].([]any) require.True(t, ok) assert.Empty(t, versions) @@ -258,7 +260,7 @@ func TestListWorkflowVersions_FiltersByType(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) versions2, ok := resp2["items"].([]any) require.True(t, ok) assert.Len(t, versions2, 1) diff --git a/services/omics/lifecycle_poll_test.go b/services/omics/lifecycle_poll_test.go index d8bf3b0b1f..3d38213b66 100644 --- a/services/omics/lifecycle_poll_test.go +++ b/services/omics/lifecycle_poll_test.go @@ -11,12 +11,14 @@ package omics_test // way real AWS eventually does. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func Test_GetWorkflow_AdvancesCreatingToActiveOnPoll(t *testing.T) { @@ -30,7 +32,7 @@ func Test_GetWorkflow_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) id := created["id"].(string) require.Equal(t, "CREATING", created["status"]) @@ -38,7 +40,7 @@ func Test_GetWorkflow_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "ACTIVE", got["status"], "waiter clients poll GetWorkflow until Status==ACTIVE") } @@ -53,14 +55,14 @@ func Test_GetAnnotationStore_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) require.Equal(t, "CREATING", created["status"]) getRec := doRequest(t, h, http.MethodGet, "/annotationStore/my-store", nil) require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "ACTIVE", got["status"]) } @@ -74,14 +76,14 @@ func Test_GetVariantStore_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) require.Equal(t, "CREATING", created["status"]) getRec := doRequest(t, h, http.MethodGet, "/variantStore/my-vstore", nil) require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "ACTIVE", got["status"]) } @@ -96,7 +98,7 @@ func Test_GetWorkflowVersion_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) workflowID := wf["id"].(string) verRec := doRequest(t, h, http.MethodPost, "/workflow/"+workflowID+"/version", map[string]any{ @@ -105,14 +107,14 @@ func Test_GetWorkflowVersion_AdvancesCreatingToActiveOnPoll(t *testing.T) { require.Equal(t, http.StatusCreated, verRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &created, jsonx.V1())) require.Equal(t, "CREATING", created["status"]) getRec := doRequest(t, h, http.MethodGet, "/workflow/"+workflowID+"/version/v1", nil) require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "ACTIVE", got["status"]) } @@ -131,7 +133,7 @@ func Test_GetRun_AdvancesPendingToRunningToCompletedAcrossPolls(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var started map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started, jsonx.V1())) id := started["id"].(string) require.Equal(t, "PENDING", started["status"]) @@ -139,7 +141,7 @@ func Test_GetRun_AdvancesPendingToRunningToCompletedAcrossPolls(t *testing.T) { require.Equal(t, http.StatusOK, poll1.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(poll1.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(poll1.Body.Bytes(), &p1, jsonx.V1())) assert.Equal(t, "RUNNING", p1["status"], "RunRunningWaiter polls until Status==RUNNING") assert.NotEmpty(t, p1["startTime"]) @@ -147,7 +149,7 @@ func Test_GetRun_AdvancesPendingToRunningToCompletedAcrossPolls(t *testing.T) { require.Equal(t, http.StatusOK, poll2.Code) var p2 map[string]any - require.NoError(t, json.Unmarshal(poll2.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(poll2.Body.Bytes(), &p2, jsonx.V1())) assert.Equal(t, "COMPLETED", p2["status"], "RunCompletedWaiter polls until Status==COMPLETED") assert.NotEmpty(t, p2["stopTime"]) } @@ -167,14 +169,14 @@ func Test_GetRunTask_AdvancesPendingToRunningToCompletedAcrossPolls(t *testing.T require.Equal(t, http.StatusCreated, startRec.Code) var started map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started, jsonx.V1())) runID := started["id"].(string) tasksRec := doRequest(t, h, http.MethodGet, "/run/"+runID+"/task", nil) require.Equal(t, http.StatusOK, tasksRec.Code) var tasksResp map[string]any - require.NoError(t, json.Unmarshal(tasksRec.Body.Bytes(), &tasksResp)) + require.NoError(t, json.Unmarshal(tasksRec.Body.Bytes(), &tasksResp, jsonx.V1())) tasks := tasksResp["items"].([]any) require.Len(t, tasks, 1) taskID := tasks[0].(map[string]any)["taskId"].(string) @@ -183,14 +185,14 @@ func Test_GetRunTask_AdvancesPendingToRunningToCompletedAcrossPolls(t *testing.T require.Equal(t, http.StatusOK, poll1.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(poll1.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(poll1.Body.Bytes(), &p1, jsonx.V1())) assert.Equal(t, "RUNNING", p1["status"]) poll2 := doRequest(t, h, http.MethodGet, "/run/"+runID+"/task/"+taskID, nil) require.Equal(t, http.StatusOK, poll2.Code) var p2 map[string]any - require.NoError(t, json.Unmarshal(poll2.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(poll2.Body.Bytes(), &p2, jsonx.V1())) assert.Equal(t, "COMPLETED", p2["status"]) } @@ -209,7 +211,7 @@ func Test_CancelRun_TerminalStateIsNotAdvancedByFurtherPolls(t *testing.T) { require.Equal(t, http.StatusCreated, startRec.Code) var started map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &started, jsonx.V1())) id := started["id"].(string) cancelRec := doRequest(t, h, http.MethodPost, "/run/"+id+"/cancel", nil) @@ -220,7 +222,7 @@ func Test_CancelRun_TerminalStateIsNotAdvancedByFurtherPolls(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "CANCELLED", got["status"]) } } diff --git a/services/omics/list_summary_shapes_test.go b/services/omics/list_summary_shapes_test.go index 2bc2f2afeb..42f90e7ed8 100644 --- a/services/omics/list_summary_shapes_test.go +++ b/services/omics/list_summary_shapes_test.go @@ -2,7 +2,8 @@ package omics_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +13,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListSummaries_MemberRoundTrips drives each op's Create/Start then List @@ -166,7 +169,7 @@ func TestListReadSetUploadParts_RawBodyUsesPartSourceKey(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) uploadRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/upload", map[string]any{ @@ -178,7 +181,7 @@ func TestListReadSetUploadParts_RawBodyUsesPartSourceKey(t *testing.T) { require.Equal(t, http.StatusCreated, uploadRec.Code) var upload map[string]any - require.NoError(t, json.Unmarshal(uploadRec.Body.Bytes(), &upload)) + require.NoError(t, json.Unmarshal(uploadRec.Body.Bytes(), &upload, jsonx.V1())) uploadID := upload["uploadId"].(string) partRec := doRequest( @@ -198,7 +201,7 @@ func TestListReadSetUploadParts_RawBodyUsesPartSourceKey(t *testing.T) { var listResp struct { Parts []map[string]any `json:"parts"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Parts, 1) assert.Equal(t, "SOURCE1", listResp.Parts[0]["partSource"]) @@ -244,7 +247,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/activationjob", map[string]any{ @@ -269,7 +272,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/importjob", map[string]any{ @@ -295,7 +298,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/sequencestore/"+storeID+"/importjob", map[string]any{ @@ -321,7 +324,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+storeID+"/importjob", map[string]any{ @@ -347,7 +350,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, storeRec.Code) var store map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &store, jsonx.V1())) storeID := store["id"].(string) jobRec := doRequest(t, h, http.MethodPost, "/referencestore/"+storeID+"/importjob", map[string]any{ @@ -413,7 +416,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) wfID := wf["id"].(string) runRec := doRequest(t, h, http.MethodPost, "/run", map[string]any{ @@ -425,7 +428,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, runRec.Code) var run map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run, jsonx.V1())) runID := run["id"].(string) listRec := doRequest(t, h, http.MethodGet, "/run/"+runID+"/task", nil) @@ -445,7 +448,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) wfID := wf["id"].(string) runRec := doRequest(t, h, http.MethodPost, "/run", map[string]any{ @@ -478,7 +481,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) wfID := wf["id"].(string) batchRec := doRequest(t, h, http.MethodPost, "/runBatch", map[string]any{ @@ -495,7 +498,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) listRec := doRequest(t, h, http.MethodGet, "/runBatch/"+batchID+"/run", nil) @@ -543,7 +546,7 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { require.Equal(t, http.StatusCreated, wfRec.Code) var wf map[string]any - require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf)) + require.NoError(t, json.Unmarshal(wfRec.Body.Bytes(), &wf, jsonx.V1())) wfID := wf["id"].(string) verRec := doRequest(t, h, http.MethodPost, "/workflow/"+wfID+"/version", map[string]any{ @@ -566,14 +569,14 @@ func TestListSummaries_OmitDescribeOnlyFields(t *testing.T) { func listItems(t *testing.T, body []byte, key string) []map[string]any { t.Helper() - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(body, &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) raw, ok := resp[key] require.True(t, ok, "response missing key %q", key) var items []map[string]any - require.NoError(t, json.Unmarshal(raw, &items)) + require.NoError(t, json.Unmarshal(raw, &items, jsonx.V1())) return items } diff --git a/services/omics/multi_region_test.go b/services/omics/multi_region_test.go index 160187145f..dad3847770 100644 --- a/services/omics/multi_region_test.go +++ b/services/omics/multi_region_test.go @@ -2,7 +2,8 @@ package omics_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -172,8 +174,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/omics/pagination_query_params_test.go b/services/omics/pagination_query_params_test.go index 3e37de9490..c36d89d4ac 100644 --- a/services/omics/pagination_query_params_test.go +++ b/services/omics/pagination_query_params_test.go @@ -18,12 +18,14 @@ package omics_test // "maxResults" as the query key; that is covered separately below. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func Test_ListReferenceStores_PaginatesViaQueryString(t *testing.T) { @@ -46,7 +48,7 @@ func Test_ListReferenceStores_PaginatesViaQueryString(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores := resp["referenceStores"].([]any) assert.Len(t, stores, 2, "maxResults=2 from the query string must be honored") require.NotEmpty(t, resp["nextToken"], "a nextToken must be returned when more pages remain") @@ -59,7 +61,7 @@ func Test_ListReferenceStores_PaginatesViaQueryString(t *testing.T) { require.Equal(t, http.StatusOK, nextRec.Code) var nextResp map[string]any - require.NoError(t, json.Unmarshal(nextRec.Body.Bytes(), &nextResp)) + require.NoError(t, json.Unmarshal(nextRec.Body.Bytes(), &nextResp, jsonx.V1())) remaining := nextResp["referenceStores"].([]any) assert.Len(t, remaining, 1, "nextToken from the query string must resume from page two") } @@ -81,7 +83,7 @@ func Test_ListAnnotationStores_PaginatesViaQueryString(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores := resp["annotationStores"].([]any) assert.Len(t, stores, 2) require.NotEmpty(t, resp["nextToken"]) @@ -104,13 +106,13 @@ func Test_ListRunBatches_UsesMaxItemsNotMaxResultsQueryParam(t *testing.T) { require.Equal(t, http.StatusOK, ignoredRec.Code) var ignoredResp map[string]any - require.NoError(t, json.Unmarshal(ignoredRec.Body.Bytes(), &ignoredResp)) + require.NoError(t, json.Unmarshal(ignoredRec.Body.Bytes(), &ignoredResp, jsonx.V1())) assert.Len(t, ignoredResp["items"].([]any), 3, "maxResults must not cap ListBatch") limitedRec := doRequest(t, h, http.MethodGet, "/runBatch?maxItems=1", nil) require.Equal(t, http.StatusOK, limitedRec.Code) var limitedResp map[string]any - require.NoError(t, json.Unmarshal(limitedRec.Body.Bytes(), &limitedResp)) + require.NoError(t, json.Unmarshal(limitedRec.Body.Bytes(), &limitedResp, jsonx.V1())) assert.Len(t, limitedResp["items"].([]any), 1, "maxItems must cap ListBatch") } diff --git a/services/omics/persistence.go b/services/omics/persistence.go index 4ea867662e..3ce9b72b5e 100644 --- a/services/omics/persistence.go +++ b/services/omics/persistence.go @@ -2,7 +2,7 @@ package omics import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -40,7 +40,7 @@ const omicsSnapshotVersion = 2 // restored directly. The five left-raw maps (see the InMemoryBackend doc // comment in store.go) are persisted as plain fields alongside Tables. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` UploadParts map[string]map[string][]*ReadSetUploadPart `json:"uploadParts"` UploadPartData map[string]map[string]map[string]map[int][]byte `json:"uploadPartData"` ReadSetBytes map[string]map[string][]byte `json:"readSetBytes"` diff --git a/services/omics/run_options_test.go b/services/omics/run_options_test.go index 3d4181be6f..8b49ec326d 100644 --- a/services/omics/run_options_test.go +++ b/services/omics/run_options_test.go @@ -1,20 +1,22 @@ package omics_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func decodeBody(t *testing.T, body []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1())) return m } diff --git a/services/omics/runbatch_route_test.go b/services/omics/runbatch_route_test.go index 53c9700d6c..8bcd8f6683 100644 --- a/services/omics/runbatch_route_test.go +++ b/services/omics/runbatch_route_test.go @@ -15,7 +15,7 @@ package omics_test // batch *records* — the reverse of what those two real operations do. import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -23,6 +23,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Test_ListRunsInBatch_ReachableOnlyViaGET proves the real AWS wire shape @@ -37,14 +39,14 @@ func Test_ListRunsInBatch_ReachableOnlyViaGET(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) getRec := doRequest(t, h, http.MethodGet, "/runBatch/"+batchID+"/run", nil) assert.Equal(t, http.StatusOK, getRec.Code, "real ListRunsInBatch is GET") var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotNil(t, getResp["runs"], "GET must be classified as ListRunsInBatch") e := echo.New() @@ -68,7 +70,7 @@ func Test_DeleteBatch_DeletesBatchResourceViaURIID(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) delRec := doRequest(t, h, http.MethodDelete, "/runBatch/"+batchID, nil) @@ -92,7 +94,7 @@ func Test_DeleteRunBatch_DeletesOnlyRunsLeavesBatchIntact(t *testing.T) { require.Equal(t, http.StatusCreated, batchRec.Code) var batch map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batch, jsonx.V1())) batchID := batch["id"].(string) runRec := doRequest(t, h, http.MethodPost, "/run", map[string]any{ @@ -104,7 +106,7 @@ func Test_DeleteRunBatch_DeletesOnlyRunsLeavesBatchIntact(t *testing.T) { require.Equal(t, http.StatusCreated, runRec.Code) var run map[string]any - require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run)) + require.NoError(t, json.Unmarshal(runRec.Body.Bytes(), &run, jsonx.V1())) runID := run["id"].(string) delRec := doRequest(t, h, http.MethodPost, "/runBatch/delete", map[string]any{ @@ -113,7 +115,7 @@ func Test_DeleteRunBatch_DeletesOnlyRunsLeavesBatchIntact(t *testing.T) { require.Equal(t, http.StatusOK, delRec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp, jsonx.V1())) assert.Empty(t, delResp, "real DeleteRunBatchOutput is empty — no errors array") getBatchRec := doRequest(t, h, http.MethodGet, "/runBatch/"+batchID, nil) diff --git a/services/omics/s3_source.go b/services/omics/s3_source.go index c6f4fdc5fe..3b5a3139a0 100644 --- a/services/omics/s3_source.go +++ b/services/omics/s3_source.go @@ -2,10 +2,12 @@ package omics import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -78,7 +80,7 @@ func (h *Handler) registryMapFromURI(ctx context.Context, m map[string]any, uri } var out map[string]any - if err = json.Unmarshal(data, &out); err != nil || out == nil { + if err = json.Unmarshal(data, &out, jsonx.V1()); err != nil || out == nil { return nil, fmt.Errorf("%w: containerRegistryMapUri must contain a JSON object", ErrValidation) } @@ -92,7 +94,7 @@ func (h *Handler) inlineSettingsFromURI(ctx context.Context, uri string) ([]inli } var out []inlineRunSettingWire - if err = json.Unmarshal(data, &out); err != nil { + if err = json.Unmarshal(data, &out, jsonx.V1()); err != nil { return nil, fmt.Errorf( "%w: batchRunSettings.s3UriSettings must contain a JSON array of run settings", ErrValidation, diff --git a/services/omics/wire_field_additions_test.go b/services/omics/wire_field_additions_test.go index e541cb2311..9799aa07fd 100644 --- a/services/omics/wire_field_additions_test.go +++ b/services/omics/wire_field_additions_test.go @@ -2,7 +2,7 @@ package omics_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/omics" ) @@ -578,7 +579,7 @@ func TestListAnnotationImportJobs_OmitsGetOnlyFields(t *testing.T) { var resp struct { ImportJobs []map[string]any `json:"annotationImportJobs"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ImportJobs, 1) job := resp.ImportJobs[0] @@ -626,7 +627,7 @@ func TestListVariantImportJobs_OmitsGetOnlyFields(t *testing.T) { var resp struct { ImportJobs []map[string]any `json:"variantImportJobs"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ImportJobs, 1) job := resp.ImportJobs[0] @@ -682,7 +683,7 @@ func TestOmicsStoreLists_OmitGetOnlyFields(t *testing.T) { var resp struct { Stores []map[string]any `json:"annotationStores"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Stores, 1) return resp.Stores[0] @@ -708,7 +709,7 @@ func TestOmicsStoreLists_OmitGetOnlyFields(t *testing.T) { var resp struct { Stores []map[string]any `json:"variantStores"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Stores, 1) return resp.Stores[0] @@ -737,7 +738,7 @@ func TestOmicsStoreLists_OmitGetOnlyFields(t *testing.T) { var resp struct { Versions []map[string]any `json:"annotationStoreVersions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Versions, 1) return resp.Versions[0] diff --git a/services/opensearch/access_policy.go b/services/opensearch/access_policy.go index f714881411..da063e14eb 100644 --- a/services/opensearch/access_policy.go +++ b/services/opensearch/access_policy.go @@ -1,9 +1,10 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" ) @@ -28,7 +29,7 @@ func accessPolicyDenies(accessPolicies, action, resourceARN string) bool { } var pd iam.PolicyDocument - if err := json.Unmarshal([]byte(accessPolicies), &pd); err != nil { + if err := json.Unmarshal([]byte(accessPolicies), &pd, jsonx.V1()); err != nil { return false } diff --git a/services/opensearch/bulk_test.go b/services/opensearch/bulk_test.go index 4ec6401b32..99503156e5 100644 --- a/services/opensearch/bulk_test.go +++ b/services/opensearch/bulk_test.go @@ -2,7 +2,7 @@ package opensearch_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -33,7 +34,7 @@ func doBulkRequest(t *testing.T, h *opensearch.Handler, path, contentType, ndjso var out map[string]any if len(raw) > 0 { - require.NoError(t, json.Unmarshal(raw, &out)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1())) } return resp.StatusCode, out diff --git a/services/opensearch/data_sources.go b/services/opensearch/data_sources.go index 18ea2c9aee..8b0db4d17d 100644 --- a/services/opensearch/data_sources.go +++ b/services/opensearch/data_sources.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/arn" @@ -14,7 +14,7 @@ const dataSourceStatusActive = "ACTIVE" // AddDataSource adds a data source to a domain. func (b *InMemoryBackend) AddDataSource( domainName, name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, ) (string, error) { if domainName == "" { return "", fmt.Errorf("%w: DomainName is required", ErrInvalidParameter) @@ -54,7 +54,7 @@ func (b *InMemoryBackend) AddDataSource( // AddDirectQueryDataSource adds a direct-query data source. func (b *InMemoryBackend) AddDirectQueryDataSource( name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, openSearchArns []string, dataSourceAccessPolicy ...string, ) (string, error) { @@ -132,7 +132,7 @@ func (b *InMemoryBackend) ListDataSources(domainName string) ([]*DataSource, err // Status are optional and left unchanged when omitted. func (b *InMemoryBackend) UpdateDataSource( domainName, name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, status string, ) error { b.mu.Lock("UpdateDataSource") @@ -209,7 +209,7 @@ func (b *InMemoryBackend) GetDirectQueryDataSource(name string) (*DirectQueryDat // and OpenSearchArns on every update call. func (b *InMemoryBackend) UpdateDirectQueryDataSource( name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, openSearchArns []string, dataSourceAccessPolicy string, ) (*DirectQueryDataSource, error) { diff --git a/services/opensearch/documents_handler_test.go b/services/opensearch/documents_handler_test.go index c490890057..699f1579d2 100644 --- a/services/opensearch/documents_handler_test.go +++ b/services/opensearch/documents_handler_test.go @@ -1,7 +1,7 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -23,7 +24,7 @@ func decodeBody(t *testing.T, resp *http.Response) map[string]any { var out map[string]any if len(raw) > 0 { - require.NoError(t, json.Unmarshal(raw, &out)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1())) } return out diff --git a/services/opensearch/domains_test.go b/services/opensearch/domains_test.go index eb6762cecf..bf56474b0e 100644 --- a/services/opensearch/domains_test.go +++ b/services/opensearch/domains_test.go @@ -1,7 +1,7 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -40,7 +40,7 @@ func TestDeleteDomain_Cascade(t *testing.T) { b := opensearch.NewInMemoryBackend(testAccountID, testRegion) b.AddDomainInternal("my-domain", "") - _, err := b.AddDataSource("my-domain", "ds1", "desc", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err := b.AddDataSource("my-domain", "ds1", "desc", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.NoError(t, err) b.AddPackageInternal("pkg-001", "test-pkg", "TXT-DICTIONARY") diff --git a/services/opensearch/handler.go b/services/opensearch/handler.go index ee1c6898f3..1bcaa4922e 100644 --- a/services/opensearch/handler.go +++ b/services/opensearch/handler.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/endpointstrategy" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -504,7 +505,7 @@ func (h *Handler) handleConfigPostRoute(w http.ResponseWriter, r *http.Request, body, _ := httputils.ReadBody(r) var req domainJSON if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if vErr := validateDomainOptions(&req); vErr != nil { @@ -833,7 +834,7 @@ func (h *Handler) dispatchDomainPostRoutesExtended( NodeID string `json:"NodeId"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } m, err := h.Backend.StartDomainMaintenance(domainName, req.Action, req.NodeID) if err != nil { @@ -850,7 +851,7 @@ func (h *Handler) dispatchDomainPostRoutesExtended( Account string `json:"Account"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } _ = h.Backend.RevokeVpcEndpointAccess(domainName, req.Account) w.WriteHeader(http.StatusOK) diff --git a/services/opensearch/handler_advanced.go b/services/opensearch/handler_advanced.go index 104b974477..efccba51a7 100644 --- a/services/opensearch/handler_advanced.go +++ b/services/opensearch/handler_advanced.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +9,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -159,7 +160,7 @@ func (h *Handler) handleUpgradeDomainRoutes(w http.ResponseWriter, r *http.Reque PerformCheckOnly bool `json:"PerformCheckOnly"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } upgradeErr := h.Backend.CheckUpgradeDomain(req.DomainName, req.TargetVersion) diff --git a/services/opensearch/handler_advanced_test.go b/services/opensearch/handler_advanced_test.go index 0546e25ee3..380979aec5 100644 --- a/services/opensearch/handler_advanced_test.go +++ b/services/opensearch/handler_advanced_test.go @@ -1,13 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -75,7 +77,7 @@ func TestOpenSearchHandler_GetCompatibleVersions(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) versions, ok := out["CompatibleVersions"].([]any) require.True(t, ok) @@ -102,7 +104,7 @@ func TestOpenSearchHandler_ListInstanceTypeDetails(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) details, ok := out["InstanceTypeDetails"].([]any) require.True(t, ok) @@ -168,7 +170,7 @@ func TestGetCompatibleVersions(t *testing.T) { } var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) versions, ok := out["CompatibleVersions"].([]any) require.True(t, ok) @@ -198,7 +200,7 @@ func TestListInstanceTypeDetails_InstanceRoleAndSecurity(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) details, ok := out["InstanceTypeDetails"].([]any) require.True(t, ok) @@ -268,7 +270,7 @@ func TestListVersions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) versions, ok := out["Versions"].([]any) require.True(t, ok) @@ -303,7 +305,7 @@ func TestListVersions_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, resp1.StatusCode) var page1 map[string]any - require.NoError(t, json.NewDecoder(resp1.Body).Decode(&page1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp1.Body), &page1, jsonx.V1())) versions1 := page1["Versions"].([]any) assert.Len(t, versions1, 3) @@ -316,7 +318,7 @@ func TestListVersions_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, resp2.StatusCode) var page2 map[string]any - require.NoError(t, json.NewDecoder(resp2.Body).Decode(&page2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp2.Body), &page2, jsonx.V1())) versions2 := page2["Versions"].([]any) assert.NotEmpty(t, versions2) @@ -447,7 +449,7 @@ func TestDescribeInstanceTypeLimits_ResponseFormat(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) limitsByRole, ok := out["LimitsByRole"].(map[string]any) require.True(t, ok, "LimitsByRole must be an object") @@ -517,7 +519,7 @@ func TestUpgradeDomain_HTTPHandler(t *testing.T) { if len(tt.wantFields) > 0 { var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, out, field) } @@ -590,7 +592,7 @@ func TestListInstanceTypeDetails_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) details, ok := out["InstanceTypeDetails"].([]any) require.True(t, ok) diff --git a/services/opensearch/handler_applications.go b/services/opensearch/handler_applications.go index 9ea4e60b5f..f37f08500b 100644 --- a/services/opensearch/handler_applications.go +++ b/services/opensearch/handler_applications.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -9,6 +9,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -150,7 +151,7 @@ func (h *Handler) handleDefaultApplicationSettingRoutes(w http.ResponseWriter, r } if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -215,7 +216,7 @@ func (h *Handler) handleApplicationIDRoutes(w http.ResponseWriter, r *http.Reque DataSources []appDSJSON `json:"DataSources"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } appConfigs := make([]AppConfig, len(req.AppConfigs)) for i, ac := range req.AppConfigs { @@ -289,7 +290,7 @@ func (h *Handler) handleCreateApplication(w http.ResponseWriter, r *http.Request } var req createApplicationRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_applications_test.go b/services/opensearch/handler_applications_test.go index 2d1484def3..7f023ebe13 100644 --- a/services/opensearch/handler_applications_test.go +++ b/services/opensearch/handler_applications_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -133,7 +135,7 @@ func TestApplications_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &out, jsonx.V1())) apps, ok := out["ApplicationSummaries"].([]any) require.True(t, ok) @@ -156,7 +158,7 @@ func TestApplications_GetAndUpdateWireShape(t *testing.T) { cr := doRequest(t, h, http.MethodPost, "/2021-01-01/opensearch/application", map[string]any{"Name": "wire-app"}) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) cr.Body.Close() require.Equal(t, http.StatusOK, cr.StatusCode) @@ -172,7 +174,7 @@ func TestApplications_GetAndUpdateWireShape(t *testing.T) { require.Equal(t, http.StatusOK, gr.StatusCode) var gOut map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&gOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &gOut, jsonx.V1())) assert.Equal(t, "ACTIVE", gOut["status"]) assert.NotEmpty(t, gOut["endpoint"]) assert.NotEmpty(t, gOut["createdAt"]) @@ -185,7 +187,7 @@ func TestApplications_GetAndUpdateWireShape(t *testing.T) { require.Equal(t, http.StatusOK, ur.StatusCode) var uOut map[string]any - require.NoError(t, json.NewDecoder(ur.Body).Decode(&uOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ur.Body), &uOut, jsonx.V1())) assert.NotContains(t, uOut, "status") assert.NotEmpty(t, uOut["createdAt"]) assert.NotEmpty(t, uOut["lastUpdatedAt"]) @@ -281,7 +283,7 @@ func createTestApplication(t *testing.T, h *opensearch.Handler, name string) str require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) id, ok := out["id"].(string) require.True(t, ok) @@ -301,7 +303,7 @@ func domainARN(t *testing.T, h *opensearch.Handler, domainName string) string { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) @@ -340,7 +342,7 @@ func TestDataSourceAttachments_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, ar.StatusCode) var attOut map[string]any - require.NoError(t, json.NewDecoder(ar.Body).Decode(&attOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ar.Body), &attOut, jsonx.V1())) assert.Equal(t, appID, attOut["id"]) assert.Equal(t, arn, attOut["dataSourceArn"]) assert.Equal(t, arn, attOut["arn"]) @@ -356,7 +358,7 @@ func TestDataSourceAttachments_HTTPHandler(t *testing.T) { map[string]any{"dataSourceArn": arn}) defer ar2.Body.Close() var attOut2 map[string]any - require.NoError(t, json.NewDecoder(ar2.Body).Decode(&attOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ar2.Body), &attOut2, jsonx.V1())) assert.Equal(t, attOut["attachmentId"], attOut2["attachmentId"]) lr := doRequest(t, h, http.MethodPost, @@ -365,7 +367,7 @@ func TestDataSourceAttachments_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var listOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &listOut, jsonx.V1())) attachments, ok := listOut["attachments"].([]any) require.True(t, ok) require.Len(t, attachments, 1) @@ -384,7 +386,7 @@ func TestDataSourceAttachments_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var descOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&descOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &descOut, jsonx.V1())) assert.Equal(t, "ATTACHED", descOut["status"]) detR := doRequest(t, h, http.MethodPost, @@ -619,7 +621,7 @@ func TestCapabilities_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, rr.StatusCode) var regOut map[string]any - require.NoError(t, json.NewDecoder(rr.Body).Decode(®Out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rr.Body), ®Out, jsonx.V1())) assert.Equal(t, appID, regOut["applicationId"]) assert.Equal(t, "ai-capability", regOut["capabilityName"]) assert.Equal(t, "active", regOut["status"]) @@ -633,7 +635,7 @@ func TestCapabilities_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, gr.StatusCode) var getOut map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &getOut, jsonx.V1())) assert.Equal(t, "active", getOut["status"]) assert.Equal(t, []any{}, getOut["failures"]) @@ -643,7 +645,7 @@ func TestCapabilities_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var deregOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&deregOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &deregOut, jsonx.V1())) // DeregisterCapabilityOutput always reports "deleting" per its doc, // regardless of this emulator's actual (synchronous) removal. assert.Equal(t, "deleting", deregOut["status"]) @@ -744,7 +746,7 @@ func TestMigrations_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, sr.StatusCode) var startOut map[string]any - require.NoError(t, json.NewDecoder(sr.Body).Decode(&startOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(sr.Body), &startOut, jsonx.V1())) migrationID, ok := startOut["migrationId"].(string) require.True(t, ok) require.NotEmpty(t, migrationID) @@ -757,7 +759,7 @@ func TestMigrations_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, gr.StatusCode) var getOut map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&getOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &getOut, jsonx.V1())) assert.Equal(t, appID, getOut["applicationId"]) assert.Equal(t, "SUCCEEDED", getOut["status"]) // No saved-object store exists to migrate real objects, so counts @@ -774,7 +776,7 @@ func TestMigrations_HTTPHandler(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var listOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &listOut, jsonx.V1())) migrations, ok := listOut["migrations"].([]any) require.True(t, ok) require.Len(t, migrations, 1) @@ -783,7 +785,7 @@ func TestMigrations_HTTPHandler(t *testing.T) { "/2021-01-01/opensearch/app-migrations?applicationId="+appID+"&status=PENDING", nil) defer filteredR.Body.Close() var filteredOut map[string]any - require.NoError(t, json.NewDecoder(filteredR.Body).Decode(&filteredOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(filteredR.Body), &filteredOut, jsonx.V1())) assert.Empty(t, filteredOut["migrations"]) }, }, diff --git a/services/opensearch/handler_bulk.go b/services/opensearch/handler_bulk.go index 7c508df9a4..b3be4e2030 100644 --- a/services/opensearch/handler_bulk.go +++ b/services/opensearch/handler_bulk.go @@ -2,13 +2,15 @@ package opensearch import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // bulkActionMeta is the per-action metadata object documented for every @@ -38,9 +40,9 @@ type bulkActionEnvelope struct { // scripting engine, so a non-empty Script is rejected (see // handleBulkUpdate). type bulkUpdateBody struct { - Doc map[string]any `json:"doc,omitempty"` - Script json.RawMessage `json:"script,omitempty"` - DocAsUpsert bool `json:"doc_as_upsert,omitempty"` + Doc map[string]any `json:"doc,omitempty"` + Script jsontext.Value `json:"script,omitempty"` + DocAsUpsert bool `json:"doc_as_upsert,omitempty"` } // bulkAction is one parsed action line plus its source (nil for "delete"). @@ -48,7 +50,7 @@ type bulkAction struct { Op string Index string ID string - Source json.RawMessage + Source jsontext.Value } const ( @@ -186,7 +188,7 @@ func parseBulkActions(body []byte) ([]bulkAction, error) { for i := 0; i < len(lines); i++ { var env bulkActionEnvelope - if err := json.Unmarshal(lines[i], &env); err != nil { + if err := json.Unmarshal(lines[i], &env, jsonx.V1()); err != nil { return nil, fmt.Errorf("malformed action line %d: %w", i+1, err) } @@ -238,7 +240,7 @@ func (h *Handler) processBulkAction(domainName, defaultIndex string, act bulkAct } } -func (h *Handler) processBulkIndex(domainName, index, id string, source json.RawMessage) (map[string]any, bool) { +func (h *Handler) processBulkIndex(domainName, index, id string, source jsontext.Value) (map[string]any, bool) { doc, err := unmarshalBulkSource(source) if err != nil { return bulkErrorItem(bulkOpIndex, index, id, http.StatusBadRequest, @@ -258,7 +260,7 @@ func (h *Handler) processBulkIndex(domainName, index, id string, source json.Raw return bulkSuccessItem(bulkOpIndex, index, newID, result, meta), false } -func (h *Handler) processBulkCreate(domainName, index, id string, source json.RawMessage) (map[string]any, bool) { +func (h *Handler) processBulkCreate(domainName, index, id string, source jsontext.Value) (map[string]any, bool) { doc, err := unmarshalBulkSource(source) if err != nil { return bulkErrorItem(bulkOpCreate, index, id, http.StatusBadRequest, @@ -273,7 +275,7 @@ func (h *Handler) processBulkCreate(domainName, index, id string, source json.Ra return bulkSuccessItem(bulkOpCreate, index, newID, docResultCreated, meta), false } -func (h *Handler) processBulkUpdate(domainName, index, id string, source json.RawMessage) (map[string]any, bool) { +func (h *Handler) processBulkUpdate(domainName, index, id string, source jsontext.Value) (map[string]any, bool) { if id == "" { return bulkErrorItem(bulkOpUpdate, index, id, http.StatusBadRequest, "illegal_argument_exception", "_id is required for the update action"), true @@ -281,7 +283,7 @@ func (h *Handler) processBulkUpdate(domainName, index, id string, source json.Ra var body bulkUpdateBody if len(source) > 0 { - if err := json.Unmarshal(source, &body); err != nil { + if err := json.Unmarshal(source, &body, jsonx.V1()); err != nil { return bulkErrorItem(bulkOpUpdate, index, id, http.StatusBadRequest, "illegal_argument_exception", "invalid update body"), true } @@ -339,13 +341,13 @@ func (h *Handler) processBulkDelete(domainName, index, id string) (map[string]an // unmarshalBulkSource decodes a bulk action's source line, tolerating an // absent one (an empty document, matching IndexDocument's own handling of a // missing body). -func unmarshalBulkSource(source json.RawMessage) (map[string]any, error) { +func unmarshalBulkSource(source jsontext.Value) (map[string]any, error) { if len(source) == 0 { return map[string]any{}, nil } doc := map[string]any{} - if err := json.Unmarshal(source, &doc); err != nil { + if err := json.Unmarshal(source, &doc, jsonx.V1()); err != nil { return nil, err } diff --git a/services/opensearch/handler_capabilities.go b/services/opensearch/handler_capabilities.go index 5189667b78..5b6d601d7c 100644 --- a/services/opensearch/handler_capabilities.go +++ b/services/opensearch/handler_capabilities.go @@ -1,12 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // capabilityConfigJSON is the wire shape of CapabilityBaseRequestConfig / @@ -71,7 +72,7 @@ func (h *Handler) handleRegisterCapability(w http.ResponseWriter, r *http.Reques var req registerCapabilityRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_data_source_attachments.go b/services/opensearch/handler_data_source_attachments.go index ba671b72fd..6e4b1f1461 100644 --- a/services/opensearch/handler_data_source_attachments.go +++ b/services/opensearch/handler_data_source_attachments.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Sub-path segments for the four data source attachment routes under @@ -104,7 +105,7 @@ func (h *Handler) readDataSourceAttachmentRequest( var req dataSourceAttachmentRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return dataSourceAttachmentRequest{}, false @@ -191,7 +192,7 @@ func (h *Handler) handleListDataSourceAttachments(w http.ResponseWriter, r *http var req listDataSourceAttachmentsRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_data_sources.go b/services/opensearch/handler_data_sources.go index ae193a16fb..32e97115b8 100644 --- a/services/opensearch/handler_data_sources.go +++ b/services/opensearch/handler_data_sources.go @@ -1,12 +1,14 @@ package opensearch import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // directQueryDataSourceJSON renders a DirectQueryDataSource as the wire-shape @@ -15,11 +17,11 @@ import ( // "DataSourceName" -- they are genuinely different field names on the real // API, not a typo. type directQueryDataSourceJSON struct { - DataSourceArn string `json:"DataSourceArn"` - DataSourceName string `json:"DataSourceName"` - DataSourceType json.RawMessage `json:"DataSourceType,omitempty"` - Description string `json:"Description,omitempty"` - OpenSearchArns []string `json:"OpenSearchArns"` + DataSourceArn string `json:"DataSourceArn"` + DataSourceName string `json:"DataSourceName"` + DataSourceType jsontext.Value `json:"DataSourceType,omitempty"` + Description string `json:"Description,omitempty"` + OpenSearchArns []string `json:"OpenSearchArns"` } func toDirectQueryDataSourceJSON(ds *DirectQueryDataSource) directQueryDataSourceJSON { @@ -44,10 +46,10 @@ type getDirectQueryDataSourceOutputJSON struct { // dataSourceJSON renders a DataSource as the wire-shape types.DataSourceDetails // / GetDataSourceOutput object (top-level fields, no envelope). type dataSourceJSON struct { - Description string `json:"Description,omitempty"` - Name string `json:"Name"` - Status string `json:"Status,omitempty"` - DataSourceType json.RawMessage `json:"DataSourceType,omitempty"` + Description string `json:"Description,omitempty"` + Name string `json:"Name"` + Status string `json:"Status,omitempty"` + DataSourceType jsontext.Value `json:"DataSourceType,omitempty"` } func toDataSourceJSON(ds *DataSource) dataSourceJSON { @@ -129,13 +131,13 @@ func (h *Handler) handleUpdateDirectQueryDataSource( return } var req struct { - DataSourceType json.RawMessage `json:"DataSourceType"` - Description string `json:"Description"` - DataSourceAccessPolicy string `json:"DataSourceAccessPolicy"` - OpenSearchArns []string `json:"OpenSearchArns"` + DataSourceType jsontext.Value `json:"DataSourceType"` + Description string `json:"Description"` + DataSourceAccessPolicy string `json:"DataSourceAccessPolicy"` + OpenSearchArns []string `json:"OpenSearchArns"` } if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -160,9 +162,9 @@ func (h *Handler) handleUpdateDirectQueryDataSource( // (types.UpdateDataSourceInput -- DomainName and Name come from the URL // path, not the body). type updateDataSourceRequest struct { - Description string `json:"Description"` - Status string `json:"Status"` - DataSourceType json.RawMessage `json:"DataSourceType"` + Description string `json:"Description"` + Status string `json:"Status"` + DataSourceType jsontext.Value `json:"DataSourceType"` } // handleUpdateDataSource handles PUT /2021-01-01/opensearch/domain/{DomainName}/dataSource/{Name}. @@ -176,7 +178,7 @@ func (h *Handler) handleUpdateDataSource(w http.ResponseWriter, r *http.Request, var req updateDataSourceRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -195,9 +197,9 @@ func (h *Handler) handleUpdateDataSource(w http.ResponseWriter, r *http.Request, // addDataSourceRequest is the JSON request body for AddDataSource. type addDataSourceRequest struct { - Name string `json:"Name"` - Description string `json:"Description"` - DataSourceType json.RawMessage `json:"DataSourceType"` + Name string `json:"Name"` + Description string `json:"Description"` + DataSourceType jsontext.Value `json:"DataSourceType"` } // addDataSourceOutput is the JSON response for AddDataSource. @@ -214,7 +216,7 @@ func (h *Handler) handleAddDataSource(w http.ResponseWriter, r *http.Request, do } var req addDataSourceRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -246,11 +248,11 @@ func (h *Handler) handleAddDataSource(w http.ResponseWriter, r *http.Request, do // addDirectQueryDataSourceRequest is the JSON request body for AddDirectQueryDataSource. type addDirectQueryDataSourceRequest struct { - DataSourceName string `json:"DataSourceName"` - Description string `json:"Description"` - DataSourceType json.RawMessage `json:"DataSourceType"` - DataSourceAccessPolicy string `json:"DataSourceAccessPolicy"` - OpenSearchArns []string `json:"OpenSearchArns"` + DataSourceName string `json:"DataSourceName"` + Description string `json:"Description"` + DataSourceType jsontext.Value `json:"DataSourceType"` + DataSourceAccessPolicy string `json:"DataSourceAccessPolicy"` + OpenSearchArns []string `json:"OpenSearchArns"` } // addDirectQueryDataSourceOutput is the JSON response for AddDirectQueryDataSource. @@ -267,7 +269,7 @@ func (h *Handler) handleAddDirectQueryDataSource(w http.ResponseWriter, r *http. } var req addDirectQueryDataSourceRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_data_sources_test.go b/services/opensearch/handler_data_sources_test.go index 0647e8e372..efa4f7f28b 100644 --- a/services/opensearch/handler_data_sources_test.go +++ b/services/opensearch/handler_data_sources_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -47,7 +49,7 @@ func TestAddDataSource_DataSourceTypeJSON(t *testing.T) { require.Equal(t, http.StatusOK, getResp.StatusCode) var ds map[string]any - require.NoError(t, json.NewDecoder(getResp.Body).Decode(&ds)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getResp.Body), &ds, jsonx.V1())) // No "DataSource" envelope: fields are top-level. assert.Equal(t, "my-ds", ds["Name"]) @@ -85,7 +87,7 @@ func TestAddDirectQueryDataSource_DataSourceTypeJSON(t *testing.T) { require.Equal(t, http.StatusOK, getResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(getResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getResp.Body), &out, jsonx.V1())) assert.Equal(t, "dq-ds", out["DataSourceName"]) dsType, ok := out["DataSourceType"].(map[string]any) @@ -97,7 +99,7 @@ func TestAddDataSource_DomainNotFound(t *testing.T) { t.Parallel() b := opensearch.NewInMemoryBackend(testAccountID, testRegion) - _, err := b.AddDataSource("nonexistent", "ds1", "desc", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err := b.AddDataSource("nonexistent", "ds1", "desc", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.Error(t, err) assert.ErrorIs(t, err, opensearch.ErrDomainNotFound) } @@ -106,10 +108,10 @@ func TestAddDirectQueryDataSource_Duplicate(t *testing.T) { t.Parallel() b := opensearch.NewInMemoryBackend(testAccountID, testRegion) - _, err := b.AddDirectQueryDataSource("ds-1", "desc", json.RawMessage(`{"CloudWatchLog":{}}`), nil) + _, err := b.AddDirectQueryDataSource("ds-1", "desc", jsontext.Value(`{"CloudWatchLog":{}}`), nil) require.NoError(t, err) - _, err = b.AddDirectQueryDataSource("ds-1", "desc2", json.RawMessage(`{"SecurityLake":{}}`), nil) + _, err = b.AddDirectQueryDataSource("ds-1", "desc2", jsontext.Value(`{"SecurityLake":{}}`), nil) require.Error(t, err) assert.ErrorIs(t, err, opensearch.ErrDataSourceAlreadyExists) } @@ -118,7 +120,7 @@ func TestDirectQueryDataSource_ARN(t *testing.T) { t.Parallel() b := opensearch.NewInMemoryBackend(testAccountID, testRegion) - dsARN, err := b.AddDirectQueryDataSource("my-dq", "desc", json.RawMessage(`{"CloudWatchLog":{}}`), nil) + dsARN, err := b.AddDirectQueryDataSource("my-dq", "desc", jsontext.Value(`{"CloudWatchLog":{}}`), nil) require.NoError(t, err) assert.Contains(t, dsARN, "directQueryDataSource/my-dq") assert.Contains(t, dsARN, testAccountID) @@ -185,7 +187,7 @@ func TestDataSources_Lifecycle(t *testing.T) { b.AddDomainInternal("ds-domain", "") for _, name := range tt.addSources { - _, err := b.AddDataSource("ds-domain", name, "", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err := b.AddDataSource("ds-domain", name, "", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.NoError(t, err) } @@ -234,7 +236,7 @@ func TestDirectQuery_DeleteAndList(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) sources, ok := lOut["DirectQueryDataSources"].([]any) require.True(t, ok) require.Len(t, sources, 1) @@ -250,7 +252,7 @@ func TestDirectQuery_DeleteAndList(t *testing.T) { lr2 := doRequest(t, h, http.MethodGet, "/2021-01-01/opensearch/directQueryDataSource", nil) defer lr2.Body.Close() var lOut2 map[string]any - require.NoError(t, json.NewDecoder(lr2.Body).Decode(&lOut2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr2.Body), &lOut2, jsonx.V1())) sources2 := lOut2["DirectQueryDataSources"].([]any) assert.Empty(t, sources2) } @@ -281,7 +283,7 @@ func TestDirectQuery_UpdateReturnsARN(t *testing.T) { require.Equal(t, http.StatusOK, ur.StatusCode) var uOut map[string]any - require.NoError(t, json.NewDecoder(ur.Body).Decode(&uOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ur.Body), &uOut, jsonx.V1())) assert.Contains(t, uOut, "DataSourceArn") } @@ -310,7 +312,7 @@ func TestDataSources_ListAndGet(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) sources, ok := lOut["DataSources"].([]any) require.True(t, ok) assert.Len(t, sources, 2) @@ -322,7 +324,7 @@ func TestDataSources_ListAndGet(t *testing.T) { require.Equal(t, http.StatusOK, gr.StatusCode) var ds map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&ds)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &ds, jsonx.V1())) assert.Equal(t, "ds-alpha", ds["Name"]) assert.Equal(t, "ACTIVE", ds["Status"]) } @@ -355,7 +357,7 @@ func TestDataSources_UpdateAndDelete(t *testing.T) { require.Equal(t, http.StatusOK, ur.StatusCode) var uOut map[string]any - require.NoError(t, json.NewDecoder(ur.Body).Decode(&uOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ur.Body), &uOut, jsonx.V1())) assert.Contains(t, uOut["Message"], "updated") // Get reflects the updated description. @@ -363,7 +365,7 @@ func TestDataSources_UpdateAndDelete(t *testing.T) { "/2021-01-01/opensearch/domain/ds-ud-domain/dataSource/ds-target", nil) defer gr.Body.Close() var gOut map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&gOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &gOut, jsonx.V1())) assert.Equal(t, "after", gOut["Description"]) // Delete. @@ -373,7 +375,7 @@ func TestDataSources_UpdateAndDelete(t *testing.T) { require.Equal(t, http.StatusOK, del.StatusCode) var delOut map[string]any - require.NoError(t, json.NewDecoder(del.Body).Decode(&delOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(del.Body), &delOut, jsonx.V1())) assert.Equal(t, "DataSource deleted", delOut["Message"]) // List should be empty after delete. @@ -381,7 +383,7 @@ func TestDataSources_UpdateAndDelete(t *testing.T) { "/2021-01-01/opensearch/domain/ds-ud-domain/dataSource", nil) defer lr.Body.Close() var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) remaining := lOut["DataSources"].([]any) assert.Empty(t, remaining) } @@ -584,6 +586,6 @@ func TestOpenSearchBackend_AddDataSource_MissingName(t *testing.T) { _, err := b.CreateDomain(opensearch.CreateDomainInput{Name: "my-domain"}) require.NoError(t, err) - _, err = b.AddDataSource("my-domain", "", "desc", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err = b.AddDataSource("my-domain", "", "desc", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.Error(t, err) } diff --git a/services/opensearch/handler_domain_config.go b/services/opensearch/handler_domain_config.go index 8d914a6b3f..91cc9a7530 100644 --- a/services/opensearch/handler_domain_config.go +++ b/services/opensearch/handler_domain_config.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // dryRunResultsJSON mirrors aws-sdk-go-v2 types.DryRunResults. @@ -209,7 +210,7 @@ func (h *Handler) handleCancelDomainConfigChange( var req cancelDomainConfigChangeRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_domain_config_test.go b/services/opensearch/handler_domain_config_test.go index d2961b37ab..a86c778701 100644 --- a/services/opensearch/handler_domain_config_test.go +++ b/services/opensearch/handler_domain_config_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -31,7 +33,7 @@ func TestOpenSearchHandler_UpdateDomainConfig(t *testing.T) { assert.Equal(t, http.StatusOK, upResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(upResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(upResp.Body), &out, jsonx.V1())) domainConfig, ok := out["DomainConfig"].(map[string]any) require.True(t, ok) @@ -58,7 +60,7 @@ func Test_UpdateDomainConfig_DryRun(t *testing.T) { assert.Equal(t, http.StatusOK, dryResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(dryResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dryResp.Body), &out, jsonx.V1())) domainConfig, ok := out["DomainConfig"].(map[string]any) require.True(t, ok) @@ -75,7 +77,7 @@ func Test_UpdateDomainConfig_DryRun(t *testing.T) { defer descResp.Body.Close() var descOut map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&descOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &descOut, jsonx.V1())) status, ok := descOut["DomainStatus"].(map[string]any) require.True(t, ok) assert.Equal(t, "OpenSearch_2.7", status["EngineVersion"], "dry run must not persist the change") @@ -101,7 +103,7 @@ func TestOpenSearchHandler_DefaultApplicationSetting(t *testing.T) { // GET before any setting → empty ARN. resp := doRequest(t, h, http.MethodGet, "/2021-01-01/opensearch/defaultApplicationSetting", nil) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) resp.Body.Close() assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Empty(t, out["applicationArn"]) @@ -115,7 +117,7 @@ func TestOpenSearchHandler_DefaultApplicationSetting(t *testing.T) { assert.Equal(t, http.StatusOK, putResp.StatusCode) var putOut map[string]any - require.NoError(t, json.NewDecoder(putResp.Body).Decode(&putOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(putResp.Body), &putOut, jsonx.V1())) assert.Equal(t, appArn, putOut["applicationArn"]) // GET after PUT → should return the stored ARN. @@ -125,7 +127,7 @@ func TestOpenSearchHandler_DefaultApplicationSetting(t *testing.T) { assert.Equal(t, http.StatusOK, getResp.StatusCode) var out2 map[string]any - require.NoError(t, json.NewDecoder(getResp.Body).Decode(&out2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getResp.Body), &out2, jsonx.V1())) assert.Equal(t, appArn, out2["applicationArn"]) // PUT with SetAsDefault=false clears the default. @@ -135,7 +137,7 @@ func TestOpenSearchHandler_DefaultApplicationSetting(t *testing.T) { assert.Equal(t, http.StatusOK, clearResp.StatusCode) var clearOut map[string]any - require.NoError(t, json.NewDecoder(clearResp.Body).Decode(&clearOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(clearResp.Body), &clearOut, jsonx.V1())) assert.Empty(t, clearOut["applicationArn"]) } @@ -167,7 +169,7 @@ func TestDescribeDomainConfig_RealData(t *testing.T) { require.Equal(t, http.StatusOK, cfgResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(cfgResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cfgResp.Body), &out, jsonx.V1())) domainConfig, ok := out["DomainConfig"].(map[string]any) require.True(t, ok) @@ -447,7 +449,7 @@ func TestUpdateDomainConfig_AllOptions(t *testing.T) { require.Equal(t, http.StatusOK, upResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(upResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(upResp.Body), &out, jsonx.V1())) tt.verify(t, out) }) } @@ -515,7 +517,7 @@ func TestDescribeDomainConfig_FullConfig(t *testing.T) { require.Equal(t, http.StatusOK, cfgResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(cfgResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cfgResp.Body), &out, jsonx.V1())) dc, ok := out["DomainConfig"].(map[string]any) require.True(t, ok) @@ -602,7 +604,7 @@ func TestUpdateDomainConfig_MutatesState(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) tt.verifyFn(t, out) }) } @@ -670,7 +672,7 @@ func TestCancelDomainConfigChange_ReturnsLastChangeID(t *testing.T) { var got struct { CancelledChangeIDs []string `json:"CancelledChangeIds"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &got, jsonx.V1())) if tt.wantEmptyIDs { assert.Empty(t, got.CancelledChangeIDs) diff --git a/services/opensearch/handler_domain_maintenance_test.go b/services/opensearch/handler_domain_maintenance_test.go index 610ce9c36d..e24576ca11 100644 --- a/services/opensearch/handler_domain_maintenance_test.go +++ b/services/opensearch/handler_domain_maintenance_test.go @@ -1,12 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDomainMaintenance_StartListGet(t *testing.T) { @@ -23,7 +26,7 @@ func TestDomainMaintenance_StartListGet(t *testing.T) { require.Equal(t, http.StatusOK, sr.StatusCode) var sOut map[string]any - require.NoError(t, json.NewDecoder(sr.Body).Decode(&sOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(sr.Body), &sOut, jsonx.V1())) maintID := sOut["MaintenanceId"].(string) assert.NotEmpty(t, maintID) @@ -34,7 +37,7 @@ func TestDomainMaintenance_StartListGet(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) maintenances, ok := lOut["DomainMaintenances"].([]any) require.True(t, ok) assert.Len(t, maintenances, 1) @@ -46,7 +49,7 @@ func TestDomainMaintenance_StartListGet(t *testing.T) { require.Equal(t, http.StatusOK, gr.StatusCode) var gOut map[string]any - require.NoError(t, json.NewDecoder(gr.Body).Decode(&gOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(gr.Body), &gOut, jsonx.V1())) assert.Equal(t, maintID, gOut["MaintenanceId"]) } @@ -111,7 +114,7 @@ func TestDeleteDomain_CleansUpMaintenances(t *testing.T) { var got struct { DomainMaintenances []any `json:"DomainMaintenances"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &got, jsonx.V1())) assert.Empty(t, got.DomainMaintenances, "expected no orphaned maintenances after delete+recreate") }) } diff --git a/services/opensearch/handler_domain_options.go b/services/opensearch/handler_domain_options.go index e51573f026..ea0db43575 100644 --- a/services/opensearch/handler_domain_options.go +++ b/services/opensearch/handler_domain_options.go @@ -1,11 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // domainClusterConfig holds the cluster configuration request parameters for a domain. @@ -31,12 +33,12 @@ type domainClusterConfigAlias domainClusterConfig // UnmarshalJSON records which members the request set, so UpdateDomainConfig can // leave omitted ones unchanged. func (cc *domainClusterConfig) UnmarshalJSON(data []byte) error { - if err := json.Unmarshal(data, (*domainClusterConfigAlias)(cc)); err != nil { + if err := json.Unmarshal(data, (*domainClusterConfigAlias)(cc), jsonx.V1()); err != nil { return err } - var raw map[string]json.RawMessage - if err := json.Unmarshal(data, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return err } diff --git a/services/opensearch/handler_domain_options_test.go b/services/opensearch/handler_domain_options_test.go index 6b0a397227..642f70b1af 100644 --- a/services/opensearch/handler_domain_options_test.go +++ b/services/opensearch/handler_domain_options_test.go @@ -1,13 +1,16 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAudit1_CreateDomain_FullClusterConfig verifies all ClusterConfig fields round-trip through @@ -101,7 +104,7 @@ func TestCreateDomain_FullClusterConfig(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) cc, ok := status["ClusterConfig"].(map[string]any) @@ -130,7 +133,7 @@ func TestCreateDomain_SoftwareUpdateOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) swu, ok := status["SoftwareUpdateOptions"].(map[string]any) @@ -203,7 +206,7 @@ func TestCreateDomain_EBSOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) ebs, ok := status["EBSOptions"].(map[string]any) @@ -274,7 +277,7 @@ func TestCreateDomain_EncryptionOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) @@ -344,7 +347,7 @@ func TestCreateDomain_DomainEndpointOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) deo, ok := status["DomainEndpointOptions"].(map[string]any) @@ -426,7 +429,7 @@ func TestCreateDomain_AdvancedSecurityOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) aso, ok := status["AdvancedSecurityOptions"].(map[string]any) @@ -485,7 +488,7 @@ func TestCreateDomain_VPCOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) vpc, ok := status["VPCOptions"].(map[string]any) @@ -546,7 +549,7 @@ func TestCreateDomain_CognitoOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) cognito, ok := status["CognitoOptions"].(map[string]any) @@ -624,7 +627,7 @@ func TestCreateDomain_LogPublishingOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) logs, ok := status["LogPublishingOptions"].(map[string]any) @@ -665,7 +668,7 @@ func TestCreateDomain_SnapshotOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) snapOpts, ok := status["SnapshotOptions"].(map[string]any) @@ -722,7 +725,7 @@ func TestCreateDomain_AccessPolicies(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DomainStatus"].(map[string]any) require.True(t, ok) gotPolicies, _ := status["AccessPolicies"].(string) diff --git a/services/opensearch/handler_domain_status_test.go b/services/opensearch/handler_domain_status_test.go index e6c75d119e..da99345c86 100644 --- a/services/opensearch/handler_domain_status_test.go +++ b/services/opensearch/handler_domain_status_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "strconv" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -33,7 +35,7 @@ func TestOpenSearchHandler_DescribeDomainHealth(t *testing.T) { assert.Equal(t, http.StatusOK, healthResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(healthResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(healthResp.Body), &out, jsonx.V1())) assert.Equal(t, "Active", out["DomainState"]) // 3 instances * 5 shards = 15. TotalShards is NumberOfShards, a string @@ -61,7 +63,7 @@ func TestOpenSearchHandler_DescribeDomainNodes(t *testing.T) { assert.Equal(t, http.StatusOK, nodesResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(nodesResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(nodesResp.Body), &out, jsonx.V1())) nodes, ok := out["DomainNodesStatusList"].([]any) require.True(t, ok) @@ -125,7 +127,7 @@ func TestOpenSearchHandler_DescribeDomainChangeProgress(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["ChangeProgressStatus"].(map[string]any) require.True(t, ok) @@ -151,7 +153,7 @@ func TestOpenSearchHandler_DescribeDryRunProgress(t *testing.T) { require.NoError(t, err) var out map[string]any - require.NoError(t, json.Unmarshal(bodyBytes, &out)) + require.NoError(t, json.Unmarshal(bodyBytes, &out, jsonx.V1())) status, ok := out["DryRunProgressStatus"].(map[string]any) require.True(t, ok) @@ -204,7 +206,7 @@ func TestDescribeDomainHealth_AdditionalFields(t *testing.T) { require.Equal(t, http.StatusOK, hResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(hResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(hResp.Body), &out, jsonx.V1())) // Existing fields. assert.Equal(t, "Active", out["DomainState"]) @@ -247,7 +249,7 @@ func TestDescribeDomainNodes_StorageAndAZ(t *testing.T) { require.Equal(t, http.StatusOK, nResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(nResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(nResp.Body), &out, jsonx.V1())) nodes, ok := out["DomainNodesStatusList"].([]any) require.True(t, ok) @@ -279,7 +281,7 @@ func TestDescribeDomainChangeProgress_Timestamps(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["ChangeProgressStatus"].(map[string]any) require.True(t, ok) @@ -301,7 +303,7 @@ func TestDescribeDryRunProgress_ValidationFailures(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) status, ok := out["DryRunProgressStatus"].(map[string]any) require.True(t, ok) @@ -339,7 +341,7 @@ func TestGetDomainHealth_WarmNodeCount(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) warmCount, ok := out["WarmNodeCount"].(string) require.True(t, ok, "WarmNodeCount must be present") diff --git a/services/opensearch/handler_domains.go b/services/opensearch/handler_domains.go index 6fbb7a7278..7469368f48 100644 --- a/services/opensearch/handler_domains.go +++ b/services/opensearch/handler_domains.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -124,7 +125,7 @@ func (h *Handler) handleCreateDomain(w http.ResponseWriter, r *http.Request) { } var req domainJSON - if err = json.Unmarshal(body, &req); err != nil { + if err = json.Unmarshal(body, &req, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -249,7 +250,7 @@ func (h *Handler) handleDescribeDomains(w http.ResponseWriter, r *http.Request) } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } domains, err := h.Backend.DescribeDomains(req.DomainNames) @@ -430,7 +431,7 @@ func (h *Handler) handleStartServiceSoftwareUpdate(w http.ResponseWriter, r *htt } var req startServiceSoftwareUpdateRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -482,7 +483,7 @@ func (h *Handler) handleRollbackServiceSoftwareUpdate(w http.ResponseWriter, r * } var req rollbackServiceSoftwareUpdateRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -552,7 +553,7 @@ func (h *Handler) handleCancelServiceSoftwareUpdate(w http.ResponseWriter, r *ht } var req cancelServiceSoftwareUpdateRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_domains_test.go b/services/opensearch/handler_domains_test.go index 81974116e0..98dc682469 100644 --- a/services/opensearch/handler_domains_test.go +++ b/services/opensearch/handler_domains_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -62,7 +64,7 @@ func TestStartServiceSoftwareUpdate_ScheduleAt(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) swo, ok := out["ServiceSoftwareOptions"].(map[string]any) require.True(t, ok, "expected ServiceSoftwareOptions in response") @@ -109,7 +111,7 @@ func TestOpenSearchHandler_DescribeDomains(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainStatusList"].([]any) require.True(t, ok) @@ -141,7 +143,7 @@ func TestOpenSearchHandler_DescribeDomains_All(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainStatusList"].([]any) require.True(t, ok) @@ -184,7 +186,7 @@ func TestDescribeDomains_FullStatusShape(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainStatusList"].([]any) require.True(t, ok) @@ -424,7 +426,7 @@ func TestListDomainNames_EngineTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) domainNames, ok := out["DomainNames"].([]any) require.True(t, ok) @@ -516,7 +518,7 @@ func TestDescribeDomains_FullDomainStatus(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainStatusList"].([]any) require.True(t, ok) @@ -582,7 +584,7 @@ func TestListDomainNames_ReturnsBothDomains(t *testing.T) { EngineType string `json:"EngineType"` } `json:"DomainNames"` } - require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &got, jsonx.V1())) assert.Len(t, got.DomainNames, tt.wantCount) returnedNames := make(map[string]bool, len(got.DomainNames)) @@ -682,7 +684,7 @@ func Test_DomainStatus_DomainID(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createStatus, ok := createOut["DomainStatus"].(map[string]any) require.True(t, ok, "DomainStatus missing") assert.Equal(t, "123456789012/domainid-test", createStatus["DomainId"]) @@ -693,7 +695,7 @@ func Test_DomainStatus_DomainID(t *testing.T) { require.Equal(t, http.StatusOK, describeResp.StatusCode) var describeOut map[string]any - require.NoError(t, json.NewDecoder(describeResp.Body).Decode(&describeOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(describeResp.Body), &describeOut, jsonx.V1())) describeStatus, ok := describeOut["DomainStatus"].(map[string]any) require.True(t, ok, "DomainStatus missing") assert.Equal(t, "123456789012/domainid-test", describeStatus["DomainId"]) @@ -771,7 +773,7 @@ func TestOpenSearchHandler_ListDomainNames(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) names, ok := out["DomainNames"].([]any) require.True(t, ok) @@ -1122,7 +1124,7 @@ func TestInsights_HTTPHandler(t *testing.T) { if tt.wantCode == http.StatusOK && tt.path == "/2021-01-01/opensearch/insights" { var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) insights, ok := out["Insights"].([]any) require.True(t, ok) assert.Empty(t, insights) diff --git a/services/opensearch/handler_inbound_connections.go b/services/opensearch/handler_inbound_connections.go index 9907b5a2fa..1e76155517 100644 --- a/services/opensearch/handler_inbound_connections.go +++ b/services/opensearch/handler_inbound_connections.go @@ -1,12 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // describeConnectionsRequest is the shared request body shape for @@ -154,7 +155,7 @@ func (h *Handler) readDescribeConnectionsRequest( var req describeConnectionsRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "failed to parse body") return describeConnectionsRequest{}, false diff --git a/services/opensearch/handler_inbound_connections_test.go b/services/opensearch/handler_inbound_connections_test.go index 53e5708eb5..687732156d 100644 --- a/services/opensearch/handler_inbound_connections_test.go +++ b/services/opensearch/handler_inbound_connections_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -41,7 +43,7 @@ func TestInboundConnections_DescribeRejectDelete(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) conns, ok := dOut["Connections"].([]any) require.True(t, ok) require.NotEmpty(t, conns) @@ -57,7 +59,7 @@ func TestInboundConnections_DescribeRejectDelete(t *testing.T) { require.Equal(t, http.StatusOK, rr.StatusCode) var rOut map[string]any - require.NoError(t, json.NewDecoder(rr.Body).Decode(&rOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rr.Body), &rOut, jsonx.V1())) rConn := rOut["Connection"].(map[string]any) assert.Equal(t, "REJECTED", rConn["ConnectionStatus"].(map[string]any)["StatusCode"]) @@ -68,7 +70,7 @@ func TestInboundConnections_DescribeRejectDelete(t *testing.T) { require.Equal(t, http.StatusOK, del.StatusCode) var delOut map[string]any - require.NoError(t, json.NewDecoder(del.Body).Decode(&delOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(del.Body), &delOut, jsonx.V1())) delConn := delOut["Connection"].(map[string]any) assert.Equal(t, "conn-inb-1", delConn["ConnectionId"]) } @@ -164,7 +166,7 @@ func TestOutboundConnection_CreateThenAcceptMirrorsBothSides(t *testing.T) { require.Equal(t, http.StatusOK, cr.StatusCode) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) connID := cOut["ConnectionId"].(string) // Accepting the mirrored inbound connection also activates the outbound side. @@ -174,7 +176,7 @@ func TestOutboundConnection_CreateThenAcceptMirrorsBothSides(t *testing.T) { require.Equal(t, http.StatusOK, ar.StatusCode) var aOut map[string]any - require.NoError(t, json.NewDecoder(ar.Body).Decode(&aOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ar.Body), &aOut, jsonx.V1())) aConn := aOut["Connection"].(map[string]any) assert.Equal(t, "ACTIVE", aConn["ConnectionStatus"].(map[string]any)["StatusCode"]) @@ -182,7 +184,7 @@ func TestOutboundConnection_CreateThenAcceptMirrorsBothSides(t *testing.T) { defer dr.Body.Close() var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) conns := dOut["Connections"].([]any) require.Len(t, conns, 1) outConn := conns[0].(map[string]any) diff --git a/services/opensearch/handler_indices.go b/services/opensearch/handler_indices.go index 01e5a43931..9d3488486b 100644 --- a/services/opensearch/handler_indices.go +++ b/services/opensearch/handler_indices.go @@ -1,12 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createIndexRealRequest and updateIndexRealRequest mirror @@ -46,7 +47,7 @@ func (h *Handler) handleCreateIndexRealRoute(w http.ResponseWriter, r *http.Requ var req createIndexRealRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if req.IndexName == "" { @@ -79,7 +80,7 @@ func (h *Handler) handleUpdateIndexRoute(w http.ResponseWriter, r *http.Request, body, _ := httputils.ReadBody(r) var req updateIndexRealRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } _, err := h.Backend.UpdateIndex(parts[0], parts[1], nil, nil, req.IndexSchema) @@ -301,7 +302,7 @@ func (h *Handler) handleCreateIndex(w http.ResponseWriter, r *http.Request, sp i } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if _, err := h.Backend.CreateIndex(sp.domain, sp.index, req.Mappings, req.Settings, req.Aliases, nil); err != nil { @@ -322,7 +323,7 @@ func (h *Handler) handleIndexDocument(w http.ResponseWriter, r *http.Request, sp body, _ := httputils.ReadBody(r) var doc map[string]any if len(body) > 0 { - if err := json.Unmarshal(body, &doc); err != nil { + if err := json.Unmarshal(body, &doc, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid document body") return @@ -360,7 +361,7 @@ func (h *Handler) handleSearchIndex(w http.ResponseWriter, r *http.Request, sp i Size *int `json:"size"` } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid search body") return diff --git a/services/opensearch/handler_insights.go b/services/opensearch/handler_insights.go index 6a81df76c5..2b76de02de 100644 --- a/services/opensearch/handler_insights.go +++ b/services/opensearch/handler_insights.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // insightEntityJSON matches types.InsightEntity (used by ListInsights and @@ -52,7 +53,7 @@ func (h *Handler) handleListInsights(w http.ResponseWriter, r *http.Request) { var req listInsightsRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -81,7 +82,7 @@ func (h *Handler) handleDescribeInsightDetails(w http.ResponseWriter, r *http.Re var req describeInsightDetailsRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -111,7 +112,7 @@ func (h *Handler) handleInsightFeedback(w http.ResponseWriter, r *http.Request) var req insightFeedbackRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_migrations.go b/services/opensearch/handler_migrations.go index e19243b5f4..b77be92966 100644 --- a/services/opensearch/handler_migrations.go +++ b/services/opensearch/handler_migrations.go @@ -1,13 +1,14 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strconv" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // migrationSourceJSON matches types.MigrationSource. @@ -108,7 +109,7 @@ func (h *Handler) handleStartMigration(w http.ResponseWriter, r *http.Request) { var req startMigrationRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_outbound_connections.go b/services/opensearch/handler_outbound_connections.go index 2f1e13f5e4..0452b9f938 100644 --- a/services/opensearch/handler_outbound_connections.go +++ b/services/opensearch/handler_outbound_connections.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCCOutboundRoutes handles outbound cross-cluster connection sub-routes. @@ -129,7 +130,7 @@ func (h *Handler) handleCreateOutboundConnection(w http.ResponseWriter, r *http. var req createOutboundConnectionRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "failed to parse body") return diff --git a/services/opensearch/handler_outbound_connections_test.go b/services/opensearch/handler_outbound_connections_test.go index 297c7cf987..cdd352887e 100644 --- a/services/opensearch/handler_outbound_connections_test.go +++ b/services/opensearch/handler_outbound_connections_test.go @@ -1,12 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOutboundConnections_CreateDescribeDelete(t *testing.T) { @@ -29,7 +32,7 @@ func TestOutboundConnections_CreateDescribeDelete(t *testing.T) { require.Equal(t, http.StatusOK, cr.StatusCode) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) connID := cOut["ConnectionId"].(string) assert.NotEmpty(t, connID) assert.Equal(t, "my-alias", cOut["ConnectionAlias"]) @@ -44,7 +47,7 @@ func TestOutboundConnections_CreateDescribeDelete(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) conns, ok := dOut["Connections"].([]any) require.True(t, ok) assert.Len(t, conns, 1) @@ -55,7 +58,7 @@ func TestOutboundConnections_CreateDescribeDelete(t *testing.T) { require.Equal(t, http.StatusOK, ir.StatusCode) var iOut map[string]any - require.NoError(t, json.NewDecoder(ir.Body).Decode(&iOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ir.Body), &iOut, jsonx.V1())) inConns, ok := iOut["Connections"].([]any) require.True(t, ok) require.Len(t, inConns, 1) @@ -71,7 +74,7 @@ func TestOutboundConnections_CreateDescribeDelete(t *testing.T) { require.Equal(t, http.StatusOK, del.StatusCode) var delOut map[string]any - require.NoError(t, json.NewDecoder(del.Body).Decode(&delOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(del.Body), &delOut, jsonx.V1())) conn := delOut["Connection"].(map[string]any) assert.Equal(t, connID, conn["ConnectionId"]) } diff --git a/services/opensearch/handler_packages.go b/services/opensearch/handler_packages.go index 79d0e1a3ef..29bf1e6d99 100644 --- a/services/opensearch/handler_packages.go +++ b/services/opensearch/handler_packages.go @@ -1,12 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDissociatePackage( @@ -42,7 +43,7 @@ func (h *Handler) handleDissociatePackages(w http.ResponseWriter, r *http.Reques } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } packageIDs := make([]string, 0, len(req.PackageList)) @@ -143,7 +144,7 @@ func (h *Handler) handleDescribePackages(w http.ResponseWriter, r *http.Request) MaxResults int32 `json:"MaxResults"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } filters := make(map[string][]string, len(req.Filters)) @@ -185,7 +186,7 @@ func (h *Handler) handleUpdatePackageRoute(w http.ResponseWriter, r *http.Reques PackageDescription string `json:"PackageDescription"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } pkg, updateErr := h.Backend.UpdatePackage(req.PackageID, req.PackageDescription) @@ -212,7 +213,7 @@ func (h *Handler) handleUpdatePackageScopeRoute(w http.ResponseWriter, r *http.R PackageUserList []string `json:"PackageUserList"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } pkg, err := h.Backend.UpdatePackageScope(req.PackageID, req.Operation, req.PackageUserList) @@ -363,7 +364,7 @@ func (h *Handler) handlePackageRootRoutes(w http.ResponseWriter, r *http.Request PackageDescription string `json:"PackageDescription"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } var pkgSource *PackageSource if req.PackageSource != nil { @@ -501,7 +502,7 @@ func (h *Handler) handleAssociatePackages(w http.ResponseWriter, r *http.Request } var req associatePackagesRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_packages_test.go b/services/opensearch/handler_packages_test.go index fe64f5fea3..80d437409d 100644 --- a/services/opensearch/handler_packages_test.go +++ b/services/opensearch/handler_packages_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -98,7 +100,7 @@ func TestOpenSearchHandler_DeletePackage_RejectedWhileAssociated(t *testing.T) { map[string]any{"PackageName": "delpkg-pkg", "PackageType": "TXT-DICTIONARY"}) var pkgOut map[string]any - require.NoError(t, json.NewDecoder(pkgResp.Body).Decode(&pkgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pkgResp.Body), &pkgOut, jsonx.V1())) pkgResp.Body.Close() pkgDetails, ok := pkgOut["PackageDetails"].(map[string]any) @@ -138,7 +140,7 @@ func TestOpenSearchHandler_DissociatePackage(t *testing.T) { map[string]any{"PackageName": "my-pkg", "PackageType": "TXT-DICTIONARY"}) var pkgOut map[string]any - require.NoError(t, json.NewDecoder(pkgResp.Body).Decode(&pkgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pkgResp.Body), &pkgOut, jsonx.V1())) pkgResp.Body.Close() pkgDetails, ok := pkgOut["PackageDetails"].(map[string]any) @@ -160,7 +162,7 @@ func TestOpenSearchHandler_DissociatePackage(t *testing.T) { assert.Equal(t, http.StatusOK, dissocResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(dissocResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dissocResp.Body), &out, jsonx.V1())) details2, ok := out["DomainPackageDetails"].(map[string]any) require.True(t, ok) @@ -184,7 +186,7 @@ func TestOpenSearchHandler_DissociatePackages(t *testing.T) { pkgResp := doRequest(t, h, http.MethodPost, "/2021-01-01/packages", map[string]any{"PackageName": name, "PackageType": "TXT-DICTIONARY"}) var pkgOut map[string]any - require.NoError(t, json.NewDecoder(pkgResp.Body).Decode(&pkgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pkgResp.Body), &pkgOut, jsonx.V1())) pkgResp.Body.Close() id := pkgOut["PackageDetails"].(map[string]any)["PackageID"].(string) @@ -211,7 +213,7 @@ func TestOpenSearchHandler_DissociatePackages(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) list, ok := out["DomainPackageDetailsList"].([]any) require.True(t, ok) @@ -233,7 +235,7 @@ func TestListPackagesForDomain_ReturnsDomainPackageDetailsShape(t *testing.T) { pkgResp := doRequest(t, h, http.MethodPost, "/2021-01-01/packages", map[string]any{"PackageName": "list-pkg", "PackageType": "TXT-DICTIONARY"}) var pkgOut map[string]any - require.NoError(t, json.NewDecoder(pkgResp.Body).Decode(&pkgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pkgResp.Body), &pkgOut, jsonx.V1())) pkgResp.Body.Close() pkgID := pkgOut["PackageDetails"].(map[string]any)["PackageID"].(string) @@ -250,7 +252,7 @@ func TestListPackagesForDomain_ReturnsDomainPackageDetailsShape(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) list, ok := lOut["DomainPackageDetailsList"].([]any) require.True(t, ok) require.Len(t, list, 1) @@ -272,7 +274,7 @@ func TestListDomainsForPackage_ReturnsDomainPackageDetailsShape(t *testing.T) { pkgResp := doRequest(t, h, http.MethodPost, "/2021-01-01/packages", map[string]any{"PackageName": "shared-pkg", "PackageType": "TXT-DICTIONARY"}) var pkgOut map[string]any - require.NoError(t, json.NewDecoder(pkgResp.Body).Decode(&pkgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pkgResp.Body), &pkgOut, jsonx.V1())) pkgResp.Body.Close() pkgID := pkgOut["PackageDetails"].(map[string]any)["PackageID"].(string) @@ -287,7 +289,7 @@ func TestListDomainsForPackage_ReturnsDomainPackageDetailsShape(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) list, ok := lOut["DomainPackageDetailsList"].([]any) require.True(t, ok) require.Len(t, list, 1) diff --git a/services/opensearch/handler_reserved_instances.go b/services/opensearch/handler_reserved_instances.go index 29c5550712..45276a1a41 100644 --- a/services/opensearch/handler_reserved_instances.go +++ b/services/opensearch/handler_reserved_instances.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleReservedInstancesRoutes handles GET /reservedInstances → DescribeReservedInstances. @@ -68,7 +69,7 @@ func (h *Handler) handlePurchaseReservedInstanceOffering(w http.ResponseWriter, InstanceCount int `json:"InstanceCount"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if req.InstanceCount == 0 { diff --git a/services/opensearch/handler_reserved_instances_test.go b/services/opensearch/handler_reserved_instances_test.go index 7d1b67ab08..2aed7d8114 100644 --- a/services/opensearch/handler_reserved_instances_test.go +++ b/services/opensearch/handler_reserved_instances_test.go @@ -1,12 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { @@ -21,7 +24,7 @@ func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { require.Equal(t, http.StatusOK, lor.StatusCode) var loOut map[string]any - require.NoError(t, json.NewDecoder(lor.Body).Decode(&loOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lor.Body), &loOut, jsonx.V1())) offerings, ok := loOut["ReservedInstanceOfferings"].([]any) require.True(t, ok) require.NotEmpty(t, offerings, "must have at least one offering") @@ -40,7 +43,7 @@ func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { require.Equal(t, http.StatusOK, pr.StatusCode) var pOut map[string]any - require.NoError(t, json.NewDecoder(pr.Body).Decode(&pOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(pr.Body), &pOut, jsonx.V1())) assert.NotEmpty(t, pOut["ReservedInstanceId"]) assert.Equal(t, "my-reservation", pOut["ReservationName"]) @@ -51,7 +54,7 @@ func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) instances, ok := dOut["ReservedInstances"].([]any) require.True(t, ok) require.Len(t, instances, 1) @@ -67,7 +70,7 @@ func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { require.Equal(t, http.StatusOK, fr.StatusCode) var fOut map[string]any - require.NoError(t, json.NewDecoder(fr.Body).Decode(&fOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(fr.Body), &fOut, jsonx.V1())) filtered := fOut["ReservedInstances"].([]any) require.Len(t, filtered, 1) assert.Equal(t, reservationID, filtered[0].(map[string]any)["ReservedInstanceId"]) @@ -79,7 +82,7 @@ func TestReservedInstances_ListOfferingsAndPurchase(t *testing.T) { require.Equal(t, http.StatusOK, ofr.StatusCode) var ofOut map[string]any - require.NoError(t, json.NewDecoder(ofr.Body).Decode(&ofOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ofr.Body), &ofOut, jsonx.V1())) filteredOfferings := ofOut["ReservedInstanceOfferings"].([]any) require.Len(t, filteredOfferings, 1) assert.Equal(t, offeringID, filteredOfferings[0].(map[string]any)["ReservedInstanceOfferingId"]) diff --git a/services/opensearch/handler_scheduled_actions.go b/services/opensearch/handler_scheduled_actions.go index a8aad1267c..39c36a60bc 100644 --- a/services/opensearch/handler_scheduled_actions.go +++ b/services/opensearch/handler_scheduled_actions.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // updateScheduledActionRequest is the JSON request body for @@ -30,7 +31,7 @@ func (h *Handler) handleUpdateScheduledAction(w http.ResponseWriter, r *http.Req var req updateScheduledActionRequest if len(body) > 0 { - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_scheduled_actions_test.go b/services/opensearch/handler_scheduled_actions_test.go index b72706b05b..e01eceb547 100644 --- a/services/opensearch/handler_scheduled_actions_test.go +++ b/services/opensearch/handler_scheduled_actions_test.go @@ -1,13 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -32,7 +34,7 @@ func TestScheduledActions_ListForDomain(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) actions, ok := out["ScheduledActions"].([]any) require.True(t, ok) require.Len(t, actions, 1) @@ -45,7 +47,7 @@ func TestScheduledActions_ListForDomain(t *testing.T) { require.Equal(t, http.StatusOK, otherResp.StatusCode) var otherOut map[string]any - require.NoError(t, json.NewDecoder(otherResp.Body).Decode(&otherOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(otherResp.Body), &otherOut, jsonx.V1())) assert.Empty(t, otherOut["ScheduledActions"]) } @@ -75,7 +77,7 @@ func TestScheduledActions_UpdateReschedulesExistingAction(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) action, ok := out["ScheduledAction"].(map[string]any) require.True(t, ok) assert.Equal(t, "action-upd", action["Id"]) diff --git a/services/opensearch/handler_serverless.go b/services/opensearch/handler_serverless.go index 3bed19a7c2..951544f995 100644 --- a/services/opensearch/handler_serverless.go +++ b/services/opensearch/handler_serverless.go @@ -1,11 +1,12 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -101,7 +102,7 @@ func (h *Handler) handleServerlessCollectionCreate(w http.ResponseWriter, r *htt KmsKeyArn string `json:"kmsKeyArn"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } coll, err := h.Backend.CreateServerlessCollection( @@ -166,7 +167,7 @@ func (h *Handler) handleServerlessCollectionBatchRoutes(w http.ResponseWriter, r Names []string `json:"names"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } colls := h.Backend.BatchGetServerlessCollections(req.IDs, req.Names) if colls == nil { @@ -217,7 +218,7 @@ func (h *Handler) handleServerlessPolicyCRUDRoot( Description string `json:"description"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } result, err := ops.create(req.Type, req.Name, req.Description, req.Policy) if err != nil { @@ -265,7 +266,7 @@ func (h *Handler) handleServerlessPolicyCRUDByName( PolicyVersion string `json:"policyVersion"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } result, err := ops.update(policyType, name, req.Description, req.Policy, req.PolicyVersion) if err != nil { @@ -379,7 +380,7 @@ func (h *Handler) handleServerlessSecurityConfigCollection(w http.ResponseWriter Description string `json:"description"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } sc, err := h.Backend.CreateServerlessSecurityConfig( req.Type, @@ -428,7 +429,7 @@ func (h *Handler) handleServerlessSecurityConfigItem(w http.ResponseWriter, r *h ConfigVersion string `json:"configVersion"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } sc, err := h.Backend.UpdateServerlessSecurityConfig( id, @@ -512,7 +513,7 @@ func (h *Handler) handleServerlessNetworkPolicyRoutes( Description string `json:"description"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } np, err := h.Backend.CreateServerlessNetworkPolicy( req.Type, diff --git a/services/opensearch/handler_serverless_jsonrpc.go b/services/opensearch/handler_serverless_jsonrpc.go index e9c47a43e2..58fd5ea3d3 100644 --- a/services/opensearch/handler_serverless_jsonrpc.go +++ b/services/opensearch/handler_serverless_jsonrpc.go @@ -1,7 +1,8 @@ package opensearch import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "sort" @@ -10,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // openSearchServerlessTargetPrefix is the real opensearchserverless@v1.34.4 @@ -72,7 +74,7 @@ func (h *Handler) handleServerlessJSONRPC(c *echo.Context, op string) error { var input map[string]any if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &input); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &input, jsonx.V1()); jsonErr != nil { return awserr.Write(c, awserr.ProtocolJSON10, awserr.APIError{ Code: errValidation, Message: "invalid JSON", @@ -100,7 +102,7 @@ func (h *Handler) handleServerlessJSONRPC(c *echo.Context, op string) error { return awserr.Write(c, awserr.ProtocolJSON10, apiErr) } - payload, marshalErr := json.Marshal(out) + payload, marshalErr := json.Marshal(out, jsonx.V1()) if marshalErr != nil { return awserr.Write(c, awserr.ProtocolJSON10, serverlessInternalError()) } @@ -697,7 +699,7 @@ func policyDetailJR(v any) map[string]any { m["description"] = pl.Description } if pl.Policy != "" { - m["policy"] = json.RawMessage(pl.Policy) + m["policy"] = jsontext.Value(pl.Policy) } return m @@ -732,13 +734,13 @@ func decodeSAMLOptionsJR(raw any) *ServerlessSAMLOptions { return nil } - b, err := json.Marshal(m) + b, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil } var opts ServerlessSAMLOptions - if unmarshalErr := json.Unmarshal(b, &opts); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(b, &opts, jsonx.V1()); unmarshalErr != nil { return nil } diff --git a/services/opensearch/handler_serverless_real_client_test.go b/services/opensearch/handler_serverless_real_client_test.go index 17f0134206..968fa95f0c 100644 --- a/services/opensearch/handler_serverless_real_client_test.go +++ b/services/opensearch/handler_serverless_real_client_test.go @@ -1,7 +1,7 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -70,7 +71,7 @@ func decodeDocument(t *testing.T, doc interface{ UnmarshalSmithyDocument(v any) var v any require.NoError(t, doc.UnmarshalSmithyDocument(&v)) - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return string(b) diff --git a/services/opensearch/handler_serverless_test.go b/services/opensearch/handler_serverless_test.go index cb6d55947a..9614485870 100644 --- a/services/opensearch/handler_serverless_test.go +++ b/services/opensearch/handler_serverless_test.go @@ -1,13 +1,15 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -43,7 +45,7 @@ func TestServerless_CreateCollection(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail, ok := out["createCollectionDetail"].(map[string]any) require.True(t, ok, "createCollectionDetail missing") @@ -67,7 +69,7 @@ func TestServerless_CreateCollection_DefaultType(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["createCollectionDetail"].(map[string]any) assert.Equal(t, "SEARCH", detail["type"]) } @@ -92,7 +94,7 @@ func TestServerless_BatchGetCollections(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) details, ok := out["collectionDetails"].([]any) require.True(t, ok) @@ -116,7 +118,7 @@ func TestServerless_ListCollections(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) summaries, ok := out["collectionSummaries"].([]any) require.True(t, ok) @@ -132,7 +134,7 @@ func TestServerless_DeleteCollection(t *testing.T) { createResp := doRequest(t, h, http.MethodPost, "/2021-11-01/opensearch/serverless/collection", map[string]any{"name": "to-delete"}) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() id := createOut["createCollectionDetail"].(map[string]any)["id"].(string) @@ -144,7 +146,7 @@ func TestServerless_DeleteCollection(t *testing.T) { assert.Equal(t, http.StatusOK, delResp.StatusCode) var delOut map[string]any - require.NoError(t, json.NewDecoder(delResp.Body).Decode(&delOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(delResp.Body), &delOut, jsonx.V1())) detail := delOut["deleteCollectionDetail"].(map[string]any) assert.Equal(t, "DELETED", detail["status"]) @@ -152,7 +154,7 @@ func TestServerless_DeleteCollection(t *testing.T) { listResp := doRequest(t, h, http.MethodGet, "/2021-11-01/opensearch/serverless/collections", nil) defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) summaries := listOut["collectionSummaries"].([]any) assert.Empty(t, summaries) } @@ -190,7 +192,7 @@ func TestServerless_CreateAccessPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail, ok := out["accessPolicyDetail"].(map[string]any) require.True(t, ok) @@ -221,7 +223,7 @@ func TestServerless_GetAccessPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["accessPolicyDetail"].(map[string]any) assert.Equal(t, "get-test", detail["name"]) } @@ -244,7 +246,7 @@ func TestServerless_ListAccessPolicies(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) summaries := out["accessPolicySummaries"].([]any) assert.Len(t, summaries, 2) } @@ -259,7 +261,7 @@ func TestServerless_UpdateAccessPolicy(t *testing.T) { map[string]any{"name": "update-ap", "type": "data", "policy": `[{"old":true}]`}) var createOut map[string]any - require.NoError(t, json.NewDecoder(r.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &createOut, jsonx.V1())) r.Body.Close() version := createOut["accessPolicyDetail"].(map[string]any)["policyVersion"].(string) @@ -276,7 +278,7 @@ func TestServerless_UpdateAccessPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["accessPolicyDetail"].(map[string]any) assert.Equal(t, "updated", detail["description"]) } @@ -300,7 +302,7 @@ func TestServerless_DeleteAccessPolicy(t *testing.T) { "/2021-11-01/opensearch/serverless/accesspolicies?type=data", nil) defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) assert.Empty(t, listOut["accessPolicySummaries"].([]any)) } @@ -339,7 +341,7 @@ func TestServerless_CreateSecurityConfig(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail, ok := out["securityConfigDetail"].(map[string]any) require.True(t, ok) @@ -357,7 +359,7 @@ func TestServerless_GetSecurityConfig(t *testing.T) { map[string]any{"type": "saml"}) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() id := createOut["securityConfigDetail"].(map[string]any)["id"].(string) @@ -368,7 +370,7 @@ func TestServerless_GetSecurityConfig(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, id, out["securityConfigDetail"].(map[string]any)["id"]) } @@ -390,7 +392,7 @@ func TestServerless_ListSecurityConfigs(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) summaries := out["securityConfigSummaries"].([]any) assert.Len(t, summaries, 3) } @@ -404,7 +406,7 @@ func TestServerless_UpdateSecurityConfig(t *testing.T) { map[string]any{"type": "saml", "description": "old"}) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() id := createOut["securityConfigDetail"].(map[string]any)["id"].(string) @@ -421,7 +423,7 @@ func TestServerless_UpdateSecurityConfig(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["securityConfigDetail"].(map[string]any) assert.Equal(t, "updated desc", detail["description"]) } @@ -435,7 +437,7 @@ func TestServerless_DeleteSecurityConfig(t *testing.T) { map[string]any{"type": "saml"}) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() id := createOut["securityConfigDetail"].(map[string]any)["id"].(string) @@ -472,7 +474,7 @@ func TestServerless_CreateEncryptionPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["encryptionPolicyDetail"].(map[string]any) assert.Equal(t, "enc-policy", detail["name"]) assert.NotEmpty(t, detail["policyVersion"]) @@ -495,7 +497,7 @@ func TestServerless_ListEncryptionPolicies(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) summaries := out["encryptionPolicySummaries"].([]any) assert.Len(t, summaries, 2) } @@ -515,7 +517,7 @@ func TestServerless_GetEncryptionPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, "get-ep", out["encryptionPolicyDetail"].(map[string]any)["name"]) } @@ -528,7 +530,7 @@ func TestServerless_UpdateEncryptionPolicy(t *testing.T) { map[string]any{"name": "upd-ep", "type": "encryption", "policy": `{"old":true}`}) var createOut map[string]any - require.NoError(t, json.NewDecoder(r.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(r.Body), &createOut, jsonx.V1())) r.Body.Close() version := createOut["encryptionPolicyDetail"].(map[string]any)["policyVersion"].(string) @@ -543,7 +545,7 @@ func TestServerless_UpdateEncryptionPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, "updated", out["encryptionPolicyDetail"].(map[string]any)["description"]) } @@ -587,7 +589,7 @@ func TestServerless_CreateNetworkPolicy(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) detail := out["networkPolicyDetail"].(map[string]any) assert.Equal(t, "net-policy", detail["name"]) } @@ -609,7 +611,7 @@ func TestServerless_ListNetworkPolicies(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) summaries := out["networkPolicySummaries"].([]any) assert.Len(t, summaries, 3) } @@ -655,7 +657,7 @@ func TestDomain_OffPeakWindowOptions_Create(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) st := out["DomainStatus"].(map[string]any) opw, ok := st["OffPeakWindowOptions"].(map[string]any) @@ -691,7 +693,7 @@ func TestDomain_OffPeakWindowOptions_UpdateConfig(t *testing.T) { assert.Equal(t, http.StatusOK, upResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(upResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(upResp.Body), &out, jsonx.V1())) cfg := out["DomainConfig"].(map[string]any) opwCfg := cfg["OffPeakWindowOptions"].(map[string]any) opwOpts := opwCfg["Options"].(map[string]any) @@ -721,7 +723,7 @@ func TestDomain_IdentityCenterOptions_Create(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) st := out["DomainStatus"].(map[string]any) idcOpts, ok := st["IdentityCenterOptions"].(map[string]any) @@ -752,7 +754,7 @@ func TestDomain_IdentityCenterOptions_UpdateConfig(t *testing.T) { assert.Equal(t, http.StatusOK, upResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(upResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(upResp.Body), &out, jsonx.V1())) cfg := out["DomainConfig"].(map[string]any) idcCfg := cfg["IdentityCenterOptions"].(map[string]any) idcOpts := idcCfg["Options"].(map[string]any) @@ -779,7 +781,7 @@ func TestDomain_EnableSoftwareUpdateOptions_Create(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) st := out["DomainStatus"].(map[string]any) swOpts, ok := st["SoftwareUpdateOptions"].(map[string]any) @@ -809,7 +811,7 @@ func TestDomain_ClusterConfigOmitsBlueGreenDeploymentOptions(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) cc := out["DomainStatus"].(map[string]any)["ClusterConfig"].(map[string]any) assert.NotContains(t, cc, "BlueGreenDeploymentOptions") assert.NotContains(t, cc, "ColdStorageEnabled") @@ -838,7 +840,7 @@ func TestPackage_CreateWithSource(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) pkg := out["PackageDetails"].(map[string]any) assert.Equal(t, "my-dictionary", pkg["PackageName"]) @@ -869,7 +871,7 @@ func TestPackage_CreateWithEncryptionOptions(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) pkg := out["PackageDetails"].(map[string]any) encOpts, ok := pkg["PackageEncryptionOptions"].(map[string]any) @@ -889,7 +891,7 @@ func TestPackage_AvailablePackageVersion(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) pkg := out["PackageDetails"].(map[string]any) assert.Equal(t, "1", pkg["AvailablePackageVersion"]) } @@ -908,7 +910,7 @@ func TestServerless_Persistence_CollectionsRoundTrip(t *testing.T) { map[string]any{"name": "persist-coll", "type": "TIMESERIES"}) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() origID := createOut["createCollectionDetail"].(map[string]any)["id"].(string) @@ -924,7 +926,7 @@ func TestServerless_Persistence_CollectionsRoundTrip(t *testing.T) { defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) summaries := listOut["collectionSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, origID, summaries[0].(map[string]any)["id"]) @@ -947,7 +949,7 @@ func TestServerless_Persistence_AccessPoliciesRoundTrip(t *testing.T) { defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) summaries := listOut["accessPolicySummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "persist-ap", summaries[0].(map[string]any)["name"]) diff --git a/services/opensearch/handler_tags.go b/services/opensearch/handler_tags.go index 8d35500958..5b495f50ef 100644 --- a/services/opensearch/handler_tags.go +++ b/services/opensearch/handler_tags.go @@ -1,10 +1,11 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -67,7 +68,7 @@ func (h *Handler) handleAddTags(w http.ResponseWriter, r *http.Request) { } var req addTagsInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -104,7 +105,7 @@ func (h *Handler) handleRemoveTags(w http.ResponseWriter, r *http.Request) { } var req removeTagsInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return diff --git a/services/opensearch/handler_tags_test.go b/services/opensearch/handler_tags_test.go index 84d393efe8..a75a99ccff 100644 --- a/services/opensearch/handler_tags_test.go +++ b/services/opensearch/handler_tags_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -21,7 +23,7 @@ func createDomainAndGetARN(t *testing.T, h *opensearch.Handler, domainName strin }) var createOut map[string]any - require.NoError(t, json.NewDecoder(createResp.Body).Decode(&createOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createResp.Body), &createOut, jsonx.V1())) createResp.Body.Close() status := createOut["DomainStatus"].(map[string]any) @@ -50,7 +52,7 @@ func TestOpenSearchHandler_AddTags(t *testing.T) { defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) tagList, ok := listOut["TagList"].([]any) require.True(t, ok) @@ -84,7 +86,7 @@ func TestOpenSearchHandler_RemoveTags(t *testing.T) { defer listResp.Body.Close() var listOut map[string]any - require.NoError(t, json.NewDecoder(listResp.Body).Decode(&listOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listResp.Body), &listOut, jsonx.V1())) tagList, ok := listOut["TagList"].([]any) require.True(t, ok) @@ -105,7 +107,7 @@ func TestOpenSearchHandler_ListTags_EmptyDomain(t *testing.T) { defer resp.Body.Close() var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) tagList, ok := out["TagList"].([]any) require.True(t, ok) diff --git a/services/opensearch/handler_test.go b/services/opensearch/handler_test.go index 117add802e..4658bc2a09 100644 --- a/services/opensearch/handler_test.go +++ b/services/opensearch/handler_test.go @@ -2,7 +2,7 @@ package opensearch_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -46,7 +47,7 @@ func doRequest(t *testing.T, h *opensearch.Handler, method, path string, body an var reqBody io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) diff --git a/services/opensearch/handler_vpc_endpoints.go b/services/opensearch/handler_vpc_endpoints.go index 1fb854c1bc..340232b15a 100644 --- a/services/opensearch/handler_vpc_endpoints.go +++ b/services/opensearch/handler_vpc_endpoints.go @@ -1,12 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // vpcEndpointSummaryJSON mirrors types.VpcEndpointSummary: DomainArn, Status, @@ -58,7 +59,7 @@ func (h *Handler) handleAuthorizeVpcEndpointAccess( } var req authorizeVpcEndpointAccessRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { h.writeError(r, w, http.StatusBadRequest, "ValidationException", "invalid JSON body") return @@ -95,7 +96,7 @@ func (h *Handler) handleVpcEndpointsRoutes(w http.ResponseWriter, r *http.Reques VpcEndpointIDs []string `json:"VpcEndpointIds"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } endpoints, errs := h.Backend.DescribeVpcEndpoints(req.VpcEndpointIDs) h.writeJSON(r, w, map[string]any{"VpcEndpoints": endpoints, "VpcEndpointErrors": errs}) @@ -130,7 +131,7 @@ func (h *Handler) handleUpdateVpcEndpoint(w http.ResponseWriter, r *http.Request VpcEndpointID string `json:"VpcEndpointId"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } ep, updateErr := h.Backend.UpdateVpcEndpoint(req.VpcEndpointID, req.VpcOptions) @@ -158,7 +159,7 @@ func (h *Handler) handleVpcEndpointRootRoutes(w http.ResponseWriter, r *http.Req DomainArn string `json:"DomainArn"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } ep, createErr := h.Backend.CreateVpcEndpoint(req.DomainArn, req.VpcOptions) if createErr != nil { diff --git a/services/opensearch/handler_vpc_endpoints_test.go b/services/opensearch/handler_vpc_endpoints_test.go index 3ff1e04797..b22b99ab4d 100644 --- a/services/opensearch/handler_vpc_endpoints_test.go +++ b/services/opensearch/handler_vpc_endpoints_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -102,7 +104,7 @@ func TestVpcEndpoints_CreateAndList(t *testing.T) { require.Equal(t, http.StatusOK, cr.StatusCode) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) ep := cOut["VpcEndpoint"].(map[string]any) assert.NotEmpty(t, ep["VpcEndpointId"]) assert.Equal(t, "ACTIVE", ep["Status"]) @@ -120,7 +122,7 @@ func TestVpcEndpoints_CreateAndList(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) eps, ok := lOut["VpcEndpointSummaryList"].([]any) require.True(t, ok) assert.Len(t, eps, 1) @@ -175,7 +177,7 @@ func TestVpcEndpoints_ListOmitsGetOnlyFields(t *testing.T) { require.Equal(t, http.StatusOK, lr.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &out, jsonx.V1())) items, ok := out[tt.key].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -205,7 +207,7 @@ func TestVpcEndpoints_DescribeByIDs(t *testing.T) { cr := doRequest(t, h, http.MethodPost, "/2021-01-01/opensearch/vpcEndpoints", map[string]any{"DomainArn": domARN, "VpcOptions": map[string]any{}}) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) cr.Body.Close() epID := cOut["VpcEndpoint"].(map[string]any)["VpcEndpointId"].(string) @@ -216,7 +218,7 @@ func TestVpcEndpoints_DescribeByIDs(t *testing.T) { require.Equal(t, http.StatusOK, dr.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) described, ok := dOut["VpcEndpoints"].([]any) require.True(t, ok) assert.Len(t, described, 1) @@ -232,7 +234,7 @@ func TestVpcEndpoints_UpdateAndDelete(t *testing.T) { cr := doRequest(t, h, http.MethodPost, "/2021-01-01/opensearch/vpcEndpoints", map[string]any{"DomainArn": domARN, "VpcOptions": map[string]any{}}) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) cr.Body.Close() epID := cOut["VpcEndpoint"].(map[string]any)["VpcEndpointId"].(string) @@ -248,7 +250,7 @@ func TestVpcEndpoints_UpdateAndDelete(t *testing.T) { require.Equal(t, http.StatusOK, del.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(del.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(del.Body), &dOut, jsonx.V1())) summary := dOut["VpcEndpointSummary"].(map[string]any) assert.Equal(t, epID, summary["VpcEndpointId"]) assert.Equal(t, domARN, summary["DomainArn"]) @@ -258,7 +260,7 @@ func TestVpcEndpoints_UpdateAndDelete(t *testing.T) { lr := doRequest(t, h, http.MethodGet, "/2021-01-01/opensearch/vpcEndpoints", nil) defer lr.Body.Close() var lOut map[string]any - require.NoError(t, json.NewDecoder(lr.Body).Decode(&lOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(lr.Body), &lOut, jsonx.V1())) eps := lOut["VpcEndpointSummaryList"].([]any) assert.Empty(t, eps) } @@ -304,7 +306,7 @@ func TestVpcEndpoints_DescribeUnknownIDReturnsEndpointNotFoundError(t *testing.T require.Equal(t, http.StatusOK, dr.StatusCode) var dOut map[string]any - require.NoError(t, json.NewDecoder(dr.Body).Decode(&dOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(dr.Body), &dOut, jsonx.V1())) errs, ok := dOut["VpcEndpointErrors"].([]any) require.True(t, ok) require.Len(t, errs, 1) diff --git a/services/opensearch/interfaces.go b/services/opensearch/interfaces.go index 34262241f8..1f74cdc80e 100644 --- a/services/opensearch/interfaces.go +++ b/services/opensearch/interfaces.go @@ -2,7 +2,7 @@ package opensearch import ( "context" - "encoding/json" + "encoding/json/jsontext" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -48,16 +48,16 @@ type StorageBackend interface { DeleteOutboundConnection(connectionID string) (*OutboundConnection, error) // Data source operations - AddDataSource(domainName, name, description string, dataSourceType json.RawMessage) (string, error) + AddDataSource(domainName, name, description string, dataSourceType jsontext.Value) (string, error) GetDataSource(domainName, name string) (*DataSource, error) ListDataSources(domainName string) ([]*DataSource, error) - UpdateDataSource(domainName, name, description string, dataSourceType json.RawMessage, status string) error + UpdateDataSource(domainName, name, description string, dataSourceType jsontext.Value, status string) error DeleteDataSource(domainName, name string) error // Direct-query data source operations AddDirectQueryDataSource( name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, openSearchArns []string, dataSourceAccessPolicy ...string, ) (string, error) @@ -65,7 +65,7 @@ type StorageBackend interface { GetDirectQueryDataSource(name string) (*DirectQueryDataSource, error) UpdateDirectQueryDataSource( name, description string, - dataSourceType json.RawMessage, + dataSourceType jsontext.Value, openSearchArns []string, dataSourceAccessPolicy string, ) (*DirectQueryDataSource, error) diff --git a/services/opensearch/lifecycle_policies.go b/services/opensearch/lifecycle_policies.go index 7c685a0e92..85760fa302 100644 --- a/services/opensearch/lifecycle_policies.go +++ b/services/opensearch/lifecycle_policies.go @@ -1,11 +1,13 @@ package opensearch import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // slLifecyclePolicyResourceTypeIndex is the only real ResourceType value @@ -53,7 +55,7 @@ type lifecyclePolicyDocument struct { // (matching every other serverless policy family in this package). func parseLifecycleRules(policy string) []lifecycleRule { var doc lifecyclePolicyDocument - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return nil } diff --git a/services/opensearch/models.go b/services/opensearch/models.go index afd93ec76f..6f002682be 100644 --- a/services/opensearch/models.go +++ b/services/opensearch/models.go @@ -1,7 +1,7 @@ package opensearch import ( - "encoding/json" + "encoding/json/jsontext" "regexp" "time" @@ -196,11 +196,11 @@ type InboundConnection struct { // DataSource represents a data source attached to an OpenSearch domain. type DataSource struct { - Name string `json:"name"` - Description string `json:"description"` - Status string `json:"status,omitempty"` - DomainName string `json:"-"` - DataSourceType json.RawMessage `json:"dataSourceType,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status,omitempty"` + DomainName string `json:"-"` + DataSourceType jsontext.Value `json:"dataSourceType,omitempty"` } // DirectQueryDataSource represents a direct-query data source. @@ -208,12 +208,12 @@ type DirectQueryDataSource struct { // DataSourceType is stored as raw JSON for the same reason as // DataSource.DataSourceType above -- types.DirectQueryDataSourceType is // also a tagged union (CloudWatchLog / SecurityLake members). - DataSourceType json.RawMessage `json:"dataSourceType,omitempty"` - Name string `json:"name"` - Description string `json:"description"` - DataSourceArn string `json:"dataSourceArn"` - DataSourceAccessPolicy string `json:"dataSourceAccessPolicy,omitempty"` - OpenSearchArns []string `json:"openSearchArns"` + DataSourceType jsontext.Value `json:"dataSourceType,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + DataSourceArn string `json:"dataSourceArn"` + DataSourceAccessPolicy string `json:"dataSourceAccessPolicy,omitempty"` + OpenSearchArns []string `json:"openSearchArns"` } // DomainPackageDetails holds details about a package associated with a domain. diff --git a/services/opensearch/multi_region_test.go b/services/opensearch/multi_region_test.go index f64f2ac3e4..a3e70f5598 100644 --- a/services/opensearch/multi_region_test.go +++ b/services/opensearch/multi_region_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opensearch" ) @@ -38,7 +40,7 @@ func regionDo(t *testing.T, h *opensearch.Handler, region, method, path, body st out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out @@ -127,9 +129,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/opensearch/persistence.go b/services/opensearch/persistence.go index 1120d80970..06ff58d6d4 100644 --- a/services/opensearch/persistence.go +++ b/services/opensearch/persistence.go @@ -2,7 +2,7 @@ package opensearch import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -118,11 +118,11 @@ func fromDryRunSnapshot(v *dryRunSnapshot) *DryRunStatus { } type dataSourceSnapshot struct { - Name string `json:"name"` - Description string `json:"description"` - Status string `json:"status,omitempty"` - DomainName string `json:"domainName"` - DataSourceType json.RawMessage `json:"dataSourceType,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status,omitempty"` + DomainName string `json:"domainName"` + DataSourceType jsontext.Value `json:"dataSourceType,omitempty"` } func dataSourceSnapshotKey(v *dataSourceSnapshot) string { return dataSourceKey(v.DomainName, v.Name) } @@ -381,7 +381,7 @@ var dirtyTableNames = struct { // conversion notes. type backendSnapshot struct { AccountCapacityLimits ServerlessCapacityLimits `json:"accountCapacityLimits"` - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` VpcAuthorizations map[string][]AuthorizedPrincipal `json:"vpcAuthorizations"` ScheduledActions map[string][]*ScheduledAction `json:"scheduledActions"` PackageAssociations map[string]map[string]bool `json:"packageAssociations"` diff --git a/services/opensearch/persistence_test.go b/services/opensearch/persistence_test.go index ab2a20ce0a..52b7dad38c 100644 --- a/services/opensearch/persistence_test.go +++ b/services/opensearch/persistence_test.go @@ -1,7 +1,7 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" "net/http" "net/http/httptest" "testing" @@ -633,10 +633,10 @@ func TestOpenSearchHandler_Persistence_AdditionalResources(t *testing.T) { opensearch.SeedInboundConnection(b, "conn-abc") - _, err = b.AddDataSource("snap-domain", "my-ds", "desc", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err = b.AddDataSource("snap-domain", "my-ds", "desc", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.NoError(t, err) - _, err = b.AddDirectQueryDataSource("my-dq", "desc", json.RawMessage(`{"CloudWatchLog":{}}`), []string{}) + _, err = b.AddDirectQueryDataSource("my-dq", "desc", jsontext.Value(`{"CloudWatchLog":{}}`), []string{}) require.NoError(t, err) b.AddPackageInternal("pkg-001", "test-pkg", "TXT-DICTIONARY") @@ -786,7 +786,7 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { require.NoError(t, err) // "Dirty" DTO tables: domainDataSources, domainIndexes, dryRuns. - _, err = original.AddDataSource(domain.Name, "ds-1", "a data source", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err = original.AddDataSource(domain.Name, "ds-1", "a data source", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.NoError(t, err) _, err = original.CreateIndex(domain.Name, "idx-1", nil, nil, nil, nil) @@ -806,7 +806,7 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { _, err = original.AddDirectQueryDataSource( "dq-1", "a direct query source", - json.RawMessage(`{"CloudWatchLog":{}}`), + jsontext.Value(`{"CloudWatchLog":{}}`), nil, ) require.NoError(t, err) diff --git a/services/opensearch/store_test.go b/services/opensearch/store_test.go index 8035a6941c..ed03f21384 100644 --- a/services/opensearch/store_test.go +++ b/services/opensearch/store_test.go @@ -1,7 +1,7 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" "testing" "github.com/stretchr/testify/assert" @@ -67,10 +67,10 @@ func TestExportCounts(t *testing.T) { opensearch.SeedInboundConnection(b, "conn-1") - _, err := b.AddDataSource("d1", "ds-1", "desc", json.RawMessage(`{"S3GlueDataCatalog":{}}`)) + _, err := b.AddDataSource("d1", "ds-1", "desc", jsontext.Value(`{"S3GlueDataCatalog":{}}`)) require.NoError(t, err) - _, err = b.AddDirectQueryDataSource("dq-1", "desc", json.RawMessage(`{"CloudWatchLog":{}}`), nil) + _, err = b.AddDirectQueryDataSource("dq-1", "desc", jsontext.Value(`{"CloudWatchLog":{}}`), nil) require.NoError(t, err) _, err = b.CreateApplication("app-1", nil, nil, nil) diff --git a/services/opensearch/wire_field_fixes_test.go b/services/opensearch/wire_field_fixes_test.go index 9623549ddf..2ace603785 100644 --- a/services/opensearch/wire_field_fixes_test.go +++ b/services/opensearch/wire_field_fixes_test.go @@ -1,7 +1,8 @@ package opensearch_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +12,8 @@ import ( opensearchsdktypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestUpgradeDomain_RealSDKClient proves UpgradeDomainOutput @@ -64,7 +67,7 @@ func TestUpgradeDomain_RawBody_NoInventedStepStatus(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) _, hasStepStatus := out["StepStatus"] assert.False(t, hasStepStatus, "UpgradeDomainOutput has no StepStatus member") @@ -87,7 +90,7 @@ func TestVpcEndpoint_RawBody_NoLeakedStatusUntil(t *testing.T) { cr := doRequest(t, h, http.MethodPost, "/2021-01-01/opensearch/vpcEndpoints", map[string]any{"DomainArn": domARN, "VpcOptions": map[string]any{"SubnetIds": []string{"subnet-1"}}}) var cOut map[string]any - require.NoError(t, json.NewDecoder(cr.Body).Decode(&cOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(cr.Body), &cOut, jsonx.V1())) cr.Body.Close() epID := cOut["VpcEndpoint"].(map[string]any)["VpcEndpointId"].(string) @@ -100,7 +103,7 @@ func TestVpcEndpoint_RawBody_NoLeakedStatusUntil(t *testing.T) { require.Equal(t, http.StatusOK, descResp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(descResp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(descResp.Body), &out, jsonx.V1())) endpoints, ok := out["VpcEndpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 1, "endpoint must still be present during its DELETING window") diff --git a/services/opsworks/handler.go b/services/opsworks/handler.go index 29b3c85410..41204d20b2 100644 --- a/services/opsworks/handler.go +++ b/services/opsworks/handler.go @@ -2,7 +2,7 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -270,13 +271,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): return writeError(c, http.StatusBadRequest, errResp("ResourceNotFoundException", err.Error())) @@ -287,8 +285,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err // ValidationException, not 501. return writeError(c, http.StatusBadRequest, errResp("ValidationException", err.Error())) case errors.Is(err, errInvalidRequest), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): return writeError(c, http.StatusBadRequest, errResp("ValidationException", err.Error())) default: logger.Load(c.Request().Context()).ErrorContext(c.Request().Context(), "opsworks error", "error", err) diff --git a/services/opsworks/handler_agent_versions.go b/services/opsworks/handler_agent_versions.go index aa310db815..c42b2b202b 100644 --- a/services/opsworks/handler_agent_versions.go +++ b/services/opsworks/handler_agent_versions.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDescribeAgentVersions handles DescribeAgentVersions requests. @@ -17,7 +19,7 @@ func (h *Handler) handleDescribeAgentVersions(_ context.Context, body []byte) (a } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_apps.go b/services/opsworks/handler_apps.go index 067c5359f6..5a5184dd70 100644 --- a/services/opsworks/handler_apps.go +++ b/services/opsworks/handler_apps.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateApp handles CreateApp requests. @@ -15,7 +17,7 @@ func (h *Handler) handleCreateApp(_ context.Context, body []byte) (any, error) { AppOptions } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -35,7 +37,7 @@ func (h *Handler) handleDescribeApps(_ context.Context, body []byte) (any, error } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -57,7 +59,7 @@ func (h *Handler) handleUpdateApp(_ context.Context, body []byte) (any, error) { AppOptions } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -74,7 +76,7 @@ func (h *Handler) handleDeleteApp(_ context.Context, body []byte) (any, error) { AppID string `json:"AppId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_commands.go b/services/opsworks/handler_commands.go index 404792631b..b26858f30e 100644 --- a/services/opsworks/handler_commands.go +++ b/services/opsworks/handler_commands.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDescribeCommands handles DescribeCommands requests. @@ -16,7 +18,7 @@ func (h *Handler) handleDescribeCommands(_ context.Context, body []byte) (any, e } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_deployments.go b/services/opsworks/handler_deployments.go index e1322b004c..64c329ac6a 100644 --- a/services/opsworks/handler_deployments.go +++ b/services/opsworks/handler_deployments.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateDeployment handles CreateDeployment requests. @@ -20,7 +22,7 @@ func (h *Handler) handleCreateDeployment(_ context.Context, body []byte) (any, e LayerIDs []string `json:"LayerIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -44,7 +46,7 @@ func (h *Handler) handleDescribeDeployments(_ context.Context, body []byte) (any } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_ecs_clusters.go b/services/opsworks/handler_ecs_clusters.go index 12fdb7634f..69f6c65340 100644 --- a/services/opsworks/handler_ecs_clusters.go +++ b/services/opsworks/handler_ecs_clusters.go @@ -2,9 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -21,7 +22,7 @@ func (h *Handler) handleRegisterEcsCluster(_ context.Context, body []byte) (any, StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -39,7 +40,7 @@ func (h *Handler) handleDeregisterEcsCluster(_ context.Context, body []byte) (an EcsClusterArn string `json:"EcsClusterArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -60,7 +61,7 @@ func (h *Handler) handleDescribeEcsClusters(_ context.Context, body []byte) (any } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_elastic_ips.go b/services/opsworks/handler_elastic_ips.go index 0862569cda..b5640218d2 100644 --- a/services/opsworks/handler_elastic_ips.go +++ b/services/opsworks/handler_elastic_ips.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleRegisterElasticIP handles RegisterElasticIp requests. The real @@ -16,7 +18,7 @@ func (h *Handler) handleRegisterElasticIP(_ context.Context, body []byte) (any, StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -34,7 +36,7 @@ func (h *Handler) handleDeregisterElasticIP(_ context.Context, body []byte) (any ElasticIP string `json:"ElasticIp"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -52,7 +54,7 @@ func (h *Handler) handleAssociateElasticIP(_ context.Context, body []byte) (any, InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -69,7 +71,7 @@ func (h *Handler) handleDisassociateElasticIP(_ context.Context, body []byte) (a ElasticIP string `json:"ElasticIp"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -89,7 +91,7 @@ func (h *Handler) handleDescribeElasticIps(_ context.Context, body []byte) (any, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -109,7 +111,7 @@ func (h *Handler) handleUpdateElasticIP(_ context.Context, body []byte) (any, er Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_elastic_load_balancers.go b/services/opsworks/handler_elastic_load_balancers.go index 8601ec67bb..d90a8c361b 100644 --- a/services/opsworks/handler_elastic_load_balancers.go +++ b/services/opsworks/handler_elastic_load_balancers.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleAttachElasticLoadBalancer handles AttachElasticLoadBalancer requests. @@ -13,7 +15,7 @@ func (h *Handler) handleAttachElasticLoadBalancer(_ context.Context, body []byte LayerID string `json:"LayerId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +33,7 @@ func (h *Handler) handleDetachElasticLoadBalancer(_ context.Context, body []byte LayerID string `json:"LayerId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -50,7 +52,7 @@ func (h *Handler) handleDescribeElasticLoadBalancers(_ context.Context, body []b } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_instances.go b/services/opsworks/handler_instances.go index 598ef5ceba..1d775260c4 100644 --- a/services/opsworks/handler_instances.go +++ b/services/opsworks/handler_instances.go @@ -2,10 +2,11 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" ) @@ -25,7 +26,7 @@ func (h *Handler) handleCreateInstance(_ context.Context, body []byte) (any, err LayerIDs []string `json:"LayerIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -56,7 +57,7 @@ func (h *Handler) handleRegisterInstance(_ context.Context, body []byte) (any, e InstanceExtras } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -74,7 +75,7 @@ func (h *Handler) handleDeregisterInstance(_ context.Context, body []byte) (any, InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -92,7 +93,7 @@ func (h *Handler) handleAssignInstance(_ context.Context, body []byte) (any, err LayerIDs []string `json:"LayerIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -109,7 +110,7 @@ func (h *Handler) handleUnassignInstance(_ context.Context, body []byte) (any, e InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -129,7 +130,7 @@ func (h *Handler) handleDescribeInstances(_ context.Context, body []byte) (any, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -156,7 +157,7 @@ func (h *Handler) handleUpdateInstance(_ context.Context, body []byte) (any, err LayerIDs []string `json:"LayerIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -185,7 +186,7 @@ func (h *Handler) handleDeleteInstance(_ context.Context, body []byte) (any, err InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -204,7 +205,7 @@ func (h *Handler) handleStartInstance(_ context.Context, body []byte) (any, erro InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -221,7 +222,7 @@ func (h *Handler) handleStopInstance(_ context.Context, body []byte) (any, error InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -238,7 +239,7 @@ func (h *Handler) handleRebootInstance(_ context.Context, body []byte) (any, err InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -276,9 +277,9 @@ func instancesToJSON(instances []*Instance) []map[string]any { "ReportedOs": map[string]any{}, } - if raw, err := json.Marshal(i.Extras); err == nil { + if raw, err := json.Marshal(i.Extras, jsonx.V1()); err == nil { var extra map[string]any - if json.Unmarshal(raw, &extra) == nil { + if json.Unmarshal(raw, &extra, jsonx.V1()) == nil { maps.Copy(m, extra) } } diff --git a/services/opsworks/handler_layers.go b/services/opsworks/handler_layers.go index b776cfd869..0ffb64302d 100644 --- a/services/opsworks/handler_layers.go +++ b/services/opsworks/handler_layers.go @@ -2,9 +2,11 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateLayer handles CreateLayer requests. @@ -17,7 +19,7 @@ func (h *Handler) handleCreateLayer(_ context.Context, body []byte) (any, error) LayerSettings } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -37,7 +39,7 @@ func (h *Handler) handleDescribeLayers(_ context.Context, body []byte) (any, err } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -59,7 +61,7 @@ func (h *Handler) handleUpdateLayer(_ context.Context, body []byte) (any, error) LayerSettings } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -76,7 +78,7 @@ func (h *Handler) handleDeleteLayer(_ context.Context, body []byte) (any, error) LayerID string `json:"LayerId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -121,9 +123,9 @@ func layersToJSON(layers []*Layer) []map[string]any { "Attributes": attrs, } - if raw, err := json.Marshal(l.Settings); err == nil { + if raw, err := json.Marshal(l.Settings, jsonx.V1()); err == nil { var extra map[string]any - if json.Unmarshal(raw, &extra) == nil { + if json.Unmarshal(raw, &extra, jsonx.V1()) == nil { maps.Copy(m, extra) } } diff --git a/services/opsworks/handler_load_based_scaling.go b/services/opsworks/handler_load_based_scaling.go index 057ad6731e..942075ac0c 100644 --- a/services/opsworks/handler_load_based_scaling.go +++ b/services/opsworks/handler_load_based_scaling.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleSetLoadBasedAutoScaling handles SetLoadBasedAutoScaling requests. @@ -15,7 +17,7 @@ func (h *Handler) handleSetLoadBasedAutoScaling(_ context.Context, body []byte) Enable bool `json:"Enable"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -33,7 +35,7 @@ func (h *Handler) handleDescribeLoadBasedAutoScaling(_ context.Context, body []b } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_rds_db_instances.go b/services/opsworks/handler_rds_db_instances.go index c8dfdaf3a6..fc783768ff 100644 --- a/services/opsworks/handler_rds_db_instances.go +++ b/services/opsworks/handler_rds_db_instances.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleRegisterRdsDBInstance handles RegisterRdsDbInstance requests. @@ -15,7 +17,7 @@ func (h *Handler) handleRegisterRdsDBInstance(_ context.Context, body []byte) (a DBPassword string `json:"DbPassword"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -34,7 +36,7 @@ func (h *Handler) handleDeregisterRdsDBInstance(_ context.Context, body []byte) RdsDBInstanceArn string `json:"RdsDbInstanceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -53,7 +55,7 @@ func (h *Handler) handleDescribeRdsDBInstances(_ context.Context, body []byte) ( } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -74,7 +76,7 @@ func (h *Handler) handleUpdateRdsDBInstance(_ context.Context, body []byte) (any DBPassword string `json:"DbPassword"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_service_errors.go b/services/opsworks/handler_service_errors.go index e6c43b18ef..cfcbe37102 100644 --- a/services/opsworks/handler_service_errors.go +++ b/services/opsworks/handler_service_errors.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleDescribeServiceErrors handles DescribeServiceErrors requests. @@ -15,7 +17,7 @@ func (h *Handler) handleDescribeServiceErrors(_ context.Context, body []byte) (a } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -37,7 +39,7 @@ func (h *Handler) handleDescribeRaidArrays(_ context.Context, body []byte) (any, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_stacks.go b/services/opsworks/handler_stacks.go index 6c4f58813f..800c37c4ce 100644 --- a/services/opsworks/handler_stacks.go +++ b/services/opsworks/handler_stacks.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateStack handles CreateStack requests. @@ -36,7 +38,7 @@ func (h *Handler) handleCreateStack(_ context.Context, body []byte) (any, error) Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -114,7 +116,7 @@ func (h *Handler) handleCloneStack(_ context.Context, body []byte) (any, error) CloneAppIDs []string `json:"CloneAppIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -164,7 +166,7 @@ func (h *Handler) handleDescribeStacks(_ context.Context, body []byte) (any, err } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -206,7 +208,7 @@ func (h *Handler) handleUpdateStack(_ context.Context, body []byte) (any, error) StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -252,7 +254,7 @@ func (h *Handler) handleDeleteStack(_ context.Context, body []byte) (any, error) StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -269,7 +271,7 @@ func (h *Handler) handleStartStack(_ context.Context, body []byte) (any, error) StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -286,7 +288,7 @@ func (h *Handler) handleStopStack(_ context.Context, body []byte) (any, error) { StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -303,7 +305,7 @@ func (h *Handler) handleGetHostnameSuggestion(_ context.Context, body []byte) (a LayerID string `json:"LayerId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -321,7 +323,7 @@ func (h *Handler) handleDescribeStackSummary(_ context.Context, body []byte) (an StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -339,7 +341,7 @@ func (h *Handler) handleDescribeStackProvisioningParameters(_ context.Context, b StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_tags.go b/services/opsworks/handler_tags.go index 3c10d43636..f8225e36e4 100644 --- a/services/opsworks/handler_tags.go +++ b/services/opsworks/handler_tags.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleTagResource handles TagResource requests. @@ -13,7 +15,7 @@ func (h *Handler) handleTagResource(_ context.Context, body []byte) (any, error) ResourceArn string `json:"ResourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +33,7 @@ func (h *Handler) handleUntagResource(_ context.Context, body []byte) (any, erro TagKeys []string `json:"TagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -50,7 +52,7 @@ func (h *Handler) handleListTags(_ context.Context, body []byte) (any, error) { MaxResults int32 `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_test.go b/services/opsworks/handler_test.go index 1964c9ab5a..836123741e 100644 --- a/services/opsworks/handler_test.go +++ b/services/opsworks/handler_test.go @@ -2,7 +2,7 @@ package opsworks_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/opsworks" ) @@ -29,7 +30,7 @@ func doTarget(t *testing.T, h *opsworks.Handler, operation string, body any) *ht if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -50,7 +51,7 @@ func parseJSON(t *testing.T, body []byte) map[string]any { t.Helper() var result map[string]any - require.NoError(t, json.Unmarshal(body, &result)) + require.NoError(t, json.Unmarshal(body, &result, jsonx.V1())) return result } diff --git a/services/opsworks/handler_time_based_scaling.go b/services/opsworks/handler_time_based_scaling.go index 0fe13b6653..9e7499d081 100644 --- a/services/opsworks/handler_time_based_scaling.go +++ b/services/opsworks/handler_time_based_scaling.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleSetTimeBasedAutoScaling handles SetTimeBasedAutoScaling requests. @@ -13,7 +15,7 @@ func (h *Handler) handleSetTimeBasedAutoScaling(_ context.Context, body []byte) InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +33,7 @@ func (h *Handler) handleDescribeTimeBasedAutoScaling(_ context.Context, body []b } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } diff --git a/services/opsworks/handler_users_permissions.go b/services/opsworks/handler_users_permissions.go index ee29435fb3..c562c40da9 100644 --- a/services/opsworks/handler_users_permissions.go +++ b/services/opsworks/handler_users_permissions.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleCreateUserProfile handles CreateUserProfile requests. @@ -15,7 +17,7 @@ func (h *Handler) handleCreateUserProfile(_ context.Context, body []byte) (any, AllowSelfManagement bool `json:"AllowSelfManagement"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -38,7 +40,7 @@ func (h *Handler) handleDeleteUserProfile(_ context.Context, body []byte) (any, IamUserArn string `json:"IamUserArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -56,7 +58,7 @@ func (h *Handler) handleDescribeUserProfiles(_ context.Context, body []byte) (an } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -77,7 +79,7 @@ func (h *Handler) handleUpdateUserProfile(_ context.Context, body []byte) (any, SSHPublicKey string `json:"SshPublicKey"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -112,7 +114,7 @@ func (h *Handler) handleUpdateMyUserProfile(_ context.Context, body []byte) (any SSHPublicKey string `json:"SshPublicKey"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -152,7 +154,7 @@ func (h *Handler) handleSetPermission(_ context.Context, body []byte) (any, erro AllowSudo bool `json:"AllowSudo"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -171,7 +173,7 @@ func (h *Handler) handleDescribePermissions(_ context.Context, body []byte) (any } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -206,7 +208,7 @@ func (h *Handler) handleGrantAccess(_ context.Context, body []byte) (any, error) ValidForInMinutes int32 `json:"ValidForInMinutes"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/handler_volumes.go b/services/opsworks/handler_volumes.go index 63c5c36b0d..7f1b471055 100644 --- a/services/opsworks/handler_volumes.go +++ b/services/opsworks/handler_volumes.go @@ -2,8 +2,10 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleRegisterVolume handles RegisterVolume requests. @@ -13,7 +15,7 @@ func (h *Handler) handleRegisterVolume(_ context.Context, body []byte) (any, err StackID string `json:"StackId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +33,7 @@ func (h *Handler) handleDeregisterVolume(_ context.Context, body []byte) (any, e VolumeID string `json:"VolumeId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -49,7 +51,7 @@ func (h *Handler) handleAssignVolume(_ context.Context, body []byte) (any, error InstanceID string `json:"InstanceId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -66,7 +68,7 @@ func (h *Handler) handleUnassignVolume(_ context.Context, body []byte) (any, err VolumeID string `json:"VolumeId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -87,7 +89,7 @@ func (h *Handler) handleDescribeVolumes(_ context.Context, body []byte) (any, er } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -108,7 +110,7 @@ func (h *Handler) handleUpdateVolume(_ context.Context, body []byte) (any, error MountPoint string `json:"MountPoint"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/opsworks/interfaces.go b/services/opsworks/interfaces.go index 2108c0e51f..d38a371d84 100644 --- a/services/opsworks/interfaces.go +++ b/services/opsworks/interfaces.go @@ -2,7 +2,7 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -314,10 +314,10 @@ type LayerSettings struct { Attributes map[string]string `json:"Attributes,omitempty"` CustomInstanceProfileArn string `json:"CustomInstanceProfileArn,omitempty"` CustomJSON string `json:"CustomJson,omitempty"` - CustomRecipes json.RawMessage `json:"CustomRecipes,omitempty"` - CloudWatchLogsConfiguration json.RawMessage `json:"CloudWatchLogsConfiguration,omitempty"` - LifecycleEventConfiguration json.RawMessage `json:"LifecycleEventConfiguration,omitempty"` - VolumeConfigurations json.RawMessage `json:"VolumeConfigurations,omitempty"` + CustomRecipes jsontext.Value `json:"CustomRecipes,omitempty"` + CloudWatchLogsConfiguration jsontext.Value `json:"CloudWatchLogsConfiguration,omitempty"` + LifecycleEventConfiguration jsontext.Value `json:"LifecycleEventConfiguration,omitempty"` + VolumeConfigurations jsontext.Value `json:"VolumeConfigurations,omitempty"` Packages []string `json:"Packages,omitempty"` CustomSecurityGroupIDs []string `json:"CustomSecurityGroupIds,omitempty"` } diff --git a/services/opsworks/layers.go b/services/opsworks/layers.go index 8b86af2e27..029a81572d 100644 --- a/services/opsworks/layers.go +++ b/services/opsworks/layers.go @@ -1,7 +1,7 @@ package opsworks import ( - "encoding/json" + "encoding/json/jsontext" "maps" "reflect" "slices" @@ -185,7 +185,7 @@ func overlayBool(dst **bool, src *bool) { } } -func overlayRaw(cur, in json.RawMessage) json.RawMessage { +func overlayRaw(cur, in jsontext.Value) jsontext.Value { if len(in) > 0 { return in } diff --git a/services/opsworks/models.go b/services/opsworks/models.go index d26fe01151..757f8e54c1 100644 --- a/services/opsworks/models.go +++ b/services/opsworks/models.go @@ -1,10 +1,12 @@ package opsworks import ( - "encoding/json" + "encoding/json/v2" "maps" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -176,7 +178,7 @@ func (i *storedInstance) UnmarshalJSON(data []byte) error { alias } - if err := json.Unmarshal(data, &v); err != nil { + if err := json.Unmarshal(data, &v, jsonx.V1()); err != nil { return err } diff --git a/services/opsworks/multi_region_test.go b/services/opsworks/multi_region_test.go index 4ff7f930b9..449c1d35dd 100644 --- a/services/opsworks/multi_region_test.go +++ b/services/opsworks/multi_region_test.go @@ -2,7 +2,8 @@ package opsworks_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/opsworks" ) @@ -176,8 +178,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/opsworks/persistence.go b/services/opsworks/persistence.go index 033bba6ecf..94df4a6de0 100644 --- a/services/opsworks/persistence.go +++ b/services/opsworks/persistence.go @@ -2,7 +2,7 @@ package opsworks import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -30,7 +30,7 @@ const opsworksSnapshotVersion = 1 // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` diff --git a/services/opsworks/whitebox_test.go b/services/opsworks/whitebox_test.go index be4e71e088..7a7a15be66 100644 --- a/services/opsworks/whitebox_test.go +++ b/services/opsworks/whitebox_test.go @@ -1,11 +1,13 @@ package opsworks import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestFormatOpsWorksTimeNonUTCZone builds every timestamp input in a non-UTC @@ -81,10 +83,10 @@ func TestStoredInstanceUnmarshalJSON_LegacyLayerID(t *testing.T) { t.Parallel() var legacy storedInstance - require.NoError(t, json.Unmarshal([]byte(`{"instanceId":"i-1","layerId":"l-1"}`), &legacy)) + require.NoError(t, json.Unmarshal([]byte(`{"instanceId":"i-1","layerId":"l-1"}`), &legacy, jsonx.V1())) require.Equal(t, []string{"l-1"}, legacy.LayerIDs) var current storedInstance - require.NoError(t, json.Unmarshal([]byte(`{"instanceId":"i-2","layerIds":["l-2","l-3"]}`), ¤t)) + require.NoError(t, json.Unmarshal([]byte(`{"instanceId":"i-2","layerIds":["l-2","l-3"]}`), ¤t, jsonx.V1())) require.Equal(t, []string{"l-2", "l-3"}, current.LayerIDs) } diff --git a/services/organizations/account_provisioning.go b/services/organizations/account_provisioning.go index 56a3b02127..58f43014ec 100644 --- a/services/organizations/account_provisioning.go +++ b/services/organizations/account_provisioning.go @@ -1,8 +1,10 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const defaultAccessRoleName = "OrganizationAccountAccessRole" @@ -28,7 +30,7 @@ func accessRoleTrustPolicy(managementAccountID string) string { "Principal": map[string]string{"AWS": fmt.Sprintf("arn:aws:iam::%s:root", managementAccountID)}, "Action": "sts:AssumeRole", }}, - }) + }, jsonx.V1()) return string(doc) } diff --git a/services/organizations/default_policy_test.go b/services/organizations/default_policy_test.go index 3c710b366a..3d5ead4f98 100644 --- a/services/organizations/default_policy_test.go +++ b/services/organizations/default_policy_test.go @@ -1,12 +1,15 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDefaultFullAWSAccessPolicy_ViaHandler verifies that every organization @@ -21,7 +24,7 @@ func TestDefaultFullAWSAccessPolicy_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp describePolicyResp - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "p-FullAWSAccess", resp.Policy.PolicySummary.ID) assert.Equal(t, "FullAWSAccess", resp.Policy.PolicySummary.Name) @@ -44,7 +47,7 @@ func TestDefaultFullAWSAccessPolicy_ARN_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp describePolicyResp - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "arn:aws:organizations::aws:policy/SERVICE_CONTROL_POLICY/p-FullAWSAccess", @@ -65,7 +68,7 @@ func TestDefaultFullAWSAccessPolicy_AttachedToRoot_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp listPoliciesForTargetResp - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) found := false @@ -90,7 +93,7 @@ func TestDeleteFullAWSAccessPolicy_Rejected_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "AccessDeniedException", errResp["__type"]) // The survivor check: DescribePolicy must still find it afterward. @@ -115,14 +118,14 @@ func TestUpdateFullAWSAccessPolicy_Rejected_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "AccessDeniedException", errResp["__type"]) describeRec := doRequest(t, h, "DescribePolicy", map[string]any{"PolicyId": "p-FullAWSAccess"}) require.Equal(t, http.StatusOK, describeRec.Code) var resp describePolicyResp - require.NoError(t, json.NewDecoder(describeRec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(describeRec.Body), &resp, jsonx.V1())) assert.Equal(t, "FullAWSAccess", resp.Policy.PolicySummary.Name, "name must not be hijacked") assert.JSONEq(t, `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}`, @@ -149,7 +152,7 @@ func TestUserPolicy_AwsManagedFalse_StillDeletable_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp createPolicyResp - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) assert.False(t, createResp.Policy.PolicySummary.AwsManaged, "user-created policy must not be AwsManaged") policyID := createResp.Policy.PolicySummary.ID diff --git a/services/organizations/delegated_administrators_test.go b/services/organizations/delegated_administrators_test.go index 0e508fe649..bebfba20c0 100644 --- a/services/organizations/delegated_administrators_test.go +++ b/services/organizations/delegated_administrators_test.go @@ -1,7 +1,8 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -90,7 +92,7 @@ func TestDelegatedServices(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &createResp, jsonx.V1())) accountID := createResp["CreateAccountStatus"].(map[string]any)["AccountId"].(string) rec := doRequest(t, h, "EnableAWSServiceAccess", map[string]any{ @@ -111,7 +113,7 @@ func TestDelegatedServices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) services, ok := resp["DelegatedServices"].([]any) require.True(t, ok) require.Len(t, services, 1) @@ -407,7 +409,7 @@ func TestRegisterDelegatedAdmin_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) statusObj := createResp["CreateAccountStatus"].(map[string]any) accountID := statusObj["AccountId"].(string) diff --git a/services/organizations/effective_policy.go b/services/organizations/effective_policy.go index 1ae31675ec..92c937c0c8 100644 --- a/services/organizations/effective_policy.go +++ b/services/organizations/effective_policy.go @@ -1,9 +1,11 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validEffectivePolicyTypes is the EffectivePolicyType enum (aws-sdk-go-v2 organizations v1.53.5 enums.go). @@ -130,12 +132,12 @@ func (b *InMemoryBackend) mergeTagStyleChain(chain []effectivePolicyEntry) (stri for _, entry := range slices.Backward(chain) { var obj map[string]any - if err := json.Unmarshal([]byte(entry.content), &obj); err == nil { + if err := json.Unmarshal([]byte(entry.content), &obj, jsonx.V1()); err == nil { mergeJSONObjects(merged, obj) } } - if data, err := json.Marshal(merged); err == nil { + if data, err := json.Marshal(merged, jsonx.V1()); err == nil { return string(data), chain[0].id } diff --git a/services/organizations/effective_policy_test.go b/services/organizations/effective_policy_test.go index 229f10a176..0b63e2c2fa 100644 --- a/services/organizations/effective_policy_test.go +++ b/services/organizations/effective_policy_test.go @@ -1,13 +1,15 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -137,7 +139,7 @@ func TestDescribeEffectivePolicy_MergeTagPolicy(t *testing.T) { // Merged content should have both "env" (from child) and "owner" (from parent). var merged map[string]any - require.NoError(t, json.Unmarshal([]byte(ep.PolicyContent), &merged)) + require.NoError(t, json.Unmarshal([]byte(ep.PolicyContent), &merged, jsonx.V1())) tags, hasTags := merged["tags"].(map[string]any) require.True(t, hasTags, "merged policy should have 'tags' object") @@ -234,7 +236,7 @@ func TestHandler_DescribeEffectivePolicy(t *testing.T) { require.Equal(t, http.StatusOK, acctRec.Code) var acctResp map[string]any - require.NoError(t, json.NewDecoder(acctRec.Body).Decode(&acctResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(acctRec.Body), &acctResp, jsonx.V1())) status := acctResp["CreateAccountStatus"].(map[string]any) targetID = status["AccountId"].(string) @@ -247,7 +249,7 @@ func TestHandler_DescribeEffectivePolicy(t *testing.T) { require.Equal(t, http.StatusOK, policyRec.Code) var policyResp map[string]any - require.NoError(t, json.NewDecoder(policyRec.Body).Decode(&policyResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(policyRec.Body), &policyResp, jsonx.V1())) policyObj := policyResp["Policy"].(map[string]any) summary := policyObj["PolicySummary"].(map[string]any) policyID := summary["Id"].(string) @@ -274,7 +276,7 @@ func TestHandler_DescribeEffectivePolicy(t *testing.T) { if tt.wantContent { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ep, ok := resp["EffectivePolicy"].(map[string]any) require.True(t, ok, "response must have EffectivePolicy") assert.NotEmpty(t, ep["PolicyContent"]) diff --git a/services/organizations/handler.go b/services/organizations/handler.go index b765d40a32..fac4ae4a82 100644 --- a/services/organizations/handler.go +++ b/services/organizations/handler.go @@ -1,7 +1,7 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -139,7 +140,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } diff --git a/services/organizations/handler_accounts.go b/services/organizations/handler_accounts.go index 73e71913c1..f69d557859 100644 --- a/services/organizations/handler_accounts.go +++ b/services/organizations/handler_accounts.go @@ -1,12 +1,13 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -165,7 +166,7 @@ func (h *Handler) dispatchAccount(c *echo.Context, op string, body []byte) (bool func (h *Handler) handleListAccounts(c *echo.Context, body []byte) error { var req listAccountsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } @@ -237,7 +238,7 @@ func (h *Handler) validateCreateAccountInput( func (h *Handler) handleCreateAccount(c *echo.Context, body []byte) error { var req createAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -258,7 +259,7 @@ func (h *Handler) handleCreateAccount(c *echo.Context, body []byte) error { func (h *Handler) handleDescribeCreateAccountStatus(c *echo.Context, body []byte) error { var req describeCreateAccountStatusRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -272,7 +273,7 @@ func (h *Handler) handleDescribeCreateAccountStatus(c *echo.Context, body []byte func (h *Handler) handleDescribeAccount(c *echo.Context, body []byte) error { var req describeAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -286,7 +287,7 @@ func (h *Handler) handleDescribeAccount(c *echo.Context, body []byte) error { func (h *Handler) handleRemoveAccountFromOrganization(c *echo.Context, body []byte) error { var req removeAccountFromOrganizationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -299,7 +300,7 @@ func (h *Handler) handleRemoveAccountFromOrganization(c *echo.Context, body []by func (h *Handler) handleMoveAccount(c *echo.Context, body []byte) error { var req moveAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -312,7 +313,7 @@ func (h *Handler) handleMoveAccount(c *echo.Context, body []byte) error { func (h *Handler) handleCloseAccount(c *echo.Context, body []byte) error { var req closeAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -329,7 +330,7 @@ func (h *Handler) handleCloseAccount(c *echo.Context, body []byte) error { func (h *Handler) handleCreateGovCloudAccount(c *echo.Context, body []byte) error { var req createGovCloudAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -350,7 +351,7 @@ func (h *Handler) handleCreateGovCloudAccount(c *echo.Context, body []byte) erro func (h *Handler) handleListCreateAccountStatus(c *echo.Context, body []byte) error { var req listCreateAccountStatusRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -375,7 +376,7 @@ func (h *Handler) handleListCreateAccountStatus(c *echo.Context, body []byte) er func (h *Handler) handleListAccountsWithInvalidEffectivePolicy(c *echo.Context, body []byte) error { var req listAccountsWithInvalidEffectivePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_accounts_test.go b/services/organizations/handler_accounts_test.go index d4a078af30..a660e0b34c 100644 --- a/services/organizations/handler_accounts_test.go +++ b/services/organizations/handler_accounts_test.go @@ -1,7 +1,8 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -96,7 +98,7 @@ func TestCreateAccountStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) status := createResp["CreateAccountStatus"].(map[string]any) requestID, ok := status["Id"].(string) require.True(t, ok, "CreateAccountStatus.Id must be present") @@ -106,7 +108,7 @@ func TestCreateAccountStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) statuses, ok := listResp["CreateAccountStatuses"].([]any) require.True(t, ok) @@ -208,7 +210,7 @@ func TestCreateAccount_RoleName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) // Describe account. status := resp["CreateAccountStatus"].(map[string]any) @@ -218,7 +220,7 @@ func TestCreateAccount_RoleName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&descResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &descResp, jsonx.V1())) acct := descResp["Account"].(map[string]any) assert.NotContains(t, acct, "RoleName") @@ -283,7 +285,7 @@ func TestCreateAccount_IamUserAccessToBilling(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantIamAccess != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) status := resp["CreateAccountStatus"].(map[string]any) accountID := status["AccountId"].(string) @@ -292,7 +294,7 @@ func TestCreateAccount_IamUserAccessToBilling(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&descResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &descResp, jsonx.V1())) acct := descResp["Account"].(map[string]any) assert.NotContains(t, acct, "IamUserAccessToBilling") @@ -325,7 +327,7 @@ func TestCloseAccount_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) statusObj := createResp["CreateAccountStatus"].(map[string]any) accountID := statusObj["AccountId"].(string) @@ -371,7 +373,7 @@ func TestEmailUniqueness_ViaHandler(t *testing.T) { AccountID string `json:"AccountId"` } `json:"CreateAccountStatus"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "FAILED", resp.CreateAccountStatus.State) assert.Equal(t, "EMAIL_ALREADY_EXISTS", resp.CreateAccountStatus.FailureReason) assert.Empty(t, resp.CreateAccountStatus.AccountID) @@ -392,7 +394,7 @@ func TestCreateAccount_NoGovCloudID_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) statusObj, ok := resp["CreateAccountStatus"].(map[string]any) require.True(t, ok, "response must have CreateAccountStatus") @@ -417,7 +419,7 @@ func TestGovCloudAccount_HasGovCloudID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) statusObj, ok := resp["CreateAccountStatus"].(map[string]any) require.True(t, ok, "response must have CreateAccountStatus") @@ -458,7 +460,7 @@ func TestHandler_ListAccounts(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) accounts, ok := resp["Accounts"].([]any) require.True(t, ok) assert.NotEmpty(t, accounts) @@ -531,7 +533,7 @@ func TestHandler_DescribeCreateAccountStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) status := createResp["CreateAccountStatus"].(map[string]any) statusID := status["Id"].(string) @@ -599,7 +601,7 @@ func TestHandler_MoveAccount(t *testing.T) { require.Equal(t, http.StatusOK, ouRec.Code) var ouResp map[string]any - require.NoError(t, json.NewDecoder(ouRec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ouRec.Body), &ouResp, jsonx.V1())) ou := ouResp["OrganizationalUnit"].(map[string]any) ouID := ou["Id"].(string) @@ -658,7 +660,7 @@ func TestHandler_CloseAccount(t *testing.T) { require.Equal(t, http.StatusOK, acctRec.Code) var acctResp map[string]any - require.NoError(t, json.NewDecoder(acctRec.Body).Decode(&acctResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(acctRec.Body), &acctResp, jsonx.V1())) status := acctResp["CreateAccountStatus"].(map[string]any) body = map[string]any{"AccountId": status["AccountId"]} @@ -736,7 +738,7 @@ func TestHandler_CreateGovCloudAccount(t *testing.T) { if tt.wantGovID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) status, ok := resp["CreateAccountStatus"].(map[string]any) require.True(t, ok, "response must have CreateAccountStatus") diff --git a/services/organizations/handler_delegated_administrators.go b/services/organizations/handler_delegated_administrators.go index 78559fc5bf..d44847e721 100644 --- a/services/organizations/handler_delegated_administrators.go +++ b/services/organizations/handler_delegated_administrators.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -81,7 +82,7 @@ func (h *Handler) dispatchDelegatedAdmin(c *echo.Context, op string, body []byte func (h *Handler) handleRegisterDelegatedAdministrator(c *echo.Context, body []byte) error { var req registerDelegatedAdministratorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -94,7 +95,7 @@ func (h *Handler) handleRegisterDelegatedAdministrator(c *echo.Context, body []b func (h *Handler) handleDeregisterDelegatedAdministrator(c *echo.Context, body []byte) error { var req deregisterDelegatedAdministratorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -107,7 +108,7 @@ func (h *Handler) handleDeregisterDelegatedAdministrator(c *echo.Context, body [ func (h *Handler) handleListDelegatedAdministrators(c *echo.Context, body []byte) error { var req listDelegatedAdministratorsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -144,7 +145,7 @@ func (h *Handler) handleListDelegatedAdministrators(c *echo.Context, body []byte func (h *Handler) handleListDelegatedServicesForAccount(c *echo.Context, body []byte) error { var req listDelegatedServicesForAccountRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_effective_policy.go b/services/organizations/handler_effective_policy.go index a70841fb8e..235a5db36d 100644 --- a/services/organizations/handler_effective_policy.go +++ b/services/organizations/handler_effective_policy.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -62,7 +63,7 @@ func (h *Handler) dispatchEffectivePolicy(c *echo.Context, op string, body []byt func (h *Handler) handleDescribeEffectivePolicy(c *echo.Context, body []byte) error { var req describeEffectivePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -87,7 +88,7 @@ func (h *Handler) handleDescribeEffectivePolicy(c *echo.Context, body []byte) er func (h *Handler) handleListEffectivePolicyValidationErrors(c *echo.Context, body []byte) error { var req listEffectivePolicyValidationErrorsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_handshakes.go b/services/organizations/handler_handshakes.go index 16e5f71102..459feee8d7 100644 --- a/services/organizations/handler_handshakes.go +++ b/services/organizations/handler_handshakes.go @@ -1,13 +1,14 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -274,7 +275,7 @@ func (h *Handler) dispatchTransferOps(c *echo.Context, op string, body []byte) ( func (h *Handler) handleAcceptHandshake(c *echo.Context, body []byte) error { var req acceptHandshakeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -292,7 +293,7 @@ func (h *Handler) handleAcceptHandshake(c *echo.Context, body []byte) error { func (h *Handler) handleCancelHandshake(c *echo.Context, body []byte) error { var req cancelHandshakeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -310,7 +311,7 @@ func (h *Handler) handleCancelHandshake(c *echo.Context, body []byte) error { func (h *Handler) handleDeclineHandshake(c *echo.Context, body []byte) error { var req declineHandshakeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -328,7 +329,7 @@ func (h *Handler) handleDeclineHandshake(c *echo.Context, body []byte) error { func (h *Handler) handleDescribeHandshake(c *echo.Context, body []byte) error { var req describeHandshakeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -346,7 +347,7 @@ func (h *Handler) handleDescribeHandshake(c *echo.Context, body []byte) error { func (h *Handler) handleDescribeResponsibilityTransfer(c *echo.Context, body []byte) error { var req describeResponsibilityTransferRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -376,7 +377,7 @@ func (h *Handler) handleEnableAllFeatures(c *echo.Context, _ []byte) error { func (h *Handler) handleInviteAccountToOrganization(c *echo.Context, body []byte) error { var req inviteAccountToOrganizationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -398,7 +399,7 @@ func (h *Handler) handleInviteAccountToOrganization(c *echo.Context, body []byte func (h *Handler) handleInviteOrganizationToTransferResponsibility(c *echo.Context, body []byte) error { var req inviteOrganizationToTransferResponsibilityRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -449,7 +450,7 @@ func (h *Handler) handleLeaveOrganization(c *echo.Context, _ []byte) error { func (h *Handler) handleListHandshakesForAccount(c *echo.Context, body []byte) error { var req listHandshakesFilterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } @@ -486,7 +487,7 @@ func (h *Handler) handleListHandshakesForAccount(c *echo.Context, body []byte) e func (h *Handler) handleListHandshakesForOrganization(c *echo.Context, body []byte) error { var req listHandshakesFilterRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } @@ -522,7 +523,7 @@ func (h *Handler) handleListHandshakesForOrganization(c *echo.Context, body []by func (h *Handler) handleListInboundResponsibilityTransfers(c *echo.Context, body []byte) error { var req listInboundResponsibilityTransfersRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } @@ -556,7 +557,7 @@ func (h *Handler) handleListInboundResponsibilityTransfers(c *echo.Context, body func (h *Handler) handleListOutboundResponsibilityTransfers(c *echo.Context, body []byte) error { var req listOutboundResponsibilityTransfersRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } @@ -589,7 +590,7 @@ func (h *Handler) handleListOutboundResponsibilityTransfers(c *echo.Context, bod func (h *Handler) handleTerminateResponsibilityTransfer(c *echo.Context, body []byte) error { var req terminateResponsibilityTransferRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -616,7 +617,7 @@ func (h *Handler) handleTerminateResponsibilityTransfer(c *echo.Context, body [] func (h *Handler) handleUpdateResponsibilityTransfer(c *echo.Context, body []byte) error { var req updateResponsibilityTransferRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_handshakes_test.go b/services/organizations/handler_handshakes_test.go index 40c7e46a7c..41ddf05b15 100644 --- a/services/organizations/handler_handshakes_test.go +++ b/services/organizations/handler_handshakes_test.go @@ -2,7 +2,8 @@ package organizations_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -33,7 +35,7 @@ func TestHandshakeOps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var inviteResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&inviteResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &inviteResp, jsonx.V1())) invited := inviteResp["Handshake"].(map[string]any) handshakeID, ok := invited["Id"].(string) require.True(t, ok, "InviteAccountToOrganization must return a Handshake.Id") @@ -55,7 +57,7 @@ func handshakeListContains(t *testing.T, rec *httptest.ResponseRecorder, handsha t.Helper() var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) handshakes, ok := resp["Handshakes"].([]any) require.True(t, ok) @@ -110,7 +112,7 @@ func TestEnableAllFeatures(t *testing.T) { if tt.wantAction != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) hs, ok := resp["Handshake"].(map[string]any) require.True(t, ok, "response must contain Handshake") assert.Equal(t, tt.wantAction, hs["Action"]) @@ -193,7 +195,7 @@ func TestHandshakeFilter_Handler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) handshakes := resp["Handshakes"].([]any) assert.Len(t, handshakes, tt.wantCount) @@ -333,7 +335,7 @@ func TestHandshakeExpiration_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) handshake := resp["Handshake"].(map[string]any) assert.Equal(t, "EXPIRED", handshake["State"].(string)) } @@ -355,7 +357,7 @@ func TestInviteAccount_DuplicateOpen_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, r2.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(r2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "DuplicateHandshakeException", errResp["__type"]) } @@ -513,7 +515,7 @@ func TestHandler_HandshakeLifecycle(t *testing.T) { if tt.wantState != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) hs, ok := resp["Handshake"].(map[string]any) require.True(t, ok, "response must have Handshake field") @@ -582,7 +584,7 @@ func TestHandler_DescribeResponsibilityTransfer(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) details, ok := resp["ResponsibilityTransfer"].(map[string]any) require.True(t, ok, "response must have ResponsibilityTransfer") assert.Equal(t, tt.transferID, details["Id"]) diff --git a/services/organizations/handler_organization.go b/services/organizations/handler_organization.go index e9a0b9d037..4df466400a 100644 --- a/services/organizations/handler_organization.go +++ b/services/organizations/handler_organization.go @@ -1,10 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createOrganizationRequest struct { @@ -49,7 +51,7 @@ func (h *Handler) dispatchOrg(c *echo.Context, op string, body []byte) (bool, er func (h *Handler) handleCreateOrganization(c *echo.Context, body []byte) error { var req createOrganizationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_organizational_units.go b/services/organizations/handler_organizational_units.go index 8782b8aeb2..d2f071e1eb 100644 --- a/services/organizations/handler_organizational_units.go +++ b/services/organizations/handler_organizational_units.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -122,7 +123,7 @@ func (h *Handler) dispatchOU(c *echo.Context, op string, body []byte) (bool, err func (h *Handler) handleCreateOrganizationalUnit(c *echo.Context, body []byte) error { var req createOrganizationalUnitRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -140,7 +141,7 @@ func (h *Handler) handleCreateOrganizationalUnit(c *echo.Context, body []byte) e func (h *Handler) handleDescribeOrganizationalUnit(c *echo.Context, body []byte) error { var req describeOrganizationalUnitRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -154,7 +155,7 @@ func (h *Handler) handleDescribeOrganizationalUnit(c *echo.Context, body []byte) func (h *Handler) handleDeleteOrganizationalUnit(c *echo.Context, body []byte) error { var req deleteOrganizationalUnitRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -167,7 +168,7 @@ func (h *Handler) handleDeleteOrganizationalUnit(c *echo.Context, body []byte) e func (h *Handler) handleUpdateOrganizationalUnit(c *echo.Context, body []byte) error { var req updateOrganizationalUnitRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -181,7 +182,7 @@ func (h *Handler) handleUpdateOrganizationalUnit(c *echo.Context, body []byte) e func (h *Handler) handleListOrganizationalUnitsForParent(c *echo.Context, body []byte) error { var req listOrganizationalUnitsForParentRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -202,7 +203,7 @@ func (h *Handler) handleListOrganizationalUnitsForParent(c *echo.Context, body [ func (h *Handler) handleListAccountsForParent(c *echo.Context, body []byte) error { var req listAccountsForParentRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -223,7 +224,7 @@ func (h *Handler) handleListAccountsForParent(c *echo.Context, body []byte) erro func (h *Handler) handleListParents(c *echo.Context, body []byte) error { var req listParentsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -243,7 +244,7 @@ func (h *Handler) handleListParents(c *echo.Context, body []byte) error { func (h *Handler) handleListChildren(c *echo.Context, body []byte) error { var req listChildrenRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_organizational_units_test.go b/services/organizations/handler_organizational_units_test.go index f83119a1d4..1e36a88037 100644 --- a/services/organizations/handler_organizational_units_test.go +++ b/services/organizations/handler_organizational_units_test.go @@ -1,13 +1,16 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_CreateOrganizationalUnit tests the HTTP handler for CreateOrganizationalUnit. @@ -38,7 +41,7 @@ func TestHandler_CreateOrganizationalUnit(t *testing.T) { require.Equal(t, http.StatusOK, rootsRec.Code) var rootsResp map[string]any - require.NoError(t, json.NewDecoder(rootsRec.Body).Decode(&rootsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rootsRec.Body), &rootsResp, jsonx.V1())) roots := rootsResp["Roots"].([]any) rootID := roots[0].(map[string]any)["Id"].(string) @@ -50,7 +53,7 @@ func TestHandler_CreateOrganizationalUnit(t *testing.T) { if tt.wantOU { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ou, ok := resp["OrganizationalUnit"].(map[string]any) require.True(t, ok, "response should contain OrganizationalUnit") assert.NotEmpty(t, ou["Id"]) @@ -90,7 +93,7 @@ func TestHandler_ListParents(t *testing.T) { require.Equal(t, http.StatusOK, acctRec.Code) var acctResp map[string]any - require.NoError(t, json.NewDecoder(acctRec.Body).Decode(&acctResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(acctRec.Body), &acctResp, jsonx.V1())) status := acctResp["CreateAccountStatus"].(map[string]any) accountID := status["AccountId"].(string) @@ -99,7 +102,7 @@ func TestHandler_ListParents(t *testing.T) { if tt.wantParents { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) parents, ok := resp["Parents"].([]any) require.True(t, ok, "response should contain Parents") assert.NotEmpty(t, parents) @@ -164,7 +167,7 @@ func TestOUDepth_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var rootsResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&rootsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &rootsResp, jsonx.V1())) roots := rootsResp["Roots"].([]any) rootID := roots[0].(map[string]any)["Id"].(string) @@ -179,7 +182,7 @@ func TestOUDepth_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "depth %d should succeed", d) var ouResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ouResp, jsonx.V1())) parentID = ouResp["OrganizationalUnit"].(map[string]any)["Id"].(string) } @@ -205,7 +208,7 @@ func TestOUNameUniqueness_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var rootsResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&rootsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &rootsResp, jsonx.V1())) roots := rootsResp["Roots"].([]any) rootID := roots[0].(map[string]any)["Id"].(string) @@ -258,7 +261,7 @@ func TestHandler_DescribeOrganizationalUnit(t *testing.T) { require.Equal(t, http.StatusOK, ouRec.Code) var ouResp map[string]any - require.NoError(t, json.NewDecoder(ouRec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ouRec.Body), &ouResp, jsonx.V1())) ou := ouResp["OrganizationalUnit"].(map[string]any) ouID := ou["Id"].(string) @@ -295,7 +298,7 @@ func TestHandler_DeleteOrganizationalUnit(t *testing.T) { require.Equal(t, http.StatusOK, ouRec.Code) var ouResp map[string]any - require.NoError(t, json.NewDecoder(ouRec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ouRec.Body), &ouResp, jsonx.V1())) ou := ouResp["OrganizationalUnit"].(map[string]any) ouID := ou["Id"].(string) @@ -332,7 +335,7 @@ func TestHandler_UpdateOrganizationalUnit(t *testing.T) { require.Equal(t, http.StatusOK, ouRec.Code) var ouResp map[string]any - require.NoError(t, json.NewDecoder(ouRec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(ouRec.Body), &ouResp, jsonx.V1())) ou := ouResp["OrganizationalUnit"].(map[string]any) ouID := ou["Id"].(string) diff --git a/services/organizations/handler_pagination_test.go b/services/organizations/handler_pagination_test.go index b6d51956ae..8a29247858 100644 --- a/services/organizations/handler_pagination_test.go +++ b/services/organizations/handler_pagination_test.go @@ -2,7 +2,8 @@ package organizations_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -73,7 +75,7 @@ func TestHandler_Pagination(t *testing.T) { if tt.nextToken != "" { reqBody["NextToken"] = tt.nextToken } - bodyBytes, _ := json.Marshal(reqBody) + bodyBytes, _ := json.Marshal(reqBody, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) req.Header.Set("X-Amz-Target", "AWSOrganizationsV20161128.ListAccounts") @@ -94,7 +96,7 @@ func TestHandler_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var res map[string]any - err = json.Unmarshal(rec.Body.Bytes(), &res) + err = json.Unmarshal(rec.Body.Bytes(), &res, jsonx.V1()) require.NoError(t, err) accounts, ok := res["Accounts"].([]any) @@ -139,7 +141,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` Accounts []map[string]any `json:"Accounts"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.Accounts, 2, "MaxResults=2 must truncate to 2 items") require.NotEmpty(t, resp.NextToken, "truncated page must carry a NextToken") @@ -155,7 +157,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` Accounts []map[string]any `json:"Accounts"` } - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp2, jsonx.V1())) require.Len(t, resp2.Accounts, 2, "second page must continue from the token") }) @@ -180,7 +182,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { } `json:"PolicySummary"` } `json:"Policy"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&pResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &pResp, jsonx.V1())) rec = doRequest(t, h, "AttachPolicy", map[string]any{ "PolicyId": pResp.Policy.PolicySummary.ID, @@ -200,7 +202,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` Policies []map[string]any `json:"Policies"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.Policies, 1) require.NotEmpty(t, resp.NextToken) }) @@ -221,7 +223,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { ID string `json:"Id"` } `json:"OrganizationalUnit"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ouResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ouResp, jsonx.V1())) rec = doRequest(t, h, "CreatePolicy", map[string]any{ "Name": "p", @@ -238,7 +240,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { } `json:"PolicySummary"` } `json:"Policy"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&pResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &pResp, jsonx.V1())) policyID := pResp.Policy.PolicySummary.ID for _, targetID := range []string{rootID, ouResp.OrganizationalUnit.ID} { @@ -259,7 +261,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` Targets []map[string]any `json:"Targets"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.Targets, 1) require.NotEmpty(t, resp.NextToken) }) @@ -284,7 +286,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { AccountID string `json:"AccountId"` } `json:"CreateAccountStatus"` } - require.NoError(t, json.NewDecoder(caRec.Body).Decode(&caResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(caRec.Body), &caResp, jsonx.V1())) rec = doRequest(t, h, "RegisterDelegatedAdministrator", map[string]any{ "AccountId": caResp.CreateAccountStatus.AccountID, @@ -302,7 +304,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` DelegatedAdministrators []map[string]any `json:"DelegatedAdministrators"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.DelegatedAdministrators, 1) require.NotEmpty(t, resp.NextToken) }) @@ -329,7 +331,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` CreateAccountStatuses []map[string]any `json:"CreateAccountStatuses"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.CreateAccountStatuses, 2) require.NotEmpty(t, resp.NextToken) }) @@ -359,7 +361,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` Tags []map[string]any `json:"Tags"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.Tags, 3, "all 3 tags fit under the default page size") require.Empty(t, resp.NextToken) }) @@ -383,7 +385,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` EnabledServicePrincipals []map[string]any `json:"EnabledServicePrincipals"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.EnabledServicePrincipals, 1) require.NotEmpty(t, resp.NextToken) @@ -409,7 +411,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { AccountID string `json:"AccountId"` } `json:"CreateAccountStatus"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&caResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &caResp, jsonx.V1())) accountID := caResp.CreateAccountStatus.AccountID for _, sp := range []string{"config.amazonaws.com", "cloudtrail.amazonaws.com"} { @@ -433,7 +435,7 @@ func TestListOps_HonorMaxResultsAndNextToken(t *testing.T) { NextToken string `json:"NextToken"` DelegatedServices []map[string]any `json:"DelegatedServices"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) require.Len(t, resp.DelegatedServices, 1) require.NotEmpty(t, resp.NextToken) }) diff --git a/services/organizations/handler_policies.go b/services/organizations/handler_policies.go index dd472d9a15..1ed3c8670d 100644 --- a/services/organizations/handler_policies.go +++ b/services/organizations/handler_policies.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -115,7 +116,7 @@ func (h *Handler) dispatchPolicy(c *echo.Context, op string, body []byte) (bool, func (h *Handler) handleCreatePolicy(c *echo.Context, body []byte) error { var req createPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -133,7 +134,7 @@ func (h *Handler) handleCreatePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDescribePolicy(c *echo.Context, body []byte) error { var req describePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -147,7 +148,7 @@ func (h *Handler) handleDescribePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleUpdatePolicy(c *echo.Context, body []byte) error { var req updatePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -161,7 +162,7 @@ func (h *Handler) handleUpdatePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDeletePolicy(c *echo.Context, body []byte) error { var req deletePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -174,7 +175,7 @@ func (h *Handler) handleDeletePolicy(c *echo.Context, body []byte) error { func (h *Handler) handleListPolicies(c *echo.Context, body []byte) error { var req listPoliciesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -195,7 +196,7 @@ func (h *Handler) handleListPolicies(c *echo.Context, body []byte) error { func (h *Handler) handleEnablePolicyType(c *echo.Context, body []byte) error { var req enablePolicyTypeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -209,7 +210,7 @@ func (h *Handler) handleEnablePolicyType(c *echo.Context, body []byte) error { func (h *Handler) handleDisablePolicyType(c *echo.Context, body []byte) error { var req disablePolicyTypeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_policy_attachments.go b/services/organizations/handler_policy_attachments.go index 53cab343c2..5801f0be49 100644 --- a/services/organizations/handler_policy_attachments.go +++ b/services/organizations/handler_policy_attachments.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -67,7 +68,7 @@ func (h *Handler) dispatchPolicyAttachments(c *echo.Context, op string, body []b func (h *Handler) handleAttachPolicy(c *echo.Context, body []byte) error { var req attachPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -80,7 +81,7 @@ func (h *Handler) handleAttachPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleDetachPolicy(c *echo.Context, body []byte) error { var req detachPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -93,7 +94,7 @@ func (h *Handler) handleDetachPolicy(c *echo.Context, body []byte) error { func (h *Handler) handleListPoliciesForTarget(c *echo.Context, body []byte) error { var req listPoliciesForTargetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -114,7 +115,7 @@ func (h *Handler) handleListPoliciesForTarget(c *echo.Context, body []byte) erro func (h *Handler) handleListTargetsForPolicy(c *echo.Context, body []byte) error { var req listTargetsForPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_resource_policy.go b/services/organizations/handler_resource_policy.go index 964e5a3ba3..8b213511b8 100644 --- a/services/organizations/handler_resource_policy.go +++ b/services/organizations/handler_resource_policy.go @@ -1,10 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type resourcePolicySummaryObject struct { @@ -70,7 +72,7 @@ func (h *Handler) handlePutResourcePolicy(c *echo.Context, body []byte) error { Tags []Tag `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_roots.go b/services/organizations/handler_roots.go index 2d42c4f4ff..abf114b21a 100644 --- a/services/organizations/handler_roots.go +++ b/services/organizations/handler_roots.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -43,7 +44,7 @@ type listRootsRequest struct { func (h *Handler) handleListRoots(c *echo.Context, body []byte) error { var req listRootsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } diff --git a/services/organizations/handler_service_access.go b/services/organizations/handler_service_access.go index 7f090ac437..f755a41ac2 100644 --- a/services/organizations/handler_service_access.go +++ b/services/organizations/handler_service_access.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -52,7 +53,7 @@ func (h *Handler) dispatchServiceAccess(c *echo.Context, op string, body []byte) func (h *Handler) handleEnableAWSServiceAccess(c *echo.Context, body []byte) error { var req enableAWSServiceAccessRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -65,7 +66,7 @@ func (h *Handler) handleEnableAWSServiceAccess(c *echo.Context, body []byte) err func (h *Handler) handleDisableAWSServiceAccess(c *echo.Context, body []byte) error { var req disableAWSServiceAccessRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -79,7 +80,7 @@ func (h *Handler) handleDisableAWSServiceAccess(c *echo.Context, body []byte) er func (h *Handler) handleListAWSServiceAccessForOrganization(c *echo.Context, body []byte) error { var req listAWSServiceAccessRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } } diff --git a/services/organizations/handler_tags.go b/services/organizations/handler_tags.go index b417eec0ed..c6776b0736 100644 --- a/services/organizations/handler_tags.go +++ b/services/organizations/handler_tags.go @@ -1,11 +1,12 @@ package organizations import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -49,7 +50,7 @@ func (h *Handler) dispatchTags(c *echo.Context, op string, body []byte) (bool, e func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -62,7 +63,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } @@ -75,7 +76,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { func (h *Handler) handleListTagsForResource(c *echo.Context, body []byte) error { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return h.writeError(c, http.StatusBadRequest, "SerializationException", "invalid request body") } diff --git a/services/organizations/handler_test.go b/services/organizations/handler_test.go index 3b3559145b..ba36cb0908 100644 --- a/services/organizations/handler_test.go +++ b/services/organizations/handler_test.go @@ -2,7 +2,8 @@ package organizations_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -38,7 +40,7 @@ func doRequest(t *testing.T, h *organizations.Handler, op string, body any) *htt if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -81,7 +83,7 @@ func newHandlerWithOrg(t *testing.T) (*organizations.Handler, string) { require.Equal(t, http.StatusOK, rec.Code) var rootsResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&rootsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &rootsResp, jsonx.V1())) roots := rootsResp["Roots"].([]any) rootID := roots[0].(map[string]any)["Id"].(string) @@ -99,7 +101,7 @@ func createAccountViaHandler(t *testing.T, h *organizations.Handler, name, email require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) status := resp["CreateAccountStatus"].(map[string]any) return status["AccountId"].(string) @@ -118,7 +120,7 @@ func createPolicyViaHandler(t *testing.T, h *organizations.Handler, name string) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) policy := resp["Policy"].(map[string]any) summary := policy["PolicySummary"].(map[string]any) diff --git a/services/organizations/organization_test.go b/services/organizations/organization_test.go index e87134f1ae..eeddca40df 100644 --- a/services/organizations/organization_test.go +++ b/services/organizations/organization_test.go @@ -1,13 +1,15 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -122,7 +124,7 @@ func TestHandler_CreateOrganization(t *testing.T) { if tt.wantOrgID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) org, ok := resp["Organization"].(map[string]any) require.True(t, ok, "response should contain Organization") assert.NotEmpty(t, org["Id"]) @@ -326,7 +328,7 @@ func TestAvailablePolicyTypes_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) org := resp["Organization"].(map[string]any) pts, ok := org["AvailablePolicyTypes"].([]any) @@ -549,7 +551,7 @@ func TestDeleteOrganization_WithMembers_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "OrganizationNotEmptyException", errResp["__type"]) } diff --git a/services/organizations/pagination_sort_totality_test.go b/services/organizations/pagination_sort_totality_test.go index 3156b5640a..2aa46eb3ac 100644 --- a/services/organizations/pagination_sort_totality_test.go +++ b/services/organizations/pagination_sort_totality_test.go @@ -1,12 +1,13 @@ package organizations_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -94,7 +95,7 @@ func TestListPoliciesSortIsTotal(t *testing.T) { ID string `json:"Id"` } `json:"Policies"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ids := make([]string, len(resp.Policies)) for i, p := range resp.Policies { diff --git a/services/organizations/paths_test.go b/services/organizations/paths_test.go index c1054c9425..a4f0a7191c 100644 --- a/services/organizations/paths_test.go +++ b/services/organizations/paths_test.go @@ -1,7 +1,7 @@ package organizations_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -189,10 +190,10 @@ func TestPathsDetachedOrCyclic(t *testing.T) { require.NotNil(t, data) snap := organizations.NewBackendSnapshot() - require.NoError(t, json.Unmarshal(data, snap)) + require.NoError(t, json.Unmarshal(data, snap, jsonx.V1())) snap.AccountParent[acctID] = "ou-doesnotexist-00000000" - corrupted, err := json.Marshal(snap) + corrupted, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) fresh := organizations.NewInMemoryBackend("000000000000", "us-east-1") @@ -219,10 +220,10 @@ func TestPathsDetachedOrCyclic(t *testing.T) { require.NotNil(t, data) snap := organizations.NewBackendSnapshot() - require.NoError(t, json.Unmarshal(data, snap)) + require.NoError(t, json.Unmarshal(data, snap, jsonx.V1())) var ous []organizations.OrganizationalUnit - require.NoError(t, json.Unmarshal(snap.Tables["ous"], &ous)) + require.NoError(t, json.Unmarshal(snap.Tables["ous"], &ous, jsonx.V1())) for i := range ous { switch ous[i].ID { @@ -233,14 +234,14 @@ func TestPathsDetachedOrCyclic(t *testing.T) { } } - rawOUs, err := json.Marshal(ous) + rawOUs, err := json.Marshal(ous, jsonx.V1()) require.NoError(t, err) snap.Tables["ous"] = rawOUs snap.OUParent[ouA.ID] = ouB.ID snap.OUParent[ouB.ID] = ouA.ID - corrupted, err := json.Marshal(snap) + corrupted, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) fresh := organizations.NewInMemoryBackend("000000000000", "us-east-1") diff --git a/services/organizations/persistence.go b/services/organizations/persistence.go index f55a4e9068..bed68e487c 100644 --- a/services/organizations/persistence.go +++ b/services/organizations/persistence.go @@ -2,11 +2,13 @@ package organizations import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -67,7 +69,7 @@ func newDirtyDTORegistry() (*store.Registry, *store.Table[delegatedAdminSnapshot // preserves that existing gap rather than fixing it as a side effect of the // datalayer swap. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Org *Organization `json:"org"` Root *Root `json:"root"` ResourcePolicy *ResourcePolicy `json:"resourcePolicy,omitempty"` @@ -159,7 +161,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { StatusCounter: b.statusCounter, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "organizations: failed to marshal snapshot", "error", err) @@ -229,7 +231,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreDelegatedAdminsLocked restores the "dirty" delegatedAdmins table // from tables via the ephemeral DTO registry, converting each DTO back to // its live *DelegatedAdmin. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDelegatedAdminsLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDelegatedAdminsLocked(tables map[string]jsontext.Value) error { dtoReg, daDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/organizations/persistence_test.go b/services/organizations/persistence_test.go index d167e9c099..7db4d5f1b3 100644 --- a/services/organizations/persistence_test.go +++ b/services/organizations/persistence_test.go @@ -1,12 +1,13 @@ package organizations_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -245,7 +246,7 @@ func TestPersistence_EnsureNonNilMaps(t *testing.T) { // After EnsureNonNilMaps all maps in the snapshot should be non-nil. // We can verify via round-trip: marshal+unmarshal should not panic. - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) assert.NotNil(t, data, tt.name) }) diff --git a/services/organizations/policies.go b/services/organizations/policies.go index eb307ce6f2..6fa76f43f7 100644 --- a/services/organizations/policies.go +++ b/services/organizations/policies.go @@ -2,8 +2,10 @@ package organizations import ( "cmp" - "encoding/json" + "encoding/json/jsontext" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const policyStatusEnabled = "ENABLED" @@ -103,7 +105,7 @@ func policyContentMaxSize(policyType string) (int, bool) { // MalformedPolicyDocumentException and documents exceeding the per-type size // quota with ConstraintViolationException(POLICY_CONTENT_LIMIT_EXCEEDED). func validatePolicyContent(content, policyType string) error { - if !json.Valid([]byte(content)) { + if !jsontext.Value([]byte(content)).IsValid(jsonx.V1()) { return ErrMalformedPolicyDocument } diff --git a/services/organizations/policies_test.go b/services/organizations/policies_test.go index 4d6b301258..73b0136636 100644 --- a/services/organizations/policies_test.go +++ b/services/organizations/policies_test.go @@ -1,7 +1,8 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "regexp" "strings" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -91,7 +93,7 @@ func TestHandler_EnablePolicyType(t *testing.T) { rootsRec := doRequest(t, h, "ListRoots", map[string]any{}) var rootsResp map[string]any - require.NoError(t, json.NewDecoder(rootsRec.Body).Decode(&rootsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rootsRec.Body), &rootsResp, jsonx.V1())) roots := rootsResp["Roots"].([]any) rootID := roots[0].(map[string]any)["Id"].(string) @@ -545,7 +547,7 @@ func TestDeletePolicy_Attached_ViaHandler(t *testing.T) { roots := doRequest(t, h, "ListRoots", nil) var rootsResp map[string]any - require.NoError(t, json.Unmarshal(roots.Body.Bytes(), &rootsResp)) + require.NoError(t, json.Unmarshal(roots.Body.Bytes(), &rootsResp, jsonx.V1())) rootID := rootsResp["Roots"].([]any)[0].(map[string]any)["Id"].(string) doRequest(t, h, "EnablePolicyType", map[string]any{"RootId": rootID, "PolicyType": "SERVICE_CONTROL_POLICY"}) @@ -559,7 +561,7 @@ func TestDeletePolicy_Attached_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, cp.Code) var cpResp map[string]any - require.NoError(t, json.Unmarshal(cp.Body.Bytes(), &cpResp)) + require.NoError(t, json.Unmarshal(cp.Body.Bytes(), &cpResp, jsonx.V1())) policyID := cpResp["Policy"].(map[string]any)["PolicySummary"].(map[string]any)["Id"].(string) doRequest(t, h, "AttachPolicy", map[string]any{"PolicyId": policyID, "TargetId": rootID}) @@ -568,7 +570,7 @@ func TestDeletePolicy_Attached_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "PolicyInUseException", errResp["__type"]) } @@ -689,7 +691,7 @@ func TestDisablePolicyType_WithAttached_ViaHandler(t *testing.T) { roots := doRequest(t, h, "ListRoots", nil) var rootsResp map[string]any - require.NoError(t, json.Unmarshal(roots.Body.Bytes(), &rootsResp)) + require.NoError(t, json.Unmarshal(roots.Body.Bytes(), &rootsResp, jsonx.V1())) rootID := rootsResp["Roots"].([]any)[0].(map[string]any)["Id"].(string) doRequest(t, h, "EnablePolicyType", map[string]any{"RootId": rootID, "PolicyType": "TAG_POLICY"}) @@ -703,7 +705,7 @@ func TestDisablePolicyType_WithAttached_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, cp.Code) var cpResp map[string]any - require.NoError(t, json.Unmarshal(cp.Body.Bytes(), &cpResp)) + require.NoError(t, json.Unmarshal(cp.Body.Bytes(), &cpResp, jsonx.V1())) policyID := cpResp["Policy"].(map[string]any)["PolicySummary"].(map[string]any)["Id"].(string) doRequest(t, h, "AttachPolicy", map[string]any{"PolicyId": policyID, "TargetId": rootID}) @@ -712,7 +714,7 @@ func TestDisablePolicyType_WithAttached_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConstraintViolationException", errResp["__type"]) } @@ -989,7 +991,7 @@ func TestCreatePolicy_MalformedContent_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "MalformedPolicyDocumentException", errResp["__type"]) } diff --git a/services/organizations/policy_attachments_test.go b/services/organizations/policy_attachments_test.go index 1136def74e..c1390403a0 100644 --- a/services/organizations/policy_attachments_test.go +++ b/services/organizations/policy_attachments_test.go @@ -1,7 +1,8 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -418,7 +420,7 @@ func TestAttachPolicy_UnknownTarget_ViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var polResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&polResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &polResp, jsonx.V1())) policyID := polResp["Policy"].(map[string]any)["PolicySummary"].(map[string]any)["Id"].(string) // Attempt to attach to a non-existent target. diff --git a/services/organizations/resource_policy_test.go b/services/organizations/resource_policy_test.go index 34c9e64670..d6ee82f30b 100644 --- a/services/organizations/resource_policy_test.go +++ b/services/organizations/resource_policy_test.go @@ -2,7 +2,8 @@ package organizations_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -254,7 +256,7 @@ func TestHandler_ResourcePolicy(t *testing.T) { if tt.op == "DescribeResourcePolicy" && tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) rp, ok := resp["ResourcePolicy"].(map[string]any) require.True(t, ok, "response must have ResourcePolicy") assert.NotEmpty(t, rp["Content"]) @@ -374,7 +376,7 @@ func TestPutResourcePolicy_Happy(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotNil(t, resp["ResourcePolicy"]) }) } diff --git a/services/organizations/roots_test.go b/services/organizations/roots_test.go index b8b9e9c91c..f955d95239 100644 --- a/services/organizations/roots_test.go +++ b/services/organizations/roots_test.go @@ -1,12 +1,15 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListRoots tests the HTTP handler for ListRoots. @@ -38,7 +41,7 @@ func TestHandler_ListRoots(t *testing.T) { if tt.wantRoots { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) roots, ok := resp["Roots"].([]any) require.True(t, ok, "response should contain Roots") assert.NotEmpty(t, roots) diff --git a/services/organizations/scp_test.go b/services/organizations/scp_test.go index 21c0fc92c8..89a3264ab6 100644 --- a/services/organizations/scp_test.go +++ b/services/organizations/scp_test.go @@ -2,12 +2,14 @@ package organizations_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/iam" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -124,11 +126,11 @@ func TestRestoreMigratesMissingDefaultSCPs(t *testing.T) { o := newSCPOrg(t) - var snap map[string]json.RawMessage - require.NoError(t, json.Unmarshal(o.b.Snapshot(context.Background()), &snap)) + var snap map[string]jsontext.Value + require.NoError(t, json.Unmarshal(o.b.Snapshot(context.Background()), &snap, jsonx.V1())) oldShape := func(key string, v any) { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) snap[key] = raw @@ -137,7 +139,7 @@ func TestRestoreMigratesMissingDefaultSCPs(t *testing.T) { oldShape("targetPolicies", map[string][]string{o.rootID: {"p-FullAWSAccess"}}) oldShape("policyTargets", map[string][]string{"p-FullAWSAccess": {o.rootID}}) - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) require.NoError(t, err) restored := organizations.NewInMemoryBackend("000000000000", "us-east-1") diff --git a/services/organizations/service_access_test.go b/services/organizations/service_access_test.go index 3e108f3e42..79bc281f8b 100644 --- a/services/organizations/service_access_test.go +++ b/services/organizations/service_access_test.go @@ -1,12 +1,15 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestBackend_ServiceAccess tests enabling/disabling AWS service access. @@ -181,7 +184,7 @@ func TestServiceAccess_Handler(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &resp, jsonx.V1())) principals := resp["EnabledServicePrincipals"].([]any) assert.Empty(t, principals, "disabled service should not appear") diff --git a/services/organizations/tags_test.go b/services/organizations/tags_test.go index 5842a509c8..0a0bd20058 100644 --- a/services/organizations/tags_test.go +++ b/services/organizations/tags_test.go @@ -1,7 +1,8 @@ package organizations_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/organizations" ) @@ -325,7 +327,7 @@ func TestTagOps_NonExistentResource_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "TargetNotFoundException", errResp["__type"], "%s on unknown resource must return TargetNotFoundException", tt.name) }) @@ -357,7 +359,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&descResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &descResp, jsonx.V1())) org := descResp["Organization"].(map[string]any) orgID := org["Id"].(string) @@ -373,7 +375,7 @@ func TestHandler_TagOperations(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var tagsResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&tagsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &tagsResp, jsonx.V1())) tags, ok := tagsResp["Tags"].([]any) require.True(t, ok) assert.NotEmpty(t, tags) @@ -635,7 +637,7 @@ func TestTagResource_ReservedPrefix_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "InvalidInputException", errResp["__type"]) } diff --git a/services/outposts/capacity_ledger_delete_cleanup_test.go b/services/outposts/capacity_ledger_delete_cleanup_test.go index bcc2ac96a4..00e689b6c5 100644 --- a/services/outposts/capacity_ledger_delete_cleanup_test.go +++ b/services/outposts/capacity_ledger_delete_cleanup_test.go @@ -1,17 +1,20 @@ package outposts_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" outpostssdk "github.com/aws/aws-sdk-go-v2/service/outposts" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // snapshotTables is a local decode of backendSnapshot's exported shape, // just enough to inspect one table's raw contents in a black-box test. type snapshotTables struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` } // TestDeleteOutpost_CleansRunningInstanceLedger guards against a ghost-row @@ -52,7 +55,7 @@ func TestDeleteOutpost_CleansRunningInstanceLedger(t *testing.T) { require.NoError(t, err) var snap snapshotTables - require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &snap)) + require.NoError(t, json.Unmarshal(h.Snapshot(t.Context()), &snap, jsonx.V1())) require.NotContains(t, string(snap.Tables["runningInstances"]), leakedInstanceID, "DeleteOutpost must clean up runningInstances rows for its own Asset(s), not leak them") diff --git a/services/outposts/handler.go b/services/outposts/handler.go index fdb1152d8b..907b3ea887 100644 --- a/services/outposts/handler.go +++ b/services/outposts/handler.go @@ -2,7 +2,7 @@ package outposts import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -576,7 +577,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { status, errType = http.StatusNotFound, "NotFoundException" } - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) @@ -662,7 +663,7 @@ func decodeJSONBody(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validationError("malformed request body: " + err.Error()) } @@ -672,7 +673,7 @@ func decodeJSONBody(body []byte, v any) error { // marshalResponse marshals v, wrapping any error as an internal failure // (marshaling our own well-typed wire structs should never fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, fmt.Errorf("outposts: marshal response: %w", err) } diff --git a/services/outposts/multi_region_test.go b/services/outposts/multi_region_test.go index 9e8d9f5237..c3d699d069 100644 --- a/services/outposts/multi_region_test.go +++ b/services/outposts/multi_region_test.go @@ -2,7 +2,8 @@ package outposts_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/outposts" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/outposts/persistence.go b/services/outposts/persistence.go index d3b130eb5c..c0b8856888 100644 --- a/services/outposts/persistence.go +++ b/services/outposts/persistence.go @@ -2,7 +2,7 @@ package outposts import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -19,10 +19,10 @@ const outpostsSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Outposts backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/personalize/handler.go b/services/personalize/handler.go index 11ec7f9348..b00620272a 100644 --- a/services/personalize/handler.go +++ b/services/personalize/handler.go @@ -2,7 +2,7 @@ package personalize import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -230,7 +231,7 @@ func writeRuntimeRESTError(c *echo.Context, status int, errType, message string) c.Response().Header().Set("Content-Type", personalizeRuntimeContentType) c.Response().Header().Set("X-Amzn-Errortype", errType) - payload, err := json.Marshal(map[string]string{"message": message}) + payload, err := json.Marshal(map[string]string{"message": message}, jsonx.V1()) if err != nil { return err } @@ -263,7 +264,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt var input map[string]any if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON", ErrValidation) } } @@ -280,7 +281,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt return nil, err } - return json.Marshal(output) + return json.Marshal(output, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { @@ -302,7 +303,10 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err c.Response().Header().Set("Content-Type", personalizeContentType) - payload, marshalErr := json.Marshal(map[string]string{"__type": errType, "message": errorMessage(errType, err)}) + payload, marshalErr := json.Marshal( + map[string]string{"__type": errType, "message": errorMessage(errType, err)}, + jsonx.V1(), + ) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } @@ -497,13 +501,13 @@ func decodeConfig[T any](raw map[string]any) *T { return nil } - b, marshalErr := json.Marshal(raw) + b, marshalErr := json.Marshal(raw, jsonx.V1()) if marshalErr != nil { return nil } var cfg T - if err := json.Unmarshal(b, &cfg); err != nil { + if err := json.Unmarshal(b, &cfg, jsonx.V1()); err != nil { return nil } diff --git a/services/personalize/handler_test.go b/services/personalize/handler_test.go index b4a6c14d4f..b2bba336c2 100644 --- a/services/personalize/handler_test.go +++ b/services/personalize/handler_test.go @@ -1,7 +1,7 @@ package personalize_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/personalize" ) @@ -52,7 +53,7 @@ func personalizeDoWithPrefix( ) *httptest.ResponseRecorder { t.Helper() - payload, err := json.Marshal(body) + payload, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -70,7 +71,7 @@ func personalizeUnmarshal(t *testing.T, rec *httptest.ResponseRecorder) map[stri t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/personalize/multi_region_test.go b/services/personalize/multi_region_test.go index f7b552ec40..95a0f1ecab 100644 --- a/services/personalize/multi_region_test.go +++ b/services/personalize/multi_region_test.go @@ -2,7 +2,8 @@ package personalize_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/personalize" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/personalize/persistence.go b/services/personalize/persistence.go index 367e62bee6..752b95427c 100644 --- a/services/personalize/persistence.go +++ b/services/personalize/persistence.go @@ -2,7 +2,7 @@ package personalize import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -38,7 +38,7 @@ const personalizeSnapshotVersion = 1 // *T, so it does not fit store.Table's keyed-by-identity-value shape) and is // persisted here directly instead. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountId"` Region string `json:"region"` diff --git a/services/personalize/validate.go b/services/personalize/validate.go index 7b4f839711..e630bc2022 100644 --- a/services/personalize/validate.go +++ b/services/personalize/validate.go @@ -2,11 +2,13 @@ package personalize import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -44,7 +46,7 @@ func validateSchemaDocument(schema string) error { } var doc map[string]any - if err := json.Unmarshal([]byte(schema), &doc); err != nil { + if err := json.Unmarshal([]byte(schema), &doc, jsonx.V1()); err != nil { return fmt.Errorf("%w: schema is not valid JSON", ErrValidation) } diff --git a/services/pinpoint/applications_settings_test.go b/services/pinpoint/applications_settings_test.go index 855e991429..c529116937 100644 --- a/services/pinpoint/applications_settings_test.go +++ b/services/pinpoint/applications_settings_test.go @@ -1,12 +1,15 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestApplicationSettings_BodyPersistence(t *testing.T) { @@ -82,7 +85,7 @@ func TestApplicationSettings_BodyPersistence(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) // Update settings. @@ -90,14 +93,14 @@ func TestApplicationSettings_BodyPersistence(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) // GET settings back. getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/settings", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) if tc.wantCampaignHook { hook, ok := getResp["CampaignHook"].(map[string]any) @@ -169,7 +172,7 @@ func TestApplicationSettings_CloudWatchMetrics(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "CloudWatchMetricsEnabled") }) } @@ -229,14 +232,14 @@ func TestApplicationSettings_EventTaggingCases(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) assert.NotContains(t, putResp, "EventTaggingEnabled") getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/settings", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, getResp, "EventTaggingEnabled") }) } @@ -307,7 +310,7 @@ func TestApplicationSettings_LimitsCases(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) limits, ok := resp["Limits"].(map[string]any) require.True(t, ok, "Limits must be an object") assert.InDelta(t, tc.checkVal, limits[tc.checkKey], 0.001) @@ -345,7 +348,7 @@ func TestApplicationDateRangeKpi(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, tc.kpiName, resp["KpiName"]) assert.NotNil(t, resp["KpiResult"]) @@ -464,7 +467,7 @@ func TestApplicationSettings_FullRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) assert.NotContains(t, putResp, "CloudWatchMetricsEnabled") assert.NotContains(t, putResp, "EventTaggingEnabled") assert.NotNil(t, putResp["CampaignHook"]) @@ -476,7 +479,7 @@ func TestApplicationSettings_FullRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, getResp, "CloudWatchMetricsEnabled") assert.NotContains(t, getResp, "EventTaggingEnabled") }) @@ -505,7 +508,7 @@ func TestApplicationSettings_CampaignHook(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var settings map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings, jsonx.V1())) gotHook := settings["CampaignHook"].(map[string]any) assert.Equal(t, "arn:aws:lambda:us-east-1:123456789012:function:SegmentHook", gotHook["LambdaFunctionName"]) @@ -547,7 +550,7 @@ func TestApplicationSettings_QuietTime(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var settings map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings, jsonx.V1())) qt := settings["QuietTime"].(map[string]any) assert.Equal(t, tc.wantStart, qt["Start"]) @@ -577,7 +580,7 @@ func TestApplicationSettings_Limits(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var settings map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings, jsonx.V1())) got := settings["Limits"].(map[string]any) assert.EqualValues(t, 500, got["Daily"]) @@ -620,7 +623,7 @@ func TestApplicationSettings_MultipleUpdates(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var settings map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings, jsonx.V1())) assert.NotContains(t, settings, "CloudWatchMetricsEnabled") @@ -648,7 +651,7 @@ func TestApplicationSettings_EventTagging(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var settings map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &settings, jsonx.V1())) assert.NotContains(t, settings, "EventTaggingEnabled") assert.NotContains(t, settings, "CloudWatchMetricsEnabled") @@ -690,7 +693,7 @@ func TestHandler_ApplicationSettings(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var appResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&appResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &appResp, jsonx.V1())) appID, _ := appResp["Id"].(string) require.NotEmpty(t, appID) @@ -706,7 +709,7 @@ func TestHandler_ApplicationSettings(t *testing.T) { assert.Equal(t, tt.wantStatus, rec2.Code) var settingsResp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&settingsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &settingsResp, jsonx.V1())) assert.Equal(t, appID, settingsResp["ApplicationId"]) // CampaignHook, Limits, and QuietTime must be non-nil empty objects so // the Terraform provider flatten helpers don't panic on nil dereferences. @@ -753,19 +756,19 @@ func TestAppSettings_LastModifiedDate_ConsistentAfterUpdate(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) putDate, _ := putResp["LastModifiedDate"].(string) require.NotEmpty(t, putDate, "PUT must return non-empty LastModifiedDate") getRec1 := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/settings", nil) require.Equal(t, http.StatusOK, getRec1.Code) var getResp1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1, jsonx.V1())) getRec2 := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/settings", nil) require.Equal(t, http.StatusOK, getRec2.Code) var getResp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2, jsonx.V1())) date1, _ := getResp1["LastModifiedDate"].(string) date2, _ := getResp2["LastModifiedDate"].(string) @@ -786,7 +789,7 @@ func TestAppSettings_LastModifiedDate_UpdatesOnSecondPut(t *testing.T) { map[string]any{"CloudWatchMetricsEnabled": true}) require.Equal(t, http.StatusOK, put1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(put1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(put1.Body.Bytes(), &r1, jsonx.V1())) date1, _ := r1["LastModifiedDate"].(string) require.NotEmpty(t, date1) @@ -795,7 +798,7 @@ func TestAppSettings_LastModifiedDate_UpdatesOnSecondPut(t *testing.T) { map[string]any{"EventTaggingEnabled": true}) require.Equal(t, http.StatusOK, put2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(put2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(put2.Body.Bytes(), &r2, jsonx.V1())) date2, _ := r2["LastModifiedDate"].(string) require.NotEmpty(t, date2) @@ -803,7 +806,7 @@ func TestAppSettings_LastModifiedDate_UpdatesOnSecondPut(t *testing.T) { getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/settings", nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) getDate, _ := getResp["LastModifiedDate"].(string) assert.Equal(t, date2, getDate, "GET must return the most recent PUT's LastModifiedDate") diff --git a/services/pinpoint/apps_test.go b/services/pinpoint/apps_test.go index de86fe4eee..b2ef7bbd34 100644 --- a/services/pinpoint/apps_test.go +++ b/services/pinpoint/apps_test.go @@ -1,13 +1,16 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestBackend_CreateApp(t *testing.T) { @@ -284,7 +287,7 @@ func TestAppNotFound_Returns404(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "NotFoundException", resp["__type"]) }) } @@ -301,7 +304,7 @@ func TestCreateApp_ResponseShape(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"], "Id must be present") assert.NotEmpty(t, resp["Arn"], "Arn must be present") @@ -325,7 +328,7 @@ func TestGetApps_ReflectsCreatedApps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) item := resp["Item"].([]any) assert.Len(t, item, len(names)) @@ -344,7 +347,7 @@ func TestDeleteApp_RemovesFromList(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) appID := created["Id"].(string) doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "also-here"}) @@ -358,7 +361,7 @@ func TestDeleteApp_RemovesFromList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) item := listResp["Item"].([]any) assert.Len(t, item, 1) } @@ -377,7 +380,7 @@ func TestHandlerReset(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Empty(t, items) } @@ -439,7 +442,7 @@ func TestHandler_CreateApp(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.NotEmpty(t, resp["Arn"]) } @@ -480,7 +483,7 @@ func TestHandler_GetApp(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) appID, _ = resp["Id"].(string) } else { @@ -527,7 +530,7 @@ func TestHandler_DeleteApp(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) appID, _ = resp["Id"].(string) } else { @@ -580,7 +583,7 @@ func TestHandler_GetApps(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tt.wantCount) @@ -649,7 +652,7 @@ func TestHandler_GetAppsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, ok := resp["Item"].([]any) if tt.wantCount > 0 { @@ -679,7 +682,7 @@ func TestHandler_GetAppsContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.NewDecoder(rec1.Body).Decode(&resp1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec1.Body), &resp1, jsonx.V1())) page1, ok := resp1["Item"].([]any) require.True(t, ok, "expected Item array in first-page response") @@ -695,7 +698,7 @@ func TestHandler_GetAppsContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&resp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &resp2, jsonx.V1())) page2, ok := resp2["Item"].([]any) require.True(t, ok, "expected Item array in second-page response") @@ -740,7 +743,7 @@ func TestHandler_GetApps_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) created[resp["Id"].(string)] = true } @@ -752,7 +755,7 @@ func TestHandler_GetApps_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) for _, item := range items { diff --git a/services/pinpoint/attributes_test.go b/services/pinpoint/attributes_test.go index 365ed0a2d4..e87d0aadce 100644 --- a/services/pinpoint/attributes_test.go +++ b/services/pinpoint/attributes_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestAudit6_RemoveAttributes_RemovesBlacklistedNamesOnly verifies @@ -36,14 +38,14 @@ func TestRemoveAttributes_RemovesBlacklistedNamesOnly(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "endpoint-custom-attributes", resp["AttributeType"]) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/endpoints/ep-1", nil) require.Equal(t, http.StatusOK, getRec.Code) var ep map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep, jsonx.V1())) attrs, _ := ep["Attributes"].(map[string]any) _, hasInterests := attrs["Interests"] diff --git a/services/pinpoint/campaigns_fields_test.go b/services/pinpoint/campaigns_fields_test.go index 6ce470a3c4..85119a1c42 100644 --- a/services/pinpoint/campaigns_fields_test.go +++ b/services/pinpoint/campaigns_fields_test.go @@ -2,7 +2,7 @@ package pinpoint_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCampaign_AdditionalTreatments(t *testing.T) { @@ -108,7 +110,7 @@ func TestCampaign_AdditionalTreatments(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantHasTreatment { treatments, _ := resp["AdditionalTreatments"].([]any) @@ -203,7 +205,7 @@ func TestCampaign_StateMachine(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) campaignID := createResp["Id"].(string) if tc.updateBody == nil { @@ -219,7 +221,7 @@ func TestCampaign_StateMachine(t *testing.T) { assert.Equal(t, tc.wantStatus, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) state, _ := updateResp["State"].(map[string]any) require.NotNil(t, state) @@ -258,7 +260,7 @@ func TestCampaign_DateRangeKpi(t *testing.T) { map[string]any{"Name": "kpi-camp", "SegmentId": "seg-1"}) require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campaignID := cr["Id"].(string) kpiRec := doPinpointRequest(t, h, http.MethodGet, @@ -266,7 +268,7 @@ func TestCampaign_DateRangeKpi(t *testing.T) { require.Equal(t, http.StatusOK, kpiRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(kpiRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(kpiRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, campaignID, resp["CampaignId"]) assert.Equal(t, tc.kpiName, resp["KpiName"]) @@ -314,7 +316,7 @@ func TestCampaign_GetActivities(t *testing.T) { "/v1/apps/"+appID+"/campaigns", tc.campaignBody) require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campaignID := cr["Id"].(string) actRec := doPinpointRequest(t, h, http.MethodGet, @@ -322,7 +324,7 @@ func TestCampaign_GetActivities(t *testing.T) { require.Equal(t, http.StatusOK, actRec.Code) var actResp map[string]any - require.NoError(t, json.Unmarshal(actRec.Body.Bytes(), &actResp)) + require.NoError(t, json.Unmarshal(actRec.Body.Bytes(), &actResp, jsonx.V1())) assert.Contains(t, actResp, "Item") }) } @@ -380,7 +382,7 @@ func TestCampaign_SegmentVersion(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantSegVersion != 0 { assert.InDelta(t, tc.wantSegVersion, resp["SegmentVersion"], 0.001) } else { @@ -457,14 +459,14 @@ func TestCampaign_TemplateConfiguration_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campID, nil) require.Equal(t, http.StatusOK, getRec.Code) var c map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c, jsonx.V1())) tmplCfg, ok := c["TemplateConfiguration"].(map[string]any) assert.True(t, ok, "TemplateConfiguration must be present") @@ -502,14 +504,14 @@ func TestCampaign_Limits_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campID, nil) require.Equal(t, http.StatusOK, getRec.Code) var c map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c, jsonx.V1())) got := c["Limits"].(map[string]any) assert.EqualValues(t, 200, got["Daily"]) @@ -540,14 +542,14 @@ func TestCampaign_Hook_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campID, nil) require.Equal(t, http.StatusOK, getRec.Code) var c map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c, jsonx.V1())) got := c["Hook"].(map[string]any) assert.Equal(t, "arn:aws:lambda:us-east-1:123456789012:function:SegmentFilter", got["LambdaFunctionName"]) @@ -566,7 +568,7 @@ func TestCampaign_TemplateConfiguration_OnUpdate(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campID := cr["Id"].(string) // Update to set template configuration @@ -584,7 +586,7 @@ func TestCampaign_TemplateConfiguration_OnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var c map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c, jsonx.V1())) tmpl := c["TemplateConfiguration"].(map[string]any) email := tmpl["EmailTemplate"].(map[string]any) @@ -617,14 +619,14 @@ func TestCampaign_CustomDeliveryConfiguration(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) campID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campID, nil) require.Equal(t, http.StatusOK, getRec.Code) var c map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &c, jsonx.V1())) got := c["CustomDeliveryConfiguration"].(map[string]any) assert.Equal(t, "arn:aws:lambda:us-east-1:123456789012:function:CustomDelivery", got["DeliveryUri"]) @@ -655,7 +657,7 @@ func TestPagination_Campaigns_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, p1Rec.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1, jsonx.V1())) items1, _ := p1["Item"].([]any) assert.Len(t, items1, 2, "page 1 should contain 2 items") @@ -669,7 +671,7 @@ func TestPagination_Campaigns_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, p2Rec.Code) var p2 map[string]any - require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2, jsonx.V1())) items2, _ := p2["Item"].([]any) assert.Len(t, items2, 2, "page 2 should contain 2 items") @@ -682,7 +684,7 @@ func TestPagination_Campaigns_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, p3Rec.Code) var p3 map[string]any - require.NoError(t, json.Unmarshal(p3Rec.Body.Bytes(), &p3)) + require.NoError(t, json.Unmarshal(p3Rec.Body.Bytes(), &p3, jsonx.V1())) items3, _ := p3["Item"].([]any) assert.Len(t, items3, 1, "page 3 should contain the remaining 1 item") @@ -707,7 +709,7 @@ func TestPagination_Campaigns_NoToken_ReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, 3) @@ -732,7 +734,7 @@ func TestPagination_TokenEncoding(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tok, _ := resp["NextToken"].(string) require.NotEmpty(t, tok) @@ -756,7 +758,7 @@ func TestPagination_EmptyList_NoToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Empty(t, items) @@ -778,7 +780,7 @@ func TestGetCampaignVersions_CrossAppIsolation(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) campaignID, _ := createResp["Id"].(string) require.NotEmpty(t, campaignID) diff --git a/services/pinpoint/campaigns_test.go b/services/pinpoint/campaigns_test.go index fa1bc26fe3..9ad07e45cb 100644 --- a/services/pinpoint/campaigns_test.go +++ b/services/pinpoint/campaigns_test.go @@ -1,13 +1,16 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCampaignFullDTO_Create(t *testing.T) { @@ -169,7 +172,7 @@ func TestCampaignFullDTO_Create(t *testing.T) { require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", tc.body) @@ -180,7 +183,7 @@ func TestCampaignFullDTO_Create(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, appID, resp["ApplicationId"]) @@ -299,13 +302,13 @@ func TestCampaignStateMachine_Update(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", tc.createBody) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) campaignID := created["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPut, "/v1/apps/"+appID+"/campaigns/"+campaignID, tc.updateBody) @@ -316,7 +319,7 @@ func TestCampaignStateMachine_Update(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantState != "" { state, _ := resp["State"].(map[string]any) @@ -342,14 +345,14 @@ func TestCampaignVersioning(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", map[string]any{"Name": "versioned", "SegmentId": "seg-1"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) campaignID := created["Id"].(string) // Do 3 updates. @@ -363,7 +366,7 @@ func TestCampaignVersioning(t *testing.T) { verRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campaignID+"/versions", nil) require.Equal(t, http.StatusOK, verRec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp, jsonx.V1())) items, _ := verResp["Item"].([]any) assert.Len(t, items, 4, "should have 4 versions") @@ -371,7 +374,7 @@ func TestCampaignVersioning(t *testing.T) { v1Rec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/campaigns/"+campaignID+"/versions/1", nil) require.Equal(t, http.StatusOK, v1Rec.Code) var v1Resp map[string]any - require.NoError(t, json.Unmarshal(v1Rec.Body.Bytes(), &v1Resp)) + require.NoError(t, json.Unmarshal(v1Rec.Body.Bytes(), &v1Resp, jsonx.V1())) assert.InDelta(t, float64(1), v1Resp["Version"], 0.001) } @@ -387,14 +390,14 @@ func TestCampaignPauseUnpauseCycle(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", map[string]any{"Name": "cycle", "SegmentId": "seg-1"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) campaignID := created["Id"].(string) // Check initial state. @@ -406,7 +409,7 @@ func TestCampaignPauseUnpauseCycle(t *testing.T) { map[string]any{"IsPaused": true}) require.Equal(t, http.StatusOK, pauseRec.Code) var paused map[string]any - require.NoError(t, json.Unmarshal(pauseRec.Body.Bytes(), &paused)) + require.NoError(t, json.Unmarshal(pauseRec.Body.Bytes(), &paused, jsonx.V1())) pausedState, _ := paused["State"].(map[string]any) assert.Equal(t, "PAUSED", pausedState["CampaignStatus"]) @@ -415,7 +418,7 @@ func TestCampaignPauseUnpauseCycle(t *testing.T) { map[string]any{"IsPaused": false}) require.Equal(t, http.StatusOK, unpauseRec.Code) var unpaused map[string]any - require.NoError(t, json.Unmarshal(unpauseRec.Body.Bytes(), &unpaused)) + require.NoError(t, json.Unmarshal(unpauseRec.Body.Bytes(), &unpaused, jsonx.V1())) unpausedState, _ := unpaused["State"].(map[string]any) assert.Equal(t, "SCHEDULED", unpausedState["CampaignStatus"]) } @@ -440,7 +443,7 @@ func TestCampaign_ScheduleFrequencyRoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", @@ -455,7 +458,7 @@ func TestCampaign_ScheduleFrequencyRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedule, _ := resp["Schedule"].(map[string]any) assert.Equal(t, freq, schedule["Frequency"]) @@ -471,7 +474,7 @@ func TestCampaign_AdditionalTreatments_RoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/campaigns", @@ -495,7 +498,7 @@ func TestCampaign_AdditionalTreatments_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) treatments, _ := resp["AdditionalTreatments"].([]any) assert.Len(t, treatments, 2) @@ -516,7 +519,7 @@ func TestCampaignCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) campaignID, _ := createResp["Id"].(string) require.NotEmpty(t, campaignID) @@ -626,7 +629,7 @@ func TestHandler_CreateCampaign(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, appID, resp["ApplicationId"]) @@ -652,7 +655,7 @@ func TestGetCampaignVersion_UnknownVersionNotFound(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.NewDecoder(createRec.Body).Decode(&created)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(createRec.Body), &created, jsonx.V1())) campaignID, _ := created["Id"].(string) require.NotEmpty(t, campaignID) @@ -668,7 +671,7 @@ func TestGetCampaignVersion_UnknownVersionNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, missingRec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(missingRec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(missingRec.Body), &errResp, jsonx.V1())) assert.Equal(t, "NotFoundException", errResp["__type"]) } @@ -696,7 +699,7 @@ func TestHandler_GetCampaigns_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) created[resp["Id"].(string)] = true } @@ -708,7 +711,7 @@ func TestHandler_GetCampaigns_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) for _, item := range items { diff --git a/services/pinpoint/channel_credential_echo_test.go b/services/pinpoint/channel_credential_echo_test.go index ee1740ef02..33a4e5039c 100644 --- a/services/pinpoint/channel_credential_echo_test.go +++ b/services/pinpoint/channel_credential_echo_test.go @@ -1,7 +1,7 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -207,7 +208,7 @@ func TestGetAdmChannel_NoRawSecretInBody(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &body, jsonx.V1())) assert.NotContains(t, body, "ClientId") assert.NotContains(t, body, "ClientSecret") @@ -231,7 +232,7 @@ func TestGetApnsChannel_NoRawSecretInBody(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &body, jsonx.V1())) for _, k := range []string{"BundleId", "Certificate", "TeamId", "TokenKey", "TokenKeyId"} { assert.NotContains(t, body, k) diff --git a/services/pinpoint/channels_test.go b/services/pinpoint/channels_test.go index 7048dfa951..c8d40ab382 100644 --- a/services/pinpoint/channels_test.go +++ b/services/pinpoint/channels_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestChannel_PerTypeFields(t *testing.T) { @@ -129,7 +131,7 @@ func TestChannel_PerTypeFields(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPut, @@ -141,7 +143,7 @@ func TestChannel_PerTypeFields(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantEnabled, resp["Enabled"]) assert.NotEmpty(t, resp["CreationDate"]) @@ -160,7 +162,7 @@ func TestChannel_PerTypeFields(t *testing.T) { "/v1/apps/"+appID+"/channels/"+tc.channelType, nil) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, resp["Enabled"], getResp["Enabled"]) assert.Equal(t, resp["HasCredential"], getResp["HasCredential"]) }) @@ -175,7 +177,7 @@ func TestChannel_VersionIncrements(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) for i := 1; i <= 3; i++ { @@ -184,7 +186,7 @@ func TestChannel_VersionIncrements(t *testing.T) { map[string]any{"ApiKey": "key", "Enabled": true}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InDelta(t, float64(i), resp["Version"], 0.001, "version should increment on each put") } } @@ -197,7 +199,7 @@ func TestChannel_CreationDatePreserved(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec1 := doPinpointRequest(t, h, http.MethodPut, @@ -205,7 +207,7 @@ func TestChannel_CreationDatePreserved(t *testing.T) { map[string]any{"ApiKey": "key", "Enabled": true}) require.Equal(t, http.StatusOK, rec1.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) creationDate1 := r1["CreationDate"].(string) rec2 := doPinpointRequest(t, h, http.MethodPut, @@ -213,7 +215,7 @@ func TestChannel_CreationDatePreserved(t *testing.T) { map[string]any{"ApiKey": "key-updated", "Enabled": true}) require.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) creationDate2 := r2["CreationDate"].(string) assert.Equal(t, creationDate1, creationDate2, "CreationDate should be preserved on update") @@ -231,7 +233,7 @@ func TestChannel_AllChannelsListed(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) channelTypes := []string{"gcm", "email", "sms"} @@ -250,7 +252,7 @@ func TestChannel_AllChannelsListed(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) channels, _ := listResp["Channels"].(map[string]any) assert.Len(t, channels, 3) @@ -324,7 +326,7 @@ func TestSMSChannel_Attributes(t *testing.T) { "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantEnabled, resp["Enabled"]) getRec := doPinpointRequest(t, h, http.MethodGet, @@ -332,7 +334,7 @@ func TestSMSChannel_Attributes(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr, jsonx.V1())) chType, _ := gr["ChannelType"].(string) assert.True(t, chType == "SMS" || chType == "sms", "expected SMS channel type, got %q", chType) }) @@ -412,7 +414,7 @@ func TestEmailChannel_Identity(t *testing.T) { "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantEnabled, resp["Enabled"]) chType, _ := resp["ChannelType"].(string) assert.True(t, chType == "EMAIL" || chType == "email", "expected EMAIL channel type, got %q", chType) @@ -468,7 +470,7 @@ func TestChannel_Delete_ReturnsChannel(t *testing.T) { require.Equal(t, http.StatusOK, delRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, tc.channelType, ch["ChannelType"]) }) } @@ -495,7 +497,7 @@ func TestGetChannels_IncludesExtraFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) channels, ok := resp["Channels"].(map[string]any) require.True(t, ok, "Channels must be a map") diff --git a/services/pinpoint/channels_types_test.go b/services/pinpoint/channels_types_test.go index d827e89af7..f54fedf3d5 100644 --- a/services/pinpoint/channels_types_test.go +++ b/services/pinpoint/channels_types_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestChannel_APNS_CertificateAuth(t *testing.T) { @@ -28,7 +30,7 @@ func TestChannel_APNS_CertificateAuth(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, true, ch["Enabled"]) assert.Equal(t, true, ch["HasCredential"], "certificate auth sets HasCredential") @@ -56,7 +58,7 @@ func TestChannel_APNS_TokenAuth(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, true, ch["Enabled"]) // BundleId sets HasCredential; TokenKey sets HasTokenKey @@ -83,7 +85,7 @@ func TestChannel_APNS_Sandbox(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "apns_sandbox", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -108,7 +110,7 @@ func TestChannel_ADM_HasCredential(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "adm", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -133,7 +135,7 @@ func TestChannel_Baidu_HasCredential(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "baidu", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -158,7 +160,7 @@ func TestChannel_GCM_ServiceJSON(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "gcm", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -180,7 +182,7 @@ func TestChannel_Voice_EnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "voice", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -196,7 +198,7 @@ func TestChannel_Voice_EnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var ch2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &ch2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &ch2, jsonx.V1())) assert.Equal(t, false, ch2["Enabled"]) assert.EqualValues(t, 2, ch2["Version"]) @@ -216,7 +218,7 @@ func TestChannel_InApp_EnableDisable(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, true, ch["Enabled"]) } @@ -239,7 +241,7 @@ func TestChannel_SMS_ShortCode(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ch map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ch, jsonx.V1())) assert.Equal(t, "sms", ch["ChannelType"]) assert.Equal(t, true, ch["Enabled"]) @@ -304,7 +306,7 @@ func TestSMSChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) assert.Equal(t, tc.wantEnabled, putResp["Enabled"]) assert.NotContains(t, putResp, "PromotionalMessagesPerSecond") @@ -324,7 +326,7 @@ func TestSMSChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tc.wantEnabled, getResp["Enabled"]) @@ -385,7 +387,7 @@ func TestGCMChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantAuthMethod != "" { assert.Equal(t, tc.wantAuthMethod, resp["DefaultAuthenticationMethod"]) @@ -427,7 +429,7 @@ func TestAPNSChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) // APNSChannelResponse has no BundleId/Certificate/TeamId members -- only // HasCredential/HasTokenKey booleans -- so raw request secrets must never @@ -460,7 +462,7 @@ func TestEmailChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "noreply@example.com", resp["FromAddress"]) assert.Equal(t, "arn:aws:ses:us-east-1:123456789012:identity/example.com", resp["Identity"]) @@ -475,7 +477,7 @@ func TestEmailChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "noreply@example.com", getResp["FromAddress"]) assert.Equal(t, "my-config-set", getResp["ConfigurationSet"]) @@ -499,7 +501,7 @@ func TestBaiduChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) // BaiduChannelResponse's credential member is "Credential" (the API key, // required), not "ApiKey"; SecretKey has no response member at all. @@ -525,7 +527,7 @@ func TestADMChannel_PerTypeFields(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) // ADMChannelResponse has no ClientId/ClientSecret members -- only // HasCredential -- so neither must be echoed back on the wire. @@ -546,7 +548,7 @@ func TestVoiceChannel_BasicResponse(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["Enabled"]) assert.NotEmpty(t, resp["ChannelType"]) @@ -573,6 +575,6 @@ func TestApnsChannel_DefaultAuthenticationMethod(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TOKEN", resp["DefaultAuthenticationMethod"]) } diff --git a/services/pinpoint/endpoints_test.go b/services/pinpoint/endpoints_test.go index dab1f1ed60..db218f6848 100644 --- a/services/pinpoint/endpoints_test.go +++ b/services/pinpoint/endpoints_test.go @@ -1,13 +1,15 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEndpoint_FullShape(t *testing.T) { @@ -119,7 +121,7 @@ func TestEndpoint_FullShape(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) endpointID := "ep-" + tc.name @@ -137,7 +139,7 @@ func TestEndpoint_FullShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantChannelType != "" { assert.Equal(t, tc.wantChannelType, resp["ChannelType"]) @@ -174,7 +176,7 @@ func TestEndpoint_UserAttributes(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPut, "/v1/apps/"+appID+"/endpoints/ep-1", @@ -196,7 +198,7 @@ func TestEndpoint_UserAttributes(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) user, ok := resp["User"].(map[string]any) require.True(t, ok, "User field must be present") @@ -217,7 +219,7 @@ func TestEndpointBatch_FullShape(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) // Real EndpointBatchRequest.Item is a JSON array of EndpointBatchItem, @@ -258,7 +260,7 @@ func TestEndpointBatch_FullShape(t *testing.T) { getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/endpoints/ep-batch-1", nil) require.Equal(t, http.StatusOK, getRec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r1, jsonx.V1())) assert.NotNil(t, r1["Attributes"]) assert.NotNil(t, r1["Metrics"]) assert.NotNil(t, r1["Demographic"]) @@ -267,7 +269,7 @@ func TestEndpointBatch_FullShape(t *testing.T) { getRec2 := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/endpoints/ep-batch-2", nil) require.Equal(t, http.StatusOK, getRec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &r2, jsonx.V1())) assert.Equal(t, "INACTIVE", r2["EndpointStatus"]) assert.Equal(t, "ALL", r2["OptOut"]) } @@ -284,7 +286,7 @@ func TestEndpoint_EffectiveDate_RoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) effectiveDate := "2026-01-15T10:30:00Z" @@ -299,7 +301,7 @@ func TestEndpoint_EffectiveDate_RoundTrip(t *testing.T) { getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/endpoints/ep-eff", nil) require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, effectiveDate, resp["EffectiveDate"]) } @@ -330,7 +332,7 @@ func TestEndpoint_UserAttributes_MultiValue(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ep map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep, jsonx.V1())) user := ep["User"].(map[string]any) assert.Equal(t, "user-001", user["UserId"]) @@ -390,7 +392,7 @@ func TestEndpoint_UserAttributes_UpdateMerge(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ep map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep, jsonx.V1())) ua := ep["User"].(map[string]any)["UserAttributes"].(map[string]any) assert.Equal(t, []any{"gold"}, ua["tier"]) @@ -435,7 +437,7 @@ func TestGetUserEndpoints_ByUserID(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Item"].([]any) assert.Len(t, items, 2, "only endpoints for shared-user") @@ -489,7 +491,7 @@ func TestEndpoint_UserAttributes_BatchUpdate(t *testing.T) { require.Equal(t, http.StatusOK, getRec1.Code) var ep1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &ep1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &ep1, jsonx.V1())) ua1 := ep1["User"].(map[string]any)["UserAttributes"].(map[string]any) assert.Equal(t, []any{"beta-testers"}, ua1["segment"]) @@ -499,7 +501,7 @@ func TestEndpoint_UserAttributes_BatchUpdate(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var ep2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &ep2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &ep2, jsonx.V1())) ua2 := ep2["User"].(map[string]any)["UserAttributes"].(map[string]any) assert.Equal(t, []any{"power-users"}, ua2["segment"]) } @@ -532,7 +534,7 @@ func TestEndpoint_Metrics_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ep map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep, jsonx.V1())) metrics := ep["Metrics"].(map[string]any) assert.InDelta(t, 12.0, metrics["session_count"], 0.001) @@ -577,7 +579,7 @@ func TestEndpoint_Demographic_Location(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var ep map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &ep, jsonx.V1())) demo := ep["Demographic"].(map[string]any) assert.Equal(t, "4.2.1", demo["AppVersion"]) @@ -730,7 +732,7 @@ func TestUpdateEndpoint_ReturnsMessageBody(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) msg, _ := resp["Message"].(string) assert.Equal(t, "Accepted", msg, diff --git a/services/pinpoint/error_envelope_test.go b/services/pinpoint/error_envelope_test.go index 6d933eab06..852db40172 100644 --- a/services/pinpoint/error_envelope_test.go +++ b/services/pinpoint/error_envelope_test.go @@ -1,7 +1,7 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/pinpoint" @@ -90,7 +91,7 @@ func TestErrorEnvelope_GetAppNotFoundDecodesToTypedError(t *testing.T) { require.NoError(t, err) var envelope map[string]any - require.NoError(t, json.Unmarshal(raw, &envelope)) + require.NoError(t, json.Unmarshal(raw, &envelope, jsonx.V1())) require.Equal(t, "NotFoundException", envelope["__type"], "raw body must carry __type key restjson.GetErrorInfo's fallback reads: %s", raw) diff --git a/services/pinpoint/event_streams_test.go b/services/pinpoint/event_streams_test.go index 90f69a765a..cfb20696b1 100644 --- a/services/pinpoint/event_streams_test.go +++ b/services/pinpoint/event_streams_test.go @@ -1,13 +1,15 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestEventStream_PutAndGet_Fields(t *testing.T) { @@ -30,7 +32,7 @@ func TestEventStream_PutAndGet_Fields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var es map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &es)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &es, jsonx.V1())) assert.Equal(t, destArn, es["DestinationStreamArn"]) assert.Equal(t, roleArn, es["RoleArn"]) @@ -59,7 +61,7 @@ func TestEventStream_Replace(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var es map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &es)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &es, jsonx.V1())) assert.Equal(t, stream2, es["DestinationStreamArn"], "second PUT must replace first") } @@ -101,13 +103,13 @@ func TestEventStream_AppIsolation(t *testing.T) { getA := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appA+"/eventstream", nil) require.Equal(t, http.StatusOK, getA.Code) var esA map[string]any - require.NoError(t, json.Unmarshal(getA.Body.Bytes(), &esA)) + require.NoError(t, json.Unmarshal(getA.Body.Bytes(), &esA, jsonx.V1())) assert.Equal(t, streamA, esA["DestinationStreamArn"]) getB := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appB+"/eventstream", nil) require.Equal(t, http.StatusOK, getB.Code) var esB map[string]any - require.NoError(t, json.Unmarshal(getB.Body.Bytes(), &esB)) + require.NoError(t, json.Unmarshal(getB.Body.Bytes(), &esB, jsonx.V1())) assert.Equal(t, streamB, esB["DestinationStreamArn"]) } diff --git a/services/pinpoint/events_test.go b/services/pinpoint/events_test.go index dfabb35397..6b49df5e61 100644 --- a/services/pinpoint/events_test.go +++ b/services/pinpoint/events_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPutEvents_AppLifeCycleEvent(t *testing.T) { @@ -150,7 +152,7 @@ func TestPutEvents_ReturnsEventResults(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["Results"].(map[string]any) require.True(t, ok, "EventsResponse must have Results") @@ -199,7 +201,7 @@ func TestPutEvents_MultipleEndpoints(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["Results"].(map[string]any) @@ -226,7 +228,7 @@ func TestPutEvents_EmptyBatch(t *testing.T) { require.Equal(t, http.StatusAccepted, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["Results"].(map[string]any) assert.Empty(t, results, "empty BatchItem → empty Results") diff --git a/services/pinpoint/export_import_jobs_test.go b/services/pinpoint/export_import_jobs_test.go index 041f33b3b7..295f738c2d 100644 --- a/services/pinpoint/export_import_jobs_test.go +++ b/services/pinpoint/export_import_jobs_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/pinpoint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestImportJob_CreatesSegment(t *testing.T) { @@ -33,7 +36,7 @@ func TestImportJob_CreatesSegment(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["Item"].([]any) require.Len(t, items, 1, "import job must materialise exactly one segment") @@ -70,7 +73,7 @@ func TestImportJob_DefaultSegmentName(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["Item"].([]any) require.Len(t, items, 1) @@ -97,7 +100,7 @@ func TestImportJob_SegmentGetByID(t *testing.T) { listRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments", nil) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["Item"].([]any) segID := items[0].(map[string]any)["Id"].(string) @@ -106,7 +109,7 @@ func TestImportJob_SegmentGetByID(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var s map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s, jsonx.V1())) assert.Equal(t, "IMPORT", s["SegmentType"]) assert.Equal(t, "direct-import", s["Name"]) } @@ -302,7 +305,7 @@ func TestHandler_CreateExportJob(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, "CREATED", resp["JobStatus"]) @@ -345,7 +348,7 @@ func TestHandler_CreateImportJob(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, "CREATED", resp["JobStatus"]) @@ -376,7 +379,7 @@ func TestGetSegmentImportJobs_FiltersBySegment(t *testing.T) { segsRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments", nil) require.Equal(t, http.StatusOK, segsRec.Code) var segsResp map[string]any - require.NoError(t, json.Unmarshal(segsRec.Body.Bytes(), &segsResp)) + require.NoError(t, json.Unmarshal(segsRec.Body.Bytes(), &segsResp, jsonx.V1())) items, _ := segsResp["Item"].([]any) require.Len(t, items, 1, "import job must materialise exactly one segment") @@ -388,7 +391,7 @@ func TestGetSegmentImportJobs_FiltersBySegment(t *testing.T) { require.Equal(t, http.StatusOK, jobsRec.Code) var jobsResp map[string]any - require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp)) + require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp, jsonx.V1())) jobs, _ := jobsResp["Item"].([]any) assert.Len(t, jobs, 1, "GetSegmentImportJobs must return the job that created the segment") @@ -415,7 +418,7 @@ func TestGetSegmentImportJobs_OtherSegmentReturnsEmpty(t *testing.T) { map[string]any{"Name": "unrelated-segment"}) require.Equal(t, http.StatusCreated, segRec.Code) var segResp map[string]any - require.NoError(t, json.Unmarshal(segRec.Body.Bytes(), &segResp)) + require.NoError(t, json.Unmarshal(segRec.Body.Bytes(), &segResp, jsonx.V1())) unrelatedSegID := segResp["Id"].(string) // GetSegmentImportJobs for the unrelated segment → must return empty list. @@ -424,7 +427,7 @@ func TestGetSegmentImportJobs_OtherSegmentReturnsEmpty(t *testing.T) { require.Equal(t, http.StatusOK, jobsRec.Code) var jobsResp map[string]any - require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp)) + require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp, jsonx.V1())) jobs, _ := jobsResp["Item"].([]any) assert.Empty(t, jobs, "GetSegmentImportJobs must return empty for segments not created by an import job") @@ -457,7 +460,7 @@ func TestGetSegmentImportJobs_MultipleJobs_IsolatedBySegment(t *testing.T) { segsRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments", nil) require.Equal(t, http.StatusOK, segsRec.Code) var segsResp map[string]any - require.NoError(t, json.Unmarshal(segsRec.Body.Bytes(), &segsResp)) + require.NoError(t, json.Unmarshal(segsRec.Body.Bytes(), &segsResp, jsonx.V1())) items, _ := segsResp["Item"].([]any) require.Len(t, items, 2) @@ -471,7 +474,7 @@ func TestGetSegmentImportJobs_MultipleJobs_IsolatedBySegment(t *testing.T) { require.Equal(t, http.StatusOK, jobsRec.Code) var jobsResp map[string]any - require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp)) + require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp, jsonx.V1())) jobs, _ := jobsResp["Item"].([]any) assert.Len(t, jobs, 1, "each segment's GetSegmentImportJobs must return only its own job, segment %s", segID) diff --git a/services/pinpoint/handler.go b/services/pinpoint/handler.go index 22c0f037e8..89d7cf7d5f 100644 --- a/services/pinpoint/handler.go +++ b/services/pinpoint/handler.go @@ -2,7 +2,7 @@ package pinpoint import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -682,7 +683,7 @@ func unmarshalBody(c *echo.Context, dst any) bool { return false } - if jsonErr := json.Unmarshal(body, dst); jsonErr != nil { + if jsonErr := json.Unmarshal(body, dst, jsonx.V1()); jsonErr != nil { _ = writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") return false diff --git a/services/pinpoint/handler_applications_settings.go b/services/pinpoint/handler_applications_settings.go index 4afb694d98..62390b7cd8 100644 --- a/services/pinpoint/handler_applications_settings.go +++ b/services/pinpoint/handler_applications_settings.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractSettingsOp returns the settings operation name. @@ -74,7 +75,7 @@ func (h *Handler) handleUpdateApplicationSettings(c *echo.Context, appID string) } if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &incoming); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &incoming, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } } diff --git a/services/pinpoint/handler_apps.go b/services/pinpoint/handler_apps.go index d77502e28b..688c57ab5c 100644 --- a/services/pinpoint/handler_apps.go +++ b/services/pinpoint/handler_apps.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractAppsCollectionOp returns the operation for the apps collection. @@ -78,7 +79,7 @@ func (h *Handler) handleCreateApp(c *echo.Context) error { var req createAppRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_attributes.go b/services/pinpoint/handler_attributes.go index 7a56616a01..4e4d789a1a 100644 --- a/services/pinpoint/handler_attributes.go +++ b/services/pinpoint/handler_attributes.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleRemoveAttributes handles PUT /v1/apps/{appId}/attributes/{attributeType}. @@ -21,7 +22,7 @@ func (h *Handler) handleRemoveAttributes(c *echo.Context, appID, attributeType s var req removeAttributesRequest if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } } diff --git a/services/pinpoint/handler_channels.go b/services/pinpoint/handler_channels.go index dc3fa55213..a7b2e4abbc 100644 --- a/services/pinpoint/handler_channels.go +++ b/services/pinpoint/handler_channels.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -9,6 +9,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) extractChannelOp(method, channelType string) string { @@ -186,7 +187,7 @@ func (h *Handler) handleGetChannels(c *echo.Context, appID string) error { func parseGCMChannelExtra(body []byte) (bool, map[string]any) { var req updateGCMChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -205,7 +206,7 @@ func parseGCMChannelExtra(body []byte) (bool, map[string]any) { func parseAPNSChannelExtra(body []byte) (bool, map[string]any) { var req updateAPNSChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -225,7 +226,7 @@ func parseAPNSChannelExtra(body []byte) (bool, map[string]any) { func parseEmailChannelExtra(body []byte) (bool, map[string]any) { var req updateEmailChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -246,7 +247,7 @@ func parseEmailChannelExtra(body []byte) (bool, map[string]any) { func parseSMSChannelExtra(body []byte) (bool, map[string]any) { var req updateSMSChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -276,7 +277,7 @@ func parseChannelExtra(channelType string, body []byte) (bool, map[string]any) { return parseSMSChannelExtra(body) case channelKeyADM: var req updateADMChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -293,7 +294,7 @@ func parseChannelExtra(channelType string, body []byte) (bool, map[string]any) { return req.Enabled, extra case channelKeyBaidu: var req updateBaiduChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } @@ -310,7 +311,7 @@ func parseChannelExtra(channelType string, body []byte) (bool, map[string]any) { return req.Enabled, extra default: var req updateChannelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return false, nil } diff --git a/services/pinpoint/handler_endpoints.go b/services/pinpoint/handler_endpoints.go index 6e779891ef..a8686301cd 100644 --- a/services/pinpoint/handler_endpoints.go +++ b/services/pinpoint/handler_endpoints.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) extractEndpointSubOp(method, rest string) string { @@ -136,7 +137,7 @@ func (h *Handler) handleUpdateEndpoint(c *echo.Context, appID, endpointID string } var req updateEndpointRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } @@ -221,7 +222,7 @@ func (h *Handler) handleUpdateEndpointsBatch(c *echo.Context, appID string) erro } var req updateEndpointsBatchRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_event_streams.go b/services/pinpoint/handler_event_streams.go index 91fe05dcb3..84416a2cba 100644 --- a/services/pinpoint/handler_event_streams.go +++ b/services/pinpoint/handler_event_streams.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractEventStreamOp returns the event stream operation name. @@ -71,7 +72,7 @@ func (h *Handler) handlePutEventStream(c *echo.Context, appID string) error { } var req putEventStreamRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_events.go b/services/pinpoint/handler_events.go index 53a9605954..3531a2359b 100644 --- a/services/pinpoint/handler_events.go +++ b/services/pinpoint/handler_events.go @@ -1,12 +1,13 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handlePutEvents handles POST /v1/apps/{appId}/events. @@ -26,7 +27,7 @@ func (h *Handler) handlePutEvents(c *echo.Context, appID string) error { } var req putEventsRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_export_import_jobs.go b/services/pinpoint/handler_export_import_jobs.go index b8f83679c1..6d7f481826 100644 --- a/services/pinpoint/handler_export_import_jobs.go +++ b/services/pinpoint/handler_export_import_jobs.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -83,7 +84,7 @@ func (h *Handler) handleCreateExportJob(c *echo.Context, appID string) error { } var req createExportJobRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } @@ -119,7 +120,7 @@ func (h *Handler) handleCreateImportJob(c *echo.Context, appID string) error { } var req createImportJobRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_journeys.go b/services/pinpoint/handler_journeys.go index 1e90b20632..575a518fc8 100644 --- a/services/pinpoint/handler_journeys.go +++ b/services/pinpoint/handler_journeys.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractJourneysOp returns the journeys collection operation name. @@ -149,7 +150,7 @@ func (h *Handler) dispatchJourneyRun(c *echo.Context, appID, journeyID, rest str func (h *Handler) handleCreateJourney(c *echo.Context, appID string) error { return h.handleCreateNamedAppResource(c, appID, func(body []byte, region, appID string) (any, error) { var req createJourneyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, errInvalidRequestBody } @@ -249,7 +250,7 @@ func (h *Handler) handleUpdateJourneyState(c *echo.Context, appID, journeyID str } var req updateJourneyStateRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_messages.go b/services/pinpoint/handler_messages.go index be302bc67d..fa32ae0806 100644 --- a/services/pinpoint/handler_messages.go +++ b/services/pinpoint/handler_messages.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" @@ -9,6 +9,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleSendMessages handles POST /v1/apps/{appId}/messages. @@ -23,7 +24,7 @@ func (h *Handler) handleSendMessages(c *echo.Context, appID string) error { } var req sendMessagesRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } @@ -54,7 +55,7 @@ func (h *Handler) handleSendUsersMessages(c *echo.Context, appID string) error { var req sendUsersMessagesRequest if len(body) > 0 { - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } } @@ -105,7 +106,7 @@ func (h *Handler) handleVerifyOTPMessage(c *echo.Context, appID string) error { var req verifyOTPMessageRequest if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } code := req.Otp @@ -136,7 +137,7 @@ func (h *Handler) handlePhoneNumberValidate(c *echo.Context) error { } var req phoneNumberValidateRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_recommender_configurations.go b/services/pinpoint/handler_recommender_configurations.go index d38b01d5d8..fbdf8b93c4 100644 --- a/services/pinpoint/handler_recommender_configurations.go +++ b/services/pinpoint/handler_recommender_configurations.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractRecommendersCollectionOp returns the operation for the recommenders collection. @@ -95,7 +96,7 @@ func (h *Handler) handleCreateRecommenderConfiguration(c *echo.Context) error { } var req createRecommenderConfigRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse( c, http.StatusBadRequest, @@ -236,7 +237,7 @@ func (h *Handler) handleUpdateRecommenderConfiguration( } var req createRecommenderConfigRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse( c, http.StatusBadRequest, diff --git a/services/pinpoint/handler_segments.go b/services/pinpoint/handler_segments.go index 240d5c5e68..fa9fec7652 100644 --- a/services/pinpoint/handler_segments.go +++ b/services/pinpoint/handler_segments.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractSegmentsOp returns the segments collection operation name. @@ -105,7 +106,7 @@ func (h *Handler) dispatchSegmentByID(c *echo.Context, appID, rest string) error func (h *Handler) handleCreateSegment(c *echo.Context, appID string) error { return h.handleCreateNamedAppResource(c, appID, func(body []byte, region, appID string) (any, error) { var req createSegmentRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, errInvalidRequestBody } diff --git a/services/pinpoint/handler_tags.go b/services/pinpoint/handler_tags.go index e9e47b700a..3d12cdc909 100644 --- a/services/pinpoint/handler_tags.go +++ b/services/pinpoint/handler_tags.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractTagOperation resolves the operation for tag-related paths. @@ -59,7 +60,7 @@ func (h *Handler) handleTagResource(c *echo.Context, resourceARN string) error { var req tagResourceRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_templates.go b/services/pinpoint/handler_templates.go index b44b480a96..1079ad76c8 100644 --- a/services/pinpoint/handler_templates.go +++ b/services/pinpoint/handler_templates.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractTemplateOperation resolves the operation name for paths under /v1/templates/. @@ -221,7 +222,7 @@ func (h *Handler) createTemplateByType(body []byte, region, templateName, templa func (h *Handler) createEmailTemplateARN(body []byte, region, templateName string) (string, error) { var req createEmailTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", errInvalidRequestBody } @@ -235,7 +236,7 @@ func (h *Handler) createEmailTemplateARN(body []byte, region, templateName strin func (h *Handler) createInAppTemplateARN(body []byte, region, templateName string) (string, error) { var req createInAppTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", errInvalidRequestBody } @@ -249,7 +250,7 @@ func (h *Handler) createInAppTemplateARN(body []byte, region, templateName strin func (h *Handler) createPushTemplateARN(body []byte, region, templateName string) (string, error) { var req createPushTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", errInvalidRequestBody } @@ -263,7 +264,7 @@ func (h *Handler) createPushTemplateARN(body []byte, region, templateName string func (h *Handler) createSMSTemplateARN(body []byte, region, templateName string) (string, error) { var req createSmsTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", errInvalidRequestBody } @@ -277,7 +278,7 @@ func (h *Handler) createSMSTemplateARN(body []byte, region, templateName string) func (h *Handler) createVoiceTemplateARN(body []byte, region, templateName string) (string, error) { var req createVoiceTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", errInvalidRequestBody } @@ -381,7 +382,7 @@ func (h *Handler) applyTemplateUpdate(body []byte, templateName, templateType, v // updateEmailTemplateFromBody parses and applies an email template update. func (h *Handler) updateEmailTemplateFromBody(body []byte, name, version string, createNew bool) error { var req createEmailTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return errInvalidRequestBody } @@ -396,7 +397,7 @@ func (h *Handler) updateEmailTemplateFromBody(body []byte, name, version string, // updateInAppTemplateFromBody parses and applies an in-app template update. func (h *Handler) updateInAppTemplateFromBody(body []byte, name, version string, createNew bool) error { var req createInAppTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return errInvalidRequestBody } @@ -411,7 +412,7 @@ func (h *Handler) updateInAppTemplateFromBody(body []byte, name, version string, // updatePushTemplateFromBody parses and applies a push template update. func (h *Handler) updatePushTemplateFromBody(body []byte, name, version string, createNew bool) error { var req createPushTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return errInvalidRequestBody } @@ -426,7 +427,7 @@ func (h *Handler) updatePushTemplateFromBody(body []byte, name, version string, // updateSMSTemplateFromBody parses and applies an SMS template update. func (h *Handler) updateSMSTemplateFromBody(body []byte, name, version string, createNew bool) error { var req createSmsTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return errInvalidRequestBody } @@ -441,7 +442,7 @@ func (h *Handler) updateSMSTemplateFromBody(body []byte, name, version string, c // updateVoiceTemplateFromBody parses and applies a voice template update. func (h *Handler) updateVoiceTemplateFromBody(body []byte, name, version string, createNew bool) error { var req createVoiceTemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return errInvalidRequestBody } @@ -555,7 +556,7 @@ func (h *Handler) handleUpdateTemplateActiveVersion(c *echo.Context, templateNam Version string `json:"Version"` } - if len(body) > 0 && json.Unmarshal(body, &req) != nil { + if len(body) > 0 && json.Unmarshal(body, &req, jsonx.V1()) != nil { return writeErrorResponse(c, http.StatusBadRequest, "BadRequestException", "invalid request body") } diff --git a/services/pinpoint/handler_templates_rejection_test.go b/services/pinpoint/handler_templates_rejection_test.go index 736a890c06..482b841c21 100644 --- a/services/pinpoint/handler_templates_rejection_test.go +++ b/services/pinpoint/handler_templates_rejection_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestUpdateTemplate_RejectedUpdate_DoesNotDoubleWrite is a regression test @@ -40,7 +42,7 @@ func TestUpdateTemplate_RejectedUpdate_DoesNotDoubleWrite(t *testing.T) { "a rejected update must not write a second (success) response onto the same body") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp), + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()), "response body must decode as exactly one JSON object -- a second "+ "write appended after the committed 404 corrupts it") assert.Equal(t, "NotFoundException", resp["__type"]) diff --git a/services/pinpoint/handler_test.go b/services/pinpoint/handler_test.go index 4332e4dcbf..df08cd14ea 100644 --- a/services/pinpoint/handler_test.go +++ b/services/pinpoint/handler_test.go @@ -2,7 +2,8 @@ package pinpoint_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -38,7 +40,7 @@ func doPinpointRequest(t *testing.T, h *pinpoint.Handler, method, path string, b if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -96,7 +98,7 @@ func createTestApp(t *testing.T, h *pinpoint.Handler, name string) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) id, _ := resp["Id"].(string) require.NotEmpty(t, id) @@ -109,7 +111,7 @@ func pinpointJSON(t *testing.T, body []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1())) return m } @@ -563,7 +565,7 @@ func TestHandler_ErrorPaths(t *testing.T) { bodyBytes = []byte(s) } else if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/pinpoint/journeys_activities_test.go b/services/pinpoint/journeys_activities_test.go index b20588ac00..35753a4636 100644 --- a/services/pinpoint/journeys_activities_test.go +++ b/services/pinpoint/journeys_activities_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestJourney_StateMachine(t *testing.T) { @@ -74,7 +76,7 @@ func TestJourney_StateMachine(t *testing.T) { map[string]any{"Name": "sm-journey"}) require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) journeyID := cr["Id"].(string) assert.Equal(t, "DRAFT", cr["State"]) @@ -100,7 +102,7 @@ func TestJourney_StateMachine(t *testing.T) { "/v1/apps/"+appID+"/journeys/"+journeyID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) assert.Equal(t, tc.wantFinal, j["State"]) } }) @@ -126,7 +128,7 @@ func TestJourney_ListByState(t *testing.T) { map[string]any{"Name": name}) require.Equal(t, http.StatusCreated, rec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &j, jsonx.V1())) journeyIDs[i] = j["Id"].(string) } @@ -147,7 +149,7 @@ func TestJourney_ListByState(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["Item"].([]any) assert.Len(t, items, 3) @@ -321,7 +323,7 @@ func TestJourney_Activities(t *testing.T) { "body: %s", createRec.Body.String()) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) journeyID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, @@ -329,7 +331,7 @@ func TestJourney_Activities(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) activities, _ := j["Activities"].(map[string]any) assert.NotNil(t, activities, "Activities must be present") @@ -369,7 +371,7 @@ func TestJourney_DateRangeKpi(t *testing.T) { map[string]any{"Name": "kpi-journey"}) require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) journeyID := cr["Id"].(string) kpiRec := doPinpointRequest(t, h, http.MethodGet, @@ -377,7 +379,7 @@ func TestJourney_DateRangeKpi(t *testing.T) { require.Equal(t, http.StatusOK, kpiRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(kpiRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(kpiRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, journeyID, resp["JourneyId"]) assert.Equal(t, tc.kpiName, resp["KpiName"]) @@ -421,7 +423,7 @@ func TestJourney_RunsAfterActivation(t *testing.T) { map[string]any{"Name": "runs-journey"}) require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) journeyID := cr["Id"].(string) if tc.activate { @@ -436,7 +438,7 @@ func TestJourney_RunsAfterActivation(t *testing.T) { require.Equal(t, http.StatusOK, runsRec.Code) var runsResp map[string]any - require.NoError(t, json.Unmarshal(runsRec.Body.Bytes(), &runsResp)) + require.NoError(t, json.Unmarshal(runsRec.Body.Bytes(), &runsResp, jsonx.V1())) items, _ := runsResp["Item"].([]any) assert.Len(t, items, tc.wantRuns) }) @@ -486,14 +488,14 @@ func TestJourney_LinkedActivityGraph(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) assert.Equal(t, "act-wait", j["StartActivity"]) @@ -541,14 +543,14 @@ func TestJourney_QuietTimeAndOpenHours(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) qt := j["QuietTime"].(map[string]any) assert.Equal(t, "21:00", qt["Start"]) @@ -585,14 +587,14 @@ func TestJourney_LimitsRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) got := j["Limits"].(map[string]any) assert.EqualValues(t, 100, got["DailyCap"]) @@ -645,14 +647,14 @@ func TestJourney_RandomSplitAndHoldout(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) acts := j["Activities"].(map[string]any) holdout := acts["act-holdout"].(map[string]any)["HOLDOUT"].(map[string]any) @@ -681,7 +683,7 @@ func TestJourney_UpdateActivities(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) // Add a second activity by updating the journey @@ -706,7 +708,7 @@ func TestJourney_UpdateActivities(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) acts := j["Activities"].(map[string]any) assert.Len(t, acts, 2) @@ -741,14 +743,14 @@ func TestJourney_RefreshOnSegmentUpdate(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) assert.Equal(t, true, j["RefreshOnSegmentUpdate"]) assert.Equal(t, "PT1H", j["RefreshFrequency"]) @@ -779,14 +781,14 @@ func TestJourney_StartCondition(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &cr, jsonx.V1())) jID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys/"+jID, nil) require.Equal(t, http.StatusOK, getRec.Code) var j map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &j, jsonx.V1())) sc := j["StartCondition"].(map[string]any) assert.Equal(t, "entry from segment", sc["Description"]) diff --git a/services/pinpoint/journeys_test.go b/services/pinpoint/journeys_test.go index b3f91dcf43..628e44e0ec 100644 --- a/services/pinpoint/journeys_test.go +++ b/services/pinpoint/journeys_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -12,6 +13,8 @@ import ( pinpointtypes "github.com/aws/aws-sdk-go-v2/service/pinpoint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestJourneyFullDTO_Create(t *testing.T) { @@ -146,7 +149,7 @@ func TestJourneyFullDTO_Create(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/journeys", tc.body) @@ -157,7 +160,7 @@ func TestJourneyFullDTO_Create(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, appID, resp["ApplicationId"]) @@ -285,14 +288,14 @@ func TestJourneyStateValidation(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/journeys", map[string]any{"Name": "j"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) journeyID := created["Id"].(string) for _, state := range tc.setupTransitions { @@ -318,14 +321,14 @@ func TestJourneyUpdate_BlockedWhenActive(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/journeys", map[string]any{"Name": "j"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) journeyID := created["Id"].(string) // Activate the journey. @@ -346,7 +349,7 @@ func TestJourneyUpdate_BlockedWhenActive(t *testing.T) { require.Equal(t, http.StatusConflict, updateRec.Code, updateRec.Body.String()) var errResp map[string]string - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) } @@ -469,14 +472,14 @@ func TestJourneyUpdate_RoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/journeys", map[string]any{"Name": "j"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) journeyID := created["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPut, @@ -484,7 +487,7 @@ func TestJourneyUpdate_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp[tc.checkField], "%s field must be non-nil after update", tc.checkField) }) @@ -503,7 +506,7 @@ func TestJourney_MultipleActivities_RoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) activities := map[string]any{ @@ -538,7 +541,7 @@ func TestJourney_MultipleActivities_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp, jsonx.V1())) returnedActivities, _ := resp["Activities"].(map[string]any) assert.Len(t, returnedActivities, 3, "all 3 activities round-trip") @@ -565,7 +568,7 @@ func TestPagination_Journeys_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, p1Rec.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1, jsonx.V1())) items1, _ := p1["Item"].([]any) assert.Len(t, items1, 2) @@ -576,7 +579,7 @@ func TestPagination_Journeys_NextToken(t *testing.T) { p2Rec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/journeys?page-size=2&token="+tok, nil) var p2 map[string]any - require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2, jsonx.V1())) items2, _ := p2["Item"].([]any) assert.Len(t, items2, 1, "last page returns remaining journey") @@ -599,7 +602,7 @@ func TestJourneyCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) journeyID, _ := createResp["Id"].(string) require.NotEmpty(t, journeyID) @@ -695,7 +698,7 @@ func TestJourneyARNPresent(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotContains(t, resp, "Arn") } @@ -737,7 +740,7 @@ func TestHandler_CreateJourney(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, appID, resp["ApplicationId"]) assert.Equal(t, "DRAFT", resp["State"]) @@ -770,7 +773,7 @@ func TestHandler_GetJourneys_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) created[resp["Id"].(string)] = true } @@ -782,7 +785,7 @@ func TestHandler_GetJourneys_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) for _, item := range items { diff --git a/services/pinpoint/messages_test.go b/services/pinpoint/messages_test.go index cc729048d2..29062bc1cc 100644 --- a/services/pinpoint/messages_test.go +++ b/services/pinpoint/messages_test.go @@ -1,13 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -91,7 +92,7 @@ func TestOTP_SendAndVerify(t *testing.T) { "verify body: %s", verifyRec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Valid") } }) @@ -135,7 +136,7 @@ func TestOTP_SendAndVerify_CodeMatch(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp, jsonx.V1())) results, _ := sendResp["Result"].(map[string]any) assert.NotEmpty(t, results, "SendOTP should return results") @@ -146,7 +147,7 @@ func TestOTP_SendAndVerify_CodeMatch(t *testing.T) { require.Equal(t, http.StatusOK, verifyRec.Code) var verifyResp map[string]any - require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &verifyResp)) + require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &verifyResp, jsonx.V1())) assert.Equal(t, tc.wantValid, verifyResp["Valid"], "Valid field") }) } @@ -181,7 +182,7 @@ func TestOTP_CodeMatch_CorrectCode(t *testing.T) { require.Equal(t, http.StatusOK, verifyNoCode.Code) var noCodeResp map[string]any - require.NoError(t, json.Unmarshal(verifyNoCode.Body.Bytes(), &noCodeResp)) + require.NoError(t, json.Unmarshal(verifyNoCode.Body.Bytes(), &noCodeResp, jsonx.V1())) assert.True(t, noCodeResp["Valid"].(bool), "empty code falls back to has-pending-OTP check") } @@ -204,7 +205,7 @@ func TestOTP_CodeMatch_WrongCode(t *testing.T) { require.Equal(t, http.StatusOK, verifyRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp, jsonx.V1())) assert.False(t, resp["Valid"].(bool), "wrong OTP code must return Valid=false") } @@ -222,7 +223,7 @@ func TestOTP_CodeMatch_NoOTPSent(t *testing.T) { require.Equal(t, http.StatusOK, verifyRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(verifyRec.Body.Bytes(), &resp, jsonx.V1())) assert.False(t, resp["Valid"].(bool), "no OTP sent → Valid=false") } @@ -275,7 +276,7 @@ func TestSendUsersMessages_WithEndpoints(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &resp, jsonx.V1())) result, _ := resp["Result"].(map[string]any) require.NotNil(t, result) @@ -314,7 +315,7 @@ func TestSendUsersMessages_UnknownUser(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &resp, jsonx.V1())) result, _ := resp["Result"].(map[string]any) require.NotNil(t, result) @@ -335,7 +336,7 @@ func TestSendUsersMessages_EmptyBody(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) result, _ := resp["Result"].(map[string]any) assert.Empty(t, result, "no users in request → empty result") } @@ -402,7 +403,7 @@ func TestPhoneNumberValidate_E164Input(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, wrapped := resp["NumberValidateResponse"] assert.False(t, wrapped, "NumberValidateResponse must not appear as a wrapper key") @@ -438,7 +439,7 @@ func TestPhoneNumberValidate_UnknownCountry(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) inner := resp assert.Equal(t, "ZZ", inner["CountryCodeIso2"], "unknown country returns ZZ") @@ -459,7 +460,7 @@ func TestPhoneNumberValidate_NationalFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) inner := resp assert.Equal(t, "+12125551234", inner["CleansedPhoneNumberE164"], @@ -479,7 +480,7 @@ func TestPhoneNumberValidate_PhoneTypeAndCarrier(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) inner := resp assert.NotEmpty(t, inner["PhoneType"], "PhoneType must be set") @@ -514,7 +515,7 @@ func TestSendMessages_ResponseFlat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, wrapped := resp["MessageResponse"] assert.False(t, wrapped, "MessageResponse must not appear as a wrapper key") @@ -562,7 +563,7 @@ func createTestInAppCampaign( require.Equal(t, http.StatusCreated, campRec.Code, campRec.Body.String()) var camp map[string]any - require.NoError(t, json.Unmarshal(campRec.Body.Bytes(), &camp)) + require.NoError(t, json.Unmarshal(campRec.Body.Bytes(), &camp, jsonx.V1())) campaignID, _ := camp["Id"].(string) require.NotEmpty(t, campaignID) @@ -589,7 +590,7 @@ func TestGetInAppMessages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) campaigns, ok := resp["InAppMessageCampaigns"].([]any) require.True(t, ok, "InAppMessageCampaigns must be present and be an array") assert.Empty(t, campaigns) @@ -607,7 +608,7 @@ func TestGetInAppMessages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) campaigns, ok := resp["InAppMessageCampaigns"].([]any) require.True(t, ok) require.Len(t, campaigns, 1) @@ -641,7 +642,7 @@ func TestGetInAppMessages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) campaigns, ok := resp["InAppMessageCampaigns"].([]any) require.True(t, ok) assert.Empty(t, campaigns, "a paused campaign doesn't run, per real AWS's IsPaused semantics") @@ -657,7 +658,7 @@ func TestGetInAppMessages(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "NotFoundException", resp["__type"]) }) } diff --git a/services/pinpoint/multi_region_test.go b/services/pinpoint/multi_region_test.go index 9e449aa507..b413eb2aef 100644 --- a/services/pinpoint/multi_region_test.go +++ b/services/pinpoint/multi_region_test.go @@ -2,7 +2,8 @@ package pinpoint_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -184,8 +186,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/pinpoint/payload_size.go b/services/pinpoint/payload_size.go index 647c1f0c1d..b396a5dee2 100644 --- a/services/pinpoint/payload_size.go +++ b/services/pinpoint/payload_size.go @@ -1,10 +1,13 @@ package pinpoint import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Payload size ceilings sourced from @@ -54,10 +57,10 @@ func checkPayloadSize(c *echo.Context, body []byte, limit int) bool { func putEventsExceedsEventQuota(body []byte) bool { var raw struct { BatchItem map[string]struct { - Events map[string]json.RawMessage `json:"Events"` + Events map[string]jsontext.Value `json:"Events"` } `json:"BatchItem"` } - if json.Unmarshal(body, &raw) != nil { + if json.Unmarshal(body, &raw, jsonx.V1()) != nil { return false } diff --git a/services/pinpoint/payload_size_test.go b/services/pinpoint/payload_size_test.go index 4ef39a6aa7..feffeb13d8 100644 --- a/services/pinpoint/payload_size_test.go +++ b/services/pinpoint/payload_size_test.go @@ -1,12 +1,15 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Mirrors the unexported limits in payload_size.go (7 MB / 4 MB / 15 KB from @@ -28,7 +31,7 @@ func jsonBodyOfSize(t *testing.T, prefix, suffix string, targetLen int) []byte { body := prefix + strings.Repeat("a", padLen) + suffix require.Len(t, body, targetLen) - require.True(t, json.Valid([]byte(body)), "constructed body must be valid JSON") + require.True(t, jsontext.Value([]byte(body)).IsValid(jsonx.V1()), "constructed body must be valid JSON") return []byte(body) } @@ -37,7 +40,7 @@ func requirePayloadTooLarge(t *testing.T, body []byte) { t.Helper() var envelope map[string]any - require.NoError(t, json.Unmarshal(body, &envelope)) + require.NoError(t, json.Unmarshal(body, &envelope, jsonx.V1())) require.Equal(t, "PayloadTooLargeException", envelope["__type"]) } diff --git a/services/pinpoint/persistence.go b/services/pinpoint/persistence.go index fcb72e06c7..1998caeab8 100644 --- a/services/pinpoint/persistence.go +++ b/services/pinpoint/persistence.go @@ -2,7 +2,7 @@ package pinpoint import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -48,21 +48,21 @@ const pinpointSnapshotVersion = 2 // plain JSON-friendly struct with no live/non-serialisable state, so a // direct field-for-field mapping (no separate DTO type) is sufficient. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AppSettings map[string]*StoredAppSettings `json:"appSettings"` - CampaignVersions map[string][]*Campaign `json:"campaignVersions"` - SegmentVersions map[string][]*Segment `json:"segmentVersions"` - TemplateVersionHistory map[string][]templateVersionItem `json:"templateVersionHistory"` - TemplateVersionData map[string]map[string]json.RawMessage `json:"templateVersionData"` - TemplateActiveVersion map[string]string `json:"templateActiveVersion"` - CampaignActivities map[string][]campaignActivity `json:"campaignActivities"` - JourneyRuns map[string][]*journeyRun `json:"journeyRuns"` - AppEvents map[string][]storedPinpointEvent `json:"appEvents"` - SentMessages map[string]int `json:"sentMessages"` - OtpCodes map[string]string `json:"otpCodes"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AppSettings map[string]*StoredAppSettings `json:"appSettings"` + CampaignVersions map[string][]*Campaign `json:"campaignVersions"` + SegmentVersions map[string][]*Segment `json:"segmentVersions"` + TemplateVersionHistory map[string][]templateVersionItem `json:"templateVersionHistory"` + TemplateVersionData map[string]map[string]jsontext.Value `json:"templateVersionData"` + TemplateActiveVersion map[string]string `json:"templateActiveVersion"` + CampaignActivities map[string][]campaignActivity `json:"campaignActivities"` + JourneyRuns map[string][]*journeyRun `json:"journeyRuns"` + AppEvents map[string][]storedPinpointEvent `json:"appEvents"` + SentMessages map[string]int `json:"sentMessages"` + OtpCodes map[string]string `json:"otpCodes"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // persistRegistry builds an ephemeral [store.Registry] over every @@ -183,7 +183,7 @@ func (b *InMemoryBackend) resetMapStateLocked() { b.campaignVersions = make(map[string][]*Campaign) b.segmentVersions = make(map[string][]*Segment) b.templateVersionHistory = make(map[string][]templateVersionItem) - b.templateVersionData = make(map[string]map[string]json.RawMessage) + b.templateVersionData = make(map[string]map[string]jsontext.Value) b.templateActiveVersion = make(map[string]string) b.campaignActivities = make(map[string][]campaignActivity) b.journeyRuns = make(map[string][]*journeyRun) @@ -327,9 +327,9 @@ func rebuildARNIndexLocked(b *InMemoryBackend) { } } -func nonNilStringMap2(m map[string]map[string]json.RawMessage) map[string]map[string]json.RawMessage { +func nonNilStringMap2(m map[string]map[string]jsontext.Value) map[string]map[string]jsontext.Value { if m == nil { - return make(map[string]map[string]json.RawMessage) + return make(map[string]map[string]jsontext.Value) } return m diff --git a/services/pinpoint/recommender_configurations_test.go b/services/pinpoint/recommender_configurations_test.go index 84cc1fc386..2b68006141 100644 --- a/services/pinpoint/recommender_configurations_test.go +++ b/services/pinpoint/recommender_configurations_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -282,7 +284,7 @@ func TestRecommenderConfiguration_FullCRUD(t *testing.T) { "body: %s", createRec.Body.String()) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) recID := cr["Id"].(string) require.NotEmpty(t, recID) @@ -313,7 +315,7 @@ func TestRecommenderConfiguration_FullCRUD(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, recID, gr["Id"]) updateRec := doPinpointRequest(t, h, http.MethodPut, @@ -368,7 +370,7 @@ func TestRecommenderConfiguration_ListAll(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tc.wantLen) }) @@ -520,14 +522,14 @@ func TestRecommender_Attributes_RoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) recID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/recommenders/"+recID, nil) require.Equal(t, http.StatusOK, getRec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r, jsonx.V1())) assert.Equal(t, "attr-recommender", r["Name"]) assert.EqualValues(t, 5, r["RecommendationsPerMessage"]) @@ -567,14 +569,14 @@ func TestRecommender_RecommendationsPerMessage(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) recID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/recommenders/"+recID, nil) require.Equal(t, http.StatusOK, getRec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r, jsonx.V1())) assert.EqualValues(t, tc.recommendationsPerMsg, r["RecommendationsPerMessage"]) }) @@ -600,7 +602,7 @@ func TestRecommender_UpdateAttributes(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) recID := cr["Id"].(string) putRec := doPinpointRequest(t, h, http.MethodPut, "/v1/recommenders/"+recID, @@ -621,7 +623,7 @@ func TestRecommender_UpdateAttributes(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var r map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &r, jsonx.V1())) assert.EqualValues(t, 7, r["RecommendationsPerMessage"]) gotAttrs := r["Attributes"].(map[string]any) @@ -650,7 +652,7 @@ func TestRecommender_List_MultipleConfigs(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Item"].([]any) assert.Len(t, items, 3) @@ -762,7 +764,7 @@ func TestHandler_CreateRecommenderConfiguration(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.Equal(t, "my-recommender", resp["Name"]) } diff --git a/services/pinpoint/segments_test.go b/services/pinpoint/segments_test.go index 039ed7e7a0..47fdaf9107 100644 --- a/services/pinpoint/segments_test.go +++ b/services/pinpoint/segments_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -23,7 +25,7 @@ func createTestSegment(t *testing.T, h *pinpoint.Handler, appID, name string) st require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) id, _ := resp["Id"].(string) require.NotEmpty(t, id) @@ -134,7 +136,7 @@ func TestSegmentFullDTO_Create(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/segments", tc.body) @@ -145,7 +147,7 @@ func TestSegmentFullDTO_Create(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantSegmentType, resp["SegmentType"]) assert.NotEmpty(t, resp["Id"]) @@ -223,21 +225,21 @@ func TestSegmentUpdate_DimensionsRoundTrip(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/segments", map[string]any{"Name": "seg"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) segID := created["Id"].(string) rec := doPinpointRequest(t, h, http.MethodPut, "/v1/apps/"+appID+"/segments/"+segID, tc.updateBody) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantSegmentType, resp["SegmentType"]) assert.InDelta(t, tc.wantVersion, resp["Version"], 0.001) @@ -258,14 +260,14 @@ func TestSegmentVersioning(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/segments", map[string]any{"Name": "versioned-seg"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) segID := created["Id"].(string) // 2 updates. @@ -278,7 +280,7 @@ func TestSegmentVersioning(t *testing.T) { verRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments/"+segID+"/versions", nil) require.Equal(t, http.StatusOK, verRec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &verResp, jsonx.V1())) items, _ := verResp["Item"].([]any) assert.Len(t, items, 3, "3 versions: 1 create + 2 updates") } @@ -295,14 +297,14 @@ func TestSegment_VersionRetrieval(t *testing.T) { appRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps", map[string]any{"Name": "app"}) require.Equal(t, http.StatusCreated, appRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(appRec.Body.Bytes(), &appResp, jsonx.V1())) appID := appResp["Id"].(string) createRec := doPinpointRequest(t, h, http.MethodPost, "/v1/apps/"+appID+"/segments", map[string]any{"Name": "seg-v"}) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) segID := created["Id"].(string) updateRec := doPinpointRequest(t, h, http.MethodPut, "/v1/apps/"+appID+"/segments/"+segID, @@ -314,7 +316,7 @@ func TestSegment_VersionRetrieval(t *testing.T) { "/v1/apps/"+appID+"/segments/"+segID+"/versions/1", nil) require.Equal(t, http.StatusOK, v1Rec.Code) var v1 map[string]any - require.NoError(t, json.Unmarshal(v1Rec.Body.Bytes(), &v1)) + require.NoError(t, json.Unmarshal(v1Rec.Body.Bytes(), &v1, jsonx.V1())) assert.InDelta(t, float64(1), v1["Version"], 0.001) assert.Equal(t, "seg-v", v1["Name"]) } @@ -366,7 +368,7 @@ func TestSegmentVersions_Deeper(t *testing.T) { }) require.Equal(t, http.StatusCreated, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) segID := created["Id"].(string) for i := range tc.updateCount { @@ -381,7 +383,7 @@ func TestSegmentVersions_Deeper(t *testing.T) { require.Equal(t, http.StatusOK, versionsRec.Code) var versionsResp map[string]any - require.NoError(t, json.Unmarshal(versionsRec.Body.Bytes(), &versionsResp)) + require.NoError(t, json.Unmarshal(versionsRec.Body.Bytes(), &versionsResp, jsonx.V1())) items, _ := versionsResp["Item"].([]any) assert.Len(t, items, tc.wantVersions, "wrong version count") @@ -390,7 +392,7 @@ func TestSegmentVersions_Deeper(t *testing.T) { require.Equal(t, http.StatusOK, vRec.Code) var vResp map[string]any - require.NoError(t, json.Unmarshal(vRec.Body.Bytes(), &vResp)) + require.NoError(t, json.Unmarshal(vRec.Body.Bytes(), &vResp, jsonx.V1())) assert.InDelta(t, float64(tc.fetchVersion), vResp["Version"], 0.001) }) } @@ -435,7 +437,7 @@ func TestSegmentJobsDeeper(t *testing.T) { map[string]any{"Name": "seg-for-jobs"}) require.Equal(t, http.StatusCreated, segRec.Code) var segResp map[string]any - require.NoError(t, json.Unmarshal(segRec.Body.Bytes(), &segResp)) + require.NoError(t, json.Unmarshal(segRec.Body.Bytes(), &segResp, jsonx.V1())) segID := segResp["Id"].(string) if tc.jobType == "export" { @@ -462,7 +464,7 @@ func TestSegmentJobsDeeper(t *testing.T) { require.Equal(t, http.StatusOK, jobsRec.Code) var jobsResp map[string]any - require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp)) + require.NoError(t, json.Unmarshal(jobsRec.Body.Bytes(), &jobsResp, jsonx.V1())) assert.Contains(t, jobsResp, "Item") }) } @@ -522,7 +524,7 @@ func TestSegment_ImportType(t *testing.T) { require.Equal(t, http.StatusCreated, jobRec.Code) var jr map[string]any - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr, jsonx.V1())) definition := jr["Definition"].(map[string]any) segID := definition["SegmentId"].(string) require.NotEmpty(t, segID) @@ -532,7 +534,7 @@ func TestSegment_ImportType(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var s map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s, jsonx.V1())) assert.Equal(t, "IMPORT", s["SegmentType"]) @@ -577,7 +579,7 @@ func TestSegment_DynamicWithSegmentGroups(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) // No ImportDefinition → DIMENSIONAL type assert.Equal(t, "DIMENSIONAL", cr["SegmentType"]) @@ -588,7 +590,7 @@ func TestSegment_DynamicWithSegmentGroups(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var s map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s, jsonx.V1())) assert.Equal(t, "DIMENSIONAL", s["SegmentType"]) @@ -639,14 +641,14 @@ func TestSegment_DimensionsAttributes(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) segID := cr["Id"].(string) getRec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments/"+segID, nil) require.Equal(t, http.StatusOK, getRec.Code) var s map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s, jsonx.V1())) d := s["Dimensions"].(map[string]any) @@ -685,7 +687,7 @@ func TestSegment_UpdatePreservesType(t *testing.T) { require.Equal(t, http.StatusCreated, jobRec.Code) var jr map[string]any - require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr)) + require.NoError(t, json.Unmarshal(jobRec.Body.Bytes(), &jr, jsonx.V1())) segID := jr["Definition"].(map[string]any)["SegmentId"].(string) require.NotEmpty(t, segID) @@ -695,7 +697,7 @@ func TestSegment_UpdatePreservesType(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var ur map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &ur)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &ur, jsonx.V1())) assert.Equal(t, "IMPORT", ur["SegmentType"]) assert.Equal(t, "type-preserve-seg-v2", ur["Name"]) @@ -739,7 +741,7 @@ func TestSegment_SegmentGroups_UpdateReplaces(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) segID := cr["Id"].(string) v2Groups := map[string]any{ @@ -770,7 +772,7 @@ func TestSegment_SegmentGroups_UpdateReplaces(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var s map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &s, jsonx.V1())) sg := s["SegmentGroups"].(map[string]any) assert.Equal(t, "ANY", sg["Include"]) @@ -797,7 +799,7 @@ func TestPagination_Segments_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, p1Rec.Code) var p1 map[string]any - require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1)) + require.NoError(t, json.Unmarshal(p1Rec.Body.Bytes(), &p1, jsonx.V1())) items1, _ := p1["Item"].([]any) assert.Len(t, items1, 2) @@ -807,7 +809,7 @@ func TestPagination_Segments_NextToken(t *testing.T) { p2Rec := doPinpointRequest(t, h, http.MethodGet, "/v1/apps/"+appID+"/segments?page-size=2&token="+tok, nil) var p2 map[string]any - require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2)) + require.NoError(t, json.Unmarshal(p2Rec.Body.Bytes(), &p2, jsonx.V1())) items2, _ := p2["Item"].([]any) assert.Len(t, items2, 2) @@ -828,7 +830,7 @@ func TestSegmentCRUD(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) segmentID, _ := createResp["Id"].(string) require.NotEmpty(t, segmentID) @@ -910,7 +912,7 @@ func TestHandler_CreateSegment(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, appID, resp["ApplicationId"]) @@ -945,7 +947,7 @@ func TestGetSegmentVersion_UnknownVersionNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, missingRec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(missingRec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(missingRec.Body), &errResp, jsonx.V1())) assert.Equal(t, "NotFoundException", errResp["__type"]) } @@ -973,7 +975,7 @@ func TestHandler_GetSegments_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) created[resp["Id"].(string)] = true } @@ -985,7 +987,7 @@ func TestHandler_GetSegments_DuplicateNames_NoDropOrDupAcrossPages(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) for _, item := range items { diff --git a/services/pinpoint/store.go b/services/pinpoint/store.go index e1f47359c2..46648f6e8a 100644 --- a/services/pinpoint/store.go +++ b/services/pinpoint/store.go @@ -1,7 +1,7 @@ package pinpoint import ( - "encoding/json" + "encoding/json/jsontext" "maps" "strings" "time" @@ -33,7 +33,7 @@ type InMemoryBackend struct { campaignVersions map[string][]*Campaign segmentVersions map[string][]*Segment templateVersionHistory map[string][]templateVersionItem - templateVersionData map[string]map[string]json.RawMessage + templateVersionData map[string]map[string]jsontext.Value templateActiveVersion map[string]string campaignActivities map[string][]campaignActivity journeyRuns map[string][]*journeyRun @@ -58,7 +58,7 @@ func NewInMemoryBackend(region, accountID string) *InMemoryBackend { campaignVersions: make(map[string][]*Campaign), segmentVersions: make(map[string][]*Segment), templateVersionHistory: make(map[string][]templateVersionItem), - templateVersionData: make(map[string]map[string]json.RawMessage), + templateVersionData: make(map[string]map[string]jsontext.Value), templateActiveVersion: make(map[string]string), campaignActivities: make(map[string][]campaignActivity), journeyRuns: make(map[string][]*journeyRun), @@ -84,7 +84,7 @@ func (b *InMemoryBackend) Reset() { b.campaignVersions = make(map[string][]*Campaign) b.segmentVersions = make(map[string][]*Segment) b.templateVersionHistory = make(map[string][]templateVersionItem) - b.templateVersionData = make(map[string]map[string]json.RawMessage) + b.templateVersionData = make(map[string]map[string]jsontext.Value) b.templateActiveVersion = make(map[string]string) b.campaignActivities = make(map[string][]campaignActivity) b.journeyRuns = make(map[string][]*journeyRun) diff --git a/services/pinpoint/tags_test.go b/services/pinpoint/tags_test.go index 3e38a54a26..60d3f4615d 100644 --- a/services/pinpoint/tags_test.go +++ b/services/pinpoint/tags_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "strings" @@ -9,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // pinpointEncodeARN URL-encodes an ARN for use in URL paths. @@ -66,7 +69,7 @@ func TestUntagResource_EmptyTagKeys(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &appResp, jsonx.V1())) arn, _ := appResp["Arn"].(string) require.NotEmpty(t, arn, "app ARN must be present") @@ -121,7 +124,7 @@ func TestUntagResource_RemovesSpecificTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var appResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &appResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &appResp, jsonx.V1())) arn, _ := appResp["Arn"].(string) encodedARN := pinpointEncodeARN(arn) @@ -142,7 +145,7 @@ func TestUntagResource_RemovesSpecificTags(t *testing.T) { require.Equal(t, http.StatusOK, listTagRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listTagRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listTagRec.Body.Bytes(), &tagsResp, jsonx.V1())) remaining, _ := tagsResp["tags"].(map[string]any) @@ -205,7 +208,7 @@ func TestTagCampaignViaARN(t *testing.T) { require.Equal(t, http.StatusCreated, recC.Code) var cResp map[string]any - require.NoError(t, json.NewDecoder(recC.Body).Decode(&cResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(recC.Body), &cResp, jsonx.V1())) campaignARN, _ := cResp["Arn"].(string) require.NotEmpty(t, campaignARN) @@ -219,7 +222,7 @@ func TestTagCampaignViaARN(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var tagsBody map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&tagsBody)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &tagsBody, jsonx.V1())) tagsMap, _ := tagsBody["tags"].(map[string]any) assert.Equal(t, "42", tagsMap["cost-center"]) } @@ -235,7 +238,7 @@ func TestTagJourneyViaARN(t *testing.T) { require.Equal(t, http.StatusCreated, recJ.Code) var jResp map[string]any - require.NoError(t, json.NewDecoder(recJ.Body).Decode(&jResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(recJ.Body), &jResp, jsonx.V1())) assert.NotContains(t, jResp, "Arn") journeyARN := "arn:aws:mobiletargeting:" + testRegion + ":" + testAccountID + @@ -260,7 +263,7 @@ func TestTagSegmentViaARN(t *testing.T) { require.Equal(t, http.StatusCreated, recS.Code) var sResp map[string]any - require.NoError(t, json.NewDecoder(recS.Body).Decode(&sResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(recS.Body), &sResp, jsonx.V1())) segmentARN, _ := sResp["Arn"].(string) require.NotEmpty(t, segmentARN) @@ -284,7 +287,7 @@ func TestTagEmailTemplateViaARN(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var tResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&tResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &tResp, jsonx.V1())) templateARN, _ := tResp["Arn"].(string) require.NotEmpty(t, templateARN) @@ -298,7 +301,7 @@ func TestTagEmailTemplateViaARN(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listBody map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&listBody)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &listBody, jsonx.V1())) tagsMap, _ := listBody["tags"].(map[string]any) assert.Equal(t, "email", tagsMap["type"]) @@ -334,7 +337,7 @@ func TestHandler_TagOperations(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var appResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&appResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &appResp, jsonx.V1())) appARN, _ := appResp["Arn"].(string) require.NotEmpty(t, appARN) @@ -353,7 +356,7 @@ func TestHandler_TagOperations(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.NewDecoder(listRec.Body).Decode(&tagsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(listRec.Body), &tagsResp, jsonx.V1())) tagsMap, _ := tagsResp["tags"].(map[string]any) assert.Equal(t, "integration", tagsMap["owner"]) diff --git a/services/pinpoint/template_versions.go b/services/pinpoint/template_versions.go index aca47d1d7b..a99ef33f25 100644 --- a/services/pinpoint/template_versions.go +++ b/services/pinpoint/template_versions.go @@ -1,10 +1,13 @@ package pinpoint import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const latestTemplateVersion = "latest" @@ -49,13 +52,13 @@ func (b *InMemoryBackend) nextTemplateVersionLocked( return current, nil } - raw, err := json.Marshal(live) + raw, err := json.Marshal(live, jsonx.V1()) if err != nil { return "", fmt.Errorf("snapshot template version: %w", err) } if b.templateVersionData[key] == nil { - b.templateVersionData[key] = make(map[string]json.RawMessage) + b.templateVersionData[key] = make(map[string]jsontext.Value) } b.templateVersionData[key][current] = raw @@ -137,7 +140,7 @@ func pickTemplateVersion[T any]( } var out T - if err := json.Unmarshal(raw, &out); err != nil { + if err := json.Unmarshal(raw, &out, jsonx.V1()); err != nil { return nil, false, fmt.Errorf("decode template version: %w", err) } @@ -345,7 +348,7 @@ func unlockedDelete[T any]( } // restoreLatestLocked rewinds the live template to an older snapshot, keeping its tags. -func (b *InMemoryBackend) restoreLatestLocked(name, templateType string, raw json.RawMessage) error { +func (b *InMemoryBackend) restoreLatestLocked(name, templateType string, raw jsontext.Value) error { switch strings.ToLower(templateType) { case templateTypeEmail: t, _ := b.emailTemplates.Get(name) @@ -372,9 +375,9 @@ func (b *InMemoryBackend) restoreLatestLocked(name, templateType string, raw jso return nil } -func restoreTemplate[T any](dst *T, raw json.RawMessage, tags func(*T) *map[string]string) error { +func restoreTemplate[T any](dst *T, raw jsontext.Value, tags func(*T) *map[string]string) error { var fresh T - if err := json.Unmarshal(raw, &fresh); err != nil { + if err := json.Unmarshal(raw, &fresh, jsonx.V1()); err != nil { return fmt.Errorf("restore template version: %w", err) } @@ -384,7 +387,7 @@ func restoreTemplate[T any](dst *T, raw json.RawMessage, tags func(*T) *map[stri return nil } -func (b *InMemoryBackend) liveTemplateJSONLocked(name, templateType string) (string, json.RawMessage, bool) { +func (b *InMemoryBackend) liveTemplateJSONLocked(name, templateType string) (string, jsontext.Value, bool) { var ( version string live any @@ -417,7 +420,7 @@ func (b *InMemoryBackend) liveTemplateJSONLocked(name, templateType string) (str return "", nil, false } - raw, err := json.Marshal(live) + raw, err := json.Marshal(live, jsonx.V1()) if err != nil { return "", nil, false } @@ -452,7 +455,7 @@ func (b *InMemoryBackend) ListTemplateVersions( var meta templateVersionMeta if len(raw) > 0 { - _ = json.Unmarshal(raw, &meta) + _ = json.Unmarshal(raw, &meta, jsonx.V1()) } cp.CreationDate = meta.CreationDate diff --git a/services/pinpoint/template_versions_test.go b/services/pinpoint/template_versions_test.go index b372d01730..d81de1fe50 100644 --- a/services/pinpoint/template_versions_test.go +++ b/services/pinpoint/template_versions_test.go @@ -1,19 +1,21 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func decodeBody(t *testing.T, body []byte) map[string]any { t.Helper() var got map[string]any - require.NoError(t, json.Unmarshal(body, &got)) + require.NoError(t, json.Unmarshal(body, &got, jsonx.V1())) return got } diff --git a/services/pinpoint/templates_create_new_version_test.go b/services/pinpoint/templates_create_new_version_test.go index 8a7095b0ee..b8467baf1a 100644 --- a/services/pinpoint/templates_create_new_version_test.go +++ b/services/pinpoint/templates_create_new_version_test.go @@ -1,12 +1,14 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestUpdateTemplate_CreateNewVersion(t *testing.T) { @@ -79,14 +81,14 @@ func TestUpdateTemplate_CreateNewVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var got map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, tt.wantVersion, got["Version"]) rec = doPinpointRequest(t, h, http.MethodGet, path+"/versions", nil) require.Equal(t, http.StatusOK, rec.Code) var vers map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vers)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &vers, jsonx.V1())) assert.Len(t, vers["Item"], tt.wantVersions) }) } diff --git a/services/pinpoint/templates_email_test.go b/services/pinpoint/templates_email_test.go index 932640f4dd..ded66dce11 100644 --- a/services/pinpoint/templates_email_test.go +++ b/services/pinpoint/templates_email_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -25,7 +27,7 @@ func TestListTemplates_EmailListed(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) require.NotEmpty(t, items) @@ -140,7 +142,7 @@ func TestEmailTemplate_BodyPersistence(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "get status") var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantSubject != "" { assert.Equal(t, tc.wantSubject, resp["Subject"], "Subject") @@ -219,7 +221,7 @@ func TestEmailTemplate_VersionBumpsOnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantVersion, resp["Version"], "Version after updates") }) } @@ -281,7 +283,7 @@ func TestEmailTemplate_DefaultSubstitutions(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantSubs, resp["DefaultSubstitutions"], "DefaultSubstitutions") }) @@ -377,7 +379,7 @@ func TestInAppTemplate_BodyPersistence(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "get status") var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantLayout != "" { assert.Equal(t, tc.wantLayout, resp["Layout"]) @@ -508,7 +510,7 @@ func TestInAppTemplate_ContentStructure(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "BOTTOM_BANNER", tmpl["Layout"]) assert.Equal(t, "Upgrade promo", tmpl["TemplateDescription"]) @@ -564,7 +566,7 @@ func TestInAppTemplate_MultipleContentItems(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) gotContent := tmpl["Content"].([]any) require.Len(t, gotContent, 3) @@ -609,7 +611,7 @@ func TestInAppTemplate_UpdateContent(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "TOP_BANNER", tmpl["Layout"]) content := tmpl["Content"].([]any) @@ -730,7 +732,7 @@ func TestHandler_CreateEmailTemplate(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, "Created", resp["Message"]) } @@ -765,7 +767,7 @@ func TestHandler_CreateInAppTemplate(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, "Created", resp["Message"]) } @@ -790,7 +792,7 @@ func TestEmailTemplate_TemplateType(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "EMAIL", resp["TemplateType"]) } @@ -811,7 +813,7 @@ func TestInAppTemplate_TemplateTypeAndCustomConfig(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "INAPP", resp["TemplateType"]) customConfig, _ := resp["CustomConfig"].(map[string]any) diff --git a/services/pinpoint/templates_push_test.go b/services/pinpoint/templates_push_test.go index 7fafd588dc..8ddbc7dd3e 100644 --- a/services/pinpoint/templates_push_test.go +++ b/services/pinpoint/templates_push_test.go @@ -1,7 +1,8 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pinpoint" ) @@ -25,7 +27,7 @@ func TestListTemplates_SMSListed(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) require.NotEmpty(t, items) @@ -119,7 +121,7 @@ func TestSmsTemplate_BodyPersistence(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "get status") var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantBody != "" { assert.Equal(t, tc.wantBody, resp["Body"], "Body") @@ -231,7 +233,7 @@ func TestPushTemplate_BodyPersistence(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "get status") var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantDescription != "" { assert.Equal(t, tc.wantDescription, resp["TemplateDescription"]) @@ -339,7 +341,7 @@ func TestPushTemplate_PerPlatformOverrides(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "PUSH", tmpl["TemplateType"]) assert.Equal(t, "reco-1", tmpl["RecommenderId"]) @@ -388,7 +390,7 @@ func TestPushTemplate_UpdatePerPlatform(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) def := tmpl["Default"].(map[string]any) assert.Equal(t, "v2 body", def["Body"]) @@ -431,7 +433,7 @@ func TestSMSTemplate_RecommenderID(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "Hello from {{sender}}", tmpl["Body"]) @@ -458,7 +460,7 @@ func TestSMSTemplate_UpdateRecommenderID(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "Hello v2", tmpl["Body"]) assert.Equal(t, "reco-new", tmpl["RecommenderId"]) @@ -565,7 +567,7 @@ func TestHandler_CreatePushTemplate(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, "Created", resp["Message"]) } @@ -600,7 +602,7 @@ func TestHandler_CreateSmsTemplate(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Arn"]) assert.Equal(t, "Created", resp["Message"]) } diff --git a/services/pinpoint/templates_test.go b/services/pinpoint/templates_test.go index 3c72455b17..db319eaeef 100644 --- a/services/pinpoint/templates_test.go +++ b/services/pinpoint/templates_test.go @@ -1,7 +1,7 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestVoiceTemplate_FullCRUD(t *testing.T) { @@ -62,7 +64,7 @@ func TestVoiceTemplate_FullCRUD(t *testing.T) { "body: %s", createRec.Body.String()) var cr map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &cr, jsonx.V1())) assert.NotEmpty(t, cr["Message"]) getRec := doPinpointRequest(t, h, http.MethodGet, @@ -70,7 +72,7 @@ func TestVoiceTemplate_FullCRUD(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, templateName, gr["TemplateName"]) if tc.wantBody != "SKIP" { assert.Equal(t, tc.wantBody, gr["Body"]) @@ -118,7 +120,7 @@ func TestVoiceTemplate_FullFieldSet(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var gr map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &gr, jsonx.V1())) assert.Equal(t, templateName, gr["TemplateName"]) assert.Equal(t, "VOICE", gr["TemplateType"]) @@ -139,7 +141,7 @@ func TestVoiceTemplate_FullFieldSet(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var gr2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &gr2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &gr2, jsonx.V1())) assert.Equal(t, "Matthew", gr2["VoiceId"]) assert.Equal(t, "en-GB", gr2["LanguageCode"]) @@ -190,7 +192,7 @@ func TestVoiceTemplate_VersionHistory(t *testing.T) { require.Equal(t, http.StatusOK, versionsRec.Code) var versionsResp map[string]any - require.NoError(t, json.Unmarshal(versionsRec.Body.Bytes(), &versionsResp)) + require.NoError(t, json.Unmarshal(versionsRec.Body.Bytes(), &versionsResp, jsonx.V1())) items, _ := versionsResp["Item"].([]any) assert.Len(t, items, tc.wantVersions) }) @@ -214,7 +216,7 @@ func TestListTemplates_VoiceListed(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) require.NotEmpty(t, items) @@ -243,7 +245,7 @@ func TestListTemplates_MixedTypes(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.GreaterOrEqual(t, len(items), 3) } @@ -365,7 +367,7 @@ func TestListTemplates_PrefixFilter(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tc.wantCount, "Item count with prefix=%q", tc.prefix) @@ -456,7 +458,7 @@ func TestListTemplates_TypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tc.wantCount, @@ -545,7 +547,7 @@ func TestListTemplates_PrefixAndTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tc.wantCount, @@ -622,7 +624,7 @@ func TestTemplateVersionHistory(t *testing.T) { require.Equal(t, http.StatusOK, verRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(verRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) assert.Len(t, items, tc.wantVersions, @@ -687,7 +689,7 @@ func TestTemplate_LastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, getRec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &resp1, jsonx.V1())) assert.NotEmpty(t, resp1["CreationDate"], "CreationDate should be set") assert.NotEmpty(t, resp1["LastModifiedDate"], "LastModifiedDate should be set after create") @@ -701,7 +703,7 @@ func TestTemplate_LastModifiedDate(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &resp2, jsonx.V1())) assert.NotEmpty(t, resp2["LastModifiedDate"], "LastModifiedDate should be present after update") }) @@ -750,7 +752,7 @@ func TestListTemplates_SortedOutput(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) require.Len(t, items, len(tc.creates)) @@ -821,7 +823,7 @@ func TestTemplate_ARNPresent(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["Arn"].(string) assert.NotEmpty(t, arn, "template ARN must be present in GET response") @@ -888,7 +890,7 @@ func TestTemplate_TagsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) tagsRaw, _ := resp["tags"].(map[string]any) @@ -925,7 +927,7 @@ func TestListTemplates_FieldCompleteness(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Item"].([]any) require.Len(t, items, 3) @@ -964,7 +966,7 @@ func TestVoiceTemplate_TagsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var tmpl map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &tmpl, jsonx.V1())) assert.Equal(t, "Hello {{user.FirstName}}, your appointment is tomorrow.", tmpl["Body"]) assert.NotEmpty(t, tmpl["Arn"]) @@ -988,7 +990,7 @@ func TestVoiceTemplate_TagRoundTrip(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) templateARN, _ := createResp["Arn"].(string) require.NotEmpty(t, templateARN) @@ -1003,7 +1005,7 @@ func TestVoiceTemplate_TagRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags, _ := tagsResp["tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) diff --git a/services/pinpoint/wire_field_fixes_test.go b/services/pinpoint/wire_field_fixes_test.go index e9af4f80b0..bcc23fb650 100644 --- a/services/pinpoint/wire_field_fixes_test.go +++ b/services/pinpoint/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package pinpoint_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/pinpoint/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestApplicationDateRangeKpi_RequiredTimeRange covers gopherstack-6flj: @@ -216,7 +218,7 @@ func TestCreateSegment_RawImportDefinitionFieldIgnored(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "DIMENSIONAL", out["SegmentType"], "CreateSegment must not accept ImportDefinition; the real WriteSegmentRequest has no such member") assert.Nil(t, out["ImportDefinition"]) @@ -245,7 +247,7 @@ func TestCreateJourney_RawTagsFieldIgnored(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Nil(t, out["tags"], "CreateJourney must not accept or echo tags; the real WriteJourneyRequest/JourneyResponse have no such member") diff --git a/services/pipes/enrichment_test.go b/services/pipes/enrichment_test.go index 28778976b8..fdb351ce45 100644 --- a/services/pipes/enrichment_test.go +++ b/services/pipes/enrichment_test.go @@ -6,12 +6,13 @@ package pipes_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -297,7 +298,7 @@ func TestEnrichment_LambdaInvocation(t *testing.T) { } else { // Nil response: original records payload forwarded. var event map[string]any - require.NoError(t, json.Unmarshal(targetPayloads[0], &event)) + require.NoError(t, json.Unmarshal(targetPayloads[0], &event, jsonx.V1())) _, hasRecords := event["Records"] assert.True(t, hasRecords, "original Records payload should be forwarded when enricher returns nil") } diff --git a/services/pipes/errsweep_wire_shape_test.go b/services/pipes/errsweep_wire_shape_test.go index 9617e60448..4da8d5bfd4 100644 --- a/services/pipes/errsweep_wire_shape_test.go +++ b/services/pipes/errsweep_wire_shape_test.go @@ -1,7 +1,7 @@ package pipes_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/pipes" @@ -58,7 +59,7 @@ func TestHandler_MalformedBodyCarriesTypedErrorCode(t *testing.T) { require.NoError(t, err) var decoded map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) + require.NoError(t, json.Unmarshal(raw, &decoded, jsonx.V1())) require.Equal(t, "ValidationException", decoded["__type"], "raw response body must carry __type; got: %s", raw) require.NotEmpty(t, decoded["message"]) diff --git a/services/pipes/filter.go b/services/pipes/filter.go index b135ffe5ac..977e94f122 100644 --- a/services/pipes/filter.go +++ b/services/pipes/filter.go @@ -2,12 +2,14 @@ package pipes import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "reflect" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/eventpattern" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // matchesAnyFilter returns true if body passes at least one of the given filters. @@ -67,8 +69,8 @@ func matchesSingleFilter(body string, f Filter) bool { // and/or content-filter objects) or a nested object; a bare scalar is not a // valid EventBridge pattern value and never matches (see isJSONObject). func matchesJSONPattern(msgBody, pattern string) bool { - var patternMap map[string]json.RawMessage - if err := json.Unmarshal([]byte(pattern), &patternMap); err != nil { + var patternMap map[string]jsontext.Value + if err := json.Unmarshal([]byte(pattern), &patternMap, jsonx.V1()); err != nil { // Malformed pattern: fall back to substring. return strings.Contains(msgBody, pattern) } @@ -77,8 +79,8 @@ func matchesJSONPattern(msgBody, pattern string) bool { return true } - var msgMap map[string]json.RawMessage - if err := json.Unmarshal([]byte(msgBody), &msgMap); err != nil { + var msgMap map[string]jsontext.Value + if err := json.Unmarshal([]byte(msgBody), &msgMap, jsonx.V1()); err != nil { // Message body is not a JSON object; pattern cannot match. return false } @@ -89,7 +91,7 @@ func matchesJSONPattern(msgBody, pattern string) bool { // matchesPatternObject reports whether every field in patternMap is // satisfied by msgMap (fields at one level are ANDed). "$or" is a // combinator, not a message field, so it is dispatched separately. -func matchesPatternObject(patternMap, msgMap map[string]json.RawMessage) bool { +func matchesPatternObject(patternMap, msgMap map[string]jsontext.Value) bool { for field, ruleRaw := range patternMap { if field == "$or" { if !matchesOrCombinator(ruleRaw, msgMap) { @@ -111,9 +113,9 @@ func matchesPatternObject(patternMap, msgMap map[string]json.RawMessage) bool { // matchesOrCombinator evaluates a "$or" combinator // (eb-filtering-complex-example-or): ruleRaw must decode to an array of // pattern objects, and msgMap matches if any one of them fully matches. -func matchesOrCombinator(ruleRaw json.RawMessage, msgMap map[string]json.RawMessage) bool { - var alternatives []json.RawMessage - if err := json.Unmarshal(ruleRaw, &alternatives); err != nil { +func matchesOrCombinator(ruleRaw jsontext.Value, msgMap map[string]jsontext.Value) bool { + var alternatives []jsontext.Value + if err := json.Unmarshal(ruleRaw, &alternatives, jsonx.V1()); err != nil { return false } @@ -135,15 +137,15 @@ func matchesOrCombinator(ruleRaw json.RawMessage, msgMap map[string]json.RawMess // its first non-whitespace byte rather than by attempting to unmarshal it -- // unmarshalling JSON `null` into a map succeeds with a nil map, which would // otherwise misclassify a literal null rule element as an object. -func isJSONObject(raw json.RawMessage) bool { +func isJSONObject(raw jsontext.Value) bool { trimmed := bytes.TrimSpace(raw) return len(trimmed) > 0 && trimmed[0] == '{' } -func decodeJSONObject(raw json.RawMessage) (map[string]json.RawMessage, bool) { - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil { +func decodeJSONObject(raw jsontext.Value) (map[string]jsontext.Value, bool) { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil { return nil, false } @@ -157,13 +159,13 @@ func decodeJSONObject(raw json.RawMessage) (map[string]json.RawMessage, bool) { // needed because {"exists": false} matches precisely when the field is // absent, so evaluation cannot short-circuit on absence the way every other // operator does. -func fieldMatchesRule(msgVal json.RawMessage, exists bool, ruleRaw json.RawMessage) bool { +func fieldMatchesRule(msgVal jsontext.Value, exists bool, ruleRaw jsontext.Value) bool { if isJSONObject(ruleRaw) { return matchesNestedRule(msgVal, exists, ruleRaw) } - var rules []json.RawMessage - if err := json.Unmarshal(ruleRaw, &rules); err != nil { + var rules []jsontext.Value + if err := json.Unmarshal(ruleRaw, &rules, jsonx.V1()); err != nil { return false } @@ -179,7 +181,7 @@ func fieldMatchesRule(msgVal json.RawMessage, exists bool, ruleRaw json.RawMessa // matchesNestedRule descends one level into the event for a nested pattern // object. A nested pattern can only match a present, JSON-object-shaped // field. -func matchesNestedRule(msgVal json.RawMessage, exists bool, ruleRaw json.RawMessage) bool { +func matchesNestedRule(msgVal jsontext.Value, exists bool, ruleRaw jsontext.Value) bool { if !exists { return false } @@ -216,7 +218,7 @@ func matchesNestedRule(msgVal json.RawMessage, exists bool, ruleRaw json.RawMess // msgExists is false whenever the field was absent from the message; every // operator besides exists requires a value to compare against, so absence // fails them all except an explicit {"exists": false}. -func matchesRule(msgVal json.RawMessage, msgExists bool, rule json.RawMessage) bool { +func matchesRule(msgVal jsontext.Value, msgExists bool, rule jsontext.Value) bool { if isJSONObject(rule) { ruleObj, ok := decodeJSONObject(rule) if !ok { @@ -247,13 +249,13 @@ func matchesRule(msgVal json.RawMessage, msgExists bool, rule json.RawMessage) b // numerically-equal event number). reflect.DeepEqual (not ==) is required // because a malformed pattern or event can decode to a non-comparable type // (a JSON array or object), which would panic under ==. -func matchesExactRule(msgVal, rule json.RawMessage) bool { +func matchesExactRule(msgVal, rule jsontext.Value) bool { var ruleAny, msgAny any - if err := json.Unmarshal(rule, &ruleAny); err != nil { + if err := json.Unmarshal(rule, &ruleAny, jsonx.V1()); err != nil { return false } - if err := json.Unmarshal(msgVal, &msgAny); err != nil { + if err := json.Unmarshal(msgVal, &msgAny, jsonx.V1()); err != nil { return false } @@ -263,7 +265,7 @@ func matchesExactRule(msgVal, rule json.RawMessage) bool { // matchesRuleObject dispatches a single-key content-filter object to its // matcher. Supported: prefix, suffix, numeric, cidr, anything-but, // equals-ignore-case. -func matchesRuleObject(msgVal json.RawMessage, ruleObj map[string]json.RawMessage) bool { +func matchesRuleObject(msgVal jsontext.Value, ruleObj map[string]jsontext.Value) bool { if prefixRaw, ok := ruleObj["prefix"]; ok { return matchesPrefixRule(msgVal, prefixRaw) } @@ -291,18 +293,18 @@ func matchesRuleObject(msgVal json.RawMessage, ruleObj map[string]json.RawMessag return false } -func decodeString(raw json.RawMessage) (string, bool) { +func decodeString(raw jsontext.Value) (string, bool) { var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return "", false } return s, true } -func decodeFloat(raw json.RawMessage) (float64, bool) { +func decodeFloat(raw jsontext.Value) (float64, bool) { var f float64 - if err := json.Unmarshal(raw, &f); err != nil { + if err := json.Unmarshal(raw, &f, jsonx.V1()); err != nil { return 0, false } @@ -312,7 +314,7 @@ func decodeFloat(raw json.RawMessage) (float64, bool) { // matchesPrefixRule matches a prefix rule value against msgVal. AWS supports // both a plain string prefix and a case-insensitive nested form // (eb-filtering-prefix-matching-ignore-case): {"prefix": {"equals-ignore-case": "foo"}}. -func matchesPrefixRule(msgVal, prefixRaw json.RawMessage) bool { +func matchesPrefixRule(msgVal, prefixRaw jsontext.Value) bool { msgStr, ok := decodeString(msgVal) if !ok { return false @@ -338,7 +340,7 @@ func matchesPrefixRule(msgVal, prefixRaw json.RawMessage) bool { // matchesSuffixRule matches a suffix rule value against msgVal. AWS supports // both a plain string suffix and a case-insensitive nested form // (eb-filtering-suffix-matching-ignore-case): {"suffix": {"equals-ignore-case": "foo"}}. -func matchesSuffixRule(msgVal, suffixRaw json.RawMessage) bool { +func matchesSuffixRule(msgVal, suffixRaw jsontext.Value) bool { msgStr, ok := decodeString(msgVal) if !ok { return false @@ -363,7 +365,7 @@ func matchesSuffixRule(msgVal, suffixRaw json.RawMessage) bool { // nestedEqualsIgnoreCase extracts the string value of a nested // {"equals-ignore-case": "..."} object, as used inside prefix/suffix rules. -func nestedEqualsIgnoreCase(nested map[string]json.RawMessage) (string, bool) { +func nestedEqualsIgnoreCase(nested map[string]jsontext.Value) (string, bool) { ciRaw, ok := nested["equals-ignore-case"] if !ok { return "", false @@ -374,7 +376,7 @@ func nestedEqualsIgnoreCase(nested map[string]json.RawMessage) (string, bool) { // matchesEqualsIgnoreCaseRule matches a standalone // {"equals-ignore-case": "..."} rule (eb-filtering-equals-ignore-case-matching). -func matchesEqualsIgnoreCaseRule(msgVal, valRaw json.RawMessage) bool { +func matchesEqualsIgnoreCaseRule(msgVal, valRaw jsontext.Value) bool { msgStr, ok := decodeString(msgVal) if !ok { return false @@ -393,9 +395,9 @@ func matchesEqualsIgnoreCaseRule(msgVal, valRaw json.RawMessage) bool { // services/eventbridge/pattern.go's matchNumeric. msgVal must decode as a // JSON number; a string-encoded number (as DynamoDB Streams' "N" attribute // wrapper produces) does not match. -func matchesNumericRule(msgVal, rulesRaw json.RawMessage) bool { +func matchesNumericRule(msgVal, rulesRaw jsontext.Value) bool { var ruleList []any - if err := json.Unmarshal(rulesRaw, &ruleList); err != nil { + if err := json.Unmarshal(rulesRaw, &ruleList, jsonx.V1()); err != nil { return false } @@ -410,7 +412,7 @@ func matchesNumericRule(msgVal, rulesRaw json.RawMessage) bool { // matchesCIDRRule reports whether the string msgVal is an IP address inside // the given CIDR range (eb-filtering-ip-address), via pkgs/eventpattern, // shared with services/eventbridge/pattern.go's matchCIDR. -func matchesCIDRRule(msgVal, cidrRaw json.RawMessage) bool { +func matchesCIDRRule(msgVal, cidrRaw jsontext.Value) bool { ipStr, ok := decodeString(msgVal) if !ok { return false @@ -426,14 +428,14 @@ func matchesCIDRRule(msgVal, cidrRaw json.RawMessage) bool { // existsWant extracts {"exists": true/false}'s boolean, ok=false if the key // is absent or not a bool. -func existsWant(ruleObj map[string]json.RawMessage) (bool, bool) { +func existsWant(ruleObj map[string]jsontext.Value) (bool, bool) { existsRaw, hasKey := ruleObj["exists"] if !hasKey { return false, false } var want bool - if err := json.Unmarshal(existsRaw, &want); err != nil { + if err := json.Unmarshal(existsRaw, &want, jsonx.V1()); err != nil { return false, false } @@ -448,19 +450,19 @@ func existsWant(ruleObj map[string]json.RawMessage) (bool, bool) { // records use, where every AttributeValue is string-wrapped); a non-string // msgVal is never equal to a string exclusion value, so it always counts as // "anything but" (matchesRuleObject negates this function's result). -func matchesAnythingBut(anythingButRaw, msgVal json.RawMessage) bool { +func matchesAnythingBut(anythingButRaw, msgVal jsontext.Value) bool { msgStr, ok := decodeString(msgVal) if !ok { return false } var single string - if err := json.Unmarshal(anythingButRaw, &single); err == nil { + if err := json.Unmarshal(anythingButRaw, &single, jsonx.V1()); err == nil { return msgStr == single } var excluded []string - if err := json.Unmarshal(anythingButRaw, &excluded); err == nil { + if err := json.Unmarshal(anythingButRaw, &excluded, jsonx.V1()); err == nil { return slices.Contains(excluded, msgStr) } diff --git a/services/pipes/filter_validation.go b/services/pipes/filter_validation.go index 4135b95d6c..0e4b5c9795 100644 --- a/services/pipes/filter_validation.go +++ b/services/pipes/filter_validation.go @@ -1,9 +1,11 @@ package pipes import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // isKnownPipeMatcher reports whether key is a matcher-object key that pipes' @@ -54,7 +56,7 @@ func validateFilterPattern(pattern string) error { } var patternMap map[string]any - if err := json.Unmarshal([]byte(trimmed), &patternMap); err != nil { + if err := json.Unmarshal([]byte(trimmed), &patternMap, jsonx.V1()); err != nil { return fmt.Errorf("pattern: %w: not valid JSON: %w", ErrValidation, err) } diff --git a/services/pipes/handler.go b/services/pipes/handler.go index f351c348a9..bce6f104a0 100644 --- a/services/pipes/handler.go +++ b/services/pipes/handler.go @@ -2,7 +2,7 @@ package pipes import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -350,40 +351,37 @@ func errMessage(err error) string { } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrNotFound): payload, _ := json.Marshal(map[string]string{ keyTypeField: "NotFoundException", keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusNotFound, payload) case errors.Is(err, ErrAlreadyExists), errors.Is(err, ErrConflict): payload, _ := json.Marshal(map[string]string{ keyTypeField: "ConflictException", keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusConflict, payload) case errors.Is(err, ErrQuota): payload, _ := json.Marshal(map[string]string{ keyTypeField: "ServiceQuotaExceededException", keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrValidation): payload, _ := json.Marshal(map[string]string{ keyTypeField: errCodeValidation, keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): // This branch used to omit keyTypeField entirely, so // restjson.GetErrorInfo (aws-sdk-go-v2 aws/protocol/restjson/ // decoder_util.go:15) found no code in the header (unset) or the @@ -398,7 +396,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: "InternalException", keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusInternalServerError, payload) } @@ -512,7 +510,7 @@ func (h *Handler) handleCreatePipe(ctx context.Context, path string, body []byte } var req createPipeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -535,7 +533,7 @@ func (h *Handler) handleCreatePipe(ctx context.Context, path string, body []byte return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } func (h *Handler) handleDescribePipe(ctx context.Context, path string) ([]byte, error) { @@ -549,7 +547,7 @@ func (h *Handler) handleDescribePipe(ctx context.Context, path string) ([]byte, return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } // pipeSummary matches types.Pipe (the summary shape ListPipesOutput.Pipes uses), @@ -613,7 +611,7 @@ func (h *Handler) handleListPipes(ctx context.Context, query url.Values) ([]byte }) } - return json.Marshal(listPipesResponse{Pipes: items, NextToken: res.NextToken}) + return json.Marshal(listPipesResponse{Pipes: items, NextToken: res.NextToken}, jsonx.V1()) } func (h *Handler) handleDeletePipe(ctx context.Context, path string) ([]byte, error) { @@ -627,7 +625,7 @@ func (h *Handler) handleDeletePipe(ctx context.Context, path string) ([]byte, er return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } func (h *Handler) handleUpdatePipe(ctx context.Context, path string, body []byte) ([]byte, error) { @@ -637,7 +635,7 @@ func (h *Handler) handleUpdatePipe(ctx context.Context, path string, body []byte } var req updatePipeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -657,7 +655,7 @@ func (h *Handler) handleUpdatePipe(ctx context.Context, path string, body []byte return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } func (h *Handler) handleStartPipe(ctx context.Context, path string) ([]byte, error) { @@ -671,7 +669,7 @@ func (h *Handler) handleStartPipe(ctx context.Context, path string) ([]byte, err return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } func (h *Handler) handleStopPipe(ctx context.Context, path string) ([]byte, error) { @@ -685,7 +683,7 @@ func (h *Handler) handleStopPipe(ctx context.Context, path string) ([]byte, erro return nil, err } - return json.Marshal(toPipeResponse(p)) + return json.Marshal(toPipeResponse(p), jsonx.V1()) } type tagResourceRequest struct { @@ -699,7 +697,7 @@ func (h *Handler) handleTagResource(ctx context.Context, path string, body []byt } var req tagResourceRequest - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, unmarshalErr) } @@ -750,7 +748,7 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, path string) ([ return nil, err } - return json.Marshal(listTagsResponse{Tags: tags}) + return json.Marshal(listTagsResponse{Tags: tags}, jsonx.V1()) } func extractTagsARN(path string) (string, error) { diff --git a/services/pipes/handler_test.go b/services/pipes/handler_test.go index a87aa2e720..3ca6f18f6e 100644 --- a/services/pipes/handler_test.go +++ b/services/pipes/handler_test.go @@ -3,7 +3,7 @@ package pipes_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -32,7 +33,7 @@ func doPipesRequest(t *testing.T, h *pipes.Handler, method, path string, body an if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -340,7 +341,7 @@ func createTestPipeWithARN(t *testing.T, h *pipes.Handler, pipeName string) stri require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Arn"].(string) } @@ -529,7 +530,7 @@ func TestPipes_Handler_Reset(t *testing.T) { var out struct { Pipes []any `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Pipes, tt.wantAfter) }) } @@ -634,7 +635,7 @@ func TestHandler_ListPipesFiltering(t *testing.T) { Name string `json:"Name"` } `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Pipes, 2) }) @@ -647,7 +648,7 @@ func TestHandler_ListPipesFiltering(t *testing.T) { Name string `json:"Name"` } `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Pipes, 1) assert.Equal(t, "sqs-beta", out.Pipes[0].Name) }) @@ -688,7 +689,7 @@ func TestHandler_SourceAndTargetParameters(t *testing.T) { } `json:"SqsQueueParameters"` } `json:"SourceParameters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) assert.Equal(t, 5, created.SourceParameters.SqsQueueParameters.BatchSize) assert.JSONEq(t, `{"fixed":"value"}`, created.TargetParameters.InputTemplate) } @@ -713,7 +714,7 @@ func TestHandler_ListPipesIncludesSourceTarget(t *testing.T) { var out struct { Pipes []map[string]any `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Pipes, 1) assert.Equal(t, "arn:aws:sqs:us-east-1:000000000000:my-queue", out.Pipes[0]["Source"]) assert.Equal(t, "arn:aws:lambda:us-east-1:000000000000:function:my-fn", out.Pipes[0]["Target"]) @@ -740,7 +741,7 @@ func auditDo(t *testing.T, h *pipes.Handler, method, path string, body any) *htt if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -763,7 +764,7 @@ func auditCreate(t *testing.T, h *pipes.Handler, name string, body map[string]an require.Equal(t, http.StatusOK, rec.Code, "create pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -775,7 +776,7 @@ func auditDescribe(t *testing.T, h *pipes.Handler, name string) map[string]any { require.Equal(t, http.StatusOK, rec.Code, "describe pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -839,7 +840,7 @@ func TestStartStop_ConflictException(t *testing.T) { "real AWS returns 409 ConflictException, not 400 ValidationException") var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ConflictException", resp["__type"], "error type must be ConflictException; body: %s", rec.Body.String()) }) @@ -881,7 +882,7 @@ func TestServiceQuotaExceededException(t *testing.T) { // The key assertion is the error type when it does fail. if rec.Code != http.StatusOK { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ServiceQuotaExceededException", resp["__type"], "quota exceeded must return ServiceQuotaExceededException; body: %s", rec.Body.String()) } @@ -908,7 +909,7 @@ func TestListPipes_EnrichmentInSummary(t *testing.T) { var out struct { Pipes []map[string]any `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Pipes, 1) enrichment, _ := out.Pipes[0]["Enrichment"].(string) @@ -935,7 +936,7 @@ func TestListPipes_NoEnrichmentOmitted(t *testing.T) { var out struct { Pipes []map[string]any `json:"Pipes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Pipes, 1) _, hasEnrichment := out.Pipes[0]["Enrichment"] @@ -954,7 +955,7 @@ func TestInvalidNextToken_ValidationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"], "invalid NextToken must return ValidationException; body: %s", rec.Body.String()) } @@ -1020,7 +1021,7 @@ func TestServiceQuotaErrorCode(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ServiceQuotaExceededException", resp["__type"], "1001st pipe must fail with ServiceQuotaExceededException; body: %s", rec.Body.String()) } @@ -1144,6 +1145,6 @@ func TestHandler_UpdatePipeDesiredState(t *testing.T) { var out struct { DesiredState string `json:"DesiredState"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "STOPPED", out.DesiredState) } diff --git a/services/pipes/persistence.go b/services/pipes/persistence.go index 00808669a9..e2cd4d22ef 100644 --- a/services/pipes/persistence.go +++ b/services/pipes/persistence.go @@ -2,7 +2,7 @@ package pipes import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strings" @@ -39,10 +39,10 @@ const pipesSnapshotVersion = 3 // Restore must pre-register every (resource, region) tuple found in Tables // before calling registry.RestoreAll, see preRegisterSnapshotTables below. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -82,7 +82,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { // "/" (see store_setup.go's pipesTable/ // enrichmentCountsTable); splitting on the first "/" recovers both parts // (region names never contain "/"). -func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]json.RawMessage) { +func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]jsontext.Value) { for key := range tables { name, region, found := strings.Cut(key, "/") if !found { @@ -128,7 +128,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { } if snap.Tables == nil { - snap.Tables = make(map[string]json.RawMessage) + snap.Tables = make(map[string]jsontext.Value) } b.preRegisterSnapshotTables(snap.Tables) diff --git a/services/pipes/pipe_lifecycle_test.go b/services/pipes/pipe_lifecycle_test.go index 96eba038ea..90a613a68a 100644 --- a/services/pipes/pipe_lifecycle_test.go +++ b/services/pipes/pipe_lifecycle_test.go @@ -8,7 +8,7 @@ package pipes_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -247,7 +248,7 @@ func TestLifecycle_DeleteReturnsBody(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DELETING", resp["CurrentState"]) assert.Equal(t, tt.pipeName, resp["Name"]) assert.NotEmpty(t, resp["Arn"]) diff --git a/services/pipes/pipes_test.go b/services/pipes/pipes_test.go index 9d45152f60..c26edaf1a9 100644 --- a/services/pipes/pipes_test.go +++ b/services/pipes/pipes_test.go @@ -5,13 +5,14 @@ package pipes_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -57,7 +58,7 @@ func TestPagination_Limit(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pipeList, _ := resp["Pipes"].([]any) assert.Len(t, pipeList, tt.wantCount) }) @@ -87,7 +88,7 @@ func TestPagination_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, page1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(page1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(page1.Body.Bytes(), &resp1, jsonx.V1())) list1 := resp1["Pipes"].([]any) assert.Len(t, list1, 2) assert.NotEmpty(t, resp1["NextToken"], "NextToken should be present when more pages exist") @@ -98,7 +99,7 @@ func TestPagination_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, page2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(page2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(page2.Body.Bytes(), &resp2, jsonx.V1())) list2 := resp2["Pipes"].([]any) assert.NotEmpty(t, list2) diff --git a/services/pipes/runner.go b/services/pipes/runner.go index 7288d1c616..b641961b29 100644 --- a/services/pipes/runner.go +++ b/services/pipes/runner.go @@ -2,7 +2,7 @@ package pipes import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" @@ -10,6 +10,7 @@ import ( "time" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" @@ -367,7 +368,7 @@ func (r *Runner) pollSQSPipe(ctx context.Context, p *Pipe) { return } - payload, err := json.Marshal(sqsPipeEvent{Records: buildSQSRecords(p, msgs)}) + payload, err := json.Marshal(sqsPipeEvent{Records: buildSQSRecords(p, msgs)}, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "pipes: failed to marshal SQS records", "pipe", p.Name, "error", err) diff --git a/services/pipes/runner_targets_test.go b/services/pipes/runner_targets_test.go index a2691514bc..0390007edb 100644 --- a/services/pipes/runner_targets_test.go +++ b/services/pipes/runner_targets_test.go @@ -8,7 +8,7 @@ package pipes_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "sync" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -40,7 +41,7 @@ func b3Do(t *testing.T, h *pipes.Handler, method, path string, body any) *httpte if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -63,7 +64,7 @@ func b3Create(t *testing.T, h *pipes.Handler, name string, body map[string]any) require.Equal(t, http.StatusOK, rec.Code, "create pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -75,7 +76,7 @@ func b3Describe(t *testing.T, h *pipes.Handler, name string) map[string]any { require.Equal(t, http.StatusOK, rec.Code, "describe pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -276,7 +277,7 @@ func b4Do(t *testing.T, h *pipes.Handler, method, path string, body any) *httpte var b []byte if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -297,7 +298,7 @@ func b4Create(t *testing.T, h *pipes.Handler, name string, body map[string]any) rec := b4Do(t, h, http.MethodPost, "/v1/pipes/"+name, body) require.Equal(t, http.StatusOK, rec.Code, "create pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -307,7 +308,7 @@ func b4Describe(t *testing.T, h *pipes.Handler, name string) map[string]any { rec := b4Do(t, h, http.MethodGet, "/v1/pipes/"+name, nil) require.Equal(t, http.StatusOK, rec.Code, "describe pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -925,7 +926,7 @@ func TestTarget_Firehose_InputTemplate(t *testing.T) { } else { // No template → SQS records JSON is forwarded. var event map[string]any - require.NoError(t, json.Unmarshal(records[0], &event)) + require.NoError(t, json.Unmarshal(records[0], &event, jsonx.V1())) _, hasRecords := event["Records"] assert.True(t, hasRecords, "Firehose should receive SQS records payload") } diff --git a/services/pipes/runner_test.go b/services/pipes/runner_test.go index 0f3e99d89b..5cb441616e 100644 --- a/services/pipes/runner_test.go +++ b/services/pipes/runner_test.go @@ -2,7 +2,7 @@ package pipes_test import ( "context" - "encoding/json" + "encoding/json/v2" "sync" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -138,7 +139,7 @@ func TestPipesRunner_SQSToLambda(t *testing.T) { EventSource string `json:"eventSource"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payloads[0], &event)) + require.NoError(t, json.Unmarshal(payloads[0], &event, jsonx.V1())) require.Len(t, event.Records, 2) assert.Equal(t, "msg-1", event.Records[0].MessageID) assert.Equal(t, "hello", event.Records[0].Body) @@ -313,7 +314,7 @@ func TestPipesRunner_FilterCriteria(t *testing.T) { require.Len(t, calls, 1) var event map[string]any - require.NoError(t, json.Unmarshal(payloads[0], &event)) + require.NoError(t, json.Unmarshal(payloads[0], &event, jsonx.V1())) require.Len(t, event, 1) // payload is forwarded - just check Lambda was called once diff --git a/services/pipes/runner_timestream.go b/services/pipes/runner_timestream.go index 6676d8c05f..e3d92103af 100644 --- a/services/pipes/runner_timestream.go +++ b/services/pipes/runner_timestream.go @@ -3,12 +3,14 @@ package pipes import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -88,7 +90,7 @@ func (r *Runner) invokeTimestreamTarget(ctx context.Context, p *Pipe, payload [] // or the payload itself as a single event. func splitPipeEvents(payload []byte) []any { var v any - if err := json.Unmarshal(payload, &v); err != nil { + if err := json.Unmarshal(payload, &v, jsonx.V1()); err != nil { return nil } @@ -331,11 +333,11 @@ func decodeEmbeddedJSON(v any) any { } var out any - if json.Unmarshal([]byte(s), &out) == nil { + if json.Unmarshal([]byte(s), &out, jsonx.V1()) == nil { return out } - if raw, err := base64.StdEncoding.DecodeString(s); err == nil && json.Unmarshal(raw, &out) == nil { + if raw, err := base64.StdEncoding.DecodeString(s); err == nil && json.Unmarshal(raw, &out, jsonx.V1()) == nil { return out } @@ -354,7 +356,7 @@ func pipeScalarString(v any) string { return "" } - b, _ := json.Marshal(v) + b, _ := json.Marshal(v, jsonx.V1()) return string(b) } diff --git a/services/pipes/sources_poll.go b/services/pipes/sources_poll.go index 508b443796..2ee04025e2 100644 --- a/services/pipes/sources_poll.go +++ b/services/pipes/sources_poll.go @@ -3,9 +3,10 @@ package pipes import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -148,7 +149,7 @@ func (r *Runner) pollKinesisShard(ctx context.Context, p *Pipe, shardID, startin return } - payload, err := json.Marshal(kinesisPipeEvent{Records: buildKinesisRecords(p, shardID, records)}) + payload, err := json.Marshal(kinesisPipeEvent{Records: buildKinesisRecords(p, shardID, records)}, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "pipes: failed to marshal Kinesis records", "pipe", p.Name, "error", err) @@ -218,7 +219,7 @@ func (r *Runner) pollDynamoDBShard(ctx context.Context, p *Pipe, shardID, starti return } - payload, err := json.Marshal(dynamoDBPipeEvent{Records: buildDynamoDBRecords(p, records)}) + payload, err := json.Marshal(dynamoDBPipeEvent{Records: buildDynamoDBRecords(p, records)}, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "pipes: failed to marshal DynamoDB stream records", "pipe", p.Name, "error", err) @@ -312,7 +313,7 @@ func filterDynamoDBRecords(p *Pipe, records []DynamoDBStreamRecord) []DynamoDBSt NewImage: rec.NewImage, OldImage: rec.OldImage, }, - }) + }, jsonx.V1()) if err == nil && matchesAnyFilter(string(body), filters) { out = append(out, rec) } diff --git a/services/pipes/sources_test.go b/services/pipes/sources_test.go index bd36b94b67..70b31e069d 100644 --- a/services/pipes/sources_test.go +++ b/services/pipes/sources_test.go @@ -15,13 +15,14 @@ package pipes_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -828,7 +829,7 @@ func TestPipesRunner_StreamSourcePolling(t *testing.T) { EventSource string `json:"eventSource"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payloads[0], &event)) + require.NoError(t, json.Unmarshal(payloads[0], &event, jsonx.V1())) require.Len(t, event.Records, 1) assert.Equal(t, "pk1", event.Records[0].Kinesis.PartitionKey) assert.Equal(t, "aws:kinesis", event.Records[0].EventSource) @@ -997,7 +998,7 @@ func TestPipesRunner_StreamSourcePolling(t *testing.T) { EventName string `json:"eventName"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payloads[0], &event)) + require.NoError(t, json.Unmarshal(payloads[0], &event, jsonx.V1())) require.Len(t, event.Records, 1) assert.Equal(t, "INSERT", event.Records[0].EventName) }, diff --git a/services/pipes/tags_test.go b/services/pipes/tags_test.go index c92bd68b2d..7c524cf576 100644 --- a/services/pipes/tags_test.go +++ b/services/pipes/tags_test.go @@ -1,12 +1,14 @@ package pipes_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestTags_CreateAndDescribe verifies tags are stored on create and returned on describe. @@ -96,7 +98,7 @@ func TestTags_UpdateViaTagResource(t *testing.T) { tagsRec := auditDo(t, h, http.MethodGet, "/tags/"+pipeARN, nil) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags, _ := tagsResp["tags"].(map[string]any) for k, v := range tt.wantTags { diff --git a/services/pipes/targets_test.go b/services/pipes/targets_test.go index 6ad1309a74..d0c4db5f2d 100644 --- a/services/pipes/targets_test.go +++ b/services/pipes/targets_test.go @@ -9,7 +9,7 @@ package pipes_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -38,7 +39,7 @@ func b2Do(t *testing.T, h *pipes.Handler, method, path string, body any) *httpte var b []byte if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } e := echo.New() @@ -58,7 +59,7 @@ func b2Create(t *testing.T, h *pipes.Handler, name string, body map[string]any) rec := b2Do(t, h, http.MethodPost, "/v1/pipes/"+name, body) require.Equal(t, http.StatusOK, rec.Code, "create pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -68,7 +69,7 @@ func b2Describe(t *testing.T, h *pipes.Handler, name string) map[string]any { rec := b2Do(t, h, http.MethodGet, "/v1/pipes/"+name, nil) require.Equal(t, http.StatusOK, rec.Code, "describe pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -78,7 +79,7 @@ func b2Update(t *testing.T, h *pipes.Handler, name string, body map[string]any) rec := b2Do(t, h, http.MethodPut, "/v1/pipes/"+name, body) require.Equal(t, http.StatusOK, rec.Code, "update pipe %q: %s", name, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/pipes/wire_key_case_restore_test.go b/services/pipes/wire_key_case_restore_test.go index b6f47fc77f..83969a1403 100644 --- a/services/pipes/wire_key_case_restore_test.go +++ b/services/pipes/wire_key_case_restore_test.go @@ -1,12 +1,13 @@ package pipes_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -41,7 +42,7 @@ func TestWireKeyCase_OldSnapshotKeysStillDecode(t *testing.T) { var got pipes.ECSTaskTargetParameters - require.NoError(t, json.Unmarshal([]byte(tt.raw), &got)) + require.NoError(t, json.Unmarshal([]byte(tt.raw), &got, jsonx.V1())) assert.Equal(t, []string{"s-1"}, got.NetworkConfiguration.AwsvpcConfiguration.Subnets) assert.Equal(t, "FARGATE", got.CapacityProviderStrategy[0].CapacityProvider) assert.Equal(t, 2, got.CapacityProviderStrategy[0].Weight) diff --git a/services/pipes/wire_time.go b/services/pipes/wire_time.go index 1940351891..d60d35497c 100644 --- a/services/pipes/wire_time.go +++ b/services/pipes/wire_time.go @@ -1,8 +1,10 @@ package pipes import ( - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // epochSecondsPtr converts *time.Time into the epoch-seconds-with-millisecond- @@ -44,7 +46,7 @@ func (k KinesisStreamSourceParameters) MarshalJSON() ([]byte, error) { }{ StartingPositionTimestamp: epochSecondsPtr(k.StartingPositionTimestamp), alias: alias(k), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of MarshalJSON. Without it, a real client's @@ -59,7 +61,7 @@ func (k *KinesisStreamSourceParameters) UnmarshalJSON(data []byte) error { *alias }{alias: (*alias)(k)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/pipes/wire_time_test.go b/services/pipes/wire_time_test.go index 2d7cbe608e..33d9914184 100644 --- a/services/pipes/wire_time_test.go +++ b/services/pipes/wire_time_test.go @@ -1,7 +1,7 @@ package pipes_test import ( - "encoding/json" + "encoding/json/v2" "net/http/httptest" "testing" "time" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/pipes" ) @@ -60,7 +61,7 @@ func TestKinesisStreamSourceParameters_UnmarshalJSON_TimeIsEpochSeconds(t *testi t.Parallel() var params pipes.KinesisStreamSourceParameters - err := json.Unmarshal([]byte(tt.body), ¶ms) + err := json.Unmarshal([]byte(tt.body), ¶ms, jsonx.V1()) if tt.wantErr { require.Error(t, err) diff --git a/services/polly/handler.go b/services/polly/handler.go index 89e09fc4cb..c4c7fa2672 100644 --- a/services/polly/handler.go +++ b/services/polly/handler.go @@ -2,7 +2,7 @@ package polly import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -318,7 +319,7 @@ func (h *Handler) startSpeechSynthesisStream(c *echo.Context) error { return encodeErr } - closed, err := json.Marshal(map[string]int{"RequestCharacters": result.RequestCharacters}) + closed, err := json.Marshal(map[string]int{"RequestCharacters": result.RequestCharacters}, jsonx.V1()) if err != nil { return err } @@ -383,7 +384,7 @@ func decodeStreamText(body io.Reader) (string, string, error) { } var event streamTextEvent - if jsonErr := json.Unmarshal(message.Payload, &event); jsonErr != nil { + if jsonErr := json.Unmarshal(message.Payload, &event, jsonx.V1()); jsonErr != nil { return "", "", jsonErr } texts = append(texts, event.Text) @@ -613,7 +614,7 @@ func decodeRequest(c *echo.Context, value any) error { if err != nil { return fmt.Errorf("%w: failed to read body", ErrValidation) } - if jsonErr := json.Unmarshal(body, value); jsonErr != nil { + if jsonErr := json.Unmarshal(body, value, jsonx.V1()); jsonErr != nil { return fmt.Errorf("%w: invalid JSON: %w", ErrValidation, jsonErr) } diff --git a/services/polly/handler_test.go b/services/polly/handler_test.go index ed29c5f91a..65775fb85c 100644 --- a/services/polly/handler_test.go +++ b/services/polly/handler_test.go @@ -2,7 +2,7 @@ package polly_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/polly" ) @@ -30,7 +31,7 @@ func request( var payload []byte if body != nil { var err error - payload, err = json.Marshal(body) + payload, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -47,7 +48,7 @@ func responseMap(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/polly/multi_region_test.go b/services/polly/multi_region_test.go index 9a1c0d2748..31f87290ac 100644 --- a/services/polly/multi_region_test.go +++ b/services/polly/multi_region_test.go @@ -2,7 +2,8 @@ package polly_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/polly" ) @@ -174,8 +176,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/polly/persistence.go b/services/polly/persistence.go index 4cb7d5c3da..2ebc758956 100644 --- a/services/polly/persistence.go +++ b/services/polly/persistence.go @@ -2,7 +2,7 @@ package polly import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -36,10 +36,10 @@ const pollySnapshotVersion = 2 // The built-in voice catalogue (InMemoryBackend.voices) is static read-only // data, not a mutable resource collection, and is intentionally excluded. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. It implements diff --git a/services/polly/speech_test.go b/services/polly/speech_test.go index 5e16aa19e0..28ddb2f189 100644 --- a/services/polly/speech_test.go +++ b/services/polly/speech_test.go @@ -2,7 +2,7 @@ package polly_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/polly" ) @@ -190,7 +191,7 @@ func TestStartSpeechSynthesisStreamValidationExceptionTaxonomy(t *testing.T) { // "ValidationException: EngineNotSupportedException: voice ..."), so // assert on the parsed field rather than searching the raw body. var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ValidationException", out["__type"]) }) } @@ -240,7 +241,7 @@ func TestStartSpeechSynthesisStreamRequiresGenerativeEngine(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ValidationException", out["__type"]) }) } @@ -648,7 +649,7 @@ func TestSpeechMarksVisemeVariety(t *testing.T) { } var m map[string]any - require.NoError(t, json.Unmarshal(line, &m)) + require.NoError(t, json.Unmarshal(line, &m, jsonx.V1())) if m["type"] == "viseme" { marks = append(marks, m) @@ -780,7 +781,7 @@ func TestSpeechMarkCounts(t *testing.T) { continue } var ev map[string]any - require.NoError(t, json.Unmarshal([]byte(line), &ev), "invalid JSON line: %s", line) + require.NoError(t, json.Unmarshal([]byte(line), &ev, jsonx.V1()), "invalid JSON line: %s", line) typ, _ := ev["type"].(string) counts[typ]++ } @@ -836,7 +837,7 @@ func TestSpeechMarkTimeOrder(t *testing.T) { continue } var ev map[string]any - require.NoError(t, json.Unmarshal([]byte(line), &ev)) + require.NoError(t, json.Unmarshal([]byte(line), &ev, jsonx.V1())) timeVal, _ := ev["time"].(float64) assert.GreaterOrEqual(t, timeVal, prevTime, "marks must be non-decreasing by time") prevTime = timeVal diff --git a/services/polly/throttle_test.go b/services/polly/throttle_test.go index f4566ac17d..be817c5712 100644 --- a/services/polly/throttle_test.go +++ b/services/polly/throttle_test.go @@ -2,7 +2,7 @@ package polly_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/polly" ) @@ -162,7 +163,7 @@ func TestStartSpeechSynthesisStream_ThrottlingHTTPWireShape(t *testing.T) { require.Equal(t, http.StatusBadRequest, second.Code) var out map[string]string - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ThrottlingException", out["__type"]) } @@ -189,6 +190,6 @@ func TestStartSpeechSynthesisStream_ServiceQuotaHTTPWireShape(t *testing.T) { require.Equal(t, http.StatusPaymentRequired, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ServiceQuotaExceededException", out["__type"]) } diff --git a/services/pricing/catalog.go b/services/pricing/catalog.go index f4f90a6e0e..2e6380a2f6 100644 --- a/services/pricing/catalog.go +++ b/services/pricing/catalog.go @@ -3,13 +3,15 @@ package pricing import ( "crypto/sha256" "encoding/base32" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" "sort" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -129,7 +131,7 @@ func render(s *spec) *entry { Product: productJSON{Attributes: attrs, ProductFamily: s.family, SKU: sku}, ServiceCode: s.service, Terms: terms, Version: catalogVersion, PublicationDate: publicationDate, } - raw, _ := json.Marshal(item) + raw, _ := json.Marshal(item, jsonx.V1()) return &entry{item: item, json: string(raw), region: s.region.code, service: s.service} } diff --git a/services/pricing/handler.go b/services/pricing/handler.go index 0ad13cad0a..309551dd0c 100644 --- a/services/pricing/handler.go +++ b/services/pricing/handler.go @@ -2,7 +2,7 @@ package pricing import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -33,7 +34,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrInvalidParameter, "malformed request body: %v", err) } } diff --git a/services/pricing/pricelists.go b/services/pricing/pricelists.go index f176e1e331..d1fb720ec5 100644 --- a/services/pricing/pricelists.go +++ b/services/pricing/pricelists.go @@ -1,11 +1,13 @@ package pricing import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -213,7 +215,7 @@ func RenderFile(code, ver, region, kind string) ([]byte, string, bool) { } } - raw, _ := json.Marshal(f) + raw, _ := json.Marshal(f, jsonx.V1()) return raw, "application/json", true } diff --git a/services/pricing/pricing_more_test.go b/services/pricing/pricing_more_test.go index bc174a683e..0ca7c46d52 100644 --- a/services/pricing/pricing_more_test.go +++ b/services/pricing/pricing_more_test.go @@ -1,7 +1,7 @@ package pricing_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/pricing" ) @@ -77,7 +78,7 @@ func TestReservedTermMatrix(t *testing.T) { } `json:"terms"` } - require.NoError(t, json.Unmarshal([]byte(out.PriceList[0]), &item)) + require.NoError(t, json.Unmarshal([]byte(out.PriceList[0]), &item, jsonx.V1())) require.Len(t, item.Terms.Reserved, 12) seen := map[string]bool{} @@ -168,7 +169,7 @@ func TestSavingsPlansPriceLists(t *testing.T) { } `json:"products"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &file)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &file, jsonx.V1())) require.Len(t, file.Terms.SavingsPlan, 6) assert.Len(t, file.Products, 6) require.NotEmpty(t, file.Terms.SavingsPlan[0].Rates) diff --git a/services/pricing/pricing_test.go b/services/pricing/pricing_test.go index 55e19f5b40..d3732cf002 100644 --- a/services/pricing/pricing_test.go +++ b/services/pricing/pricing_test.go @@ -1,7 +1,7 @@ package pricing_test import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/services/pricing" ) @@ -206,7 +207,7 @@ func TestProductShape(t *testing.T) { ServiceCode string `json:"serviceCode"` } - require.NoError(t, json.Unmarshal([]byte(out.PriceList[0]), &item)) + require.NoError(t, json.Unmarshal([]byte(out.PriceList[0]), &item, jsonx.V1())) assert.Equal(t, "AmazonEC2", item.ServiceCode) assert.Equal(t, "Compute Instance", item.Product.ProductFamily) assert.Equal(t, "US East (N. Virginia)", item.Product.Attributes["location"]) @@ -305,7 +306,7 @@ func TestPriceListsAndFile(t *testing.T) { OfferCode string `json:"offerCode"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &file)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &file, jsonx.V1())) assert.Equal(t, "AmazonS3", file.OfferCode) assert.NotEmpty(t, file.Products) } else { diff --git a/services/pricing/savingsplans.go b/services/pricing/savingsplans.go index 577b69e358..0db1c1188a 100644 --- a/services/pricing/savingsplans.go +++ b/services/pricing/savingsplans.go @@ -3,11 +3,13 @@ package pricing import ( "crypto/sha256" "encoding/base32" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -152,7 +154,7 @@ func renderSavingsPlans(region string) []byte { }) } - raw, _ := json.Marshal(f) + raw, _ := json.Marshal(f, jsonx.V1()) return raw } diff --git a/services/quicksight/dataset_security.go b/services/quicksight/dataset_security.go index 94b3487486..9388e95989 100644 --- a/services/quicksight/dataset_security.go +++ b/services/quicksight/dataset_security.go @@ -1,8 +1,10 @@ package quicksight import ( - "encoding/json" + "encoding/json/v2" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const dataSetUseAsRLSRules = "RLS_RULES" @@ -32,13 +34,13 @@ func validateDataSetSecurity(s DataSetSecurity, allowUseAs bool) error { } func cloneJSONValue[T any](v T) T { - raw, err := json.Marshal(v) + raw, err := json.Marshal(v, jsonx.V1()) if err != nil { return v } var out T - if json.Unmarshal(raw, &out) != nil { + if json.Unmarshal(raw, &out, jsonx.V1()) != nil { return v } diff --git a/services/quicksight/error_envelope_test.go b/services/quicksight/error_envelope_test.go index ba7d52131a..2bafd09075 100644 --- a/services/quicksight/error_envelope_test.go +++ b/services/quicksight/error_envelope_test.go @@ -1,7 +1,7 @@ package quicksight_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/quicksight" @@ -92,7 +93,7 @@ func TestErrorEnvelope_DescribeDataSetNotFoundDecodesToTypedError(t *testing.T) require.NoError(t, err) var envelope map[string]any - require.NoError(t, json.Unmarshal(raw, &envelope)) + require.NoError(t, json.Unmarshal(raw, &envelope, jsonx.V1())) require.Equal(t, "ResourceNotFoundException", envelope["Code"], "raw body must carry the Code key restjson.GetErrorInfo checks first: %s", raw) diff --git a/services/quicksight/handler_paths.go b/services/quicksight/handler_paths.go index 7b1404e484..47bcf7a7aa 100644 --- a/services/quicksight/handler_paths.go +++ b/services/quicksight/handler_paths.go @@ -1,7 +1,8 @@ package quicksight import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "io" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -971,7 +973,7 @@ func classifyRestorePaths(method string, segs []string, n int) (string, string) func readBody(c *echo.Context) (map[string]any, error) { var body map[string]any - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil { return nil, err } @@ -1090,7 +1092,7 @@ func writeJSON(c *echo.Context, code int, body any) error { c.Response().Header().Set("Content-Type", "application/json") c.Response().WriteHeader(code) - return json.NewEncoder(c.Response()).Encode(body) + return json.MarshalEncode(jsontext.NewEncoder(c.Response(), jsonx.V1()), body) } func writeError(c *echo.Context, code int, errCode, msg string) error { diff --git a/services/quicksight/handler_test.go b/services/quicksight/handler_test.go index 9a643a2a90..b7a8328d6e 100644 --- a/services/quicksight/handler_test.go +++ b/services/quicksight/handler_test.go @@ -2,7 +2,7 @@ package quicksight_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/quicksight" ) @@ -187,7 +188,7 @@ func doRequest(t *testing.T, h *quicksight.Handler, method, path string, body an var bodyBytes []byte if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -209,7 +210,7 @@ func parseBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/quicksight/models.go b/services/quicksight/models.go index b76543114f..8f6e3c3b27 100644 --- a/services/quicksight/models.go +++ b/services/quicksight/models.go @@ -1,7 +1,7 @@ package quicksight import ( - "encoding/json" + "encoding/json/jsontext" "regexp" "time" ) @@ -288,7 +288,7 @@ func (a *storedAnalysis) toAnalysis() *Analysis { // field of its own (see store_setup.go's doc comment for the full list and // rationale) -- these round-trip directly, same as before conversion. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` GroupMembers map[string]bool `json:"groupMembers"` Tags map[string]map[string]string `json:"tags"` diff --git a/services/quicksight/multi_region_test.go b/services/quicksight/multi_region_test.go index a432ebd472..730bedf430 100644 --- a/services/quicksight/multi_region_test.go +++ b/services/quicksight/multi_region_test.go @@ -2,7 +2,8 @@ package quicksight_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/quicksight" ) @@ -176,8 +178,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/ram/errsweep_wire_shape_test.go b/services/ram/errsweep_wire_shape_test.go index 6a929860e5..1599357a91 100644 --- a/services/ram/errsweep_wire_shape_test.go +++ b/services/ram/errsweep_wire_shape_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/ram" @@ -61,7 +62,7 @@ func TestHandler_MalformedBodyCarriesTypedErrorCode(t *testing.T) { require.NoError(t, err) var decoded map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) + require.NoError(t, json.Unmarshal(raw, &decoded, jsonx.V1())) require.Equal(t, "InvalidParameterException", decoded["__type"], "raw response body must carry __type; got: %s", raw) require.NotEmpty(t, decoded["message"]) diff --git a/services/ram/handler.go b/services/ram/handler.go index 59688b40cf..3e5c5eff49 100644 --- a/services/ram/handler.go +++ b/services/ram/handler.go @@ -3,7 +3,7 @@ package ram import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/collections" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -669,7 +670,7 @@ func writeInternalServerError(c *echo.Context) error { payload, err := json.Marshal(map[string]string{ keyTypeField: "ServerInternalException", keyMessageField: "internal server error", - }) + }, jsonx.V1()) if err != nil { return err } @@ -724,7 +725,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { if errors.Is(err, idempotency.ErrParamsMismatch) { payload, _ := json.Marshal(map[string]string{ keyTypeField: "IdempotentParameterMismatchException", keyMessageField: err.Error(), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) } @@ -732,19 +733,15 @@ func (h *Handler) handleError(c *echo.Context, err error) error { for _, e := range errCodeLookup { if errors.Is(err, e.err) { payload, _ := json.Marshal( - map[string]string{keyTypeField: e.code, keyMessageField: errorMessage(err, e.code)}, + map[string]string{keyTypeField: e.code, keyMessageField: errorMessage(err, e.code)}, jsonx.V1(), ) return c.JSONBlob(http.StatusBadRequest, payload) } } - - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): // These branches used to omit keyTypeField entirely, so // restjson.GetErrorInfo (aws-sdk-go-v2 aws/protocol/restjson/ // decoder_util.go:15) found no code in the header (unset) or the diff --git a/services/ram/handler_permission_versions.go b/services/ram/handler_permission_versions.go index e944138c6a..8ec3b189bb 100644 --- a/services/ram/handler_permission_versions.go +++ b/services/ram/handler_permission_versions.go @@ -2,7 +2,7 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createPermissionVersionRequest struct { @@ -29,7 +30,7 @@ type createPermissionVersionResponse struct { func (h *Handler) handleCreatePermissionVersion(_ context.Context, body []byte) ([]byte, error) { var req createPermissionVersionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -72,7 +73,7 @@ func (h *Handler) handleCreatePermissionVersion(_ context.Context, body []byte) pv := p.Versions[version] return json.Marshal( - createPermissionVersionResponse{Permission: toPermissionDetailObject(p, pv)}, + createPermissionVersionResponse{Permission: toPermissionDetailObject(p, pv)}, jsonx.V1(), ) } @@ -113,7 +114,7 @@ func (h *Handler) handleDeletePermissionVersion( } return json.Marshal( - deletePermissionVersionResponse{ReturnValue: true, PermissionStatus: "DELETING"}, + deletePermissionVersionResponse{ReturnValue: true, PermissionStatus: "DELETING"}, jsonx.V1(), ) } @@ -146,7 +147,7 @@ func toPermissionVersionSummaryObject(p *Permission, pv *PermissionVersion) perm func (h *Handler) handleListPermissionVersions(_ context.Context, body []byte) ([]byte, error) { var req listPermissionVersionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -175,7 +176,7 @@ func (h *Handler) handleListPermissionVersions(_ context.Context, body []byte) ( return nil, pErr2 } - return json.Marshal(listPermissionVersionsResponse{NextToken: nextToken, Permissions: paged}) + return json.Marshal(listPermissionVersionsResponse{NextToken: nextToken, Permissions: paged}, jsonx.V1()) } type setDefaultPermissionVersionRequest struct { @@ -192,7 +193,7 @@ func (h *Handler) handleSetDefaultPermissionVersion( body []byte, ) ([]byte, error) { var req setDefaultPermissionVersionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -208,5 +209,5 @@ func (h *Handler) handleSetDefaultPermissionVersion( return nil, err } - return json.Marshal(setDefaultPermissionVersionResponse{ReturnValue: true}) + return json.Marshal(setDefaultPermissionVersionResponse{ReturnValue: true}, jsonx.V1()) } diff --git a/services/ram/handler_permission_versions_test.go b/services/ram/handler_permission_versions_test.go index 19147a50b4..ef1213231b 100644 --- a/services/ram/handler_permission_versions_test.go +++ b/services/ram/handler_permission_versions_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -44,7 +45,7 @@ func TestListPermissionVersions_BuiltIn(t *testing.T) { Version string `json:"version"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Permissions, 1) assert.Equal(t, "1", resp.Permissions[0].Version) }) @@ -358,7 +359,7 @@ func TestListPermissionVersions_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -367,7 +368,7 @@ func TestListPermissionVersions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) totalSeen += len(resp.Permissions) nextToken = resp.NextToken diff --git a/services/ram/handler_permissions.go b/services/ram/handler_permissions.go index 6f29b8afbc..0e3f9ebd33 100644 --- a/services/ram/handler_permissions.go +++ b/services/ram/handler_permissions.go @@ -2,13 +2,14 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // permissionStatusAttachable is the steady-state PermissionStatus for every @@ -134,7 +135,7 @@ type createPermissionResponse struct { func (h *Handler) handleCreatePermission(_ context.Context, body []byte) ([]byte, error) { var req createPermissionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -169,7 +170,7 @@ func (h *Handler) handleCreatePermission(_ context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(createPermissionResponse{Permission: toPermissionSummaryObject(p)}) + return json.Marshal(createPermissionResponse{Permission: toPermissionSummaryObject(p)}, jsonx.V1()) } type deletePermissionResponse struct { @@ -187,7 +188,7 @@ func (h *Handler) handleDeletePermission(_ context.Context, c *echo.Context) ([] return nil, err } - return json.Marshal(deletePermissionResponse{ReturnValue: true, PermissionStatus: "DELETING"}) + return json.Marshal(deletePermissionResponse{ReturnValue: true, PermissionStatus: "DELETING"}, jsonx.V1()) } type getPermissionRequest struct { @@ -201,7 +202,7 @@ type getPermissionResponse struct { func (h *Handler) handleGetPermission(_ context.Context, body []byte) ([]byte, error) { var req getPermissionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -214,7 +215,7 @@ func (h *Handler) handleGetPermission(_ context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(getPermissionResponse{Permission: toPermissionDetailObject(p, pv)}) + return json.Marshal(getPermissionResponse{Permission: toPermissionDetailObject(p, pv)}, jsonx.V1()) } type listPermissionsRequest struct { @@ -231,7 +232,7 @@ type listPermissionsResponse struct { func (h *Handler) handleListPermissions(_ context.Context, body []byte) ([]byte, error) { var req listPermissionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -252,7 +253,7 @@ func (h *Handler) handleListPermissions(_ context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(listPermissionsResponse{NextToken: nextToken, Permissions: page}) + return json.Marshal(listPermissionsResponse{NextToken: nextToken, Permissions: page}, jsonx.V1()) } type promotePermissionCreatedFromPolicyRequest struct { @@ -269,7 +270,7 @@ func (h *Handler) handlePromotePermissionCreatedFromPolicy( body []byte, ) ([]byte, error) { var req promotePermissionCreatedFromPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -283,6 +284,6 @@ func (h *Handler) handlePromotePermissionCreatedFromPolicy( } return json.Marshal( - promotePermissionCreatedFromPolicyResponse{Permission: toPermissionSummaryObject(p)}, + promotePermissionCreatedFromPolicyResponse{Permission: toPermissionSummaryObject(p)}, jsonx.V1(), ) } diff --git a/services/ram/handler_permissions_test.go b/services/ram/handler_permissions_test.go index b5dee68c5d..82106b869e 100644 --- a/services/ram/handler_permissions_test.go +++ b/services/ram/handler_permissions_test.go @@ -1,7 +1,8 @@ package ram_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -45,7 +47,7 @@ func TestBuiltInPermissions_Present(t *testing.T) { PermissionType string `json:"permissionType"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.Permissions, "built-in permission for %s should exist", tt.resourceType) assert.Equal(t, tt.resourceType, resp.Permissions[0].ResourceType) assert.Equal(t, "AWS_MANAGED", resp.Permissions[0].PermissionType) @@ -61,9 +63,9 @@ func TestBuiltInPermissions_Count(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Permissions, ram.BuiltInPermissionCount) } @@ -106,9 +108,9 @@ func TestListPermissions_TypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Permissions, tt.wantCount) }) } @@ -174,9 +176,9 @@ func TestListPermissions_TypeFilter_WithCustom(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Permissions, tt.wantCount) }) } @@ -229,7 +231,7 @@ func TestBuiltInPermission_PermissionType(t *testing.T) { IsResourceTypeDefault bool `json:"isResourceTypeDefault"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantPermissionType, resp.Permission.PermissionType) assert.Equal(t, tt.wantIsDefault, resp.Permission.IsResourceTypeDefault) }) @@ -289,7 +291,7 @@ func TestDeleteCustomPermission_Allowed(t *testing.T) { Arn string `json:"arn"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) path := fmt.Sprintf("/deletepermission?permissionArn=%s", createResp.Permission.Arn) rec := doRAMRawRequest(t, h, http.MethodDelete, path, nil) @@ -338,12 +340,12 @@ func TestPermissionResponses_NoResourceRegionScopeField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Permission json.RawMessage `json:"permission"` + Permission jsontext.Value `json:"permission"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) - var permFields map[string]json.RawMessage - require.NoError(t, json.Unmarshal(resp.Permission, &permFields)) + var permFields map[string]jsontext.Value + require.NoError(t, json.Unmarshal(resp.Permission, &permFields, jsonx.V1())) assert.NotContains(t, permFields, "resourceRegionScope") }) } @@ -379,7 +381,7 @@ func TestIsResourceTypeDefault(t *testing.T) { IsResourceTypeDefault bool `json:"isResourceTypeDefault"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantDefault, resp.Permission.IsResourceTypeDefault) }) } @@ -402,7 +404,7 @@ func TestCustomerPermission_NotResourceTypeDefault(t *testing.T) { IsResourceTypeDefault bool `json:"isResourceTypeDefault"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) assert.False(t, createResp.Permission.IsResourceTypeDefault) } @@ -446,7 +448,7 @@ func TestListPermissions_ResourceTypeFilter(t *testing.T) { ResourceType string `json:"resourceType"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantMinCount == 0 { assert.Empty(t, resp.Permissions) @@ -480,7 +482,7 @@ func TestListPermissions_ResourceTypeFilter_CaseInsensitive(t *testing.T) { ResourceType string `json:"resourceType"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty( t, resp.Permissions, "resourceType filter must match case-insensitively, per the doc's own example", @@ -527,12 +529,12 @@ func TestGetPermission_BuiltIn_HasPolicy(t *testing.T) { Permission string `json:"permission"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.Permission.Permission, "built-in permission should have non-empty policy") // Policy should be valid JSON. var policy map[string]any - assert.NoError(t, json.Unmarshal([]byte(resp.Permission.Permission), &policy)) + assert.NoError(t, json.Unmarshal([]byte(resp.Permission.Permission), &policy, jsonx.V1())) }) } } @@ -558,7 +560,7 @@ func TestListPermissions_SummaryHasType(t *testing.T) { PermissionType string `json:"permissionType"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, p := range resp.Permissions { assert.NotEmpty(t, p.PermissionType, "permission %q should have PermissionType set", p.Name) @@ -669,7 +671,7 @@ func TestPermissionNotFound_UsesUnknownResourceException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "UnknownResourceException", resp["__type"]) } @@ -949,7 +951,7 @@ func TestListPermissions_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -958,7 +960,7 @@ func TestListPermissions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.Permissions) @@ -1069,7 +1071,7 @@ func TestPromotePermissionCreatedFromPolicy_Table(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) perm := resp["permission"].(map[string]any) assert.Equal(t, tc.wantType, perm["permissionType"]) assert.Equal(t, tc.wantName, perm["name"]) @@ -1087,7 +1089,7 @@ func TestListPermissions_Smoke(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) perms, ok := listResp["permissions"].([]any) require.True(t, ok) assert.NotEmpty(t, perms, "ListPermissions must return the built-in AWS-managed permissions") @@ -1099,7 +1101,7 @@ func TestListPermissions_Smoke(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var versionsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &versionsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &versionsResp, jsonx.V1())) versions, ok := versionsResp["permissions"].([]any) require.True(t, ok) require.NotEmpty(t, versions, "AWSRAMDefaultPermissionEC2Subnet must have at least one version") diff --git a/services/ram/handler_principals.go b/services/ram/handler_principals.go index e554023d3d..b4f413b08b 100644 --- a/services/ram/handler_principals.go +++ b/services/ram/handler_principals.go @@ -2,9 +2,11 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type principalObject struct { @@ -42,7 +44,7 @@ type listPrincipalsResponse struct { func (h *Handler) handleListPrincipals(_ context.Context, body []byte) ([]byte, error) { var req listPrincipalsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -81,5 +83,5 @@ func (h *Handler) handleListPrincipals(_ context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(listPrincipalsResponse{NextToken: nextToken, Principals: page}) + return json.Marshal(listPrincipalsResponse{NextToken: nextToken, Principals: page}, jsonx.V1()) } diff --git a/services/ram/handler_principals_test.go b/services/ram/handler_principals_test.go index 81a7e21b78..8678f399c5 100644 --- a/services/ram/handler_principals_test.go +++ b/services/ram/handler_principals_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -31,7 +32,7 @@ func TestListPrincipals_Pagination(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) shareARN := createResp.ResourceShare.ResourceShareArn require.NotEmpty(t, shareARN) @@ -104,7 +105,7 @@ func TestListPrincipals_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -113,7 +114,7 @@ func TestListPrincipals_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.Principals) @@ -151,7 +152,7 @@ func TestListPrincipals_ResourceShareArnsFilter(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(createA.Body.Bytes(), &respA)) + require.NoError(t, json.Unmarshal(createA.Body.Bytes(), &respA, jsonx.V1())) shareArnA := respA.ResourceShare.ResourceShareArn require.NotEmpty(t, shareArnA) @@ -174,7 +175,7 @@ func TestListPrincipals_ResourceShareArnsFilter(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"principals"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len( t, resp.Principals, 1, "resourceShareArns must scope results to the requested share, not return every share", @@ -229,7 +230,7 @@ func TestListPrincipals_ResourceOwnerFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) principals := resp["principals"].([]any) assert.Len(t, principals, tc.wantCount) }) diff --git a/services/ram/handler_resource_shares.go b/services/ram/handler_resource_shares.go index 9e53ccad40..36d64430a3 100644 --- a/services/ram/handler_resource_shares.go +++ b/services/ram/handler_resource_shares.go @@ -2,13 +2,14 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // resourceShareObject is the JSON representation of a ResourceShare. @@ -76,7 +77,7 @@ type createResourceShareResponse struct { func (h *Handler) handleCreateResourceShare(_ context.Context, body []byte) ([]byte, error) { var req createResourceShareRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -109,7 +110,7 @@ func (h *Handler) handleCreateResourceShare(_ context.Context, body []byte) ([]b return json.Marshal(createResourceShareResponse{ ResourceShare: toResourceShareObject(rs), - }) + }, jsonx.V1()) } func (h *Handler) createResourceShare(req createResourceShareRequest) (*ResourceShare, error) { @@ -173,7 +174,7 @@ type getResourceSharesResponse struct { func (h *Handler) handleGetResourceShares(_ context.Context, body []byte) ([]byte, error) { var req getResourceSharesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +202,7 @@ func (h *Handler) handleGetResourceShares(_ context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(getResourceSharesResponse{NextToken: nextToken, ResourceShares: page}) + return json.Marshal(getResourceSharesResponse{NextToken: nextToken, ResourceShares: page}, jsonx.V1()) } // getResourceSharesByARN looks up each requested ARN individually, applying @@ -322,7 +323,7 @@ type updateResourceShareResponse struct { func (h *Handler) handleUpdateResourceShare(_ context.Context, body []byte) ([]byte, error) { var req updateResourceShareRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -349,7 +350,7 @@ func (h *Handler) handleUpdateResourceShare(_ context.Context, body []byte) ([]b return json.Marshal(updateResourceShareResponse{ ResourceShare: toResourceShareObject(rs), - }) + }, jsonx.V1()) } type deleteResourceShareResponse struct { @@ -373,7 +374,7 @@ func (h *Handler) handleDeleteResourceShare(_ context.Context, c *echo.Context) return nil, err } - return json.Marshal(deleteResourceShareResponse{ReturnValue: true}) + return json.Marshal(deleteResourceShareResponse{ReturnValue: true}, jsonx.V1()) } type enableSharingWithAwsOrganizationResponse struct { @@ -385,7 +386,7 @@ func (h *Handler) handleEnableSharingWithAwsOrganization() ([]byte, error) { return nil, err } - return json.Marshal(enableSharingWithAwsOrganizationResponse{ReturnValue: true}) + return json.Marshal(enableSharingWithAwsOrganizationResponse{ReturnValue: true}, jsonx.V1()) } type promoteResourceShareCreatedFromPolicyResponse struct { @@ -415,5 +416,5 @@ func (h *Handler) handlePromoteResourceShareCreatedFromPolicy( return nil, err } - return json.Marshal(promoteResourceShareCreatedFromPolicyResponse{ReturnValue: true}) + return json.Marshal(promoteResourceShareCreatedFromPolicyResponse{ReturnValue: true}, jsonx.V1()) } diff --git a/services/ram/handler_resource_shares_test.go b/services/ram/handler_resource_shares_test.go index 04648796c5..5602dc395e 100644 --- a/services/ram/handler_resource_shares_test.go +++ b/services/ram/handler_resource_shares_test.go @@ -1,7 +1,8 @@ package ram_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -387,7 +389,7 @@ func TestCreateResourceShare_PermissionArns(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) shareARN := createResp.ResourceShare.ResourceShareArn // Verify permissions were associated. @@ -397,9 +399,9 @@ func TestCreateResourceShare_PermissionArns(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Permissions, tt.wantPermCount) }) } @@ -455,9 +457,9 @@ func TestGetResourceShares_OtherAccounts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - ResourceShares []json.RawMessage `json:"resourceShares"` + ResourceShares []jsontext.Value `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceShares, tt.wantCount) }) } @@ -499,7 +501,7 @@ func TestAllowExternalPrincipals_Default(t *testing.T) { AllowExternalPrincipals bool `json:"allowExternalPrincipals"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantExternal, resp.ResourceShare.AllowExternalPrincipals) }) } @@ -576,7 +578,7 @@ func TestCreateResourceShare_WithPrincipalsAndResources(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp, jsonx.V1())) shareARN := shareResp.ResourceShare.ResourceShareArn // Check principals. @@ -587,9 +589,9 @@ func TestCreateResourceShare_WithPrincipalsAndResources(t *testing.T) { require.Equal(t, http.StatusOK, principalRec.Code) var principalResp struct { - Principals []json.RawMessage `json:"principals"` + Principals []jsontext.Value `json:"principals"` } - require.NoError(t, json.Unmarshal(principalRec.Body.Bytes(), &principalResp)) + require.NoError(t, json.Unmarshal(principalRec.Body.Bytes(), &principalResp, jsonx.V1())) assert.Len(t, principalResp.Principals, tt.wantPrincipals) // Check resources. @@ -600,9 +602,9 @@ func TestCreateResourceShare_WithPrincipalsAndResources(t *testing.T) { require.Equal(t, http.StatusOK, resourceRec.Code) var resourceResp struct { - Resources []json.RawMessage `json:"resources"` + Resources []jsontext.Value `json:"resources"` } - require.NoError(t, json.Unmarshal(resourceRec.Body.Bytes(), &resourceResp)) + require.NoError(t, json.Unmarshal(resourceRec.Body.Bytes(), &resourceResp, jsonx.V1())) assert.Len(t, resourceResp.Resources, tt.wantResources) }) } @@ -654,7 +656,7 @@ func TestCreateResourceShare_AutoAssociatesDefaultPermission(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp, jsonx.V1())) listRec := doRAMRequest(t, h, "/listresourcesharepermissions", map[string]any{ "resourceShareArn": shareResp.ResourceShare.ResourceShareArn, @@ -666,7 +668,7 @@ func TestCreateResourceShare_AutoAssociatesDefaultPermission(t *testing.T) { Arn string `json:"arn"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Permissions, 1) assert.Equal(t, "arn:aws:ram::aws:permission/AWSRAMDefaultPermissionEC2Subnet", listResp.Permissions[0].Arn) }) @@ -689,7 +691,7 @@ func TestCreateResourceShare_NoAutoAttachWithoutResources(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &shareResp, jsonx.V1())) listRec := doRAMRequest(t, h, "/listresourcesharepermissions", map[string]any{ "resourceShareArn": shareResp.ResourceShare.ResourceShareArn, @@ -725,7 +727,7 @@ func TestGetResourceShares_PermissionArnFilter(t *testing.T) { Name string `json:"name"` } `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ResourceShares, 1) assert.Equal(t, "with-perm-share", resp.ResourceShares[0].Name) } @@ -761,7 +763,7 @@ func TestGetResourceShares_TagFilters(t *testing.T) { Name string `json:"name"` } `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ResourceShares, 1) assert.Equal(t, "tagged-share", resp.ResourceShares[0].Name) } @@ -811,9 +813,9 @@ func TestGetResourceShares_NameFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - ResourceShares []json.RawMessage `json:"resourceShares"` + ResourceShares []jsontext.Value `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceShares, tt.wantCount) }) } @@ -863,7 +865,7 @@ func TestResourceShare_TagsInResponse(t *testing.T) { } `json:"tags"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceShare.Tags, tt.wantTags) }) } @@ -883,7 +885,7 @@ func TestResourceShare_FeatureSetStandard(t *testing.T) { FeatureSet string `json:"featureSet"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "STANDARD", resp.ResourceShare.FeatureSet) } @@ -1046,7 +1048,7 @@ func TestGetResourceShares_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -1055,7 +1057,7 @@ func TestGetResourceShares_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.ResourceShares) @@ -1087,7 +1089,7 @@ func TestGetResourceShares_StatusFilter(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) delRec := doRAMRawRequest(t, h, http.MethodDelete, "/deleteresourceshare?resourceShareArn="+createResp.ResourceShare.ResourceShareArn, nil) @@ -1109,7 +1111,7 @@ func TestGetResourceShares_StatusFilter(t *testing.T) { Name string `json:"name"` } `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceShares, 1) assert.Equal(t, "live-share", resp.ResourceShares[0].Name) } @@ -1149,7 +1151,7 @@ func TestDeleteResourceShare_RemovesFromMap(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var createBody map[string]any - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createBody)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &createBody, jsonx.V1())) shareARN := createBody["resourceShare"].(map[string]any)["resourceShareArn"].(string) require.NotEmpty(t, shareARN) @@ -1168,7 +1170,7 @@ func TestDeleteResourceShare_RemovesFromMap(t *testing.T) { require.Equal(t, http.StatusOK, listResp.Code) var listBody map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listBody)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listBody, jsonx.V1())) shares := listBody["resourceShares"].([]any) for _, s := range shares { @@ -1200,14 +1202,14 @@ func TestPaginationTokenIsOpaque(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nextToken, _ := resp["nextToken"].(string) require.NotEmpty(t, nextToken, "expected a nextToken for multi-page result") // The token must not be a plain integer. var plainInt int - err := json.Unmarshal([]byte(nextToken), &plainInt) + err := json.Unmarshal([]byte(nextToken), &plainInt, jsonx.V1()) require.Error(t, err, "nextToken should not be a plain integer") // Following the token must yield the next page without overlap. @@ -1219,7 +1221,7 @@ func TestPaginationTokenIsOpaque(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) page1Shares := resp["resourceShares"].([]any) page2Shares := resp2["resourceShares"].([]any) diff --git a/services/ram/handler_resources.go b/services/ram/handler_resources.go index 60683074c9..10d493c367 100644 --- a/services/ram/handler_resources.go +++ b/services/ram/handler_resources.go @@ -2,11 +2,12 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -52,7 +53,7 @@ type listResourcesResponse struct { func (h *Handler) handleListResources(_ context.Context, body []byte) ([]byte, error) { var req listResourcesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -85,7 +86,7 @@ func (h *Handler) handleListResources(_ context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(listResourcesResponse{NextToken: nextToken, Resources: page}) + return json.Marshal(listResourcesResponse{NextToken: nextToken, Resources: page}, jsonx.V1()) } type listPendingInvitationResourcesRequest struct { @@ -104,7 +105,7 @@ func (h *Handler) handleListPendingInvitationResources( body []byte, ) ([]byte, error) { var req listPendingInvitationResourcesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -129,7 +130,7 @@ func (h *Handler) handleListPendingInvitationResources( } return json.Marshal( - listPendingInvitationResourcesResponse{NextToken: nextToken, Resources: page}, + listPendingInvitationResourcesResponse{NextToken: nextToken, Resources: page}, jsonx.V1(), ) } @@ -147,7 +148,7 @@ type getResourcePoliciesResponse struct { func (h *Handler) handleGetResourcePolicies(_ context.Context, body []byte) ([]byte, error) { var req getResourcePoliciesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -177,7 +178,7 @@ func (h *Handler) handleGetResourcePolicies(_ context.Context, body []byte) ([]b return nil, err } - return json.Marshal(getResourcePoliciesResponse{NextToken: nextToken, Policies: page}) + return json.Marshal(getResourcePoliciesResponse{NextToken: nextToken, Policies: page}, jsonx.V1()) } type resourceTypeObject struct { @@ -320,7 +321,7 @@ type listResourceTypesRequest struct { func (h *Handler) handleListResourceTypes(_ context.Context, body []byte) ([]byte, error) { var req listResourceTypesRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -350,7 +351,7 @@ func (h *Handler) handleListResourceTypes(_ context.Context, body []byte) ([]byt pg := page.New(types, req.NextToken, limit, len(awsShareableResourceTypes)) - return json.Marshal(listResourceTypesResponse{ResourceTypes: pg.Data, NextToken: pg.Next}) + return json.Marshal(listResourceTypesResponse{ResourceTypes: pg.Data, NextToken: pg.Next}, jsonx.V1()) } // associatedSourceObject is the JSON representation of an AssociatedSource (RAM's wire @@ -386,7 +387,7 @@ type listSourceAssociationsRequest struct { func (h *Handler) handleListSourceAssociations(_ context.Context, body []byte) ([]byte, error) { var req listSourceAssociationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -408,5 +409,5 @@ func (h *Handler) handleListSourceAssociations(_ context.Context, body []byte) ( return nil, err } - return json.Marshal(listSourceAssociationsResponse{NextToken: nextToken, SourceAssociations: page}) + return json.Marshal(listSourceAssociationsResponse{NextToken: nextToken, SourceAssociations: page}, jsonx.V1()) } diff --git a/services/ram/handler_resources_test.go b/services/ram/handler_resources_test.go index ebbcbd2619..69ee1aa8a5 100644 --- a/services/ram/handler_resources_test.go +++ b/services/ram/handler_resources_test.go @@ -1,7 +1,8 @@ package ram_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -28,7 +30,7 @@ func TestListSourceAssociations_WireShape(t *testing.T) { SourceID string `json:"sourceId"` } `json:"sourceAssociations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.SourceAssociations) assert.NotContains( t, rec.Body.String(), `"associations"`, @@ -106,7 +108,7 @@ func TestResourceRegionScope_InListResources(t *testing.T) { ResourceRegionScope string `json:"resourceRegionScope"` } `json:"resources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Resources, 1) assert.Equal(t, tt.resourceARN, resp.Resources[0].Arn) assert.Equal(t, tt.wantType, resp.Resources[0].Type) @@ -148,7 +150,7 @@ func TestListResources_ResourceShareArnsFilter(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len( t, resp.Resources, 1, "resourceShareArns must scope results to the requested share, not return every share", @@ -215,7 +217,7 @@ func TestResourceTypeDerivation(t *testing.T) { Type string `json:"type"` } `json:"resources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Resources, 1) assert.Equal(t, tt.wantType, resp.Resources[0].Type) }) @@ -291,9 +293,9 @@ func TestListPendingInvitationResources(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp struct { - Resources []json.RawMessage `json:"resources"` + Resources []jsontext.Value `json:"resources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Resources, tt.wantResources) }) } @@ -312,7 +314,7 @@ func TestListResourceTypes(t *testing.T) { ServiceName string `json:"serviceName"` } `json:"resourceTypes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.ResourceTypes) @@ -340,7 +342,7 @@ func TestListResourceTypes_ExpandedSet(t *testing.T) { ResourceRegionScope string `json:"resourceRegionScope"` } `json:"resourceTypes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Greater(t, len(resp.ResourceTypes), 4, "should list more than the original 4 types") @@ -490,7 +492,7 @@ func TestListResources_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -499,7 +501,7 @@ func TestListResources_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.Resources) @@ -580,7 +582,7 @@ func TestListPendingInvitationResources_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -589,7 +591,7 @@ func TestListPendingInvitationResources_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp.Resources) }) @@ -645,7 +647,7 @@ func TestGetResourcePolicies_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -667,7 +669,7 @@ func TestGetResourcePolicies_EmptyArns(t *testing.T) { var resp struct { Policies []string `json:"policies"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.Policies) } @@ -722,7 +724,7 @@ func TestListResources_ResourceOwnerFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources := resp["resources"].([]any) assert.Len(t, resources, tc.wantCount) }) @@ -788,7 +790,7 @@ func TestListResources_ResourceTypeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources := resp["resources"].([]any) assert.Len(t, resources, tc.wantCount) }) @@ -852,7 +854,7 @@ func TestGetResourcePolicies_OnlySharedResourcesGetPolicies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) var policies []any if p, ok := resp["policies"]; ok { diff --git a/services/ram/handler_share_associations.go b/services/ram/handler_share_associations.go index d4bdb79baa..c8f4df67bf 100644 --- a/services/ram/handler_share_associations.go +++ b/services/ram/handler_share_associations.go @@ -2,8 +2,10 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // associationObject is the JSON representation of a ResourceShareAssociation. @@ -46,7 +48,7 @@ type associateResourceShareResponse struct { func (h *Handler) handleAssociateResourceShare(_ context.Context, body []byte) ([]byte, error) { var req associateResourceShareRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -86,7 +88,7 @@ func (h *Handler) handleAssociateResourceShare(_ context.Context, body []byte) ( objs = append(objs, toAssociationObject(a)) } - return json.Marshal(associateResourceShareResponse{ResourceShareAssociations: objs}) + return json.Marshal(associateResourceShareResponse{ResourceShareAssociations: objs}, jsonx.V1()) } type disassociateResourceShareRequest struct { @@ -102,7 +104,7 @@ type disassociateResourceShareResponse struct { func (h *Handler) handleDisassociateResourceShare(_ context.Context, body []byte) ([]byte, error) { var req disassociateResourceShareRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -129,7 +131,7 @@ func (h *Handler) handleDisassociateResourceShare(_ context.Context, body []byte objs = append(objs, toAssociationObject(a)) } - return json.Marshal(disassociateResourceShareResponse{ResourceShareAssociations: objs}) + return json.Marshal(disassociateResourceShareResponse{ResourceShareAssociations: objs}, jsonx.V1()) } type getResourceShareAssociationsRequest struct { @@ -152,7 +154,7 @@ func (h *Handler) handleGetResourceShareAssociations( body []byte, ) ([]byte, error) { var req getResourceShareAssociationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -194,6 +196,6 @@ func (h *Handler) handleGetResourceShareAssociations( } return json.Marshal( - getResourceShareAssociationsResponse{NextToken: nextToken, ResourceShareAssociations: page}, + getResourceShareAssociationsResponse{NextToken: nextToken, ResourceShareAssociations: page}, jsonx.V1(), ) } diff --git a/services/ram/handler_share_associations_test.go b/services/ram/handler_share_associations_test.go index a5b0568d58..e078029fa5 100644 --- a/services/ram/handler_share_associations_test.go +++ b/services/ram/handler_share_associations_test.go @@ -1,13 +1,15 @@ package ram_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -271,9 +273,9 @@ func TestGetResourceShareAssociations_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - ResourceShareAssociations []json.RawMessage `json:"resourceShareAssociations"` + ResourceShareAssociations []jsontext.Value `json:"resourceShareAssociations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.ResourceShareAssociations, tt.wantCount) }) } @@ -315,15 +317,15 @@ func TestAssociationStatusMessage_InResponse(t *testing.T) { StatusMessage string `json:"statusMessage"` } `json:"resourceShareAssociations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.ResourceShareAssociations) // statusMessage is optional; verify the field exists in JSON (can be empty). // We check by re-parsing as raw map. var raw struct { - ResourceShareAssociations []map[string]json.RawMessage `json:"resourceShareAssociations"` + ResourceShareAssociations []map[string]jsontext.Value `json:"resourceShareAssociations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.NotEmpty(t, raw.ResourceShareAssociations) // statusMessage should NOT appear when empty (omitempty). _, present := raw.ResourceShareAssociations[0]["statusMessage"] @@ -358,7 +360,7 @@ func TestAssociateResourceShare_AutoAssociatesDefaultPermission(t *testing.T) { Arn string `json:"arn"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Permissions, 1) assert.Equal(t, "arn:aws:ram::aws:permission/AWSRAMDefaultPermissionEC2Subnet", listResp.Permissions[0].Arn) @@ -372,7 +374,7 @@ func TestAssociateResourceShare_AutoAssociatesDefaultPermission(t *testing.T) { listRec = doRAMRequest(t, h, "/listresourcesharepermissions", map[string]any{ "resourceShareArn": rs.ARN, }) - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.Permissions, 1, "adding a second resource of an already-covered type must not duplicate") } @@ -446,7 +448,7 @@ func TestGetResourceShareAssociations_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -455,7 +457,7 @@ func TestGetResourceShareAssociations_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) totalSeen += len(resp.ResourceShareAssociations) nextToken = resp.NextToken diff --git a/services/ram/handler_share_invitations.go b/services/ram/handler_share_invitations.go index 260c813ddd..02fc8f2f39 100644 --- a/services/ram/handler_share_invitations.go +++ b/services/ram/handler_share_invitations.go @@ -2,8 +2,10 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // invitationObject is the JSON representation of a ResourceShareInvitation. @@ -42,7 +44,7 @@ func (h *Handler) handleAcceptResourceShareInvitation( body []byte, ) ([]byte, error) { var req acceptResourceShareInvitationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -57,7 +59,7 @@ func (h *Handler) handleAcceptResourceShareInvitation( return json.Marshal(acceptResourceShareInvitationResponse{ ResourceShareInvitation: toInvitationObject(inv), - }) + }, jsonx.V1()) } type rejectResourceShareInvitationRequest struct { @@ -73,7 +75,7 @@ func (h *Handler) handleRejectResourceShareInvitation( body []byte, ) ([]byte, error) { var req rejectResourceShareInvitationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -88,7 +90,7 @@ func (h *Handler) handleRejectResourceShareInvitation( return json.Marshal(rejectResourceShareInvitationResponse{ ResourceShareInvitation: toInvitationObject(inv), - }) + }, jsonx.V1()) } type getResourceShareInvitationsRequest struct { @@ -108,7 +110,7 @@ func (h *Handler) handleGetResourceShareInvitations( body []byte, ) ([]byte, error) { var req getResourceShareInvitationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -128,6 +130,6 @@ func (h *Handler) handleGetResourceShareInvitations( } return json.Marshal( - getResourceShareInvitationsResponse{NextToken: nextToken, ResourceShareInvitations: page}, + getResourceShareInvitationsResponse{NextToken: nextToken, ResourceShareInvitations: page}, jsonx.V1(), ) } diff --git a/services/ram/handler_share_invitations_test.go b/services/ram/handler_share_invitations_test.go index 3681611d28..b7623c2207 100644 --- a/services/ram/handler_share_invitations_test.go +++ b/services/ram/handler_share_invitations_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -97,7 +98,7 @@ func TestGetResourceShareInvitations_Status(t *testing.T) { Status string `json:"status"` } `json:"resourceShareInvitations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ResourceShareInvitations, 1) assert.Equal(t, tt.wantStatus, resp.ResourceShareInvitations[0].Status) }) @@ -371,7 +372,7 @@ func TestGetResourceShareInvitations_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -380,7 +381,7 @@ func TestGetResourceShareInvitations_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.ResourceShareInvitations) @@ -417,7 +418,7 @@ func TestAcceptInvitation_HTTP(t *testing.T) { Status string `json:"status"` } `json:"resourceShareInvitation"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ACCEPTED", resp.ResourceShareInvitation.Status) // Double-accept should return AlreadyAccepted. diff --git a/services/ram/handler_share_permissions.go b/services/ram/handler_share_permissions.go index 80ddf52212..dcac65641b 100644 --- a/services/ram/handler_share_permissions.go +++ b/services/ram/handler_share_permissions.go @@ -2,9 +2,11 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listResourceSharePermissionsRequest struct { @@ -24,7 +26,7 @@ func (h *Handler) handleListResourceSharePermissions( body []byte, ) ([]byte, error) { var req listResourceSharePermissionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -45,7 +47,7 @@ func (h *Handler) handleListResourceSharePermissions( } return json.Marshal( - listResourceSharePermissionsResponse{NextToken: nextToken, Permissions: page}, + listResourceSharePermissionsResponse{NextToken: nextToken, Permissions: page}, jsonx.V1(), ) } @@ -65,7 +67,7 @@ func (h *Handler) handleAssociateResourceSharePermission( body []byte, ) ([]byte, error) { var req associateResourceSharePermissionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -83,7 +85,7 @@ func (h *Handler) handleAssociateResourceSharePermission( return nil, err } - return json.Marshal(associateResourceSharePermissionResponse{ReturnValue: true}) + return json.Marshal(associateResourceSharePermissionResponse{ReturnValue: true}, jsonx.V1()) } type disassociateResourceSharePermissionRequest struct { @@ -100,7 +102,7 @@ func (h *Handler) handleDisassociateResourceSharePermission( body []byte, ) ([]byte, error) { var req disassociateResourceSharePermissionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -116,7 +118,7 @@ func (h *Handler) handleDisassociateResourceSharePermission( return nil, err } - return json.Marshal(disassociateResourceSharePermissionResponse{ReturnValue: true}) + return json.Marshal(disassociateResourceSharePermissionResponse{ReturnValue: true}, jsonx.V1()) } type replacePermissionAssociationsRequest struct { @@ -166,7 +168,7 @@ func (h *Handler) handleReplacePermissionAssociations( body []byte, ) ([]byte, error) { var req replacePermissionAssociationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -189,7 +191,7 @@ func (h *Handler) handleReplacePermissionAssociations( return json.Marshal(replacePermissionAssociationsResponse{ ReplacePermissionAssociationsWork: toReplacePermissionAssociationsWorkObject(work), - }) + }, jsonx.V1()) } // permissionAssociationObject matches types.AssociatedPermission @@ -221,7 +223,7 @@ type listPermissionAssociationsResponse struct { func (h *Handler) handleListPermissionAssociations(_ context.Context, body []byte) ([]byte, error) { var req listPermissionAssociationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -250,7 +252,7 @@ func (h *Handler) handleListPermissionAssociations(_ context.Context, body []byt return nil, err } - return json.Marshal(listPermissionAssociationsResponse{NextToken: nextToken, Permissions: page}) + return json.Marshal(listPermissionAssociationsResponse{NextToken: nextToken, Permissions: page}, jsonx.V1()) } // shareMatchesPermissionFilter applies the featureSet and associationStatus filters. @@ -288,7 +290,7 @@ func (h *Handler) handleListReplacePermissionAssociationsWork( body []byte, ) ([]byte, error) { var req listReplacePermissionAssociationsWorkRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -307,5 +309,5 @@ func (h *Handler) handleListReplacePermissionAssociationsWork( return json.Marshal(listReplacePermissionAssociationsWorkResponse{ NextToken: nextToken, ReplacePermissionAssociationsWorks: page, - }) + }, jsonx.V1()) } diff --git a/services/ram/handler_share_permissions_test.go b/services/ram/handler_share_permissions_test.go index 2f3ef71190..91e2f03040 100644 --- a/services/ram/handler_share_permissions_test.go +++ b/services/ram/handler_share_permissions_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -75,7 +76,7 @@ func TestAssociateBuiltInPermission(t *testing.T) { PermissionType string `json:"permissionType"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Permissions, 1) assert.Equal(t, tt.permARN, resp.Permissions[0].Arn) assert.Equal(t, "AWS_MANAGED", resp.Permissions[0].PermissionType) @@ -145,7 +146,7 @@ func TestReplacePermissionAssociations(t *testing.T) { ID string `json:"id"` } `json:"replacePermissionAssociationsWork"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.ReplacePermissionAssociationsWork.ID) // This mock performs the association swap synchronously (no async worker), // so the work item is already COMPLETED by the time the call returns. @@ -181,7 +182,7 @@ func TestListReplacePermissionAssociationsWork(t *testing.T) { ID string `json:"id"` } `json:"replacePermissionAssociationsWork"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) workID := createResp.ReplacePermissionAssociationsWork.ID require.NotEmpty(t, workID) @@ -203,7 +204,7 @@ func TestListReplacePermissionAssociationsWork(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp listResp - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ReplacePermissionAssociationsWorks, 1) w := resp.ReplacePermissionAssociationsWorks[0] @@ -224,7 +225,7 @@ func TestListReplacePermissionAssociationsWork(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp listResp - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.ReplacePermissionAssociationsWorks) }) @@ -237,7 +238,7 @@ func TestListReplacePermissionAssociationsWork(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp listResp - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.ReplacePermissionAssociationsWorks) }) } @@ -415,7 +416,7 @@ func TestDisassociateResourceSharePermission_BlockedWhileResourceOfTypeAttached( assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "OperationNotPermittedException", errResp["__type"]) // The permission must still be associated -- the disassociation was refused, not @@ -516,7 +517,7 @@ func TestListResourceSharePermissions_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -525,7 +526,7 @@ func TestListResourceSharePermissions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ nextToken = resp.NextToken @@ -580,7 +581,7 @@ func TestListPermissionAssociations_PermissionVersionFilter(t *testing.T) { PermissionVersion string `json:"permissionVersion"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len( t, resp.Permissions, 1, "permissionVersion must scope results to shares pinned to that version", @@ -670,7 +671,7 @@ func TestListPermissionAssociations_Pagination(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) return @@ -679,7 +680,7 @@ func TestListPermissionAssociations_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp respBody - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pages++ totalSeen += len(resp.Permissions) @@ -712,7 +713,7 @@ func TestListResourceSharePermissions_RealData(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp, jsonx.V1())) shareARN := shareResp.ResourceShare.ResourceShareArn permRec := doRAMRequest(t, h, "/createpermission", map[string]any{ @@ -727,7 +728,7 @@ func TestListResourceSharePermissions_RealData(t *testing.T) { Arn string `json:"arn"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(permRec.Body.Bytes(), &permResp)) + require.NoError(t, json.Unmarshal(permRec.Body.Bytes(), &permResp, jsonx.V1())) permARN := permResp.Permission.Arn // Associate permission with share. @@ -748,7 +749,7 @@ func TestListResourceSharePermissions_RealData(t *testing.T) { Arn string `json:"arn"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Permissions, 1) assert.Equal(t, permARN, listResp.Permissions[0].Arn) } @@ -781,7 +782,7 @@ func TestListResourceSharePermissions_ReportsAssociatedVersion(t *testing.T) { ResourceShareArn string `json:"resourceShareArn"` } `json:"resourceShare"` } - require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp)) + require.NoError(t, json.Unmarshal(shareRec.Body.Bytes(), &shareResp, jsonx.V1())) shareARN := shareResp.ResourceShare.ResourceShareArn permRec := doRAMRequest(t, h, "/createpermission", map[string]any{ @@ -796,7 +797,7 @@ func TestListResourceSharePermissions_ReportsAssociatedVersion(t *testing.T) { Arn string `json:"arn"` } `json:"permission"` } - require.NoError(t, json.Unmarshal(permRec.Body.Bytes(), &permResp)) + require.NoError(t, json.Unmarshal(permRec.Body.Bytes(), &permResp, jsonx.V1())) permARN := permResp.Permission.Arn // Create a second version and make it the default, then pin the share to @@ -833,7 +834,7 @@ func TestListResourceSharePermissions_ReportsAssociatedVersion(t *testing.T) { DefaultVersion bool `json:"defaultVersion"` } `json:"permissions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Permissions, 1) assert.Equal( t, "1", listResp.Permissions[0].Version, diff --git a/services/ram/handler_tags.go b/services/ram/handler_tags.go index 1a696f54b5..07afce382c 100644 --- a/services/ram/handler_tags.go +++ b/services/ram/handler_tags.go @@ -2,8 +2,10 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type tagResourceRequest struct { @@ -14,7 +16,7 @@ type tagResourceRequest struct { func (h *Handler) handleTagResource(_ context.Context, body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -42,7 +44,7 @@ type untagResourceRequest struct { func (h *Handler) handleUntagResource(_ context.Context, body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -73,7 +75,7 @@ type listTagsForResourceResponse struct { func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]byte, error) { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -91,5 +93,5 @@ func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]b return nil, err } - return json.Marshal(listTagsForResourceResponse{Tags: toTagObjects(tags)}) + return json.Marshal(listTagsForResourceResponse{Tags: toTagObjects(tags)}, jsonx.V1()) } diff --git a/services/ram/handler_tags_test.go b/services/ram/handler_tags_test.go index 95ac326fd6..6c546df151 100644 --- a/services/ram/handler_tags_test.go +++ b/services/ram/handler_tags_test.go @@ -1,13 +1,14 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -221,7 +222,7 @@ func TestToTagObjectsSorted(t *testing.T) { } `json:"tags"` } `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ResourceShares, 1) tags := resp.ResourceShares[0].Tags @@ -259,7 +260,7 @@ func TestTagsRoundTrip(t *testing.T) { Value string `json:"value"` } `json:"tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Tags, 1) assert.Equal(t, "env", listResp.Tags[0].Key) assert.Equal(t, "prod", listResp.Tags[0].Value) @@ -276,6 +277,6 @@ func TestTagsRoundTrip(t *testing.T) { "resourceShareArn": rs.ARN, }) require.Equal(t, http.StatusOK, listRec2.Code) - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &listResp, jsonx.V1())) assert.Empty(t, listResp.Tags) } diff --git a/services/ram/handler_test.go b/services/ram/handler_test.go index 56b3781ab5..7efb50f55c 100644 --- a/services/ram/handler_test.go +++ b/services/ram/handler_test.go @@ -2,7 +2,8 @@ package ram_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -29,7 +31,7 @@ func doRAMRequest(t *testing.T, h *ram.Handler, path string, body any) *httptest if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -249,7 +251,7 @@ func TestHandler_Reset(t *testing.T) { ResourceShares []any `json:"resourceShares"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.ResourceShares) } @@ -286,9 +288,9 @@ func TestReset_ReSeedsBuiltIns(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var custResp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &custResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &custResp, jsonx.V1())) assert.Empty(t, custResp.Permissions, "customer permissions cleared after reset") rec2 := doRAMRequest(t, h, "/listpermissions", map[string]any{ @@ -297,9 +299,9 @@ func TestReset_ReSeedsBuiltIns(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var awsResp struct { - Permissions []json.RawMessage `json:"permissions"` + Permissions []jsontext.Value `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &awsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &awsResp, jsonx.V1())) assert.Len(t, awsResp.Permissions, ram.BuiltInPermissionCount, "built-in permissions re-seeded after reset") }) diff --git a/services/ram/idempotent_ops.go b/services/ram/idempotent_ops.go index bb26a8dab5..294dd9f4ff 100644 --- a/services/ram/idempotent_ops.go +++ b/services/ram/idempotent_ops.go @@ -2,11 +2,12 @@ package ram import ( "context" - "encoding/json" + "encoding/json/v2" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func usesClientToken(op string) bool { @@ -32,7 +33,7 @@ func (h *Handler) replayClientToken( token := query.Get("clientToken") var fields map[string]any - if len(body) > 0 && json.Unmarshal(body, &fields) == nil { + if len(body) > 0 && json.Unmarshal(body, &fields, jsonx.V1()) == nil { if t, ok := fields["clientToken"].(string); ok && token == "" { token = t } diff --git a/services/ram/multi_region_test.go b/services/ram/multi_region_test.go index a808a85ea5..c6ef35283a 100644 --- a/services/ram/multi_region_test.go +++ b/services/ram/multi_region_test.go @@ -2,7 +2,8 @@ package ram_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -178,8 +180,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/ram/permission_version_shape_test.go b/services/ram/permission_version_shape_test.go index b23b59ec55..f14b14bc28 100644 --- a/services/ram/permission_version_shape_test.go +++ b/services/ram/permission_version_shape_test.go @@ -1,7 +1,7 @@ package ram_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/ram" ) @@ -104,7 +105,7 @@ func Test_ListPermissionVersions_OmitsPolicyDocumentField(t *testing.T) { var raw struct { Permissions []map[string]any `json:"permissions"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.Permissions, 2) for _, item := range raw.Permissions { diff --git a/services/ram/persistence.go b/services/ram/persistence.go index e2796621c3..5914e499fb 100644 --- a/services/ram/persistence.go +++ b/services/ram/persistence.go @@ -2,10 +2,12 @@ package ram import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -35,7 +37,7 @@ const ramSnapshotVersion = 1 // newer) build of this backend as though it were the current shape; see // Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` SharePermissions map[string]map[string]int32 `json:"sharePermissions"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -88,7 +90,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ram: failed to snapshot backend state", "error", err) diff --git a/services/ram/policy_shares.go b/services/ram/policy_shares.go index 9edd05861c..25cfa942bb 100644 --- a/services/ram/policy_shares.go +++ b/services/ram/policy_shares.go @@ -1,7 +1,7 @@ package ram import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // policyShareNamePrefix names every CREATED_FROM_POLICY share this backend creates via @@ -227,7 +228,7 @@ func policyTemplateFromActions(actions []string) string { "Effect": "Allow", "Action": actions, "Resource": "*", - }) + }, jsonx.V1()) if err != nil { return `{"Effect":"Allow","Action":[],"Resource":"*"}` } diff --git a/services/rds/engine_dumpstore.go b/services/rds/engine_dumpstore.go index bb85a6e748..e810e4cb6a 100644 --- a/services/rds/engine_dumpstore.go +++ b/services/rds/engine_dumpstore.go @@ -2,12 +2,14 @@ package rds import ( "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "os" "path/filepath" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -47,7 +49,7 @@ func (s *dumpStore) save(key string, at time.Time, d engineData) error { rec := dumpRecord{At: at, Key: key, Dump: d.dump, Password: d.password, DBName: d.dbName} - raw, err := json.Marshal(rec) //nolint:gosec // the master password must come back with the restored data + raw, err := json.Marshal(rec, jsonx.V1()) if err != nil { return fmt.Errorf("marshal dump: %w", err) } @@ -102,7 +104,7 @@ func (s *dumpStore) load() []dumpRecord { } var rec dumpRecord - if json.Unmarshal(raw, &rec) == nil && rec.Key != "" { + if json.Unmarshal(raw, &rec, jsonx.V1()) == nil && rec.Key != "" { out = append(out, rec) } } diff --git a/services/rds/engine_pgext_docker_test.go b/services/rds/engine_pgext_docker_test.go index bcba13989a..8fa2c9234a 100644 --- a/services/rds/engine_pgext_docker_test.go +++ b/services/rds/engine_pgext_docker_test.go @@ -3,7 +3,8 @@ package rds_test import ( "context" "database/sql" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/rds" "github.com/blackbirdworks/gopherstack/services/s3" @@ -71,7 +73,7 @@ func startPgExtEnv(t *testing.T) *pgExtEnv { return } - out, _ := json.Marshal(map[string]any{"fn": r.PathValue("name"), "echo": json.RawMessage(in)}) + out, _ := json.Marshal(map[string]any{"fn": r.PathValue("name"), "echo": jsontext.Value(in)}, jsonx.V1()) _, _ = w.Write(out) }) mux.Handle("/", s3e) diff --git a/services/rds/master_secret.go b/services/rds/master_secret.go index 1a5e5f6202..119680524a 100644 --- a/services/rds/master_secret.go +++ b/services/rds/master_secret.go @@ -3,13 +3,14 @@ package rds import ( "crypto/rand" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net/url" "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -86,7 +87,7 @@ func masterSecretBody(user string) (string, error) { return "", fmt.Errorf("generate master password: %w", err) } - body, err := json.Marshal(map[string]string{"username": user, "password": hex.EncodeToString(raw)}) + body, err := json.Marshal(map[string]string{"username": user, "password": hex.EncodeToString(raw)}, jsonx.V1()) if err != nil { return "", fmt.Errorf("encode master user secret: %w", err) } diff --git a/services/rds/persistence.go b/services/rds/persistence.go index 9d0e03bcc1..f24572656d 100644 --- a/services/rds/persistence.go +++ b/services/rds/persistence.go @@ -2,11 +2,13 @@ package rds import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -30,7 +32,7 @@ import ( const rdsSnapshotVersion = 3 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string][]Tag `json:"tags"` ClusterRoles map[string][]DBClusterRole `json:"clusterRoles"` InstanceRoles map[string]map[string]string `json:"instanceRoles"` @@ -86,7 +88,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { DefaultCACertificateID: b.defaultCACertificateID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "rds: failed to marshal snapshot", "error", err) @@ -235,11 +237,11 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { return h.Backend.Snapshot(ctx) } - snaps := make(map[string]json.RawMessage) + snaps := make(map[string]jsontext.Value) for region, b := range h.backends { snaps[region] = b.Snapshot(ctx) } - data, _ := json.Marshal(snaps) + data, _ := json.Marshal(snaps, jsonx.V1()) return data } @@ -253,8 +255,8 @@ func (h *Handler) Restore(ctx context.Context, data []byte) error { return h.Backend.Restore(ctx, data) } - var snaps map[string]json.RawMessage - if err := json.Unmarshal(data, &snaps); err != nil { + var snaps map[string]jsontext.Value + if err := json.Unmarshal(data, &snaps, jsonx.V1()); err != nil { // Fallback for backwards compatibility with single-region snapshots. return h.Backend.Restore(ctx, data) } diff --git a/services/rds/persistence_test.go b/services/rds/persistence_test.go index bf73b2a246..e99badbeaf 100644 --- a/services/rds/persistence_test.go +++ b/services/rds/persistence_test.go @@ -1,13 +1,15 @@ package rds_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rds" ) @@ -21,16 +23,16 @@ func TestPersistence_SnapshotJSON_ContainsExtendedKeys(t *testing.T) { snap := b.Snapshot(t.Context()) require.NotNil(t, snap) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) // As of the Phase 3.3 pkgs/store conversion, resource collections backed by // a store.Table are serialized under the nested "tables" blob (produced by // store.Registry.SnapshotAll) rather than as top-level snapshot keys. require.Contains(t, raw, "tables") - var tables map[string]json.RawMessage - require.NoError(t, json.Unmarshal(raw["tables"], &tables)) + var tables map[string]jsontext.Value + require.NoError(t, json.Unmarshal(raw["tables"], &tables, jsonx.V1())) for _, key := range []string{ "customEngineVersions", diff --git a/services/rdsdata/column_origin_realclient_test.go b/services/rdsdata/column_origin_realclient_test.go index a9a36efccb..83227f7c8f 100644 --- a/services/rdsdata/column_origin_realclient_test.go +++ b/services/rdsdata/column_origin_realclient_test.go @@ -1,7 +1,7 @@ package rdsdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rdsdata" ) @@ -93,7 +94,7 @@ func TestExecuteStatement_ColumnMetadata_TableOrigin_InsideTransaction(t *testin require.Equal(t, http.StatusOK, rec.Code) var begin map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &begin)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &begin, jsonx.V1())) txID, ok := begin["transactionId"].(string) require.True(t, ok) @@ -107,7 +108,7 @@ func TestExecuteStatement_ColumnMetadata_TableOrigin_InsideTransaction(t *testin require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cols, ok := resp["columnMetadata"].([]any) require.True(t, ok) diff --git a/services/rdsdata/errsweep_wire_shape_test.go b/services/rdsdata/errsweep_wire_shape_test.go index 892396fd70..e4a4d72ce3 100644 --- a/services/rdsdata/errsweep_wire_shape_test.go +++ b/services/rdsdata/errsweep_wire_shape_test.go @@ -1,7 +1,7 @@ package rdsdata_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/rdsdata" @@ -61,7 +62,7 @@ func TestHandler_MalformedBodyCarriesTypedErrorCode(t *testing.T) { require.NoError(t, err) var decoded map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) + require.NoError(t, json.Unmarshal(raw, &decoded, jsonx.V1())) require.Equal(t, "BadRequestException", decoded["__type"], "raw response body must carry __type; got: %s", raw) require.NotEmpty(t, decoded["message"]) diff --git a/services/rdsdata/handler.go b/services/rdsdata/handler.go index 7b8ef820ae..fea68e1acc 100644 --- a/services/rdsdata/handler.go +++ b/services/rdsdata/handler.go @@ -3,7 +3,7 @@ package rdsdata import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -257,7 +258,7 @@ func writeInternalServerError(c *echo.Context) error { payload, err := json.Marshal(map[string]string{ keyTypeField: "InternalServerErrorException", keyMessageField: "internal server error", - }) + }, jsonx.V1()) if err != nil { return err } @@ -270,32 +271,32 @@ func stripCode(err error, code string) string { } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var apiErr *apiError switch { case errors.As(err, &apiErr): - payload, _ := json.Marshal(map[string]string{keyTypeField: apiErr.code, keyMessageField: apiErr.msg}) + payload, _ := json.Marshal( + map[string]string{keyTypeField: apiErr.code, keyMessageField: apiErr.msg}, + jsonx.V1(), + ) return c.JSONBlob(apiErr.status, payload) case errors.Is(err, ErrTransactionNotFound): payload, _ := json.Marshal(map[string]string{ keyTypeField: "TransactionNotFoundException", keyMessageField: stripCode(err, "TransactionNotFoundException"), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusNotFound, payload) case errIsValidation(err): payload, _ := json.Marshal(map[string]string{ keyTypeField: "BadRequestException", keyMessageField: stripCode(err, "BadRequestException"), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): // These branches used to omit keyTypeField entirely, so // restjson.GetErrorInfo (aws-sdk-go-v2 aws/protocol/restjson/ // decoder_util.go:15) found no code in the header (unset) or the @@ -410,7 +411,7 @@ func validateRequiredFields(fields ...requiredField) error { func (h *Handler) handleExecuteStatement(ctx context.Context, body []byte) ([]byte, error) { var req executeStatementRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -489,7 +490,7 @@ func marshalExecuteResponse( } } - out, err := json.Marshal(resp) + out, err := json.Marshal(resp, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal response: %w", err) } @@ -532,7 +533,7 @@ func formatRecordsAsJSONString(records [][]Field, columns []ColumnMetadata) (str rows[i] = row } - out, err := json.Marshal(rows) + out, err := json.Marshal(rows, jsonx.V1()) if err != nil { return "", fmt.Errorf("marshal formatted records: %w", err) } @@ -571,7 +572,7 @@ type batchExecuteStatementResponse struct { func (h *Handler) handleBatchExecuteStatement(ctx context.Context, body []byte) ([]byte, error) { var req batchExecuteStatementRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -600,7 +601,7 @@ func (h *Handler) handleBatchExecuteStatement(ctx context.Context, body []byte) return nil, err } - return json.Marshal(batchExecuteStatementResponse{UpdateResults: results}) + return json.Marshal(batchExecuteStatementResponse{UpdateResults: results}, jsonx.V1()) } type beginTransactionRequest struct { @@ -616,7 +617,7 @@ type beginTransactionResponse struct { func (h *Handler) handleBeginTransaction(ctx context.Context, body []byte) ([]byte, error) { var req beginTransactionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -634,7 +635,7 @@ func (h *Handler) handleBeginTransaction(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(beginTransactionResponse{TransactionID: txID}) + return json.Marshal(beginTransactionResponse{TransactionID: txID}, jsonx.V1()) } type commitTransactionRequest struct { @@ -649,7 +650,7 @@ type commitTransactionResponse struct { func (h *Handler) handleCommitTransaction(ctx context.Context, body []byte) ([]byte, error) { var req commitTransactionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -666,7 +667,7 @@ func (h *Handler) handleCommitTransaction(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(commitTransactionResponse{TransactionStatus: status}) + return json.Marshal(commitTransactionResponse{TransactionStatus: status}, jsonx.V1()) } type rollbackTransactionRequest struct { @@ -681,7 +682,7 @@ type rollbackTransactionResponse struct { func (h *Handler) handleRollbackTransaction(ctx context.Context, body []byte) ([]byte, error) { var req rollbackTransactionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -698,7 +699,7 @@ func (h *Handler) handleRollbackTransaction(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(rollbackTransactionResponse{TransactionStatus: status}) + return json.Marshal(rollbackTransactionResponse{TransactionStatus: status}, jsonx.V1()) } type executeSQLRequest struct { @@ -715,7 +716,7 @@ type executeSQLResponse struct { func (h *Handler) handleExecuteSQL(ctx context.Context, body []byte) ([]byte, error) { var req executeSQLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -734,7 +735,7 @@ func (h *Handler) handleExecuteSQL(ctx context.Context, body []byte) ([]byte, er return nil, err } - return json.Marshal(executeSQLResponse{SQLStatementResults: results}) + return json.Marshal(executeSQLResponse{SQLStatementResults: results}, jsonx.V1()) } func arrayToJSONValue(a *ArrayValue) any { diff --git a/services/rdsdata/handler_test.go b/services/rdsdata/handler_test.go index f1c565618a..9d59e1c8d9 100644 --- a/services/rdsdata/handler_test.go +++ b/services/rdsdata/handler_test.go @@ -2,7 +2,7 @@ package rdsdata_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/rdsdata" ) @@ -30,7 +31,7 @@ func doRDSDataRequest(t *testing.T, h *rdsdata.Handler, path string, body any) * if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -348,7 +349,7 @@ func TestHandler_ConcurrentRequests_Race(t *testing.T) { "resourceArn": "arn:aws:rds:us-east-1:000000000000:cluster:c", "secretArn": "arn:aws:secretsmanager:us-east-1:000000000000:secret:s", "sql": "SELECT " + string(rune('0'+i%10)), - }) + }, jsonx.V1()) e := echo.New() req := httptest.NewRequest(http.MethodPost, "/Execute", bytes.NewReader(body)) diff --git a/services/rdsdata/real_docker_test.go b/services/rdsdata/real_docker_test.go index e38ce4eb3e..8579fdabc9 100644 --- a/services/rdsdata/real_docker_test.go +++ b/services/rdsdata/real_docker_test.go @@ -1,7 +1,7 @@ package rdsdata_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "os/exec" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rds" "github.com/blackbirdworks/gopherstack/services/rdsdata" "github.com/blackbirdworks/gopherstack/services/secretsmanager" @@ -95,7 +96,7 @@ func (d *dockerDataAPI) call(path string, body map[string]any) (int, map[string] out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(d.t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(d.t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return rec.Code, out diff --git a/services/rdsdata/real_engine.go b/services/rdsdata/real_engine.go index 1bb884cc44..6163d0156e 100644 --- a/services/rdsdata/real_engine.go +++ b/services/rdsdata/real_engine.go @@ -3,7 +3,7 @@ package rdsdata import ( "context" "database/sql" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/url" @@ -15,6 +15,7 @@ import ( "github.com/google/uuid" _ "github.com/jackc/pgx/v5/stdlib" // registers the "pgx" database/sql driver + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -127,7 +128,7 @@ func (e *realEngine) loginFor(ctx context.Context, resourceARN string) (realLogi Password string `json:"password"` } - if json.Unmarshal([]byte(raw), &creds) != nil || creds.Username == "" || creds.Password == "" { + if json.Unmarshal([]byte(raw), &creds, jsonx.V1()) != nil || creds.Username == "" || creds.Password == "" { return realLogin{}, false, errInvalidSecret("the secret must be a JSON object with username and password") } diff --git a/services/rdsdata/real_handler_test.go b/services/rdsdata/real_handler_test.go index c4ac215514..3080618e3e 100644 --- a/services/rdsdata/real_handler_test.go +++ b/services/rdsdata/real_handler_test.go @@ -2,7 +2,7 @@ package rdsdata //nolint:testpackage // white-box tests of the unexported placeh import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandlerMapsModeledExceptions(t *testing.T) { @@ -50,7 +52,7 @@ func TestHandlerMapsModeledExceptions(t *testing.T) { body, err := json.Marshal(map[string]string{ "resourceArn": testClusterARN, "secretArn": testSecretARN, "sql": "SELECT 1", - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/Execute", bytes.NewReader(body)) @@ -63,7 +65,7 @@ func TestHandlerMapsModeledExceptions(t *testing.T) { var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["__type"]) }) } diff --git a/services/rdsdata/sql_test.go b/services/rdsdata/sql_test.go index d5af4b393f..41d1645149 100644 --- a/services/rdsdata/sql_test.go +++ b/services/rdsdata/sql_test.go @@ -2,7 +2,7 @@ package rdsdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rdsdata" ) @@ -125,7 +126,7 @@ func TestHandler_ExecuteSQL_Response(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["sqlStatementResults"] require.True(t, ok) @@ -148,6 +149,6 @@ func TestHandler_ExecuteSQL_Response_NotNull(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["sqlStatementResults"]) } diff --git a/services/rdsdata/statements_test.go b/services/rdsdata/statements_test.go index f9b662525b..9af0f197a1 100644 --- a/services/rdsdata/statements_test.go +++ b/services/rdsdata/statements_test.go @@ -2,7 +2,7 @@ package rdsdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rdsdata" ) @@ -93,7 +94,7 @@ func TestHandler_ExecuteStatement_WithTransaction(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rec := doRDSDataRequest(t, h, "/Execute", map[string]any{ @@ -150,7 +151,7 @@ func TestHandler_ExecuteStatement_Response(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasRecords := resp["records"] assert.True(t, hasRecords, "records key must be present") @@ -202,7 +203,7 @@ func TestHandler_ExecuteStatement_IncludeResultMetadata(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasCols := resp["columnMetadata"] assert.Equal(t, tt.wantColumnMetadata, hasCols, @@ -246,7 +247,7 @@ func TestHandler_ExecuteStatement_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, field := range tt.fields { _, ok := resp[field] @@ -274,7 +275,7 @@ func TestHandler_ExecuteStatement_FormatRecordsAsJSON(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasRecords := resp["records"] assert.False(t, hasRecords, "records must be omitted when formatRecordsAs=JSON") @@ -285,7 +286,7 @@ func TestHandler_ExecuteStatement_FormatRecordsAsJSON(t *testing.T) { require.True(t, ok, "formattedRecords must be a JSON string") var rows []map[string]any - require.NoError(t, json.Unmarshal([]byte(formatted), &rows)) + require.NoError(t, json.Unmarshal([]byte(formatted), &rows, jsonx.V1())) require.Len(t, rows, 1) require.Len(t, rows[0], 1, "row must have exactly one column") @@ -311,7 +312,7 @@ func TestHandler_ExecuteStatement_FormatRecordsAsIgnoredForDML(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasFormatted := resp["formattedRecords"] assert.False(t, hasFormatted, "formattedRecords must not appear for a DML statement") @@ -336,7 +337,7 @@ func TestHandler_ExecuteStatement_FormatRecordsAsInvalid(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) } @@ -357,7 +358,7 @@ func TestHandler_ExecuteStatement_ColumnMetadataFullShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cols, ok := resp["columnMetadata"].([]any) require.True(t, ok) @@ -421,7 +422,7 @@ func TestHandler_ExecuteStatement_ArrayParameterRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) } @@ -472,7 +473,7 @@ func TestHandler_ExecuteStatement_ResultSetOptions_LongReturnType(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records := resp["records"].([]any) require.Len(t, records, 1) @@ -531,7 +532,7 @@ func TestHandler_ExecuteStatement_ResultSetOptions_DecimalReturnType(t *testing. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records := resp["records"].([]any) require.Len(t, records, 1) @@ -570,7 +571,7 @@ func TestHandler_ExecuteStatement_ResultSetOptions_InvalidEnum(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) }) } @@ -675,7 +676,7 @@ func TestHandler_BatchExecuteStatement_EmptyParamSets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["updateResults"] require.True(t, ok, "updateResults key must exist") @@ -703,7 +704,7 @@ func TestHandler_BatchExecuteStatement_ArrayParameterRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) } @@ -746,7 +747,7 @@ func TestHandler_BatchExecuteStatement_MultipleParamSets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["updateResults"].([]any) assert.Len(t, results, 3) } @@ -795,7 +796,7 @@ func TestHandler_BatchExecuteStatement_OneResultPerParamSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["updateResults"].([]any) require.True(t, ok, "updateResults must be an array") diff --git a/services/rdsdata/transactions_test.go b/services/rdsdata/transactions_test.go index 7c69ab5127..f2c85b1bdc 100644 --- a/services/rdsdata/transactions_test.go +++ b/services/rdsdata/transactions_test.go @@ -2,7 +2,7 @@ package rdsdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rdsdata" ) @@ -81,7 +82,7 @@ func TestHandler_BeginTransaction_ReturnsTransactionID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) txID, ok := resp["transactionId"].(string) require.True(t, ok) assert.NotEmpty(t, txID) @@ -103,7 +104,7 @@ func TestHandler_BeginTransaction_UniqueIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) txID, ok := resp["transactionId"].(string) require.True(t, ok) @@ -169,7 +170,7 @@ func TestHandler_CommitTransaction(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rec = doRDSDataRequest(t, h, "/CommitTransaction", map[string]any{ @@ -247,7 +248,7 @@ func TestHandler_RollbackTransaction(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rec = doRDSDataRequest(t, h, "/RollbackTransaction", map[string]any{ @@ -286,7 +287,7 @@ func TestHandler_TransactionLifecycle(t *testing.T) { assert.Equal(t, 1, rdsdata.TransactionCount(b)) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) // Execute @@ -308,7 +309,7 @@ func TestHandler_TransactionLifecycle(t *testing.T) { assert.Equal(t, 0, rdsdata.TransactionCount(b)) var commitResp map[string]any - require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp)) + require.NoError(t, json.Unmarshal(commitRec.Body.Bytes(), &commitResp, jsonx.V1())) assert.Equal(t, "Transaction committed", commitResp["transactionStatus"]) } @@ -325,7 +326,7 @@ func TestHandler_TransactionRollbackLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rollbackRec := doRDSDataRequest(t, h, "/RollbackTransaction", map[string]any{ @@ -336,7 +337,7 @@ func TestHandler_TransactionRollbackLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rollbackRec.Code) var rollbackResp map[string]any - require.NoError(t, json.Unmarshal(rollbackRec.Body.Bytes(), &rollbackResp)) + require.NoError(t, json.Unmarshal(rollbackRec.Body.Bytes(), &rollbackResp, jsonx.V1())) assert.Equal(t, "Transaction rolled back", rollbackResp["transactionStatus"]) } @@ -353,7 +354,7 @@ func TestHandler_CommitAfterRollback_Fails(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) // Rollback first @@ -401,7 +402,7 @@ func TestHandler_CommitThenReuse(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) commitRec := doRDSDataRequest(t, h, "/CommitTransaction", map[string]any{ @@ -421,7 +422,7 @@ func TestHandler_CommitThenReuse(t *testing.T) { "path %s: reusing committed txID must fail", tt.path) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "TransactionNotFoundException", errResp["__type"]) }) } @@ -463,7 +464,7 @@ func TestHandler_ExecuteAfterTransactionClose(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) // Execute within transaction — should succeed. @@ -508,7 +509,7 @@ func TestHandler_BatchExecuteWithTransaction(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rec := doRDSDataRequest(t, h, "/BatchExecute", map[string]any{ @@ -558,7 +559,7 @@ func TestHandler_TransactionStatusConstants(t *testing.T) { require.Equal(t, http.StatusOK, beginRec.Code) var beginResp map[string]any - require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp)) + require.NoError(t, json.Unmarshal(beginRec.Body.Bytes(), &beginResp, jsonx.V1())) txID := beginResp["transactionId"].(string) rec := doRDSDataRequest(t, h, tt.path, map[string]any{ @@ -569,7 +570,7 @@ func TestHandler_TransactionStatusConstants(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantStatus, resp["transactionStatus"], "transactionStatus must match exact AWS string") }) @@ -608,7 +609,7 @@ func TestHandler_TransactionNotFoundErrorType(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TransactionNotFoundException", resp["__type"], "path %s: error type must be TransactionNotFoundException", tt.path) }) @@ -629,7 +630,7 @@ func TestHandler_TransactionNotFoundErrorShape(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "TransactionNotFoundException", errResp["__type"]) assert.NotEmpty(t, errResp["message"]) } diff --git a/services/rdsdata/typehints.go b/services/rdsdata/typehints.go index b8b05bac86..4e082b335d 100644 --- a/services/rdsdata/typehints.go +++ b/services/rdsdata/typehints.go @@ -1,11 +1,13 @@ package rdsdata import ( - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "regexp" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // SqlParameter.TypeHint enum values (rdsdata@v1.35.4 types/enums.go's @@ -89,7 +91,7 @@ func validateTypeHintFormat(hint, value string) error { return fmt.Errorf("%w: %s requires a decimal number", errBadTypeHintValue, typeHintDecimal) } case typeHintJSON: - if !json.Valid([]byte(value)) { + if !jsontext.Value([]byte(value)).IsValid(jsonx.V1()) { return fmt.Errorf("%w: %s requires valid JSON", errBadTypeHintValue, typeHintJSON) } case typeHintUUID: diff --git a/services/redshift/handler_serverless.go b/services/redshift/handler_serverless.go index e08b248748..6effc32229 100644 --- a/services/redshift/handler_serverless.go +++ b/services/redshift/handler_serverless.go @@ -2,7 +2,8 @@ package redshift import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strings" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/awstime" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -197,7 +199,7 @@ func (h *ServerlessHandler) ExtractResource(c *echo.Context) string { WorkgroupName string `json:"workgroupName"` } - if json.Unmarshal(body, &probe) != nil { + if json.Unmarshal(body, &probe, jsonx.V1()) != nil { return "" } @@ -348,7 +350,7 @@ func (h *ServerlessHandler) handleCreateNamespace(c *echo.Context, body []byte) ManageAdminPassword bool `json:"manageAdminPassword"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -382,7 +384,7 @@ func (h *ServerlessHandler) handleGetNamespace(c *echo.Context, body []byte) err NamespaceName string `json:"namespaceName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -401,7 +403,7 @@ func (h *ServerlessHandler) handleListNamespaces(c *echo.Context, body []byte) e } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -429,7 +431,7 @@ func (h *ServerlessHandler) handleUpdateNamespace(c *echo.Context, body []byte) LogExports []string `json:"logExports"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -457,7 +459,7 @@ func (h *ServerlessHandler) handleDeleteNamespace(c *echo.Context, body []byte) FinalSnapshotRetentionPeriod int `json:"finalSnapshotRetentionPeriod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -514,7 +516,7 @@ func (r slWorkgroupReq) toParams() WorkgroupParams { func (h *ServerlessHandler) handleCreateWorkgroup(c *echo.Context, body []byte) error { var req slWorkgroupReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -535,7 +537,7 @@ func (h *ServerlessHandler) handleGetWorkgroup(c *echo.Context, body []byte) err WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -555,7 +557,7 @@ func (h *ServerlessHandler) handleListWorkgroups(c *echo.Context, body []byte) e } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -573,7 +575,7 @@ func (h *ServerlessHandler) handleListWorkgroups(c *echo.Context, body []byte) e func (h *ServerlessHandler) handleUpdateWorkgroup(c *echo.Context, body []byte) error { var req slWorkgroupReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -590,7 +592,7 @@ func (h *ServerlessHandler) handleDeleteWorkgroup(c *echo.Context, body []byte) WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -610,7 +612,7 @@ func (h *ServerlessHandler) handleGetCredentials(c *echo.Context, body []byte) e DurationSeconds int `json:"durationSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -659,7 +661,7 @@ func (h *ServerlessHandler) handleCreateSnapshot(c *echo.Context, body []byte) e RetentionPeriod int `json:"retentionPeriod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -680,7 +682,7 @@ func (h *ServerlessHandler) handleGetSnapshot(c *echo.Context, body []byte) erro OwnerAccount string `json:"ownerAccount"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -709,7 +711,7 @@ func (h *ServerlessHandler) handleListSnapshots(c *echo.Context, body []byte) er } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -737,7 +739,7 @@ func (h *ServerlessHandler) handleDeleteSnapshot(c *echo.Context, body []byte) e SnapshotName string `json:"snapshotName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -755,7 +757,7 @@ func (h *ServerlessHandler) handleUpdateSnapshot(c *echo.Context, body []byte) e SnapshotName string `json:"snapshotName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -784,7 +786,7 @@ func (h *ServerlessHandler) handleCreateUsageLimit(c *echo.Context, body []byte) Amount int64 `json:"amount"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -807,7 +809,7 @@ func (h *ServerlessHandler) handleGetUsageLimit(c *echo.Context, body []byte) er UsageLimitID string `json:"usageLimitId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -828,7 +830,7 @@ func (h *ServerlessHandler) handleListUsageLimits(c *echo.Context, body []byte) } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -850,7 +852,7 @@ func (h *ServerlessHandler) handleUpdateUsageLimit(c *echo.Context, body []byte) Amount int64 `json:"amount"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -867,7 +869,7 @@ func (h *ServerlessHandler) handleDeleteUsageLimit(c *echo.Context, body []byte) UsageLimitID string `json:"usageLimitId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -892,17 +894,17 @@ func (h *ServerlessHandler) handleDeleteUsageLimit(c *echo.Context, body []byte) // conversion at the response boundary, same pattern as // applicationautoscaling's epochSecondsPtr. type slScheduledActionWire struct { - StartTime *float64 `json:"startTime,omitempty"` - EndTime *float64 `json:"endTime,omitempty"` - NamespaceName string `json:"namespaceName,omitempty"` - RoleArn string `json:"roleArn,omitempty"` - ScheduledActionDescription string `json:"scheduledActionDescription,omitempty"` - ScheduledActionName string `json:"scheduledActionName"` - ScheduledActionUUID string `json:"scheduledActionUuid,omitempty"` - State string `json:"state"` - NextInvocations []float64 `json:"nextInvocations,omitempty"` - Schedule json.RawMessage `json:"schedule,omitempty"` - TargetAction json.RawMessage `json:"targetAction,omitempty"` + StartTime *float64 `json:"startTime,omitempty"` + EndTime *float64 `json:"endTime,omitempty"` + NamespaceName string `json:"namespaceName,omitempty"` + RoleArn string `json:"roleArn,omitempty"` + ScheduledActionDescription string `json:"scheduledActionDescription,omitempty"` + ScheduledActionName string `json:"scheduledActionName"` + ScheduledActionUUID string `json:"scheduledActionUuid,omitempty"` + State string `json:"state"` + NextInvocations []float64 `json:"nextInvocations,omitempty"` + Schedule jsontext.Value `json:"schedule,omitempty"` + TargetAction jsontext.Value `json:"targetAction,omitempty"` } func slEpochPtr(t time.Time) *float64 { @@ -933,18 +935,18 @@ func toScheduledActionWire(sa *ServerlessScheduledAction) *slScheduledActionWire func (h *ServerlessHandler) handleCreateScheduledAction(c *echo.Context, body []byte) error { var req struct { - Enabled *bool `json:"enabled"` - StartTime *float64 `json:"startTime"` - EndTime *float64 `json:"endTime"` - ScheduledActionName string `json:"scheduledActionName"` - NamespaceName string `json:"namespaceName"` - RoleArn string `json:"roleArn"` - ScheduledActionDescription string `json:"scheduledActionDescription"` - Schedule json.RawMessage `json:"schedule"` - TargetAction json.RawMessage `json:"targetAction"` - } - - if err := json.Unmarshal(body, &req); err != nil { + Enabled *bool `json:"enabled"` + StartTime *float64 `json:"startTime"` + EndTime *float64 `json:"endTime"` + ScheduledActionName string `json:"scheduledActionName"` + NamespaceName string `json:"namespaceName"` + RoleArn string `json:"roleArn"` + ScheduledActionDescription string `json:"scheduledActionDescription"` + Schedule jsontext.Value `json:"schedule"` + TargetAction jsontext.Value `json:"targetAction"` + } + + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -983,7 +985,7 @@ func (h *ServerlessHandler) handleGetScheduledAction(c *echo.Context, body []byt ScheduledActionName string `json:"scheduledActionName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1014,7 +1016,7 @@ func (h *ServerlessHandler) handleListScheduledActions(c *echo.Context, body []b } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -1040,17 +1042,17 @@ func (h *ServerlessHandler) handleListScheduledActions(c *echo.Context, body []b func (h *ServerlessHandler) handleUpdateScheduledAction(c *echo.Context, body []byte) error { var req struct { - ScheduledActionDescription *string `json:"scheduledActionDescription"` - Enabled *bool `json:"enabled"` - StartTime *float64 `json:"startTime"` - EndTime *float64 `json:"endTime"` - ScheduledActionName string `json:"scheduledActionName"` - RoleArn string `json:"roleArn"` - Schedule json.RawMessage `json:"schedule"` - TargetAction json.RawMessage `json:"targetAction"` + ScheduledActionDescription *string `json:"scheduledActionDescription"` + Enabled *bool `json:"enabled"` + StartTime *float64 `json:"startTime"` + EndTime *float64 `json:"endTime"` + ScheduledActionName string `json:"scheduledActionName"` + RoleArn string `json:"roleArn"` + Schedule jsontext.Value `json:"schedule"` + TargetAction jsontext.Value `json:"targetAction"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1075,7 +1077,7 @@ func (h *ServerlessHandler) handleDeleteScheduledAction(c *echo.Context, body [] ScheduledActionName string `json:"scheduledActionName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1096,7 +1098,7 @@ func (h *ServerlessHandler) handleGetTrack(c *echo.Context, body []byte) error { TrackName string `json:"trackName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1119,7 +1121,7 @@ func (h *ServerlessHandler) handleListTracks(c *echo.Context, body []byte) error } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -1148,7 +1150,7 @@ func (h *ServerlessHandler) handleUpdateLakehouseConfiguration(c *echo.Context, DryRun bool `json:"dryRun"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1180,7 +1182,7 @@ func (h *ServerlessHandler) handleGetIdentityCenterAuthTokenSL(c *echo.Context, WorkgroupNames []string `json:"workgroupNames"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1209,7 +1211,7 @@ func (h *ServerlessHandler) handleCreateReservation(c *echo.Context, body []byte Capacity int32 `json:"capacity"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1226,7 +1228,7 @@ func (h *ServerlessHandler) handleGetReservation(c *echo.Context, body []byte) e ReservationID string `json:"reservationId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1245,7 +1247,7 @@ func (h *ServerlessHandler) handleListReservations(c *echo.Context, body []byte) } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -1265,7 +1267,7 @@ func (h *ServerlessHandler) handleGetReservationOffering(c *echo.Context, body [ OfferingID string `json:"offeringId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -1284,7 +1286,7 @@ func (h *ServerlessHandler) handleListReservationOfferings(c *echo.Context, body } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } diff --git a/services/redshift/handler_serverless_custom_domains.go b/services/redshift/handler_serverless_custom_domains.go index 87d206127f..16bebe65b2 100644 --- a/services/redshift/handler_serverless_custom_domains.go +++ b/services/redshift/handler_serverless_custom_domains.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Create/Get/UpdateCustomDomainAssociationResponse all serialize the @@ -22,7 +24,7 @@ func (h *ServerlessHandler) handleCreateCustomDomainAssociation(c *echo.Context, WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -46,7 +48,7 @@ func (h *ServerlessHandler) handleGetCustomDomainAssociation(c *echo.Context, bo WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -67,7 +69,7 @@ func (h *ServerlessHandler) handleListCustomDomainAssociations(c *echo.Context, } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -91,7 +93,7 @@ func (h *ServerlessHandler) handleUpdateCustomDomainAssociation(c *echo.Context, WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -115,7 +117,7 @@ func (h *ServerlessHandler) handleDeleteCustomDomainAssociation(c *echo.Context, WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } diff --git a/services/redshift/handler_serverless_custom_domains_test.go b/services/redshift/handler_serverless_custom_domains_test.go index b51fe9013c..b27e0ee913 100644 --- a/services/redshift/handler_serverless_custom_domains_test.go +++ b/services/redshift/handler_serverless_custom_domains_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestServerless_CustomDomainAssociationCRUD covers Create/Get/List/Update/ @@ -29,7 +32,7 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) assert.Equal(t, "db.example.com", createResp["customDomainName"]) assert.Equal(t, "cd-wg", createResp["workgroupName"]) assert.NotContains(t, createResp, "association", "response is flat, not wrapped in an envelope key") @@ -39,7 +42,7 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var wgResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&wgResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &wgResp, jsonx.V1())) wgData, _ := wgResp["workgroup"].(map[string]any) require.NotNil(t, wgData) assert.Equal(t, "db.example.com", wgData["customDomainName"]) @@ -56,7 +59,7 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) assocs, _ := listResp["associations"].([]any) require.Len(t, assocs, 1) @@ -65,7 +68,7 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var credResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&credResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &credResp, jsonx.V1())) dbUser, _ := credResp["dbUser"].(string) assert.NotEmpty(t, dbUser) @@ -77,7 +80,7 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) assert.Equal(t, "arn:aws:acm:us-east-1:000000000000:certificate/def", updateResp["customDomainCertificateArn"]) rec = doServerlessOp(t, h, "DeleteCustomDomainAssociation", map[string]any{ @@ -87,13 +90,13 @@ func TestServerless_CustomDomainAssociationCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&deleteResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &deleteResp, jsonx.V1())) assert.Empty(t, deleteResp, "DeleteCustomDomainAssociationResponse has zero members on the real wire") // The mirrored workgroup fields are cleared too. rec = doServerlessOp(t, h, "GetWorkgroup", map[string]any{"workgroupName": "cd-wg"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.NewDecoder(rec.Body).Decode(&wgResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &wgResp, jsonx.V1())) wgData, _ = wgResp["workgroup"].(map[string]any) assert.NotContains(t, wgData, "customDomainName") @@ -143,7 +146,7 @@ func TestServerless_CustomDomainAssociation_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -172,7 +175,7 @@ func TestServerless_CustomDomainAssociation_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) }) @@ -185,7 +188,7 @@ func TestServerless_CustomDomainAssociation_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) }) @@ -198,7 +201,7 @@ func TestServerless_CustomDomainAssociation_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) }) } diff --git a/services/redshift/handler_serverless_endpoint_access.go b/services/redshift/handler_serverless_endpoint_access.go index 7e6e8c360a..52a84a61ee 100644 --- a/services/redshift/handler_serverless_endpoint_access.go +++ b/services/redshift/handler_serverless_endpoint_access.go @@ -1,11 +1,13 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // slVpcSecurityGroupMembership mirrors types.VpcSecurityGroupMembership -- @@ -61,7 +63,7 @@ func (h *ServerlessHandler) handleCreateEndpointAccessSL(c *echo.Context, body [ VpcSecurityGroupIDs []string `json:"vpcSecurityGroupIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -80,7 +82,7 @@ func (h *ServerlessHandler) handleGetEndpointAccessSL(c *echo.Context, body []by EndpointName string `json:"endpointName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -108,7 +110,7 @@ func (h *ServerlessHandler) handleListEndpointAccessSL(c *echo.Context, body []b } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -135,7 +137,7 @@ func (h *ServerlessHandler) handleUpdateEndpointAccessSL(c *echo.Context, body [ VpcSecurityGroupIDs []string `json:"vpcSecurityGroupIds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -152,7 +154,7 @@ func (h *ServerlessHandler) handleDeleteEndpointAccessSL(c *echo.Context, body [ EndpointName string `json:"endpointName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -175,7 +177,7 @@ func (h *ServerlessHandler) handleListManagedWorkgroups(c *echo.Context, body [] } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } diff --git a/services/redshift/handler_serverless_endpoint_access_test.go b/services/redshift/handler_serverless_endpoint_access_test.go index aab95da2f0..d54f9e2917 100644 --- a/services/redshift/handler_serverless_endpoint_access_test.go +++ b/services/redshift/handler_serverless_endpoint_access_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_EndpointAccess_CRUD(t *testing.T) { @@ -27,7 +30,7 @@ func TestServerless_EndpointAccess_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) ep, _ := createResp["endpoint"].(map[string]any) require.NotNil(t, ep) assert.Equal(t, "ea-endpoint", ep["endpointName"]) @@ -46,7 +49,7 @@ func TestServerless_EndpointAccess_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) got, _ := getResp["endpoint"].(map[string]any) require.NotNil(t, got) assert.Equal(t, "ea-endpoint", got["endpointName"]) @@ -55,7 +58,7 @@ func TestServerless_EndpointAccess_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) list, _ := listResp["endpoints"].([]any) require.Len(t, list, 1) @@ -66,7 +69,7 @@ func TestServerless_EndpointAccess_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) updated, _ := updateResp["endpoint"].(map[string]any) require.NotNil(t, updated) groups, _ = updated["vpcSecurityGroups"].([]any) @@ -76,7 +79,7 @@ func TestServerless_EndpointAccess_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&deleteResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &deleteResp, jsonx.V1())) deleted, _ := deleteResp["endpoint"].(map[string]any) require.NotNil(t, deleted, "DeleteEndpointAccessResponse echoes the deleted object on the real wire") assert.Equal(t, "ea-endpoint", deleted["endpointName"]) @@ -149,7 +152,7 @@ func TestServerless_EndpointAccess_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -173,7 +176,7 @@ func TestServerless_EndpointAccess_DuplicateName(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) } @@ -186,7 +189,7 @@ func TestServerless_ListManagedWorkgroups_AlwaysEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) list, ok := resp["managedWorkgroups"].([]any) require.True(t, ok, "managedWorkgroups must be a correctly-shaped (empty) array, not absent") assert.Empty(t, list) diff --git a/services/redshift/handler_serverless_gaps_test.go b/services/redshift/handler_serverless_gaps_test.go index fe8b4e9af7..b005ec26ba 100644 --- a/services/redshift/handler_serverless_gaps_test.go +++ b/services/redshift/handler_serverless_gaps_test.go @@ -1,7 +1,8 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshift" ) @@ -36,7 +38,7 @@ func TestServerless_UpdateNamespace_DBNameNotMutated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) ns, _ := resp["namespace"].(map[string]any) require.NotNil(t, ns) assert.Equal(t, "original", ns["dbName"], "UpdateNamespace's dbName must be silently ignored, not applied") @@ -64,7 +66,7 @@ func TestServerless_Namespace_AdminUserPassword_NeverEchoed(t *testing.T) { assert.NotContains(t, body, "s3cr3t-Passw0rd") var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) ns, _ := createResp["namespace"].(map[string]any) require.NotNil(t, ns) _, hasPassword := ns["adminUserPassword"] @@ -155,7 +157,7 @@ func TestServerless_ListSnapshots_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var nsResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&nsResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &nsResp, jsonx.V1())) ns, _ := nsResp["namespace"].(map[string]any) require.NotNil(t, ns) nsArn, _ := ns["namespaceArn"].(string) @@ -165,7 +167,7 @@ func TestServerless_ListSnapshots_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var allResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&allResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &allResp, jsonx.V1())) allSnaps, _ := allResp["snapshots"].([]any) require.Len(t, allSnaps, 3, "sanity: three snapshots exist before filtering") @@ -173,7 +175,7 @@ func TestServerless_ListSnapshots_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var filteredResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&filteredResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &filteredResp, jsonx.V1())) filtered, _ := filteredResp["snapshots"].([]any) assert.Len(t, filtered, 2, "namespaceArn filter must narrow to only lsf-ns-a's snapshots") @@ -186,7 +188,7 @@ func TestServerless_ListSnapshots_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var otherAcctResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&otherAcctResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &otherAcctResp, jsonx.V1())) otherAcctSnaps, _ := otherAcctResp["snapshots"].([]any) assert.Empty(t, otherAcctSnaps, "no snapshot in a single-account backend can be owned by another account") @@ -194,7 +196,7 @@ func TestServerless_ListSnapshots_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var ownAcctResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ownAcctResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ownAcctResp, jsonx.V1())) ownAcctSnaps, _ := ownAcctResp["snapshots"].([]any) assert.Len(t, ownAcctSnaps, 3) } @@ -220,7 +222,7 @@ func TestServerless_GetSnapshot_OwnerAccount(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -239,7 +241,7 @@ func TestServerless_ListWorkgroups_OwnerAccount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var ownResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&ownResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &ownResp, jsonx.V1())) own, _ := ownResp["workgroups"].([]any) require.Len(t, own, 2, "sanity: two workgroups exist and match this backend's own account") @@ -247,7 +249,7 @@ func TestServerless_ListWorkgroups_OwnerAccount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var otherResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&otherResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &otherResp, jsonx.V1())) other, _ := otherResp["workgroups"].([]any) assert.Empty(t, other, "ownerAccount filter must narrow to zero for an account this backend never owns") } @@ -275,7 +277,7 @@ func TestServerless_ListUsageLimits_UsageType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var allResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&allResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &allResp, jsonx.V1())) all, _ := allResp["usageLimits"].([]any) require.Len(t, all, 2, "sanity: two usage limits exist before filtering") @@ -283,7 +285,7 @@ func TestServerless_ListUsageLimits_UsageType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var filteredResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&filteredResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &filteredResp, jsonx.V1())) filtered, _ := filteredResp["usageLimits"].([]any) require.Len(t, filtered, 1, "usageType filter must narrow to the single matching limit") got, _ := filtered[0].(map[string]any) diff --git a/services/redshift/handler_serverless_idc_token_test.go b/services/redshift/handler_serverless_idc_token_test.go index 1eed87a897..bd8a95be45 100644 --- a/services/redshift/handler_serverless_idc_token_test.go +++ b/services/redshift/handler_serverless_idc_token_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_GetIdentityCenterAuthToken(t *testing.T) { @@ -28,7 +31,7 @@ func TestServerless_GetIdentityCenterAuthToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) token1, _ := resp["token"].(string) require.NotEmpty(t, token1) @@ -40,7 +43,7 @@ func TestServerless_GetIdentityCenterAuthToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp2 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp2, jsonx.V1())) token2, _ := resp2["token"].(string) require.NotEmpty(t, token2) @@ -86,7 +89,7 @@ func TestServerless_GetIdentityCenterAuthToken_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -113,6 +116,6 @@ func TestServerless_GetIdentityCenterAuthToken_OneUnknownAmongMany(t *testing.T) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } diff --git a/services/redshift/handler_serverless_lakehouse_test.go b/services/redshift/handler_serverless_lakehouse_test.go index 8eb1b09501..734613e0d3 100644 --- a/services/redshift/handler_serverless_lakehouse_test.go +++ b/services/redshift/handler_serverless_lakehouse_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_UpdateLakehouseConfiguration_Errors(t *testing.T) { @@ -42,7 +45,7 @@ func TestServerless_UpdateLakehouseConfiguration_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -68,14 +71,14 @@ func TestServerless_UpdateLakehouseConfiguration_DryRun(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "DryRunException", errResp["__type"]) rec = doServerlessOp(t, h, "GetNamespace", map[string]any{"namespaceName": "lh-dryrun-ns"}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) ns, _ := getResp["namespace"].(map[string]any) require.NotNil(t, ns) assert.Empty(t, ns["catalogArn"], "dry run must not mutate the namespace's catalogArn") @@ -104,7 +107,7 @@ func TestServerless_UpdateLakehouseConfiguration_RegisterAndAssociate(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "lh-ns", resp["namespaceName"]) assert.Contains(t, resp["catalogArn"], "mycatalog") assert.Equal(t, "Registered", resp["lakehouseRegistrationStatus"]) @@ -114,7 +117,7 @@ func TestServerless_UpdateLakehouseConfiguration_RegisterAndAssociate(t *testing require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) ns, _ := getResp["namespace"].(map[string]any) require.NotNil(t, ns) assert.Contains(t, ns["catalogArn"], "mycatalog") @@ -131,7 +134,7 @@ func TestServerless_UpdateLakehouseConfiguration_RegisterAndAssociate(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp2 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp2, jsonx.V1())) assert.Equal(t, "Deregistered", resp2["lakehouseRegistrationStatus"]) assert.Equal(t, "arn:aws:sso::000000000000:application/idc-app-1", resp2["lakehouseIdcApplicationArn"]) @@ -143,6 +146,6 @@ func TestServerless_UpdateLakehouseConfiguration_RegisterAndAssociate(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp3 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp3)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp3, jsonx.V1())) assert.NotContains(t, resp3, "lakehouseIdcApplicationArn") } diff --git a/services/redshift/handler_serverless_recovery.go b/services/redshift/handler_serverless_recovery.go index f60c2f48c4..712ad998c8 100644 --- a/services/redshift/handler_serverless_recovery.go +++ b/services/redshift/handler_serverless_recovery.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *ServerlessHandler) handleGetRecoveryPoint(c *echo.Context, body []byte) error { @@ -12,7 +14,7 @@ func (h *ServerlessHandler) handleGetRecoveryPoint(c *echo.Context, body []byte) RecoveryPointID string `json:"recoveryPointId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -39,7 +41,7 @@ func (h *ServerlessHandler) handleListRecoveryPoints(c *echo.Context, body []byt } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } @@ -69,7 +71,7 @@ func (h *ServerlessHandler) handleRestoreFromRecoveryPoint(c *echo.Context, body WorkgroupName string `json:"workgroupName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } diff --git a/services/redshift/handler_serverless_recovery_test.go b/services/redshift/handler_serverless_recovery_test.go index 27357e2920..a4c2b5cc2a 100644 --- a/services/redshift/handler_serverless_recovery_test.go +++ b/services/redshift/handler_serverless_recovery_test.go @@ -1,13 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshift" ) @@ -31,7 +33,7 @@ func seedNamespaceAndWorkgroup( require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) points, _ := listResp["recoveryPoints"].([]any) require.Len(t, points, 1, "CreateWorkgroup must generate exactly one recovery point") @@ -57,7 +59,7 @@ func TestServerless_RecoveryPoint_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) got, _ := getResp["recoveryPoint"].(map[string]any) require.NotNil(t, got) assert.Equal(t, recoveryPointID, got["recoveryPointId"]) @@ -70,7 +72,7 @@ func TestServerless_RecoveryPoint_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var restoreResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&restoreResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &restoreResp, jsonx.V1())) assert.Equal(t, recoveryPointID, restoreResp["recoveryPointId"]) ns, _ := restoreResp["namespace"].(map[string]any) require.NotNil(t, ns) @@ -129,7 +131,7 @@ func TestServerless_RecoveryPoint_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -154,7 +156,7 @@ func TestServerless_RestoreFromRecoveryPoint_WorkgroupNamespaceMismatch(t *testi require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) points, _ := listResp["recoveryPoints"].([]any) require.Len(t, points, 1) rp, _ := points[0].(map[string]any) @@ -168,6 +170,6 @@ func TestServerless_RestoreFromRecoveryPoint_WorkgroupNamespaceMismatch(t *testi require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) } diff --git a/services/redshift/handler_serverless_resource_policy.go b/services/redshift/handler_serverless_resource_policy.go index cec9c04f2b..18a039986a 100644 --- a/services/redshift/handler_serverless_resource_policy.go +++ b/services/redshift/handler_serverless_resource_policy.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *ServerlessHandler) handleGetResourcePolicySL(c *echo.Context, body []byte) error { @@ -12,7 +14,7 @@ func (h *ServerlessHandler) handleGetResourcePolicySL(c *echo.Context, body []by ResourceArn string `json:"resourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -34,7 +36,7 @@ func (h *ServerlessHandler) handlePutResourcePolicySL(c *echo.Context, body []by Policy string `json:"policy"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -55,7 +57,7 @@ func (h *ServerlessHandler) handleDeleteResourcePolicySL(c *echo.Context, body [ ResourceArn string `json:"resourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } diff --git a/services/redshift/handler_serverless_resource_policy_test.go b/services/redshift/handler_serverless_resource_policy_test.go index 8769de09bf..60f753262f 100644 --- a/services/redshift/handler_serverless_resource_policy_test.go +++ b/services/redshift/handler_serverless_resource_policy_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_ResourcePolicy_CRUD(t *testing.T) { @@ -23,7 +26,7 @@ func TestServerless_ResourcePolicy_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&putResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &putResp, jsonx.V1())) rp, _ := putResp["resourcePolicy"].(map[string]any) require.NotNil(t, rp) assert.Equal(t, resourceArn, rp["resourceArn"]) @@ -33,7 +36,7 @@ func TestServerless_ResourcePolicy_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) rp, _ = getResp["resourcePolicy"].(map[string]any) require.NotNil(t, rp) assert.Equal(t, policy, rp["policy"]) @@ -42,7 +45,7 @@ func TestServerless_ResourcePolicy_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&deleteResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &deleteResp, jsonx.V1())) assert.Empty(t, deleteResp, "DeleteResourcePolicyResponse has zero members on the real wire") rec = doServerlessOp(t, h, "GetResourcePolicy", map[string]any{"resourceArn": resourceArn}) @@ -105,7 +108,7 @@ func TestServerless_ResourcePolicy_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } diff --git a/services/redshift/handler_serverless_restore.go b/services/redshift/handler_serverless_restore.go index 6736681278..efd10066a4 100644 --- a/services/redshift/handler_serverless_restore.go +++ b/services/redshift/handler_serverless_restore.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *ServerlessHandler) handleRestoreFromSnapshot(c *echo.Context, body []byte) error { @@ -19,7 +21,7 @@ func (h *ServerlessHandler) handleRestoreFromSnapshot(c *echo.Context, body []by ManageAdminPassword bool `json:"manageAdminPassword"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -64,7 +66,7 @@ func (h *ServerlessHandler) handleConvertRecoveryPointToSnapshot(c *echo.Context RetentionPeriod int `json:"retentionPeriod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } diff --git a/services/redshift/handler_serverless_restore_test.go b/services/redshift/handler_serverless_restore_test.go index a859a476ca..7b2b95ea90 100644 --- a/services/redshift/handler_serverless_restore_test.go +++ b/services/redshift/handler_serverless_restore_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_RestoreFromSnapshot(t *testing.T) { @@ -26,7 +29,7 @@ func TestServerless_RestoreFromSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "rfs-snap", resp["snapshotName"]) ns, _ := resp["namespace"].(map[string]any) require.NotNil(t, ns) @@ -76,7 +79,7 @@ func TestServerless_RestoreFromSnapshot_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -104,7 +107,7 @@ func TestServerless_RestoreFromSnapshot_WorkgroupNamespaceMismatch(t *testing.T) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) } @@ -123,7 +126,7 @@ func TestServerless_ConvertRecoveryPointToSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) snap, _ := resp["snapshot"].(map[string]any) require.NotNil(t, snap) assert.Equal(t, "crp-snap", snap["snapshotName"]) @@ -167,7 +170,7 @@ func TestServerless_ConvertRecoveryPointToSnapshot_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -191,6 +194,6 @@ func TestServerless_ConvertRecoveryPointToSnapshot_DuplicateName(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) } diff --git a/services/redshift/handler_serverless_snapshot_copy_config.go b/services/redshift/handler_serverless_snapshot_copy_config.go index 60a0c02140..0ed92be44d 100644 --- a/services/redshift/handler_serverless_snapshot_copy_config.go +++ b/services/redshift/handler_serverless_snapshot_copy_config.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *ServerlessHandler) handleCreateSnapshotCopyConfigurationSL(c *echo.Context, body []byte) error { @@ -15,7 +17,7 @@ func (h *ServerlessHandler) handleCreateSnapshotCopyConfigurationSL(c *echo.Cont SnapshotRetentionPeriod int `json:"snapshotRetentionPeriod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -39,7 +41,7 @@ func (h *ServerlessHandler) handleUpdateSnapshotCopyConfigurationSL(c *echo.Cont SnapshotRetentionPeriod int `json:"snapshotRetentionPeriod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -62,7 +64,7 @@ func (h *ServerlessHandler) handleDeleteSnapshotCopyConfigurationSL(c *echo.Cont SnapshotCopyConfigurationID string `json:"snapshotCopyConfigurationId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -89,7 +91,7 @@ func (h *ServerlessHandler) handleListSnapshotCopyConfigurationsSL(c *echo.Conte } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } diff --git a/services/redshift/handler_serverless_snapshot_copy_config_test.go b/services/redshift/handler_serverless_snapshot_copy_config_test.go index 9642aad5c9..f318a6aafb 100644 --- a/services/redshift/handler_serverless_snapshot_copy_config_test.go +++ b/services/redshift/handler_serverless_snapshot_copy_config_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_SnapshotCopyConfiguration_CRUD(t *testing.T) { @@ -23,7 +26,7 @@ func TestServerless_SnapshotCopyConfiguration_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) cfg, _ := createResp["snapshotCopyConfiguration"].(map[string]any) require.NotNil(t, cfg) assert.Equal(t, "scc-ns", cfg["namespaceName"]) @@ -37,7 +40,7 @@ func TestServerless_SnapshotCopyConfiguration_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) list, _ := listResp["snapshotCopyConfigurations"].([]any) require.Len(t, list, 1) @@ -48,7 +51,7 @@ func TestServerless_SnapshotCopyConfiguration_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) cfg, _ = updateResp["snapshotCopyConfiguration"].(map[string]any) require.NotNil(t, cfg) assert.InEpsilon(t, float64(30), cfg["snapshotRetentionPeriod"], 0) @@ -59,14 +62,14 @@ func TestServerless_SnapshotCopyConfiguration_CRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&deleteResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &deleteResp, jsonx.V1())) cfg, _ = deleteResp["snapshotCopyConfiguration"].(map[string]any) require.NotNil(t, cfg, "DeleteSnapshotCopyConfigurationResponse echoes the deleted object on the real wire") assert.Equal(t, id, cfg["snapshotCopyConfigurationId"]) rec = doServerlessOp(t, h, "ListSnapshotCopyConfigurations", map[string]any{"namespaceName": "scc-ns"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) list, _ = listResp["snapshotCopyConfigurations"].([]any) assert.Empty(t, list) } @@ -114,7 +117,7 @@ func TestServerless_SnapshotCopyConfiguration_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } diff --git a/services/redshift/handler_serverless_table_restore.go b/services/redshift/handler_serverless_table_restore.go index a063770ca7..0b42f554f8 100644 --- a/services/redshift/handler_serverless_table_restore.go +++ b/services/redshift/handler_serverless_table_restore.go @@ -1,10 +1,12 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // slTableRestoreStatusWire is the wire shape for ServerlessTableRestoreStatus @@ -73,7 +75,7 @@ func (r slTableRestoreReq) toParams() RestoreTableFromSnapshotParams { func (h *ServerlessHandler) handleRestoreTableFromSnapshot(c *echo.Context, body []byte) error { var req slTableRestoreReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -96,7 +98,7 @@ func (h *ServerlessHandler) handleRestoreTableFromSnapshot(c *echo.Context, body func (h *ServerlessHandler) handleRestoreTableFromRecoveryPoint(c *echo.Context, body []byte) error { var req slTableRestoreReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -121,7 +123,7 @@ func (h *ServerlessHandler) handleGetTableRestoreStatus(c *echo.Context, body [] TableRestoreRequestID string `json:"tableRestoreRequestId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -146,7 +148,7 @@ func (h *ServerlessHandler) handleListTableRestoreStatus(c *echo.Context, body [ } if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } } diff --git a/services/redshift/handler_serverless_table_restore_test.go b/services/redshift/handler_serverless_table_restore_test.go index e9e5fff896..6fcadcb5b8 100644 --- a/services/redshift/handler_serverless_table_restore_test.go +++ b/services/redshift/handler_serverless_table_restore_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_TableRestore_FromSnapshotAndRecoveryPoint(t *testing.T) { @@ -32,7 +35,7 @@ func TestServerless_TableRestore_FromSnapshotAndRecoveryPoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fromSnapResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fromSnapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fromSnapResp, jsonx.V1())) status1, _ := fromSnapResp["tableRestoreStatus"].(map[string]any) require.NotNil(t, status1) assert.Equal(t, "SUCCEEDED", status1["status"]) @@ -53,7 +56,7 @@ func TestServerless_TableRestore_FromSnapshotAndRecoveryPoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var fromRPResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&fromRPResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &fromRPResp, jsonx.V1())) status2, _ := fromRPResp["tableRestoreStatus"].(map[string]any) require.NotNil(t, status2) assert.Equal(t, recoveryPointID, status2["recoveryPointId"]) @@ -65,7 +68,7 @@ func TestServerless_TableRestore_FromSnapshotAndRecoveryPoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) got, _ := getResp["tableRestoreStatus"].(map[string]any) require.NotNil(t, got) assert.Equal(t, requestID1, got["tableRestoreRequestId"]) @@ -74,7 +77,7 @@ func TestServerless_TableRestore_FromSnapshotAndRecoveryPoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) list, _ := listResp["tableRestoreStatuses"].([]any) assert.Len(t, list, 2) } @@ -149,7 +152,7 @@ func TestServerless_TableRestore_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } diff --git a/services/redshift/handler_serverless_tagging.go b/services/redshift/handler_serverless_tagging.go index c8486e2849..f0a9ee7a5f 100644 --- a/services/redshift/handler_serverless_tagging.go +++ b/services/redshift/handler_serverless_tagging.go @@ -1,11 +1,13 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // slTagWire is the wire shape of a single Redshift Serverless tag -- a @@ -56,7 +58,7 @@ func (h *ServerlessHandler) handleTagResource(c *echo.Context, body []byte) erro Tags []slTagWire `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -77,7 +79,7 @@ func (h *ServerlessHandler) handleUntagResource(c *echo.Context, body []byte) er TagKeys []string `json:"tagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } @@ -97,7 +99,7 @@ func (h *ServerlessHandler) handleListTagsForResource(c *echo.Context, body []by ResourceArn string `json:"resourceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return slBadRequest(c, "invalid request body") } diff --git a/services/redshift/handler_serverless_tagging_test.go b/services/redshift/handler_serverless_tagging_test.go index 33564331a0..ea6254f83e 100644 --- a/services/redshift/handler_serverless_tagging_test.go +++ b/services/redshift/handler_serverless_tagging_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestServerless_TagResource_RoundTrip proves creation-time Tags on @@ -28,7 +31,7 @@ func TestServerless_TagResource_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) nsData, _ := createResp["namespace"].(map[string]any) require.NotNil(t, nsData) nsArn, _ := nsData["namespaceArn"].(string) @@ -42,7 +45,7 @@ func TestServerless_TagResource_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) tagList, _ := listResp["tags"].([]any) require.Len(t, tagList, 1) @@ -61,7 +64,7 @@ func TestServerless_TagResource_RoundTrip(t *testing.T) { rec = doServerlessOp(t, h, "ListTagsForResource", map[string]any{"resourceArn": nsArn}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) tagList, _ = listResp["tags"].([]any) assert.Len(t, tagList, 2) @@ -73,7 +76,7 @@ func TestServerless_TagResource_RoundTrip(t *testing.T) { rec = doServerlessOp(t, h, "ListTagsForResource", map[string]any{"resourceArn": nsArn}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) tagList, _ = listResp["tags"].([]any) require.Len(t, tagList, 1) @@ -99,7 +102,7 @@ func TestServerless_TagResource_WorkgroupAndSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var wgResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&wgResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &wgResp, jsonx.V1())) wgData, _ := wgResp["workgroup"].(map[string]any) wgArn, _ := wgData["workgroupArn"].(string) require.NotEmpty(t, wgArn) @@ -108,7 +111,7 @@ func TestServerless_TagResource_WorkgroupAndSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) tagList, _ := listResp["tags"].([]any) require.Len(t, tagList, 1) @@ -120,14 +123,14 @@ func TestServerless_TagResource_WorkgroupAndSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var snapResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&snapResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &snapResp, jsonx.V1())) snapData, _ := snapResp["snapshot"].(map[string]any) snapArn, _ := snapData["snapshotArn"].(string) require.NotEmpty(t, snapArn) rec = doServerlessOp(t, h, "ListTagsForResource", map[string]any{"resourceArn": snapArn}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) tagList, _ = listResp["tags"].([]any) assert.Len(t, tagList, 1) } @@ -174,7 +177,7 @@ func TestServerless_TagResource_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) }) } @@ -190,7 +193,7 @@ func TestServerless_TagResource_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) }) @@ -205,7 +208,7 @@ func TestServerless_TagResource_Errors(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) }) } diff --git a/services/redshift/handler_serverless_test.go b/services/redshift/handler_serverless_test.go index cb961f6311..2d26eaf7af 100644 --- a/services/redshift/handler_serverless_test.go +++ b/services/redshift/handler_serverless_test.go @@ -2,7 +2,8 @@ package redshift_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshift" ) @@ -34,7 +36,7 @@ func doServerlessOp( var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) } else { @@ -108,7 +110,7 @@ func TestServerless_NamespaceCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) nsData, _ := createResp["namespace"].(map[string]any) require.NotNil(t, nsData) assert.Equal(t, "arn:aws:iam::000000000000:role/default", nsData["defaultIamRoleArn"]) @@ -132,7 +134,7 @@ func TestServerless_NamespaceCRUD(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -155,7 +157,7 @@ func TestServerless_DeleteNamespace_WithFinalSnapshot(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) snap, _ := resp["snapshot"].(map[string]any) require.NotNil(t, snap) assert.InEpsilon(t, float64(14), snap["snapshotRetentionPeriod"], 0) @@ -182,7 +184,7 @@ func TestServerless_WorkgroupCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) wgData, _ := createResp["workgroup"].(map[string]any) require.NotNil(t, wgData) assert.InEpsilon(t, float64(64), wgData["maxCapacity"], 0) @@ -200,7 +202,7 @@ func TestServerless_WorkgroupCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var credResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&credResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &credResp, jsonx.V1())) // Expiration is wire-encoded as a JSON number (epoch seconds), not an // RFC3339 string -- see the wire-shape audit note in handler_serverless.go. _, isNumber := credResp["expiration"].(float64) @@ -247,7 +249,7 @@ func TestServerless_SnapshotCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) updated, _ := updateResp["snapshot"].(map[string]any) require.NotNil(t, updated) assert.InEpsilon(t, float64(30), updated["snapshotRetentionPeriod"], 0) @@ -256,7 +258,7 @@ func TestServerless_SnapshotCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) got, _ := getResp["snapshot"].(map[string]any) require.NotNil(t, got) assert.InEpsilon(t, float64(30), got["snapshotRetentionPeriod"], 0, @@ -285,7 +287,7 @@ func TestServerless_UsageLimitsCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) ulData, _ := createResp["usageLimit"].(map[string]any) ulID := "" @@ -333,7 +335,7 @@ func TestServerless_ScheduledActionCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&createResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &createResp, jsonx.V1())) saData, _ := createResp["scheduledAction"].(map[string]any) require.NotNil(t, saData) assert.Equal(t, "DISABLED", saData["state"], "state (not status) is the real wire key") @@ -362,7 +364,7 @@ func TestServerless_ScheduledActionCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&updateResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &updateResp, jsonx.V1())) updated, _ := updateResp["scheduledAction"].(map[string]any) require.NotNil(t, updated) assert.Equal(t, "ACTIVE", updated["state"]) diff --git a/services/redshift/handler_serverless_tracks_test.go b/services/redshift/handler_serverless_tracks_test.go index bde435ea76..70a0603c1a 100644 --- a/services/redshift/handler_serverless_tracks_test.go +++ b/services/redshift/handler_serverless_tracks_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_GetTrack(t *testing.T) { @@ -55,7 +58,7 @@ func TestServerless_GetTrack(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) if tt.wantFound { track, _ := resp["track"].(map[string]any) @@ -80,7 +83,7 @@ func TestServerless_ListTracks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) tracks, _ := resp["tracks"].([]any) require.Len(t, tracks, 2, "real Redshift Serverless exposes exactly two maintenance tracks") @@ -105,7 +108,7 @@ func TestServerless_ListTracks_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&page1)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &page1, jsonx.V1())) tracks1, _ := page1["tracks"].([]any) require.Len(t, tracks1, 1) @@ -117,7 +120,7 @@ func TestServerless_ListTracks_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&page2)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &page2, jsonx.V1())) tracks2, _ := page2["tracks"].([]any) require.Len(t, tracks2, 1) diff --git a/services/redshift/handler_serverless_update_snapshot_test.go b/services/redshift/handler_serverless_update_snapshot_test.go index 1d9f741653..12ec5f3e04 100644 --- a/services/redshift/handler_serverless_update_snapshot_test.go +++ b/services/redshift/handler_serverless_update_snapshot_test.go @@ -1,12 +1,15 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestServerless_UpdateSnapshot_Errors(t *testing.T) { @@ -42,7 +45,7 @@ func TestServerless_UpdateSnapshot_Errors(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -70,7 +73,7 @@ func TestServerless_UpdateSnapshot_OmittedRetentionPeriodUnchanged(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) snap, _ := resp["snapshot"].(map[string]any) require.NotNil(t, snap) assert.InEpsilon(t, float64(14), snap["snapshotRetentionPeriod"], 0) diff --git a/services/redshift/multi_region_test.go b/services/redshift/multi_region_test.go index ff5d188dd0..999acf08be 100644 --- a/services/redshift/multi_region_test.go +++ b/services/redshift/multi_region_test.go @@ -1,7 +1,8 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshift" ) @@ -129,9 +131,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/redshift/persistence.go b/services/redshift/persistence.go index 3585be85c0..3703325374 100644 --- a/services/redshift/persistence.go +++ b/services/redshift/persistence.go @@ -2,9 +2,11 @@ package redshift import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -34,7 +36,7 @@ const redshiftSnapshotVersion = 1 // each is keyed externally by clusterID rather than by a field on the value // itself), plus backend-level scalar configuration. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ActiveResizes map[string]*ResizeProgress `json:"activeResizes"` SnapshotCopyConfigs map[string]*SnapshotCopyConfig `json:"snapshotCopyConfigs"` LoggingStatuses map[string]*LoggingStatus `json:"loggingStatuses"` @@ -84,7 +86,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "redshift: failed to marshal snapshot", "error", err) diff --git a/services/redshift/persistence_test.go b/services/redshift/persistence_test.go index e2c497e10a..a43db5f883 100644 --- a/services/redshift/persistence_test.go +++ b/services/redshift/persistence_test.go @@ -1,7 +1,8 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshift" ) @@ -334,8 +336,8 @@ func TestInMemoryBackend_FullStateRoundTrip(t *testing.T) { ScheduledActionName: "rt-slscheduledaction", NamespaceName: "rt-namespace", RoleArn: "arn:aws:iam::000000000000:role/scheduler", - Schedule: json.RawMessage(`{"at":1893456000}`), - TargetAction: json.RawMessage( + Schedule: jsontext.Value(`{"at":1893456000}`), + TargetAction: jsontext.Value( `{"createSnapshot":{"namespaceName":"rt-namespace","snapshotName":"rt-slsnapshot2"}}`, ), StartTime: time.Now(), @@ -661,8 +663,8 @@ func TestPersistence_ScheduledActionWindowSurvivesRestore(t *testing.T) { ScheduledActionName: "sa-rt-action", NamespaceName: "sa-rt-ns", RoleArn: "arn:aws:iam::000000000000:role/scheduler", - Schedule: json.RawMessage(`{"cron":"0 10 ? * MON *"}`), - TargetAction: json.RawMessage( + Schedule: jsontext.Value(`{"cron":"0 10 ? * MON *"}`), + TargetAction: jsontext.Value( `{"createSnapshot":{"namespaceName":"sa-rt-ns","snapshotName":"sa-rt-snap"}}`, ), StartTime: tt.startTime, @@ -686,7 +688,7 @@ func TestPersistence_ScheduledActionWindowSurvivesRestore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) saData, _ := resp["scheduledAction"].(map[string]any) require.NotNil(t, saData) diff --git a/services/redshift/schedule.go b/services/redshift/schedule.go index f1867e9c02..ce7b195268 100644 --- a/services/redshift/schedule.go +++ b/services/redshift/schedule.go @@ -1,12 +1,13 @@ package redshift import ( - "encoding/json" + "encoding/json/v2" "strings" "time" "github.com/blackbirdworks/gopherstack/pkgs/awscron" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxNextInvocations bounds how many upcoming ScheduledActionTime entries this @@ -133,7 +134,7 @@ func slNextInvocations(sa *ServerlessScheduledAction, now time.Time) []float64 { Cron string `json:"cron"` } - if json.Unmarshal(sa.Schedule, &sched) != nil { + if json.Unmarshal(sa.Schedule, &sched, jsonx.V1()) != nil { return nil } diff --git a/services/redshift/serverless.go b/services/redshift/serverless.go index 63ff14b396..ffa73f7439 100644 --- a/services/redshift/serverless.go +++ b/services/redshift/serverless.go @@ -3,7 +3,7 @@ package redshift import ( "crypto/rand" "encoding/hex" - "encoding/json" + "encoding/json/jsontext" "errors" "strconv" "time" @@ -253,16 +253,16 @@ type ServerlessUsageLimit struct { // unlike opensearch's DataSourceAttachment) and the registry marshals the live // struct with its own tags (gopherstack-n746d). type ServerlessScheduledAction struct { - StartTime time.Time `json:"startTime,omitzero"` - EndTime time.Time `json:"endTime,omitzero"` - NamespaceName string `json:"namespaceName,omitempty"` - RoleArn string `json:"roleArn,omitempty"` - ScheduledActionDescription string `json:"scheduledActionDescription,omitempty"` - ScheduledActionName string `json:"scheduledActionName"` - ScheduledActionUUID string `json:"scheduledActionUuid,omitempty"` - State string `json:"state"` - Schedule json.RawMessage `json:"schedule,omitempty"` - TargetAction json.RawMessage `json:"targetAction,omitempty"` + StartTime time.Time `json:"startTime,omitzero"` + EndTime time.Time `json:"endTime,omitzero"` + NamespaceName string `json:"namespaceName,omitempty"` + RoleArn string `json:"roleArn,omitempty"` + ScheduledActionDescription string `json:"scheduledActionDescription,omitempty"` + ScheduledActionName string `json:"scheduledActionName"` + ScheduledActionUUID string `json:"scheduledActionUuid,omitempty"` + State string `json:"state"` + Schedule jsontext.Value `json:"schedule,omitempty"` + TargetAction jsontext.Value `json:"targetAction,omitempty"` } // ServerlessCustomDomainAssociation represents a Redshift Serverless custom diff --git a/services/redshift/serverless_index_test.go b/services/redshift/serverless_index_test.go index b5325daeac..2ca8122b65 100644 --- a/services/redshift/serverless_index_test.go +++ b/services/redshift/serverless_index_test.go @@ -1,7 +1,7 @@ package redshift_test import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" "testing" @@ -254,8 +254,8 @@ func TestServerlessScheduledActionIndex_OrderedWithFilter(t *testing.T) { ScheduledActionName: s.name, NamespaceName: s.ns, RoleArn: "arn:aws:iam::000000000000:role/scheduler", - Schedule: json.RawMessage(`{"cron":"0 10 ? * MON *"}`), - TargetAction: json.RawMessage( + Schedule: jsontext.Value(`{"cron":"0 10 ? * MON *"}`), + TargetAction: jsontext.Value( `{"createSnapshot":{"namespaceName":"` + s.ns + `","snapshotName":"snap"}}`, ), StartTime: start, diff --git a/services/redshift/serverless_scheduled_actions.go b/services/redshift/serverless_scheduled_actions.go index 31b0134829..a7a7178a4c 100644 --- a/services/redshift/serverless_scheduled_actions.go +++ b/services/redshift/serverless_scheduled_actions.go @@ -1,7 +1,7 @@ package redshift import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "strconv" "time" @@ -23,8 +23,8 @@ type CreateScheduledActionParams struct { NamespaceName string RoleArn string ScheduledActionDescription string - Schedule json.RawMessage - TargetAction json.RawMessage + Schedule jsontext.Value + TargetAction jsontext.Value } // UpdateScheduledActionParams holds the mutable fields accepted by @@ -35,8 +35,8 @@ type UpdateScheduledActionParams struct { ScheduledActionDescription *string Enabled *bool RoleArn string - Schedule json.RawMessage - TargetAction json.RawMessage + Schedule jsontext.Value + TargetAction jsontext.Value } // CreateServerlessScheduledAction creates a serverless scheduled action. @@ -220,11 +220,11 @@ func (b *InMemoryBackend) DeleteServerlessScheduledAction( func cloneServerlessScheduledAction(sa *ServerlessScheduledAction) *ServerlessScheduledAction { cp := *sa if sa.Schedule != nil { - cp.Schedule = append(json.RawMessage(nil), sa.Schedule...) + cp.Schedule = append(jsontext.Value(nil), sa.Schedule...) } if sa.TargetAction != nil { - cp.TargetAction = append(json.RawMessage(nil), sa.TargetAction...) + cp.TargetAction = append(jsontext.Value(nil), sa.TargetAction...) } return &cp diff --git a/services/redshiftdata/engine.go b/services/redshiftdata/engine.go index 9d415b17ac..1df1644fe6 100644 --- a/services/redshiftdata/engine.go +++ b/services/redshiftdata/engine.go @@ -2,11 +2,13 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // EngineLogin identifies the master login of a cluster's live SQL engine. @@ -196,7 +198,7 @@ func resultSize(rs *ResultSet) int64 { return 0 } - raw, err := json.Marshal(rs.Rows) + raw, err := json.Marshal(rs.Rows, jsonx.V1()) if err != nil { return 0 } diff --git a/services/redshiftdata/engine_pgx.go b/services/redshiftdata/engine_pgx.go index b170f4ff37..7db0d3069c 100644 --- a/services/redshiftdata/engine_pgx.go +++ b/services/redshiftdata/engine_pgx.go @@ -4,7 +4,7 @@ import ( "context" "database/sql" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "math" @@ -17,6 +17,8 @@ import ( "github.com/jackc/pgx/v5/pgconn" _ "github.com/jackc/pgx/v5/stdlib" // registers the "pgx" database/sql driver + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -314,7 +316,7 @@ func bytesField(dbType string, b []byte) Field { func compositeString(v any) string { switch reflect.ValueOf(v).Kind() { case reflect.Map, reflect.Slice, reflect.Array, reflect.Struct: - if raw, err := json.Marshal(v); err == nil { + if raw, err := json.Marshal(v, jsonx.V1()); err == nil { return string(raw) } default: diff --git a/services/redshiftdata/engine_result.go b/services/redshiftdata/engine_result.go index 19b066b898..7a282ac192 100644 --- a/services/redshiftdata/engine_result.go +++ b/services/redshiftdata/engine_result.go @@ -4,9 +4,11 @@ import ( "bytes" "context" "encoding/csv" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -45,7 +47,7 @@ func (h *Handler) engineStatementResult( keyColumnMetadata: rs.Columns, keyTotalNumRows: int64(len(rs.Rows)), keyNextToken: "", - }) + }, jsonx.V1()) } func renderCSVResult(rs *ResultSet) ([]byte, error) { @@ -76,7 +78,7 @@ func renderCSVResult(rs *ResultSet) ([]byte, error) { keyTotalNumRows: int64(len(rs.Rows)), keyResultFormat: resultFormatCSV, keyNextToken: "", - }) + }, jsonx.V1()) } func csvCell(f Field) string { diff --git a/services/redshiftdata/handler.go b/services/redshiftdata/handler.go index d20a4f3750..025ee8d4b5 100644 --- a/services/redshiftdata/handler.go +++ b/services/redshiftdata/handler.go @@ -2,7 +2,7 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -178,7 +179,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -262,7 +263,7 @@ func writeInternalServerError(c *echo.Context) error { payload, err := json.Marshal(map[string]string{ keyTypeField: "InternalServerException", keyMessageField: "internal server error", - }) + }, jsonx.V1()) if err != nil { return err } @@ -271,15 +272,12 @@ func writeInternalServerError(c *echo.Context) error { } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrNotFound): payload, _ := json.Marshal(map[string]string{ keyTypeField: "ResourceNotFoundException", keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrTerminalState), @@ -288,7 +286,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: errValidationException, keyMessageField: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, errUnknownAction): @@ -297,7 +295,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { keyMessageField: err.Error(), }) case errors.Is(err, errInvalidRequest), errors.Is(err, errMissingID), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyTypeField: errValidationException, keyMessageField: err.Error(), diff --git a/services/redshiftdata/handler_databases.go b/services/redshiftdata/handler_databases.go index 31bcb0ffce..38b254465e 100644 --- a/services/redshiftdata/handler_databases.go +++ b/services/redshiftdata/handler_databases.go @@ -2,8 +2,10 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListDatabases(ctx context.Context, body []byte) ([]byte, error) { @@ -17,7 +19,7 @@ func (h *Handler) handleListDatabases(ctx context.Context, body []byte) ([]byte, MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -40,7 +42,7 @@ func (h *Handler) handleListDatabases(ctx context.Context, body []byte) ([]byte, resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleListSchemas(ctx context.Context, body []byte) ([]byte, error) { @@ -62,7 +64,7 @@ func (h *Handler) handleListSchemas(ctx context.Context, body []byte) ([]byte, e MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -90,7 +92,7 @@ func (h *Handler) handleListSchemas(ctx context.Context, body []byte) ([]byte, e resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // paginateStrings applies cursor-based pagination to a sorted string slice. diff --git a/services/redshiftdata/handler_databases_test.go b/services/redshiftdata/handler_databases_test.go index f025636bf1..c7f35e6ad6 100644 --- a/services/redshiftdata/handler_databases_test.go +++ b/services/redshiftdata/handler_databases_test.go @@ -1,12 +1,14 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListDatabases(t *testing.T) { @@ -17,7 +19,7 @@ func TestHandler_ListDatabases(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Databases"]) } @@ -38,7 +40,7 @@ func TestHandler_ListSchemas(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Schemas"]) } @@ -59,7 +61,7 @@ func TestHandler_ListDatabases_ReturnsNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dbs, ok := resp["Databases"].([]any) require.True(t, ok) @@ -74,7 +76,7 @@ func TestHandler_ListDatabases_NextTokenOmittedWhenComplete(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["NextToken"] assert.False(t, ok, "NextToken should be omitted when the demo database list fits on one page") @@ -88,7 +90,7 @@ func TestHandler_ListDatabases_MaxResults1_PaginatesWithToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dbs, ok := resp["Databases"].([]any) require.True(t, ok) @@ -117,7 +119,7 @@ func TestHandler_ListDatabases_NextToken_ResumesFromCursor(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) token, _ := page1["NextToken"].(string) require.NotEmpty(t, token) @@ -127,7 +129,7 @@ func TestHandler_ListDatabases_NextToken_ResumesFromCursor(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) dbs2, _ := page2["Databases"].([]any) dbs1, _ := page1["Databases"].([]any) @@ -143,7 +145,7 @@ func TestHandler_ListSchemas_ReturnsNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas, ok := resp["Schemas"].([]any) require.True(t, ok) @@ -175,7 +177,7 @@ func TestHandler_ListSchemas_SchemaPattern_WildcardMatchesAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas, ok := resp["Schemas"].([]any) require.True(t, ok) @@ -195,7 +197,7 @@ func TestHandler_ListSchemas_SchemaPattern_NoMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas, _ := resp["Schemas"].([]any) assert.Empty(t, schemas, "non-matching pattern should return empty schemas") @@ -237,7 +239,7 @@ func TestHandler_ListSchemas_SQLLike_UnderscoreWildcard(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas, _ := resp["Schemas"].([]any) if tt.want { @@ -262,7 +264,7 @@ func TestListDatabases_ReturnsDemoData(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dbs, ok := resp["Databases"].([]any) require.True(t, ok) @@ -283,7 +285,7 @@ func TestListSchemas_ReturnsDemoData(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas, ok := resp["Schemas"].([]any) require.True(t, ok) @@ -303,7 +305,7 @@ func TestListDatabases_HasNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) token, ok := resp["NextToken"] assert.True(t, ok, "NextToken should be present when more pages remain") diff --git a/services/redshiftdata/handler_sessions.go b/services/redshiftdata/handler_sessions.go index 2529ca95d9..fa48d11e72 100644 --- a/services/redshiftdata/handler_sessions.go +++ b/services/redshiftdata/handler_sessions.go @@ -2,8 +2,10 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleListSessions handles ListSessions. Field names verified against @@ -22,7 +24,7 @@ func (h *Handler) handleListSessions(ctx context.Context, body []byte) ([]byte, MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -59,7 +61,7 @@ func (h *Handler) handleListSessions(ctx context.Context, body []byte) ([]byte, resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // sessionToListItem converts a derived SessionData to the SessionData wire shape diff --git a/services/redshiftdata/handler_statements.go b/services/redshiftdata/handler_statements.go index 2af3f445f5..13a793e35b 100644 --- a/services/redshiftdata/handler_statements.go +++ b/services/redshiftdata/handler_statements.go @@ -2,11 +2,13 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // handleExecuteStatement: a positive SessionKeepAliveSeconds without SessionId mints a session @@ -29,7 +31,7 @@ func (h *Handler) handleExecuteStatement(ctx context.Context, body []byte) ([]by WithEvent bool `json:"WithEvent"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -50,7 +52,7 @@ func (h *Handler) handleExecuteStatement(ctx context.Context, body []byte) ([]by if id, ok := h.lookupIdempotentStatement(tokenKey); ok { if stmt, descErr := h.Backend.DescribeStatement(ctx, id); descErr == nil { - return json.Marshal(statementCreateResponse(stmt)) + return json.Marshal(statementCreateResponse(stmt), jsonx.V1()) } } @@ -78,7 +80,7 @@ func (h *Handler) handleExecuteStatement(ctx context.Context, body []byte) ([]by } } - return json.Marshal(statementCreateResponse(stmt)) + return json.Marshal(statementCreateResponse(stmt), jsonx.V1()) } const maxSessionKeepAliveSeconds = 24 * 60 * 60 @@ -117,7 +119,7 @@ func (h *Handler) handleBatchExecuteStatement(ctx context.Context, body []byte) WithEvent bool `json:"WithEvent"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -138,7 +140,7 @@ func (h *Handler) handleBatchExecuteStatement(ctx context.Context, body []byte) if id, ok := h.lookupIdempotentStatement(tokenKey); ok { if stmt, descErr := h.Backend.DescribeStatement(ctx, id); descErr == nil { - return json.Marshal(statementCreateResponse(stmt)) + return json.Marshal(statementCreateResponse(stmt), jsonx.V1()) } } @@ -166,7 +168,7 @@ func (h *Handler) handleBatchExecuteStatement(ctx context.Context, body []byte) } } - return json.Marshal(statementCreateResponse(stmt)) + return json.Marshal(statementCreateResponse(stmt), jsonx.V1()) } // statementCreateResponse builds the shared ExecuteStatementOutput/ @@ -205,7 +207,7 @@ func (h *Handler) handleDescribeStatement(ctx context.Context, body []byte) ([]b WaitTimeSeconds int32 `json:"WaitTimeSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -222,7 +224,7 @@ func (h *Handler) handleDescribeStatement(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(statementToDescribeResponse(stmt)) + return json.Marshal(statementToDescribeResponse(stmt), jsonx.V1()) } func (h *Handler) handleGetStatementResult(ctx context.Context, body []byte) ([]byte, error) { @@ -232,7 +234,7 @@ func (h *Handler) handleGetStatementResult(ctx context.Context, body []byte) ([] WaitTimeSeconds int32 `json:"WaitTimeSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -282,7 +284,7 @@ func (h *Handler) handleGetStatementResult(ctx context.Context, body []byte) ([] }, keyTotalNumRows: int64(1), keyNextToken: "", - }) + }, jsonx.V1()) } func (h *Handler) handleGetStatementResultV2(ctx context.Context, body []byte) ([]byte, error) { @@ -292,7 +294,7 @@ func (h *Handler) handleGetStatementResultV2(ctx context.Context, body []byte) ( WaitTimeSeconds int32 `json:"WaitTimeSeconds"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -348,7 +350,7 @@ func (h *Handler) handleGetStatementResultV2(ctx context.Context, body []byte) ( keyTotalNumRows: int64(1), keyResultFormat: resultFormatCSV, keyNextToken: "", - }) + }, jsonx.V1()) } func (h *Handler) handleListStatements(ctx context.Context, body []byte) ([]byte, error) { @@ -363,7 +365,7 @@ func (h *Handler) handleListStatements(ctx context.Context, body []byte) ([]byte MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -410,7 +412,7 @@ func (h *Handler) handleListStatements(ctx context.Context, body []byte) ([]byte resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleCancelStatement(ctx context.Context, body []byte) ([]byte, error) { @@ -418,7 +420,7 @@ func (h *Handler) handleCancelStatement(ctx context.Context, body []byte) ([]byt ID string `json:"Id"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -432,7 +434,7 @@ func (h *Handler) handleCancelStatement(ctx context.Context, body []byte) ([]byt return json.Marshal(map[string]any{ keyStatusField: true, - }) + }, jsonx.V1()) } // epochSeconds converts a [time.Time] to Unix epoch seconds as float64, diff --git a/services/redshiftdata/handler_statements_lifecycle_test.go b/services/redshiftdata/handler_statements_lifecycle_test.go index 9f604016dc..123834f347 100644 --- a/services/redshiftdata/handler_statements_lifecycle_test.go +++ b/services/redshiftdata/handler_statements_lifecycle_test.go @@ -2,7 +2,7 @@ package redshiftdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -27,7 +28,7 @@ func TestCancelStatement_SuccessStatusBoolean(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) status, ok := resp["Status"].(bool) require.True(t, ok, "Status should be a boolean") @@ -50,7 +51,7 @@ func TestBatchExecuteStatement_QueryStringIsFirstSQL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -58,7 +59,7 @@ func TestBatchExecuteStatement_QueryStringIsFirstSQL(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SELECT 1", resp["QueryString"], "QueryString should equal the first SQL") } @@ -78,7 +79,7 @@ func TestDescribeStatement_ResultSizeAndRows(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -86,7 +87,7 @@ func TestDescribeStatement_ResultSizeAndRows(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["ResultRows"], "ResultRows should be present") assert.NotNil(t, resp["ResultSize"], "ResultSize should be present") @@ -109,7 +110,7 @@ func TestDescribeStatement_SubStatements(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -117,7 +118,7 @@ func TestDescribeStatement_SubStatements(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) subs, ok := resp["SubStatements"].([]any) require.True(t, ok, "SubStatements should be present") @@ -143,7 +144,7 @@ func TestGetStatementResult_ReturnsDemoRow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -151,7 +152,7 @@ func TestGetStatementResult_ReturnsDemoRow(t *testing.T) { require.Equal(t, http.StatusOK, resultRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(resultRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(resultRec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["Records"].([]any) require.True(t, ok, "Records should be a slice") @@ -191,7 +192,7 @@ func TestListStatements_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) assert.Len(t, stmts, testMaxResultsLimit, "should be truncated to MaxResults") @@ -212,7 +213,7 @@ func TestListStatements_MaxResultsTooLarge(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -239,7 +240,7 @@ func TestWithEvent_AcceptedButNotEchoed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotContains(t, createResp, "WithEvent") id := createResp["Id"].(string) @@ -248,7 +249,7 @@ func TestWithEvent_AcceptedButNotEchoed(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "WithEvent") } @@ -270,7 +271,7 @@ func TestListStatements_StatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) require.Len(t, stmts, 1) @@ -373,7 +374,7 @@ func TestListStatements_WorkgroupFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) require.Len(t, stmts, 1, "should only return statements for wg-a") @@ -396,7 +397,7 @@ func TestGetStatementResultV2_DemoRow(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -404,7 +405,7 @@ func TestGetStatementResultV2_DemoRow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["Records"].([]any) require.True(t, ok, "Records should be present") @@ -427,7 +428,7 @@ func TestCancelStatement_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -442,7 +443,7 @@ func TestGetStatementResult_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -460,7 +461,7 @@ func TestBatchExecuteStatement_EmptySqls(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -475,7 +476,7 @@ func TestDescribeStatement_MissingID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -494,7 +495,7 @@ func TestDescribeStatement_RedshiftQueryId(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -502,7 +503,7 @@ func TestDescribeStatement_RedshiftQueryId(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["RedshiftQueryId"] assert.True(t, ok, "RedshiftQueryId should be present in DescribeStatement response") @@ -547,7 +548,7 @@ func TestGetStatementResult_HasNextToken(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -555,7 +556,7 @@ func TestGetStatementResult_HasNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["NextToken"] assert.True(t, ok, "NextToken field should always be present in GetStatementResult response") diff --git a/services/redshiftdata/handler_statements_semantics_test.go b/services/redshiftdata/handler_statements_semantics_test.go index 7b7fe90ee0..88b5c09225 100644 --- a/services/redshiftdata/handler_statements_semantics_test.go +++ b/services/redshiftdata/handler_statements_semantics_test.go @@ -1,13 +1,15 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -63,14 +65,14 @@ func TestResultFormatControlsResultAPI(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp, jsonx.V1())) rec = doRequest(t, h, tt.getOperation, map[string]any{"Id": execResp["Id"]}) assert.Equal(t, tt.wantStatus, rec.Code) if tt.wantResultFmt != "" { var resultResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resultResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resultResp, jsonx.V1())) assert.Equal(t, tt.wantResultFmt, resultResp["ResultFormat"]) } }) @@ -109,13 +111,13 @@ func TestResultFormatMetadataAndValidation(t *testing.T) { } var execResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp, jsonx.V1())) rec = doRequest(t, h, "DescribeStatement", map[string]any{"Id": execResp["Id"]}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) wantFormat := tt.format if wantFormat == "" { wantFormat = "JSON" @@ -159,7 +161,7 @@ func TestListStatementsFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Statements"], tt.wantCount) }) } @@ -181,7 +183,7 @@ func TestListStatementsNextToken(t *testing.T) { require.Equal(t, http.StatusOK, first.Code) var firstResp map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &firstResp, jsonx.V1())) token, ok := firstResp["NextToken"].(string) require.True(t, ok) @@ -252,7 +254,7 @@ func TestHasResultSet_BySQL(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -260,7 +262,7 @@ func TestHasResultSet_BySQL(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantResultSet, descResp["HasResultSet"], "SQL %q: HasResultSet mismatch", tt.sql) @@ -298,7 +300,7 @@ func TestGetStatementResult_RequiresResultSet(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -308,7 +310,7 @@ func TestGetStatementResult_RequiresResultSet(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } }) @@ -373,7 +375,7 @@ func TestParameters_AcceptedAndStored(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"], "Id should be returned") // DescribeStatementOutput.QueryParameters must round-trip whatever @@ -385,7 +387,7 @@ func TestParameters_AcceptedAndStored(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) if len(tt.params) == 0 { assert.Nil(t, descResp["QueryParameters"], @@ -430,13 +432,13 @@ func TestBatchExecuteStatement_ParametersAcceptedAndStored(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) descRec := doRequest(t, h, "DescribeStatement", map[string]any{"Id": createResp["Id"]}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) gotParams, ok := descResp["QueryParameters"].([]any) require.True(t, ok, "QueryParameters should be a list") @@ -470,21 +472,21 @@ func TestSessionID_EchoedByExecuteStatement(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp, jsonx.V1())) assert.Equal(t, "sess-abc123", execResp["SessionId"]) descRec := doRequest(t, h, "DescribeStatement", map[string]any{"Id": execResp["Id"]}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "sess-abc123", descResp["SessionId"]) listRec := doRequest(t, h, "ListStatements", map[string]any{"Status": "ALL"}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) stmts, ok := listResp["Statements"].([]any) require.True(t, ok) @@ -505,14 +507,14 @@ func TestSessionID_EchoedByExecuteStatement(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp, jsonx.V1())) assert.Nil(t, execResp["SessionId"], "SessionId should be omitted, not empty-string, when not supplied") descRec := doRequest(t, h, "DescribeStatement", map[string]any{"Id": execResp["Id"]}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Nil(t, descResp["SessionId"]) }) } @@ -533,14 +535,14 @@ func TestSessionID_EchoedByBatchExecuteStatement(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &execResp, jsonx.V1())) assert.Equal(t, "sess-batch-1", execResp["SessionId"]) descRec := doRequest(t, h, "DescribeStatement", map[string]any{"Id": execResp["Id"]}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "sess-batch-1", descResp["SessionId"]) } @@ -574,7 +576,7 @@ func TestExecuteStatement_CaseInsensitiveSQL(t *testing.T) { require.Equal(t, http.StatusOK, execRec.Code) var execResp map[string]any - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) id := execResp["Id"].(string) @@ -582,7 +584,7 @@ func TestExecuteStatement_CaseInsensitiveSQL(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, tt.wantResultSet, descResp["HasResultSet"], "SQL %q: HasResultSet mismatch", tt.sql) @@ -619,9 +621,9 @@ func TestListStatements_NoFabricatedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Statements []map[string]json.RawMessage `json:"Statements"` + Statements []map[string]jsontext.Value `json:"Statements"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.Statements) assert.NotContains(t, resp.Statements[0], field) }) diff --git a/services/redshiftdata/handler_statements_test.go b/services/redshiftdata/handler_statements_test.go index fa38c42830..67422bd7ff 100644 --- a/services/redshiftdata/handler_statements_test.go +++ b/services/redshiftdata/handler_statements_test.go @@ -2,13 +2,14 @@ package redshiftdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -64,7 +65,7 @@ func TestHandler_ExecuteStatement(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) } }) @@ -107,7 +108,7 @@ func TestHandler_BatchExecuteStatement(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["Id"]) } }) @@ -134,7 +135,7 @@ func TestHandler_DescribeStatement(t *testing.T) { "Database": "testdb", }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Id"].(string) }, @@ -171,7 +172,7 @@ func TestHandler_DescribeStatement(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantStatus2 != "" { assert.Equal(t, tt.wantStatus2, resp["Status"]) @@ -201,7 +202,7 @@ func TestHandler_GetStatementResult(t *testing.T) { "Database": "testdb", }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Id"].(string) }, @@ -225,7 +226,7 @@ func TestHandler_GetStatementResult(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Records"]) } }) @@ -252,7 +253,7 @@ func TestHandler_GetStatementResultV2(t *testing.T) { "ResultFormat": "CSV", }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Id"].(string) }, @@ -291,7 +292,7 @@ func TestHandler_GetStatementResultV2(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.checkRecords { assert.NotNil(t, resp["Records"]) @@ -349,7 +350,7 @@ func TestHandler_ListStatements(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["Statements"].([]any) require.True(t, ok) @@ -399,7 +400,7 @@ func TestHandler_CancelStatement(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) }) } @@ -419,7 +420,7 @@ func TestHandler_CancelStatement_AlreadyFinished(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id := resp["Id"].(string) @@ -428,7 +429,7 @@ func TestHandler_CancelStatement_AlreadyFinished(t *testing.T) { assert.Equal(t, http.StatusBadRequest, cancelRec.Code) var cancelResp map[string]any - require.NoError(t, json.Unmarshal(cancelRec.Body.Bytes(), &cancelResp)) + require.NoError(t, json.Unmarshal(cancelRec.Body.Bytes(), &cancelResp, jsonx.V1())) assert.Equal(t, "ValidationException", cancelResp["__type"]) } @@ -456,7 +457,7 @@ func TestHandler_ListStatements_WithFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) assert.Len(t, stmts, 1) @@ -484,7 +485,7 @@ func TestHandler_ListStatements_WithWorkgroupFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) assert.Len(t, stmts, 1) @@ -507,7 +508,7 @@ func TestHandler_DescribeStatement_AllFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -515,7 +516,7 @@ func TestHandler_DescribeStatement_AllFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, id, resp["Id"]) assert.Equal(t, "FINISHED", resp["Status"]) @@ -545,7 +546,7 @@ func TestHandler_BatchExecuteStatement_AllFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -553,7 +554,7 @@ func TestHandler_BatchExecuteStatement_AllFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, id, resp["Id"]) // IsBatchStatement/QueryStrings are real StatementData (ListStatements @@ -569,7 +570,7 @@ func TestHandler_BatchExecuteStatement_AllFields(t *testing.T) { var listResp struct { Statements []map[string]any `json:"Statements"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.NotEmpty(t, listResp.Statements) assert.Equal(t, true, listResp.Statements[0]["IsBatchStatement"]) @@ -595,7 +596,7 @@ func TestHandler_ListStatements_WithSecretARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) require.Len(t, stmts, 1) @@ -782,7 +783,7 @@ func TestHandler_ListSessions(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantErrType != "" { assert.Equal(t, tt.wantErrType, resp["__type"]) diff --git a/services/redshiftdata/handler_statements_validation_test.go b/services/redshiftdata/handler_statements_validation_test.go index 620a72f2af..7a3c0ab92c 100644 --- a/services/redshiftdata/handler_statements_validation_test.go +++ b/services/redshiftdata/handler_statements_validation_test.go @@ -2,7 +2,7 @@ package redshiftdata_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -210,7 +211,7 @@ func TestHandler_ListStatements_MaxResults_Paginates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, _ := resp["Statements"].([]any) assert.Len(t, stmts, 2) @@ -336,14 +337,14 @@ func TestGetStatementResult_NoResultSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp, jsonx.V1())) id := batchResp["Id"].(string) rec = doRequest(t, h, "GetStatementResult", map[string]any{"Id": id}) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -362,14 +363,14 @@ func TestGetStatementResultV2_NoResultSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &batchResp, jsonx.V1())) id := batchResp["Id"].(string) rec = doRequest(t, h, "GetStatementResultV2", map[string]any{"Id": id}) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -389,14 +390,14 @@ func TestGetStatementResultV2_ResultFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) rec = doRequest(t, h, "GetStatementResultV2", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CSV", resp["ResultFormat"]) assert.EqualValues(t, 1, resp["TotalNumRows"]) assert.NotNil(t, resp["Records"]) @@ -417,7 +418,7 @@ func TestCancelStatement_AbortedIsTerminal(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -435,7 +436,7 @@ func TestCancelStatement_FailedIsTerminal(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -459,7 +460,7 @@ func TestListStatements_SortedNewestFirst(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["Statements"].([]any) require.Len(t, stmts, 3) @@ -487,7 +488,7 @@ func TestExecuteStatement_HasResultSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -495,7 +496,7 @@ func TestExecuteStatement_HasResultSet(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, true, descResp["HasResultSet"]) } @@ -513,7 +514,7 @@ func TestBatchExecuteStatement_HasNoResultSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Id"].(string) @@ -521,7 +522,7 @@ func TestBatchExecuteStatement_HasNoResultSet(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, false, descResp["HasResultSet"]) } diff --git a/services/redshiftdata/handler_tables.go b/services/redshiftdata/handler_tables.go index 881fdcb86f..3936c63313 100644 --- a/services/redshiftdata/handler_tables.go +++ b/services/redshiftdata/handler_tables.go @@ -2,9 +2,11 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListTables(ctx context.Context, body []byte) ([]byte, error) { @@ -25,7 +27,7 @@ func (h *Handler) handleListTables(ctx context.Context, body []byte) ([]byte, er MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -49,7 +51,7 @@ func (h *Handler) handleListTables(ctx context.Context, body []byte) ([]byte, er resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // filterDemoTables applies SchemaPattern and TablePattern filters to the demo table list. @@ -99,7 +101,7 @@ func (h *Handler) handleDescribeTable(ctx context.Context, body []byte) ([]byte, MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -130,7 +132,7 @@ func (h *Handler) handleDescribeTable(ctx context.Context, body []byte) ([]byte, resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // paginateMaps applies cursor-based pagination to a slice of maps keyed by "name". diff --git a/services/redshiftdata/handler_tables_test.go b/services/redshiftdata/handler_tables_test.go index 07fa661a52..943767167d 100644 --- a/services/redshiftdata/handler_tables_test.go +++ b/services/redshiftdata/handler_tables_test.go @@ -1,12 +1,14 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListTables(t *testing.T) { @@ -17,7 +19,7 @@ func TestHandler_ListTables(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Tables"]) } @@ -38,7 +40,7 @@ func TestHandler_DescribeTable(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["ColumnList"]) } @@ -59,7 +61,7 @@ func TestHandler_ListTables_ReturnsNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, ok := resp["Tables"].([]any) require.True(t, ok) @@ -77,7 +79,7 @@ func TestHandler_ListTables_SchemaPattern_WildcardMatchesAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, ok := resp["Tables"].([]any) require.True(t, ok) @@ -95,7 +97,7 @@ func TestHandler_ListTables_TablePattern_WildcardMatchesAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, ok := resp["Tables"].([]any) require.True(t, ok) @@ -113,7 +115,7 @@ func TestHandler_ListTables_TablePattern_PrefixMatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, _ := resp["Tables"].([]any) require.NotEmpty(t, tables, "user% should match 'users'") @@ -141,7 +143,7 @@ func TestHandler_ListTables_MaxResults1_Paginates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, ok := resp["Tables"].([]any) require.True(t, ok) @@ -166,7 +168,7 @@ func TestDescribeTable_ReturnsDemoColumns(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cols, ok := resp["ColumnList"].([]any) require.True(t, ok, "ColumnList should be a slice") @@ -192,7 +194,7 @@ func TestListTables_ReturnsDemoData(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tables, ok := resp["Tables"].([]any) require.True(t, ok) diff --git a/services/redshiftdata/handler_test.go b/services/redshiftdata/handler_test.go index 9ff56e5773..52e639db20 100644 --- a/services/redshiftdata/handler_test.go +++ b/services/redshiftdata/handler_test.go @@ -3,7 +3,7 @@ package redshiftdata_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -37,7 +38,7 @@ func doRequest(t *testing.T, h *redshiftdata.Handler, op string, body any) *http if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -257,7 +258,7 @@ func TestHandler_UnknownOperation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -272,7 +273,7 @@ func TestUnknownOperation_Returns400(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } diff --git a/services/redshiftdata/idempotency_test.go b/services/redshiftdata/idempotency_test.go index c806124d77..f30e30f553 100644 --- a/services/redshiftdata/idempotency_test.go +++ b/services/redshiftdata/idempotency_test.go @@ -1,12 +1,14 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestExecuteStatement_ClientToken_ReplaysOnRetry verifies that retrying an @@ -35,13 +37,13 @@ func TestExecuteStatement_ClientToken_ReplaysOnRetry(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rec2 := doRequest(t, h, "ExecuteStatement", req) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1["Id"], out2["Id"], "retry with the same ClientToken must replay the same statement Id") @@ -51,7 +53,7 @@ func TestExecuteStatement_ClientToken_ReplaysOnRetry(t *testing.T) { var list struct { Statements []map[string]any `json:"Statements"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list, jsonx.V1())) assert.Len(t, list.Statements, 1, "exactly one statement should actually be created") } @@ -74,13 +76,13 @@ func TestExecuteStatement_NoClientToken_CreatesDistinctStatements(t *testing.T) require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rec2 := doRequest(t, h, "ExecuteStatement", req) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.NotEqual(t, out1["Id"], out2["Id"], "without a ClientToken each call must create a distinct statement") } @@ -105,13 +107,13 @@ func TestBatchExecuteStatement_ClientToken_ReplaysOnRetry(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rec2 := doRequest(t, h, "BatchExecuteStatement", req) require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1["Id"], out2["Id"], "retry with the same ClientToken must replay the same batch statement Id") } diff --git a/services/redshiftdata/persistence.go b/services/redshiftdata/persistence.go index a39f8d820f..c64f021e3f 100644 --- a/services/redshiftdata/persistence.go +++ b/services/redshiftdata/persistence.go @@ -2,8 +2,9 @@ package redshiftdata import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -72,7 +73,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.defaultRegion, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "redshiftdata: failed to marshal snapshot", "error", err) diff --git a/services/redshiftdata/persistence_test.go b/services/redshiftdata/persistence_test.go index 6d0a03793a..ecd6a85ad8 100644 --- a/services/redshiftdata/persistence_test.go +++ b/services/redshiftdata/persistence_test.go @@ -2,12 +2,14 @@ package redshiftdata //nolint:testpackage // needs the unexported region ctx key import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const persistenceTestAccountID = "000000000000" @@ -155,10 +157,10 @@ func runVersionMismatchCase(t *testing.T, mutate func(raw map[string]any)) { require.NotNil(t, snap) var raw map[string]any - require.NoError(t, json.Unmarshal(snap, &raw)) + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) mutate(raw) - data, err := json.Marshal(raw) + data, err := json.Marshal(raw, jsonx.V1()) require.NoError(t, err) require.NoError(t, target.Restore(t.Context(), data)) diff --git a/services/redshiftdata/s3_copy.go b/services/redshiftdata/s3_copy.go index 2855f8ab00..3ff848bd2e 100644 --- a/services/redshiftdata/s3_copy.go +++ b/services/redshiftdata/s3_copy.go @@ -6,7 +6,8 @@ import ( "compress/gzip" "context" "database/sql" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,7 @@ import ( "github.com/jackc/pgx/v5/stdlib" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -166,7 +168,7 @@ func (r *s3Runner) manifestObjects(ctx context.Context, bucket, key string) ([]s } `json:"entries"` } - if err = json.Unmarshal(raw, &m); err != nil { + if err = json.Unmarshal(raw, &m, jsonx.V1()); err != nil { return nil, stmtErrorf("Manifest file is not in correct json format") } @@ -331,12 +333,11 @@ func appendJSON( return err } - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() + dec := jsontext.NewDecoder(bytes.NewReader(data)) for { var rec any - if err = dec.Decode(&rec); errors.Is(err, io.EOF) { + if err = json.UnmarshalDecode(dec, &rec, jsonx.V1(), jsonx.UseNumber()); errors.Is(err, io.EOF) { return nil } else if err != nil { return stmtErrorf("Invalid JSON record in S3 object: %v", err) @@ -379,7 +380,7 @@ func jsonPathsFor(s *copySpec, cols []string, fetch func(string) ([]byte, error) JSONPaths []string `json:"jsonpaths"` } - if err = json.Unmarshal(raw, &doc); err != nil { + if err = json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return nil, stmtErrorf("The JSONPaths file is not valid JSON: %v", err) } @@ -504,14 +505,14 @@ func textCell(v any) *string { return nil case string: s = x - case json.Number: + case jsonx.Number: s = x.String() case bool: s = strconv.FormatBool(x) case int64, float64, int32, int: s = fmt.Sprint(x) default: - raw, err := json.Marshal(x) + raw, err := json.Marshal(x, jsonx.V1()) if err != nil { s = fmt.Sprint(x) } else { diff --git a/services/redshiftdata/s3_unload.go b/services/redshiftdata/s3_unload.go index 1cc9be978f..dc134f53ce 100644 --- a/services/redshiftdata/s3_unload.go +++ b/services/redshiftdata/s3_unload.go @@ -7,12 +7,13 @@ import ( "encoding/base64" "encoding/csv" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net/url" "strconv" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -402,12 +403,12 @@ func encodeJSONLines(cols []ColumnMetadata, rows [][]Field) ([]byte, error) { buf.WriteByte('{') for i, f := range row { - name, err := json.Marshal(cols[i].Name) + name, err := json.Marshal(cols[i].Name, jsonx.V1()) if err != nil { return nil, stmtErrorf("could not write JSON: %v", err) } - val, err := json.Marshal(jsonValue(f)) + val, err := json.Marshal(jsonValue(f), jsonx.V1()) if err != nil { return nil, stmtErrorf("could not write JSON: %v", err) } @@ -559,7 +560,7 @@ func unloadManifest(s *unloadSpec, rs *ResultSet, files []unloadFile) []byte { doc["schema"] = map[string]any{"elements": elems} } - raw, err := json.Marshal(doc) + raw, err := json.Marshal(doc, jsonx.V1()) if err != nil { return []byte("{}") } diff --git a/services/redshiftdata/session_keepalive_test.go b/services/redshiftdata/session_keepalive_test.go index ab0cfda77e..6420108f43 100644 --- a/services/redshiftdata/session_keepalive_test.go +++ b/services/redshiftdata/session_keepalive_test.go @@ -1,7 +1,7 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSessionKeepAlive_Expiry(t *testing.T) { @@ -82,7 +84,7 @@ func decodeBody(t *testing.T, rec interface{ Bytes() []byte }) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(rec.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/redshiftdata/wire_sdk_roundtrip_test.go b/services/redshiftdata/wire_sdk_roundtrip_test.go index e874bebf33..e845b0db11 100644 --- a/services/redshiftdata/wire_sdk_roundtrip_test.go +++ b/services/redshiftdata/wire_sdk_roundtrip_test.go @@ -1,7 +1,8 @@ package redshiftdata_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/redshiftdata" ) @@ -219,13 +221,13 @@ func TestDescribeStatement_NoFabricatedFields(t *testing.T) { var execResp struct { ID string `json:"Id"` } - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) rec := doRequest(t, h, "DescribeStatement", map[string]any{"Id": execResp.ID}) require.Equal(t, http.StatusOK, rec.Code) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, tt.field) }) } diff --git a/services/rekognition/datasets.go b/services/rekognition/datasets.go index 8fd91ba972..c05e49d956 100644 --- a/services/rekognition/datasets.go +++ b/services/rekognition/datasets.go @@ -2,7 +2,8 @@ package rekognition import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" @@ -11,6 +12,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (b *InMemoryBackend) datasetARN(projectARN, datasetType string) string { @@ -161,8 +164,8 @@ func matchesDatasetEntryFilter(entry string, filter ListDatasetEntriesFilter) bo // name found across its "-metadata" blocks, plus whether it carried at // least one such block (i.e. is "labeled"). func entryLabels(entry string) ([]string, bool) { - var obj map[string]json.RawMessage - if err := json.Unmarshal([]byte(entry), &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal([]byte(entry), &obj, jsonx.V1()); err != nil { return nil, false } @@ -186,8 +189,8 @@ func entryLabels(entry string) ([]string, bool) { // entrySourceRef parses one JSON-lines dataset entry and returns its // "source-ref" field (the image's S3 location), or "" if absent/malformed. func entrySourceRef(entry string) string { - var obj map[string]json.RawMessage - if err := json.Unmarshal([]byte(entry), &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal([]byte(entry), &obj, jsonx.V1()); err != nil { return "" } @@ -197,7 +200,7 @@ func entrySourceRef(entry string) string { } var ref string - _ = json.Unmarshal(raw, &ref) + _ = json.Unmarshal(raw, &ref, jsonx.V1()) return ref } @@ -292,9 +295,9 @@ func computeDatasetStats(entries []string) DatasetStats { } // labelNamesFromMeta parses a -metadata block and returns its label names. -func labelNamesFromMeta(raw json.RawMessage) []string { - var meta map[string]json.RawMessage - if err := json.Unmarshal(raw, &meta); err != nil { +func labelNamesFromMeta(raw jsontext.Value) []string { + var meta map[string]jsontext.Value + if err := json.Unmarshal(raw, &meta, jsonx.V1()); err != nil { return nil } @@ -303,15 +306,15 @@ func labelNamesFromMeta(raw json.RawMessage) []string { // Single-label: "class-name" if cn, ok := meta["class-name"]; ok { var name string - if err := json.Unmarshal(cn, &name); err == nil && name != "" { + if err := json.Unmarshal(cn, &name, jsonx.V1()); err == nil && name != "" { names = append(names, name) } } // Multi-label: "class-map": {"LabelA": ..., "LabelB": ...} if cm, ok := meta["class-map"]; ok { - var classMap map[string]json.RawMessage - if err := json.Unmarshal(cm, &classMap); err == nil { + var classMap map[string]jsontext.Value + if err := json.Unmarshal(cm, &classMap, jsonx.V1()); err == nil { for name := range classMap { names = append(names, name) } @@ -333,7 +336,7 @@ func decodeDatasetPageToken(token string) int { } var tok datasetPaginationToken - if err = json.Unmarshal(decoded, &tok); err != nil || tok.Offset <= 0 { + if err = json.Unmarshal(decoded, &tok, jsonx.V1()); err != nil || tok.Offset <= 0 { return 0 } @@ -395,7 +398,7 @@ func (b *InMemoryBackend) ListDatasetLabels( var outToken string if end < len(names) { - tok, _ := json.Marshal(datasetPaginationToken{Offset: end}) + tok, _ := json.Marshal(datasetPaginationToken{Offset: end}, jsonx.V1()) outToken = base64.RawURLEncoding.EncodeToString(tok) } diff --git a/services/rekognition/handler.go b/services/rekognition/handler.go index ce06aa2530..4b04241d02 100644 --- a/services/rekognition/handler.go +++ b/services/rekognition/handler.go @@ -2,7 +2,7 @@ package rekognition import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -140,7 +141,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { diff --git a/services/rekognition/handler_async_video_fields_test.go b/services/rekognition/handler_async_video_fields_test.go index b47437d86d..97b5cf7fc0 100644 --- a/services/rekognition/handler_async_video_fields_test.go +++ b/services/rekognition/handler_async_video_fields_test.go @@ -1,13 +1,15 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -80,7 +82,7 @@ func TestAsyncVideoJob_JobTagAndVideoRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, tc.startAction) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) @@ -88,7 +90,7 @@ func TestAsyncVideoJob_JobTagAndVideoRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, tc.getAction) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-job-tag", getResp["JobTag"]) video, _ := getResp["Video"].(map[string]any) @@ -111,7 +113,7 @@ func TestAsyncVideoJob_NoVideoOmitsVideoField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) @@ -119,7 +121,7 @@ func TestAsyncVideoJob_NoVideoOmitsVideoField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.NotContains(t, getResp, "Video") assert.NotContains(t, getResp, "JobTag") } @@ -142,7 +144,7 @@ func TestGetSegmentDetection_SelectedSegmentTypesEchoesRequest(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) @@ -150,7 +152,7 @@ func TestGetSegmentDetection_SelectedSegmentTypesEchoesRequest(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) selected, ok := getResp["SelectedSegmentTypes"].([]any) require.True(t, ok) require.Len(t, selected, 2) @@ -199,7 +201,7 @@ func TestGetLabelDetection_GetRequestMetadata(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) @@ -210,7 +212,7 @@ func TestGetLabelDetection_GetRequestMetadata(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) metadata, _ := getResp["GetRequestMetadata"].(map[string]any) require.NotNil(t, metadata) assert.Equal(t, tc.wantSortBy, metadata["SortBy"]) @@ -233,7 +235,7 @@ func TestGetContentModeration_GetRequestMetadataDefaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) @@ -241,7 +243,7 @@ func TestGetContentModeration_GetRequestMetadataDefaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) metadata, _ := getResp["GetRequestMetadata"].(map[string]any) require.NotNil(t, metadata) assert.Equal(t, "TIMESTAMP", metadata["SortBy"]) diff --git a/services/rekognition/handler_celebrities_test.go b/services/rekognition/handler_celebrities_test.go index 0ecdeedcfa..1896aa676c 100644 --- a/services/rekognition/handler_celebrities_test.go +++ b/services/rekognition/handler_celebrities_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCelebrities_ImageAnalysis(t *testing.T) { @@ -29,7 +31,7 @@ func TestCelebrities_ImageAnalysis(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["CelebrityFaces"]) }, }, @@ -41,7 +43,7 @@ func TestCelebrities_ImageAnalysis(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["Name"]) assert.NotNil(t, resp["KnownGender"]) }, @@ -76,7 +78,7 @@ func TestAsyncVideoJobs_CelebrityRecognition(t *testing.T) { //nolint:parallelte require.Equal(t, http.StatusOK, rec.Code, "StartCelebrityRecognition") var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -86,7 +88,7 @@ func TestAsyncVideoJobs_CelebrityRecognition(t *testing.T) { //nolint:parallelte require.Equal(t, http.StatusOK, rec.Code, "GetCelebrityRecognition") var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED @@ -94,7 +96,7 @@ func TestAsyncVideoJobs_CelebrityRecognition(t *testing.T) { //nolint:parallelte require.Equal(t, http.StatusOK, rec.Code, "GetCelebrityRecognition") var secondResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", secondResp["JobStatus"]) assert.NotNil(t, secondResp["Celebrities"]) } diff --git a/services/rekognition/handler_collections_test.go b/services/rekognition/handler_collections_test.go index 133e8a8155..397d9d6cde 100644 --- a/services/rekognition/handler_collections_test.go +++ b/services/rekognition/handler_collections_test.go @@ -1,7 +1,8 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -31,7 +33,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["CollectionArn"], "arn:aws:rekognition:") assert.Equal(t, "7.0", resp["FaceModelVersion"]) }, @@ -47,7 +49,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ResourceAlreadyExistsException", resp["__type"]) }, }, @@ -62,7 +64,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.InDelta(t, float64(200), resp["StatusCode"], 0) }, }, @@ -74,7 +76,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }, }, @@ -89,7 +91,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["CollectionARN"], "arn:aws:rekognition:") assert.InDelta(t, float64(0), resp["FaceCount"], 0) }, @@ -102,7 +104,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) ids, _ := resp["CollectionIds"].([]any) assert.Empty(t, ids) }, @@ -118,7 +120,7 @@ func TestRekognition_Collections(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) ids, _ := resp["CollectionIds"].([]any) assert.Len(t, ids, 1) assert.Equal(t, "list-coll", ids[0]) @@ -165,7 +167,7 @@ func TestListCollections_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) ids1, _ := page1["CollectionIds"].([]any) require.Len(t, ids1, 2) @@ -180,7 +182,7 @@ func TestListCollections_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) ids2, _ := page2["CollectionIds"].([]any) require.Len(t, ids2, 1) @@ -205,7 +207,7 @@ func TestDeleteCollection_CascadesToFaces(t *testing.T) { rec := doRequest(t, h, "ListFaces", map[string]any{"CollectionId": "cascade-coll"}) require.Equal(t, http.StatusOK, rec.Code) var lf map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lf)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lf, jsonx.V1())) faces, _ := lf["Faces"].([]any) require.Len(t, faces, 2) @@ -218,7 +220,7 @@ func TestDeleteCollection_CascadesToFaces(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -234,7 +236,7 @@ func TestDescribeCollection_NotFound_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -344,13 +346,13 @@ func TestListCollections_EmptyReturnsSlices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) // Decode raw to check for non-null slice presence. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasIDs := raw["CollectionIds"] assert.True(t, hasIDs, "CollectionIds must be present") var ids []any - require.NoError(t, json.Unmarshal(raw["CollectionIds"], &ids)) + require.NoError(t, json.Unmarshal(raw["CollectionIds"], &ids, jsonx.V1())) assert.NotNil(t, ids, "CollectionIds must not be null") } diff --git a/services/rekognition/handler_custom_labels_test.go b/services/rekognition/handler_custom_labels_test.go index dbbeb4bee9..8eb25cdb9d 100644 --- a/services/rekognition/handler_custom_labels_test.go +++ b/services/rekognition/handler_custom_labels_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetectCustomLabels_ReturnsEmptyList(t *testing.T) { @@ -20,6 +22,6 @@ func TestDetectCustomLabels_ReturnsEmptyList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["CustomLabels"]) } diff --git a/services/rekognition/handler_datasets_test.go b/services/rekognition/handler_datasets_test.go index 12012eb510..3493972cd3 100644 --- a/services/rekognition/handler_datasets_test.go +++ b/services/rekognition/handler_datasets_test.go @@ -2,12 +2,14 @@ package rekognition_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractLabels extracts the label list from a ListDatasetLabels response @@ -18,7 +20,7 @@ func extractLabels(t *testing.T, body []byte) []any { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) v, _ := resp["DatasetLabelDescriptions"].([]any) @@ -36,7 +38,7 @@ func TestListDatasetLabels_SingleLabel(t *testing.T) { //nolint:paralleltest // rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "lbl-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateDataset", map[string]any{ @@ -45,7 +47,7 @@ func TestListDatasetLabels_SingleLabel(t *testing.T) { //nolint:paralleltest // }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) dsARN := dsResp["DatasetArn"].(string) // Add two entries: cat appears twice, dog once @@ -83,7 +85,7 @@ func TestListDatasetLabels_MultiLabel(t *testing.T) { //nolint:paralleltest // s rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "ml-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) rec = doRequest(t, h, "CreateDataset", map[string]any{ "ProjectArn": projResp["ProjectArn"].(string), @@ -91,7 +93,7 @@ func TestListDatasetLabels_MultiLabel(t *testing.T) { //nolint:paralleltest // s }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) dsARN := dsResp["DatasetArn"].(string) // Multi-label entry: sunglasses + hat @@ -121,7 +123,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "page-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) rec = doRequest(t, h, "CreateDataset", map[string]any{ "ProjectArn": projResp["ProjectArn"].(string), @@ -129,7 +131,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) dsARN := dsResp["DatasetArn"].(string) // Add 3 entries with distinct labels @@ -137,7 +139,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // entry, _ := json.Marshal(map[string]any{ "source-ref": "s3://b/img" + string(rune('0'+i)) + ".jpg", "labels-metadata": map[string]any{"class-name": lbl}, - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateDatasetEntries", map[string]any{ "DatasetArn": dsARN, "Changes": map[string]any{"GroundTruth": entry}, @@ -156,7 +158,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // require.Len(t, firstPageLabels, 2) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) nextToken, hasToken := page1["NextToken"].(string) require.True(t, hasToken, "expected NextToken for more pages") @@ -166,7 +168,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // decoded, err := base64.RawURLEncoding.DecodeString(nextToken) require.NoError(t, err) var tok map[string]any - require.NoError(t, json.Unmarshal(decoded, &tok)) + require.NoError(t, json.Unmarshal(decoded, &tok, jsonx.V1())) assert.InDelta(t, float64(2), tok["o"], 0) // Second page @@ -181,7 +183,7 @@ func TestListDatasetLabels_OpaqueToken(t *testing.T) { //nolint:paralleltest // require.Len(t, secondPageLabels, 1, "expected 1 label on second page") var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) _, hasMore := page2["NextToken"].(string) assert.False(t, hasMore, "should not have next token on last page") } @@ -192,7 +194,7 @@ func TestListDatasetLabels_InvalidToken(t *testing.T) { //nolint:paralleltest // rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "inv-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) rec = doRequest(t, h, "CreateDataset", map[string]any{ "ProjectArn": projResp["ProjectArn"].(string), @@ -200,7 +202,7 @@ func TestListDatasetLabels_InvalidToken(t *testing.T) { //nolint:paralleltest // }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) // Invalid token treated as offset 0 (returns from start, no error) rec = doRequest(t, h, "ListDatasetLabels", map[string]any{ @@ -220,7 +222,7 @@ func TestDistributeDatasetEntries_SetsInProgress(t *testing.T) { //nolint:parall rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "dist-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateDataset", map[string]any{ @@ -229,7 +231,7 @@ func TestDistributeDatasetEntries_SetsInProgress(t *testing.T) { //nolint:parall }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) dsARN := dsResp["DatasetArn"].(string) rec = doRequest(t, h, "DistributeDatasetEntries", map[string]any{ @@ -244,7 +246,7 @@ func TestDistributeDatasetEntries_SetsInProgress(t *testing.T) { //nolint:parall require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) desc := descResp["DatasetDescription"].(map[string]any) assert.Equal(t, "UPDATE_IN_PROGRESS", desc["Status"]) } @@ -260,7 +262,7 @@ func TestDeleteDataset_WhileUpdating_Rejected(t *testing.T) { //nolint:parallelt rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "del-ds-proj"}) require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateDataset", map[string]any{ @@ -269,7 +271,7 @@ func TestDeleteDataset_WhileUpdating_Rejected(t *testing.T) { //nolint:parallelt }) require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) dsARN := dsResp["DatasetArn"].(string) rec = doRequest(t, h, "DistributeDatasetEntries", map[string]any{ @@ -283,7 +285,7 @@ func TestDeleteDataset_WhileUpdating_Rejected(t *testing.T) { //nolint:parallelt require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceInUseException", errResp["__type"]) } @@ -308,7 +310,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) tests := []struct { @@ -329,7 +331,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) string { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) arn := resp["DatasetArn"].(string) assert.Contains(t, arn, "arn:aws:rekognition:") @@ -362,7 +364,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) desc := resp["DatasetDescription"].(map[string]any) assert.Equal(t, "CREATE_COMPLETE", desc["Status"]) assert.Equal(t, "TRAIN", desc["DatasetType"]) @@ -388,7 +390,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["DatasetEntries"]) }) @@ -403,7 +405,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["DatasetLabelDescriptions"]) }) @@ -429,7 +431,7 @@ func TestDatasets(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var dsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dsResp, jsonx.V1())) freshARN := dsResp["DatasetArn"].(string) rec = doRequest(t, h, "DeleteDataset", map[string]any{"DatasetArn": freshARN}) @@ -456,7 +458,7 @@ func TestCreateDataset_DuplicateProjectAndType_Rejected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateDataset", map[string]any{ @@ -473,7 +475,7 @@ func TestCreateDataset_DuplicateProjectAndType_Rejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceAlreadyExistsException", errResp["__type"]) // Same project, different DatasetType -> allowed. diff --git a/services/rekognition/handler_face_liveness_test.go b/services/rekognition/handler_face_liveness_test.go index d7869673e5..cb98ad81b9 100644 --- a/services/rekognition/handler_face_liveness_test.go +++ b/services/rekognition/handler_face_liveness_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ============================================================================= @@ -25,14 +27,14 @@ func TestFaceLiveness_ConfidenceRange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) sessionID := createResp["SessionId"].(string) rec = doRequest(t, h, "GetFaceLivenessSessionResults", map[string]any{"SessionId": sessionID}) require.Equal(t, http.StatusOK, rec.Code) var resultResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resultResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resultResp, jsonx.V1())) confidence := resultResp["Confidence"].(float64) assert.GreaterOrEqual(t, confidence, float64(75), "confidence must be >= 75") assert.Less(t, confidence, float64(100), "confidence must be < 100") @@ -47,7 +49,7 @@ func TestFaceLiveness_TwoSessionsDifferentConfidence(t *testing.T) { //nolint:pa rec := doRequest(t, h, "GetFaceLivenessSessionResults", map[string]any{"SessionId": sessionID}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Confidence"].(float64) } @@ -58,7 +60,7 @@ func TestFaceLiveness_TwoSessionsDifferentConfidence(t *testing.T) { //nolint:pa rec := doRequest(t, h, "CreateFaceLivenessSession", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) c := getConfidence(resp["SessionId"].(string)) confidences[c] = true } @@ -84,7 +86,7 @@ func TestFaceLiveness(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["SessionId"]) }, }, @@ -103,7 +105,7 @@ func TestFaceLiveness(t *testing.T) { //nolint:paralleltest // existing issue. if tc.action == "CreateFaceLivenessSession" && rec.Code == http.StatusOK { var resp map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &resp) + _ = json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) sessionID = resp["SessionId"].(string) } }) @@ -114,7 +116,7 @@ func TestFaceLiveness(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["Status"]) assert.Equal(t, sessionID, resp["SessionId"]) }) diff --git a/services/rekognition/handler_faces_test.go b/services/rekognition/handler_faces_test.go index 473a3cba4c..a91482ca8a 100644 --- a/services/rekognition/handler_faces_test.go +++ b/services/rekognition/handler_faces_test.go @@ -1,13 +1,15 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -33,7 +35,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) records, _ := resp["FaceRecords"].([]any) assert.Len(t, records, 1) }, @@ -46,7 +48,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }, }, @@ -62,7 +64,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) faces, _ := resp["Faces"].([]any) assert.Len(t, faces, 1) }, @@ -82,7 +84,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["DeletedFaces"]) }, }, @@ -100,7 +102,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) }, }, @@ -116,7 +118,7 @@ func TestRekognition_Faces(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) matches, _ := resp["FaceMatches"].([]any) assert.Len(t, matches, 1) }, @@ -167,7 +169,7 @@ func TestListFaces_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) faces1, _ := page1["Faces"].([]any) require.Len(t, faces1, 2) @@ -183,7 +185,7 @@ func TestListFaces_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) faces2, _ := page2["Faces"].([]any) require.Len(t, faces2, 1) @@ -216,7 +218,7 @@ func TestSearchFaces_RealFaceId_ReturnsMatches(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var idx1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx1, jsonx.V1())) records, _ := idx1["FaceRecords"].([]any) require.Len(t, records, 1) rec1, _ := records[0].(map[string]any) @@ -241,7 +243,7 @@ func TestSearchFaces_RealFaceId_ReturnsMatches(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var sf map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sf)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sf, jsonx.V1())) matches, _ := sf["FaceMatches"].([]any) assert.Len(t, matches, 2) assert.Equal(t, faceID, sf["SearchedFaceId"]) @@ -264,7 +266,7 @@ func TestSearchFaces_UnknownFaceId_Returns404(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -286,7 +288,7 @@ func TestDeleteFaces_BySpecificIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var idx map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx, jsonx.V1())) records, _ := idx["FaceRecords"].([]any) rec0, _ := records[0].(map[string]any) face, _ := rec0["Face"].(map[string]any) @@ -301,7 +303,7 @@ func TestDeleteFaces_BySpecificIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &delResp, jsonx.V1())) deleted, _ := delResp["DeletedFaces"].([]any) assert.Len(t, deleted, 2) @@ -310,7 +312,7 @@ func TestDeleteFaces_BySpecificIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var lf map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lf)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &lf, jsonx.V1())) remaining, _ := lf["Faces"].([]any) require.Len(t, remaining, 1) remainFace, _ := remaining[0].(map[string]any) @@ -329,7 +331,7 @@ func TestListFaces_UnknownCollection_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -346,14 +348,14 @@ func TestListFaces_EmptyCollection_ReturnsNonNullSlice(t *testing.T) { rec := doRequest(t, h, "ListFaces", map[string]any{"CollectionId": "empty-faces-coll"}) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasFaces := raw["Faces"] assert.True(t, hasFaces, "Faces field must be present") var faces []any - require.NoError(t, json.Unmarshal(raw["Faces"], &faces)) + require.NoError(t, json.Unmarshal(raw["Faces"], &faces, jsonx.V1())) assert.NotNil(t, faces, "Faces must not be null") } @@ -374,7 +376,7 @@ func TestIndexFaces_ExternalImageId_RoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var idx map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &idx, jsonx.V1())) records, _ := idx["FaceRecords"].([]any) require.Len(t, records, 1) rec0, _ := records[0].(map[string]any) @@ -386,7 +388,7 @@ func TestIndexFaces_ExternalImageId_RoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var lf map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &lf)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &lf, jsonx.V1())) faces, _ := lf["Faces"].([]any) require.Len(t, faces, 1) f0, _ := faces[0].(map[string]any) @@ -404,7 +406,7 @@ func decodeFaceIDs(t *testing.T, body []byte) []string { } `json:"Face"` } `json:"FaceRecords"` } - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) ids := make([]string, 0, len(resp.FaceRecords)) for _, r := range resp.FaceRecords { @@ -437,7 +439,7 @@ func TestIndexFaces_ConfidenceDeterministicAndPlausible(t *testing.T) { } `json:"Face"` } `json:"FaceRecords"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.FaceRecords, 1) conf := resp.FaceRecords[0].Face.Confidence @@ -487,7 +489,7 @@ func TestSearchFaces_SimilarityDeterministic(t *testing.T) { Similarity float64 `json:"Similarity"` } `json:"FaceMatches"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.FaceMatches, 1) return resp.FaceMatches[0].Similarity @@ -522,7 +524,7 @@ func TestSearchFaces_SameExternalImageId_ScoresExactMatch(t *testing.T) { Similarity float64 `json:"Similarity"` } `json:"FaceMatches"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.FaceMatches, 1) assert.InDelta(t, 100.0, resp.FaceMatches[0].Similarity, 1e-9, "faces sharing an ExternalImageId must score an exact match") @@ -549,7 +551,7 @@ func TestSearchFacesByImage_SimilarityDependsOnImage(t *testing.T) { Similarity float64 `json:"Similarity"` } `json:"FaceMatches"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.FaceMatches, 1) return resp.FaceMatches[0].Similarity @@ -587,7 +589,7 @@ func TestFaceRoundTrip_IndexSearchListDelete(t *testing.T) { FaceID string `json:"FaceId"` } `json:"Faces"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &list, jsonx.V1())) require.Len(t, list.Faces, 2) // SearchFaces from id1 finds id2. FaceMatchThreshold explicitly 0: this @@ -608,7 +610,7 @@ func TestFaceRoundTrip_IndexSearchListDelete(t *testing.T) { } `json:"Face"` } `json:"FaceMatches"` } - require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &search)) + require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &search, jsonx.V1())) require.Len(t, search.FaceMatches, 1) assert.Equal(t, id2, search.FaceMatches[0].Face.FaceID) @@ -621,9 +623,9 @@ func TestFaceRoundTrip_IndexSearchListDelete(t *testing.T) { require.Equal(t, http.StatusOK, searchRec2.Code) var search2 struct { - FaceMatches []json.RawMessage `json:"FaceMatches"` + FaceMatches []jsontext.Value `json:"FaceMatches"` } - require.NoError(t, json.Unmarshal(searchRec2.Body.Bytes(), &search2)) + require.NoError(t, json.Unmarshal(searchRec2.Body.Bytes(), &search2, jsonx.V1())) assert.Empty(t, search2.FaceMatches) // SearchFaces for the deleted id returns ResourceNotFoundException. @@ -647,7 +649,7 @@ func TestImageAnalysis_CompareFaces(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["FaceMatches"]) assert.NotNil(t, resp["SourceImageFace"]) } @@ -660,7 +662,7 @@ func TestImageAnalysis_DetectFaces(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["FaceDetails"]) } @@ -713,7 +715,7 @@ func TestImageAnalysis_EnumValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) }) } @@ -779,7 +781,7 @@ func TestAsyncVideoJobs_Faces(t *testing.T) { //nolint:paralleltest // stateful checkGet: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["JobStatus"]) assert.NotNil(t, resp["Faces"]) }, @@ -791,7 +793,7 @@ func TestAsyncVideoJobs_Faces(t *testing.T) { //nolint:paralleltest // stateful checkGet: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["JobStatus"]) assert.NotNil(t, resp["Persons"]) }, @@ -805,7 +807,7 @@ func TestAsyncVideoJobs_Faces(t *testing.T) { //nolint:paralleltest // stateful require.Equal(t, http.StatusOK, rec.Code, flow.startAction) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -815,7 +817,7 @@ func TestAsyncVideoJobs_Faces(t *testing.T) { //nolint:paralleltest // stateful require.Equal(t, http.StatusOK, rec.Code, flow.getAction) var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED diff --git a/services/rekognition/handler_labels_test.go b/services/rekognition/handler_labels_test.go index 1b5cb1b78f..2a80a9dc7f 100644 --- a/services/rekognition/handler_labels_test.go +++ b/services/rekognition/handler_labels_test.go @@ -1,13 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -32,7 +33,7 @@ func TestDetectLabels_ReturnsEmptyList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Labels"]) } @@ -44,7 +45,7 @@ func TestAsyncVideoJobs_LabelDetection(t *testing.T) { //nolint:paralleltest // require.Equal(t, http.StatusOK, rec.Code, "StartLabelDetection") var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -54,7 +55,7 @@ func TestAsyncVideoJobs_LabelDetection(t *testing.T) { //nolint:paralleltest // require.Equal(t, http.StatusOK, rec.Code, "GetLabelDetection") var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED @@ -62,7 +63,7 @@ func TestAsyncVideoJobs_LabelDetection(t *testing.T) { //nolint:paralleltest // require.Equal(t, http.StatusOK, rec.Code, "GetLabelDetection") var secondResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", secondResp["JobStatus"]) assert.NotNil(t, secondResp["Labels"]) } diff --git a/services/rekognition/handler_media_analysis_test.go b/services/rekognition/handler_media_analysis_test.go index 3d5ba8ac89..fdc33a3478 100644 --- a/services/rekognition/handler_media_analysis_test.go +++ b/services/rekognition/handler_media_analysis_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ============================================================================= @@ -36,7 +38,7 @@ func TestMediaAnalysisJob_Lifecycle(t *testing.T) { //nolint:paralleltest // sta require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -46,7 +48,7 @@ func TestMediaAnalysisJob_Lifecycle(t *testing.T) { //nolint:paralleltest // sta require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, jobID, getResp["JobId"]) assert.NotEmpty(t, getResp["Status"]) @@ -73,7 +75,7 @@ func TestMediaAnalysisJob_Lifecycle(t *testing.T) { //nolint:paralleltest // sta require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) jobs, ok := listResp["MediaAnalysisJobs"].([]any) require.True(t, ok) require.GreaterOrEqual(t, len(jobs), 1) @@ -168,7 +170,7 @@ func TestAsyncVideoJobs_PersonTrackingAndSegmentDetection(t *testing.T) { //noli checkGet: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["JobStatus"]) assert.NotNil(t, resp["Persons"]) }, @@ -180,7 +182,7 @@ func TestAsyncVideoJobs_PersonTrackingAndSegmentDetection(t *testing.T) { //noli checkGet: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["JobStatus"]) assert.NotNil(t, resp["Segments"]) }, @@ -194,7 +196,7 @@ func TestAsyncVideoJobs_PersonTrackingAndSegmentDetection(t *testing.T) { //noli require.Equal(t, http.StatusOK, rec.Code, flow.startAction) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -204,7 +206,7 @@ func TestAsyncVideoJobs_PersonTrackingAndSegmentDetection(t *testing.T) { //noli require.Equal(t, http.StatusOK, rec.Code, flow.getAction) var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED @@ -265,7 +267,7 @@ func TestMediaAnalysisJobs(t *testing.T) { //nolint:paralleltest // existing iss require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"].(string) assert.NotEmpty(t, jobID) @@ -275,7 +277,7 @@ func TestMediaAnalysisJobs(t *testing.T) { //nolint:paralleltest // existing iss require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, jobID, resp["JobId"]) assert.Equal(t, "my-analysis", resp["JobName"]) assert.Equal(t, "SUCCEEDED", resp["Status"]) @@ -287,7 +289,7 @@ func TestMediaAnalysisJobs(t *testing.T) { //nolint:paralleltest // existing iss require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs, ok := resp["MediaAnalysisJobs"].([]any) require.True(t, ok) assert.Len(t, jobs, 1) diff --git a/services/rekognition/handler_moderation_test.go b/services/rekognition/handler_moderation_test.go index 18fbb736ad..ceac653a91 100644 --- a/services/rekognition/handler_moderation_test.go +++ b/services/rekognition/handler_moderation_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestModeration_ImageAnalysis(t *testing.T) { @@ -29,7 +31,7 @@ func TestModeration_ImageAnalysis(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["ModerationLabels"]) }, }, @@ -41,7 +43,7 @@ func TestModeration_ImageAnalysis(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotNil(t, resp["Persons"]) }, }, @@ -125,7 +127,7 @@ func TestDetectProtectiveEquipment_SummarizationAttributesValidation(t *testing. if tc.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) } }) @@ -140,7 +142,7 @@ func TestAsyncVideoJobs_ContentModeration(t *testing.T) { //nolint:paralleltest require.Equal(t, http.StatusOK, rec.Code, "StartContentModeration") var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -150,7 +152,7 @@ func TestAsyncVideoJobs_ContentModeration(t *testing.T) { //nolint:paralleltest require.Equal(t, http.StatusOK, rec.Code, "GetContentModeration") var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED @@ -158,7 +160,7 @@ func TestAsyncVideoJobs_ContentModeration(t *testing.T) { //nolint:paralleltest require.Equal(t, http.StatusOK, rec.Code, "GetContentModeration") var secondResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", secondResp["JobStatus"]) assert.NotNil(t, secondResp["ModerationLabels"]) } diff --git a/services/rekognition/handler_project_versions.go b/services/rekognition/handler_project_versions.go index fd63079794..74a72e8e3f 100644 --- a/services/rekognition/handler_project_versions.go +++ b/services/rekognition/handler_project_versions.go @@ -2,7 +2,7 @@ package rekognition import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -53,8 +53,8 @@ type createProjectVersionReq struct { VersionName string `json:"VersionName"` KmsKeyID string `json:"KmsKeyId"` VersionDescription string `json:"VersionDescription"` - TrainingData json.RawMessage `json:"TrainingData"` - TestingData json.RawMessage `json:"TestingData"` + TrainingData jsontext.Value `json:"TrainingData"` + TestingData jsontext.Value `json:"TestingData"` } type createProjectVersionResp struct { diff --git a/services/rekognition/handler_project_versions_test.go b/services/rekognition/handler_project_versions_test.go index 0d43d9474b..075dc12e5c 100644 --- a/services/rekognition/handler_project_versions_test.go +++ b/services/rekognition/handler_project_versions_test.go @@ -1,7 +1,7 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -31,7 +32,7 @@ func TestProjectVersions(t *testing.T) { //nolint:paralleltest // existing issue rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "ver-proj"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ProjectArn"].(string) }, @@ -39,7 +40,7 @@ func TestProjectVersions(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["ProjectVersionArn"], "arn:aws:rekognition:") }, }, @@ -55,7 +56,7 @@ func TestProjectVersions(t *testing.T) { //nolint:paralleltest // existing issue rec := doRequest(t, h, "CreateProject", map[string]any{"ProjectName": "no-output-config-proj"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ProjectArn"].(string) }, @@ -97,7 +98,7 @@ func TestProjectVersion_Lifecycle(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) // Create version @@ -109,7 +110,7 @@ func TestProjectVersion_Lifecycle(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp, jsonx.V1())) versionARN := verResp["ProjectVersionArn"].(string) // DescribeProjectVersions @@ -117,7 +118,7 @@ func TestProjectVersion_Lifecycle(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) assert.Len(t, versions, 1) @@ -129,7 +130,7 @@ func TestProjectVersion_Lifecycle(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.Equal(t, "RUNNING", startResp["Status"]) // StopProjectVersion @@ -139,7 +140,7 @@ func TestProjectVersion_Lifecycle(t *testing.T) { //nolint:paralleltest // exist require.Equal(t, http.StatusOK, rec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Equal(t, "STOPPED", stopResp["Status"]) // DeleteProjectVersion @@ -167,7 +168,7 @@ func TestProjectVersion_StateTransitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -178,13 +179,13 @@ func TestProjectVersion_StateTransitions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp, jsonx.V1())) versionARN := verResp["ProjectVersionArn"].(string) status := func() string { out := doRequest(t, h, "DescribeProjectVersions", map[string]any{"ProjectArn": projectARN}) var m map[string]any - require.NoError(t, json.Unmarshal(out.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(out.Body.Bytes(), &m, jsonx.V1())) return m["ProjectVersionDescriptions"].([]any)[0].(map[string]any)["Status"].(string) } @@ -192,7 +193,7 @@ func TestProjectVersion_StateTransitions(t *testing.T) { out := doRequest(t, h, action, body) require.Equal(t, http.StatusBadRequest, out.Code) var m map[string]any - require.NoError(t, json.Unmarshal(out.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(out.Body.Bytes(), &m, jsonx.V1())) return m["__type"].(string) } @@ -239,7 +240,7 @@ func TestCreateProjectVersion_FullFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -256,14 +257,14 @@ func TestCreateProjectVersion_FullFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp, jsonx.V1())) versionARN := verResp["ProjectVersionArn"].(string) rec = doRequest(t, h, "DescribeProjectVersions", map[string]any{"ProjectArn": projectARN}) require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, versions, 1) @@ -282,7 +283,7 @@ func TestCreateProjectVersion_FullFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags, _ := tagsResp["Tags"].(map[string]any) assert.Equal(t, "vision", tags["team"]) } @@ -295,7 +296,7 @@ func TestCopyProjectVersion(t *testing.T) { //nolint:paralleltest // existing is require.Equal(t, http.StatusOK, rec.Code) var srcProjResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &srcProjResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &srcProjResp, jsonx.V1())) srcProjectARN := srcProjResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -306,7 +307,7 @@ func TestCopyProjectVersion(t *testing.T) { //nolint:paralleltest // existing is require.Equal(t, http.StatusOK, rec.Code) var srcVerResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &srcVerResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &srcVerResp, jsonx.V1())) sourceVersionARN := srcVerResp["ProjectVersionArn"].(string) // Create destination project @@ -314,7 +315,7 @@ func TestCopyProjectVersion(t *testing.T) { //nolint:paralleltest // existing is require.Equal(t, http.StatusOK, rec.Code) var dstProjResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dstProjResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dstProjResp, jsonx.V1())) dstProjectARN := dstProjResp["ProjectArn"].(string) // Copy version fails when SourceProjectArn doesn't match the project @@ -339,7 +340,7 @@ func TestCopyProjectVersion(t *testing.T) { //nolint:paralleltest // existing is require.Equal(t, http.StatusOK, rec.Code) var copyResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ©Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ©Resp, jsonx.V1())) assert.Contains(t, copyResp["ProjectVersionArn"], "dst-proj") // SourceProjectVersionArn and OutputConfig are echoed back on the @@ -348,7 +349,7 @@ func TestCopyProjectVersion(t *testing.T) { //nolint:paralleltest // existing is require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, versions, 1) copied := versions[0].(map[string]any) @@ -421,7 +422,7 @@ func TestCreateProjectVersion_FeatureConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -438,7 +439,7 @@ func TestCreateProjectVersion_FeatureConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, versions, 1) @@ -507,7 +508,7 @@ func TestCreateProjectVersion_TrainingTestingDataMustBothBeSpecified(t *testing. require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) body := map[string]any{ @@ -536,7 +537,7 @@ func TestStartProjectVersion_MaxInferenceUnitsRoundTrip(t *testing.T) { //nolint require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -547,7 +548,7 @@ func TestStartProjectVersion_MaxInferenceUnitsRoundTrip(t *testing.T) { //nolint require.Equal(t, http.StatusOK, rec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp, jsonx.V1())) versionARN := verResp["ProjectVersionArn"].(string) rec = doRequest(t, h, "StartProjectVersion", map[string]any{ @@ -561,7 +562,7 @@ func TestStartProjectVersion_MaxInferenceUnitsRoundTrip(t *testing.T) { //nolint require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, versions, 1) desc := versions[0].(map[string]any) diff --git a/services/rekognition/handler_projects_test.go b/services/rekognition/handler_projects_test.go index 47d4906388..fbf03de8ff 100644 --- a/services/rekognition/handler_projects_test.go +++ b/services/rekognition/handler_projects_test.go @@ -1,13 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -28,7 +29,7 @@ func TestProjects(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["ProjectArn"], "arn:aws:rekognition:") assert.Contains(t, resp["ProjectArn"], "project/my-project") }, @@ -51,7 +52,7 @@ func TestProjects(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["message"]) }, }, @@ -72,7 +73,7 @@ func TestProjects(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) descriptions, ok := resp["ProjectDescriptions"].([]any) require.True(t, ok) assert.GreaterOrEqual(t, len(descriptions), 1) @@ -86,7 +87,7 @@ func TestProjects(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) descriptions, ok := resp["ProjectDescriptions"].([]any) require.True(t, ok) assert.Empty(t, descriptions) @@ -119,7 +120,7 @@ func TestProject_DeleteSuccess(t *testing.T) { //nolint:paralleltest // existing require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) projectARN := createResp["ProjectArn"].(string) // Delete it @@ -127,7 +128,7 @@ func TestProject_DeleteSuccess(t *testing.T) { //nolint:paralleltest // existing assert.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp, jsonx.V1())) assert.Equal(t, "DELETING", deleteResp["Status"]) // DescribeProjects should no longer return it @@ -135,7 +136,7 @@ func TestProject_DeleteSuccess(t *testing.T) { //nolint:paralleltest // existing require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) descriptions := descResp["ProjectDescriptions"].([]any) assert.Empty(t, descriptions) } @@ -153,7 +154,7 @@ func TestProject_DeleteWithVersions_Rejected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -167,7 +168,7 @@ func TestProject_DeleteWithVersions_Rejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceInUseException", errResp["__type"]) } @@ -179,7 +180,7 @@ func TestProjectPolicies(t *testing.T) { //nolint:paralleltest // existing issue require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) tests := []struct { @@ -201,7 +202,7 @@ func TestProjectPolicies(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.NotEmpty(t, resp["PolicyRevisionId"]) }, }, @@ -213,7 +214,7 @@ func TestProjectPolicies(t *testing.T) { //nolint:paralleltest // existing issue check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) policies, ok := resp["ProjectPolicies"].([]any) require.True(t, ok) assert.Len(t, policies, 1) @@ -254,7 +255,7 @@ func TestDeleteProject_CascadesProjectPolicies(t *testing.T) { //nolint:parallel require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "PutProjectPolicy", map[string]any{ @@ -271,7 +272,7 @@ func TestDeleteProject_CascadesProjectPolicies(t *testing.T) { //nolint:parallel require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) policies, ok := listResp["ProjectPolicies"].([]any) require.True(t, ok) assert.Empty(t, policies, "ProjectPolicies must be cascade-deleted with the project") diff --git a/services/rekognition/handler_stream_processors_test.go b/services/rekognition/handler_stream_processors_test.go index 4d11a1fd83..132b434e4c 100644 --- a/services/rekognition/handler_stream_processors_test.go +++ b/services/rekognition/handler_stream_processors_test.go @@ -1,7 +1,8 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -31,7 +33,7 @@ func TestRekognition_StreamProcessors(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Contains(t, resp["StreamProcessorArn"], "arn:aws:rekognition:") }, }, @@ -46,7 +48,7 @@ func TestRekognition_StreamProcessors(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) // AWS reports a duplicate stream processor name as // ResourceInUseException, not ResourceAlreadyExistsException // (verified against aws-sdk-go-v2/service/rekognition's @@ -65,7 +67,7 @@ func TestRekognition_StreamProcessors(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "STOPPED", resp["Status"]) assert.Contains(t, resp["StreamProcessorArn"], "arn:aws:rekognition:") }, @@ -81,7 +83,7 @@ func TestRekognition_StreamProcessors(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) procs, _ := resp["StreamProcessors"].([]any) assert.Len(t, procs, 1) }, @@ -122,7 +124,7 @@ func TestRekognition_StreamProcessors(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }, }, @@ -176,7 +178,7 @@ func TestListStreamProcessors_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) procs1, _ := page1["StreamProcessors"].([]any) require.Len(t, procs1, 2) @@ -189,7 +191,7 @@ func TestListStreamProcessors_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) procs2, _ := page2["StreamProcessors"].([]any) require.Len(t, procs2, 1) @@ -207,14 +209,14 @@ func TestListStreamProcessors_EmptyReturnsNonNullSlice(t *testing.T) { rec := doRequest(t, h, "ListStreamProcessors", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasProcs := raw["StreamProcessors"] assert.True(t, hasProcs, "StreamProcessors field must be present") var procs []any - require.NoError(t, json.Unmarshal(raw["StreamProcessors"], &procs)) + require.NoError(t, json.Unmarshal(raw["StreamProcessors"], &procs, jsonx.V1())) assert.NotNil(t, procs, "StreamProcessors must not be null") } @@ -237,7 +239,7 @@ func TestStopStreamProcessor_AlreadyStopped_IsIdempotent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "STOPPED", desc["Status"]) } @@ -253,7 +255,7 @@ func TestStartStreamProcessor_Unknown_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -269,7 +271,7 @@ func TestUpdateStreamProcessor_Unknown_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -287,7 +289,7 @@ func TestStreamProcessor_StatusTransitions(t *testing.T) { rec := doRequest(t, h, "DescribeStreamProcessor", map[string]any{"Name": "status-proc"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["Status"].(string) } @@ -423,7 +425,7 @@ func TestCreateStreamProcessor_FullFieldsRoundTripThroughDescribe(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "arn:aws:kms:us-east-1:000000000000:key/abc", resp["KmsKeyId"]) @@ -488,7 +490,7 @@ func TestUpdateStreamProcessor_AppliesFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -566,7 +568,7 @@ func TestUpdateStreamProcessor_RegionsOfInterestForUpdate_Replaces(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rois, _ := resp["RegionsOfInterest"].([]any) assert.Len(t, rois, 2) diff --git a/services/rekognition/handler_tags_test.go b/services/rekognition/handler_tags_test.go index 48632affae..2988826b99 100644 --- a/services/rekognition/handler_tags_test.go +++ b/services/rekognition/handler_tags_test.go @@ -1,7 +1,7 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -29,7 +30,7 @@ func TestRekognition_Tags(t *testing.T) { setup: func(h *rekognition.Handler) string { rec := doRequest(t, h, "CreateCollection", map[string]any{"CollectionId": "tag-coll"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["CollectionArn"].(string) }, @@ -49,7 +50,7 @@ func TestRekognition_Tags(t *testing.T) { map[string]any{"CollectionId": "ltfr-coll", "Tags": map[string]string{"k": "v"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["CollectionArn"].(string) }, @@ -60,7 +61,7 @@ func TestRekognition_Tags(t *testing.T) { check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) tags, _ := resp["Tags"].(map[string]any) assert.Equal(t, "v", tags["k"]) }, @@ -76,7 +77,7 @@ func TestRekognition_Tags(t *testing.T) { map[string]any{"CollectionId": "untag-coll", "Tags": map[string]string{"k": "v"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["CollectionArn"].(string) doRequest(t, h, "TagResource", map[string]any{"ResourceArn": arn, "Tags": map[string]string{"k": "v"}}) @@ -125,7 +126,7 @@ func TestUntagResource_UnknownARN_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -144,7 +145,7 @@ func TestListTagsForResource_StreamProcessor_ReturnsTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["StreamProcessorArn"].(string) // Tag the stream processor. @@ -158,7 +159,7 @@ func TestListTagsForResource_StreamProcessor_ReturnsTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp, jsonx.V1())) tags, _ := tagResp["Tags"].(map[string]any) assert.Equal(t, "staging", tags["env"]) assert.Equal(t, "ml", tags["team"]) @@ -222,7 +223,7 @@ func TestTagResource_KeyValueValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["CollectionArn"].(string) rec = doRequest(t, h, "TagResource", map[string]any{"ResourceArn": arn, "Tags": tc.tags}) @@ -239,7 +240,7 @@ func TestTagResource_KeyValidation_ErrorType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["CollectionArn"].(string) rec = doRequest(t, h, "TagResource", map[string]any{ @@ -249,7 +250,7 @@ func TestTagResource_KeyValidation_ErrorType(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) } @@ -265,7 +266,7 @@ func TestTagResource_CountLimit_Enforced(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["CollectionArn"].(string) // Add 200 tags in batches of 50 (keys are "tag-000" through "tag-199"). @@ -297,7 +298,7 @@ func TestTagResource_UpdateExisting_DoesNotCountAgain(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["CollectionArn"].(string) // Add one tag, then update it — should not fail due to count. @@ -322,7 +323,7 @@ func TestTagResource_UnknownARN_Returns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -341,7 +342,7 @@ func TestTagResource_StreamProcessor_TagsValidated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["StreamProcessorArn"].(string) // Valid tag. diff --git a/services/rekognition/handler_test.go b/services/rekognition/handler_test.go index 501a15517e..f94e25a1e5 100644 --- a/services/rekognition/handler_test.go +++ b/services/rekognition/handler_test.go @@ -2,7 +2,7 @@ package rekognition_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -40,7 +41,7 @@ func doRequest(t *testing.T, h *rekognition.Handler, action string, body any) *h if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -65,6 +66,6 @@ func TestRekognition_UnknownOperation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "UnknownOperationException", resp["__type"]) } diff --git a/services/rekognition/handler_text_detection_test.go b/services/rekognition/handler_text_detection_test.go index 7b4ca8f2aa..0d7734d15f 100644 --- a/services/rekognition/handler_text_detection_test.go +++ b/services/rekognition/handler_text_detection_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestDetectText_ReturnsEmptyList(t *testing.T) { @@ -17,7 +19,7 @@ func TestDetectText_ReturnsEmptyList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["TextDetections"]) } @@ -29,7 +31,7 @@ func TestAsyncVideoJobs_TextDetection(t *testing.T) { //nolint:paralleltest // s require.Equal(t, http.StatusOK, rec.Code, "StartTextDetection") var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID, ok := startResp["JobId"].(string) require.True(t, ok) assert.NotEmpty(t, jobID) @@ -39,7 +41,7 @@ func TestAsyncVideoJobs_TextDetection(t *testing.T) { //nolint:paralleltest // s require.Equal(t, http.StatusOK, rec.Code, "GetTextDetection") var firstResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &firstResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", firstResp["JobStatus"]) // Second poll returns SUCCEEDED @@ -47,7 +49,7 @@ func TestAsyncVideoJobs_TextDetection(t *testing.T) { //nolint:paralleltest // s require.Equal(t, http.StatusOK, rec.Code, "GetTextDetection") var secondResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &secondResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", secondResp["JobStatus"]) assert.NotNil(t, secondResp["TextDetections"]) } diff --git a/services/rekognition/handler_users_test.go b/services/rekognition/handler_users_test.go index ca9142f337..a6d2252259 100644 --- a/services/rekognition/handler_users_test.go +++ b/services/rekognition/handler_users_test.go @@ -1,12 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestUsers(t *testing.T) { //nolint:paralleltest // existing issue. @@ -43,7 +45,7 @@ func TestUsers(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) users, ok := resp["Users"].([]any) require.True(t, ok) assert.Len(t, users, 1) @@ -91,7 +93,7 @@ func TestAssociateFaces(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var indexResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &indexResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &indexResp, jsonx.V1())) faceRecords := indexResp["FaceRecords"].([]any) require.Len(t, faceRecords, 1) faceID := faceRecords[0].(map[string]any)["Face"].(map[string]any)["FaceId"].(string) @@ -109,7 +111,7 @@ func TestAssociateFaces(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) associated, ok := resp["AssociatedFaces"].([]any) require.True(t, ok) assert.Len(t, associated, 1) @@ -125,7 +127,7 @@ func TestAssociateFaces(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unsuccessful, ok := resp["UnsuccessfulFaceAssociations"].([]any) require.True(t, ok) assert.Len(t, unsuccessful, 1) @@ -141,7 +143,7 @@ func TestAssociateFaces(t *testing.T) { //nolint:paralleltest // existing issue. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) disassociated, ok := resp["DisassociatedFaces"].([]any) require.True(t, ok) assert.Len(t, disassociated, 1) @@ -179,7 +181,7 @@ func TestSearchUsers(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, faceModelVersion, resp["FaceModelVersion"]) matches, ok := resp["UserMatches"].([]any) require.True(t, ok) @@ -201,7 +203,7 @@ func TestSearchUsers(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) assert.Equal(t, faceModelVersion, resp["FaceModelVersion"]) matches, ok := resp["UserMatches"].([]any) require.True(t, ok) @@ -220,7 +222,7 @@ func TestSearchUsers(t *testing.T) { //nolint:paralleltest // existing issue. check: func(t *testing.T, body []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) matches, ok := resp["UserMatches"].([]any) require.True(t, ok) assert.Len(t, matches, 2) diff --git a/services/rekognition/job_notification.go b/services/rekognition/job_notification.go index dc70b2a528..29df224f8e 100644 --- a/services/rekognition/job_notification.go +++ b/services/rekognition/job_notification.go @@ -2,10 +2,11 @@ package rekognition import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sns" @@ -94,7 +95,7 @@ func buildJobNotification(jobID string, p StartAsyncJobParams) jobNotification { "Timestamp": time.Now().UnixMilli(), "Video": map[string]string{"S3ObjectName": p.VideoS3Name, "S3Bucket": p.VideoS3Bucket}, } - data, _ := json.Marshal(msg) + data, _ := json.Marshal(msg, jsonx.V1()) return jobNotification{topic: p.NotificationTopicARN, message: data} } diff --git a/services/rekognition/job_notification_test.go b/services/rekognition/job_notification_test.go index 2890de65b4..fa5d64ffa4 100644 --- a/services/rekognition/job_notification_test.go +++ b/services/rekognition/job_notification_test.go @@ -1,7 +1,7 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -99,7 +100,7 @@ func TestStartJobNotificationChannel(t *testing.T) { } var start map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &start, jsonx.V1())) if !tt.wantNotify { assert.Empty(t, n.messages) @@ -111,7 +112,7 @@ func TestStartJobNotificationChannel(t *testing.T) { assert.Equal(t, topic, n.topics[0]) var msg map[string]any - require.NoError(t, json.Unmarshal([]byte(n.messages[0]), &msg)) + require.NoError(t, json.Unmarshal([]byte(n.messages[0]), &msg, jsonx.V1())) assert.Equal(t, start["JobId"], msg["JobId"]) assert.Equal(t, "SUCCEEDED", msg["Status"]) assert.Equal(t, tt.wantAPI, msg["API"]) @@ -122,7 +123,7 @@ func TestStartJobNotificationChannel(t *testing.T) { require.Equal(t, http.StatusOK, get.Code) var got map[string]any - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &got, jsonx.V1())) assert.Equal(t, "SUCCEEDED", got["JobStatus"]) }) } diff --git a/services/rekognition/multi_region_test.go b/services/rekognition/multi_region_test.go index 31c654b1a2..5b20a48f67 100644 --- a/services/rekognition/multi_region_test.go +++ b/services/rekognition/multi_region_test.go @@ -2,7 +2,8 @@ package rekognition_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -168,8 +170,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/rekognition/omission_defaults_test.go b/services/rekognition/omission_defaults_test.go index 7dfd34e884..5398d1f3bd 100644 --- a/services/rekognition/omission_defaults_test.go +++ b/services/rekognition/omission_defaults_test.go @@ -1,13 +1,15 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListProjectPolicies_DefaultPageSize locks in the real SDK's documented @@ -24,7 +26,7 @@ func TestListProjectPolicies_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) //nolint:forcetypeassert // test for i := range 6 { @@ -40,7 +42,7 @@ func TestListProjectPolicies_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, _ := resp["ProjectPolicies"].([]any) assert.Len(t, policies, 5, "ListProjectPolicies omits MaxResults => real SDK defaults to 5 per response") diff --git a/services/rekognition/persistence.go b/services/rekognition/persistence.go index 25e2ae52b4..555c00adb5 100644 --- a/services/rekognition/persistence.go +++ b/services/rekognition/persistence.go @@ -2,7 +2,7 @@ package rekognition import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "slices" @@ -35,7 +35,7 @@ const rekognitionSnapshotVersion = 1 // a snapshot from an incompatible (older/newer/absent) build of this backend // as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` DatasetEntries map[string][]string `json:"datasetEntries"` AccountID string `json:"accountId"` diff --git a/services/rekognition/persistence_test.go b/services/rekognition/persistence_test.go index 535689e6c2..c5233379ad 100644 --- a/services/rekognition/persistence_test.go +++ b/services/rekognition/persistence_test.go @@ -1,13 +1,14 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -346,7 +347,7 @@ func TestSnapshotRestore_PreservesAllState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.InDelta(t, float64(1), descResp["FaceCount"], 0) // Verify face survives. @@ -354,7 +355,7 @@ func TestSnapshotRestore_PreservesAllState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var facesResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &facesResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &facesResp, jsonx.V1())) faces, _ := facesResp["Faces"].([]any) assert.Len(t, faces, 1) @@ -382,7 +383,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var projResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &projResp, jsonx.V1())) projectARN := projResp["ProjectArn"].(string) rec = doRequest(t, h, "CreateProjectVersion", map[string]any{ @@ -396,7 +397,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var verResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &verResp, jsonx.V1())) versionARN := verResp["ProjectVersionArn"].(string) rec = doRequest(t, h, "StartProjectVersion", map[string]any{ @@ -410,7 +411,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var dstProjResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dstProjResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &dstProjResp, jsonx.V1())) dstProjectARN := dstProjResp["ProjectArn"].(string) rec = doRequest(t, h, "CopyProjectVersion", map[string]any{ @@ -432,7 +433,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"].(string) snap := backend.Snapshot(t.Context()) @@ -446,7 +447,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) versions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, versions, 1) desc := versions[0].(map[string]any) @@ -461,7 +462,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { rec = doRequest(t, h2, "DescribeProjectVersions", map[string]any{"ProjectArn": dstProjectARN}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) dstVersions := descResp["ProjectVersionDescriptions"].([]any) require.Len(t, dstVersions, 1) assert.Equal(t, versionARN, dstVersions[0].(map[string]any)["SourceProjectVersionArn"]) @@ -470,7 +471,7 @@ func TestSnapshotRestore_ProjectVersionAndAsyncJobNewFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "persist-tag", getResp["JobTag"]) video, _ := getResp["Video"].(map[string]any) @@ -493,7 +494,7 @@ func TestSnapshotRestore_TagsSurvive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["CollectionArn"].(string) doRequest(t, h, "TagResource", map[string]any{ @@ -511,7 +512,7 @@ func TestSnapshotRestore_TagsSurvive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagResp, jsonx.V1())) tags, _ := tagResp["Tags"].(map[string]any) assert.Equal(t, "val1", tags["key1"]) assert.Equal(t, "val2", tags["key2"]) diff --git a/services/rekognition/realism_test.go b/services/rekognition/realism_test.go index 61f629651a..ed724ff305 100644 --- a/services/rekognition/realism_test.go +++ b/services/rekognition/realism_test.go @@ -1,7 +1,7 @@ package rekognition_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -17,7 +18,7 @@ func decodeMap(t *testing.T, body []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(body, &m)) + require.NoError(t, json.Unmarshal(body, &m, jsonx.V1())) return m } diff --git a/services/rekognition/s3_object_test.go b/services/rekognition/s3_object_test.go index ec2378aecf..2b572afcae 100644 --- a/services/rekognition/s3_object_test.go +++ b/services/rekognition/s3_object_test.go @@ -2,7 +2,7 @@ package rekognition_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rekognition" ) @@ -82,7 +83,7 @@ func TestImageOps_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doRequest(t, h, tt.action, map[string]any{ @@ -148,7 +149,7 @@ func TestSearchByImageOps_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doRequest(t, h, tt.action, map[string]any{ @@ -191,7 +192,7 @@ func TestStartVideoOps_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doRequest(t, h, tt.action, map[string]any{ @@ -221,7 +222,7 @@ func TestStartMediaAnalysisJob_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doRequest(t, h, "StartMediaAnalysisJob", body("good-bucket")) diff --git a/services/resiliencehub/handler.go b/services/resiliencehub/handler.go index e5bd8b8c90..804005a026 100644 --- a/services/resiliencehub/handler.go +++ b/services/resiliencehub/handler.go @@ -2,7 +2,7 @@ package resiliencehub import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -283,7 +284,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { status, errType = http.StatusNotFound, "ResourceNotFoundException" } - payload, _ := json.Marshal(body) + payload, _ := json.Marshal(body, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) @@ -395,7 +396,7 @@ func decodeJSONBody(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validationError("malformed request body: " + err.Error()) } @@ -405,7 +406,7 @@ func decodeJSONBody(body []byte, v any) error { // marshalResponse marshals v, wrapping any error as an internal failure // (marshaling our own well-typed wire structs should never fail). func marshalResponse(v any) ([]byte, error) { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, fmt.Errorf("resiliencehub: marshal response: %w", err) } diff --git a/services/resiliencehub/multi_region_test.go b/services/resiliencehub/multi_region_test.go index a551f69200..2f1d7c066e 100644 --- a/services/resiliencehub/multi_region_test.go +++ b/services/resiliencehub/multi_region_test.go @@ -2,7 +2,8 @@ package resiliencehub_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/resiliencehub" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/resiliencehub/persistence.go b/services/resiliencehub/persistence.go index b12bf6f188..2aeb85ae28 100644 --- a/services/resiliencehub/persistence.go +++ b/services/resiliencehub/persistence.go @@ -2,7 +2,7 @@ package resiliencehub import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -20,10 +20,10 @@ const resiliencehubSnapshotVersion = 1 // backendSnapshot is the top-level on-disk shape for the Resilience Hub // backend. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountId"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountId"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/resourceexplorer2/catalog.go b/services/resourceexplorer2/catalog.go index 0e6e55fed1..65058ea066 100644 --- a/services/resourceexplorer2/catalog.go +++ b/services/resourceexplorer2/catalog.go @@ -2,7 +2,7 @@ package resourceexplorer2 import ( _ "embed" // documented resource type list - "encoding/json" + "encoding/json/v2" "slices" "strings" "sync" @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -93,7 +94,7 @@ var supportedTypes = sync.OnceValue(func() []SupportedType { //nolint:gochecknog Types []string `json:"types"` } - if err := json.Unmarshal(documentedTypesJSON, &documented); err != nil { + if err := json.Unmarshal(documentedTypesJSON, &documented, jsonx.V1()); err != nil { panic("resourceexplorer2: embedded supported types are invalid: " + err.Error()) } diff --git a/services/resourceexplorer2/persistence.go b/services/resourceexplorer2/persistence.go index b15d542242..5e3052b481 100644 --- a/services/resourceexplorer2/persistence.go +++ b/services/resourceexplorer2/persistence.go @@ -2,7 +2,7 @@ package resourceexplorer2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/resourceexplorer2/types_catalog_test.go b/services/resourceexplorer2/types_catalog_test.go index 8fcbbd829a..c3b2560d58 100644 --- a/services/resourceexplorer2/types_catalog_test.go +++ b/services/resourceexplorer2/types_catalog_test.go @@ -2,12 +2,14 @@ package resourceexplorer2_test import ( _ "embed" - "encoding/json" + "encoding/json/v2" "slices" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed supported_types.json @@ -21,7 +23,7 @@ func TestDocumentedTypesCatalog(t *testing.T) { Types []string `json:"types"` } - require.NoError(t, json.Unmarshal(documentedTypes, &c)) + require.NoError(t, json.Unmarshal(documentedTypes, &c, jsonx.V1())) tests := []struct { check func(t *testing.T) diff --git a/services/resourcegroups/groups.go b/services/resourcegroups/groups.go index 8e448d40da..20678349cc 100644 --- a/services/resourcegroups/groups.go +++ b/services/resourcegroups/groups.go @@ -2,7 +2,8 @@ package resourcegroups import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "regexp" "slices" @@ -11,6 +12,7 @@ import ( "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -147,8 +149,8 @@ func validateResourceQuery(q *ResourceQuery) error { return fmt.Errorf("%w: ResourceQuery.Query must be a non-empty JSON string", ErrValidation) } - var raw json.RawMessage - if err := json.Unmarshal([]byte(q.Query), &raw); err != nil { + var raw jsontext.Value + if err := json.Unmarshal([]byte(q.Query), &raw, jsonx.V1()); err != nil { return fmt.Errorf( "%w: ResourceQuery.Query is not valid JSON: %s", ErrValidation, diff --git a/services/resourcegroups/handler.go b/services/resourcegroups/handler.go index c432a7fd47..cbc8812630 100644 --- a/services/resourcegroups/handler.go +++ b/services/resourcegroups/handler.go @@ -3,7 +3,7 @@ package resourcegroups import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +16,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -258,7 +259,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req extractResourceNameInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.Name != "" { return req.Name @@ -339,7 +340,7 @@ func mergeListGroupsQuery(body []byte, q url.Values) ([]byte, error) { doc := map[string]any{} if len(bytes.TrimSpace(body)) > 0 { - if err := json.Unmarshal(body, &doc); err != nil { + if err := json.Unmarshal(body, &doc, jsonx.V1()); err != nil { return nil, err } } @@ -357,7 +358,7 @@ func mergeListGroupsQuery(body []byte, q url.Values) ([]byte, error) { doc["NextToken"] = token } - return json.Marshal(doc) + return json.Marshal(doc, jsonx.V1()) } func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]byte, error) { @@ -371,7 +372,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // amznErrorTypeHeader carries the modeled exception type for the restjson1 @@ -388,15 +389,12 @@ const errTypeBadRequest = "BadRequestException" // NotFoundException, BadRequestException, ForbiddenException, // InternalServerErrorException, MethodNotAllowedException, TooManyRequestsException). func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - code := http.StatusInternalServerError wireType := "InternalServerErrorException" switch { case errors.Is(err, errInvalidRequest), errors.Is(err, ErrUnknownOperation), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): code, wireType = http.StatusBadRequest, errTypeBadRequest case errors.Is(err, ErrAlreadyExists): code, wireType = http.StatusBadRequest, errTypeBadRequest diff --git a/services/resourcegroups/handler_groups_config_test.go b/services/resourcegroups/handler_groups_config_test.go index 3bc43c7fd5..6b19a50221 100644 --- a/services/resourcegroups/handler_groups_config_test.go +++ b/services/resourcegroups/handler_groups_config_test.go @@ -1,13 +1,14 @@ package resourcegroups_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -102,7 +103,7 @@ func TestGetGroupQuery_ConfigVsQueryGroup(t *testing.T) { } `json:"ResourceQuery"` } `json:"GroupQuery"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotNil(t, out.GroupQuery.ResourceQuery) }) } @@ -141,7 +142,7 @@ func TestResourceGroupsHandler_GetGroupConfiguration_Status(t *testing.T) { Status string `json:"Status"` } `json:"GroupConfiguration"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "UPDATE_COMPLETE", out.GroupConfiguration.Status) assert.NotContains(t, rec.Body.String(), "GroupName") } @@ -542,7 +543,7 @@ func TestCreateGroup_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) group, ok := out["Group"].(map[string]any) require.True(t, ok) diff --git a/services/resourcegroups/handler_groups_test.go b/services/resourcegroups/handler_groups_test.go index 5e764c8cfa..dc4b7e16f5 100644 --- a/services/resourcegroups/handler_groups_test.go +++ b/services/resourcegroups/handler_groups_test.go @@ -2,7 +2,7 @@ package resourcegroups_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -230,7 +231,7 @@ func TestListGroups_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) groups1 := out1["Groups"].([]any) assert.Len(t, groups1, 3) token1, _ := out1["NextToken"].(string) @@ -244,7 +245,7 @@ func TestListGroups_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) groups2 := out2["Groups"].([]any) assert.Len(t, groups2, 3) assert.Empty(t, out2["NextToken"]) @@ -766,7 +767,7 @@ func TestResourceGroupsHandler_DeleteGroup_EchoesGroup(t *testing.T) { Description string `json:"Description"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "echo-group", out.Group.Name) assert.Equal(t, "echo desc", out.Group.Description) } @@ -791,7 +792,7 @@ func TestResourceGroupsHandler_ListGroups_IdentifierFields(t *testing.T) { var out struct { GroupIdentifiers []map[string]any `json:"GroupIdentifiers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.GroupIdentifiers, 1) assert.Equal(t, "Ident Group", out.GroupIdentifiers[0]["DisplayName"]) criticality, ok := out.GroupIdentifiers[0]["Criticality"].(float64) diff --git a/services/resourcegroups/handler_groups_validation_test.go b/services/resourcegroups/handler_groups_validation_test.go index d3a15bf87f..ee04712fda 100644 --- a/services/resourcegroups/handler_groups_validation_test.go +++ b/services/resourcegroups/handler_groups_validation_test.go @@ -1,7 +1,7 @@ package resourcegroups_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -396,7 +397,7 @@ func TestListGroupsOutputShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) groups, ok := out["Groups"].([]any) require.True(t, ok, "Groups must be a list") @@ -506,7 +507,7 @@ func TestOwnerId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) group, ok := out["Group"].(map[string]any) require.True(t, ok) @@ -532,7 +533,7 @@ func TestCreateGroupIdentityFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) group, ok := out["Group"].(map[string]any) require.True(t, ok) @@ -555,7 +556,7 @@ func TestListGroupsGroupIdentifiersShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) identifiers := out["GroupIdentifiers"].([]any) require.Len(t, identifiers, 1) diff --git a/services/resourcegroups/handler_query_test.go b/services/resourcegroups/handler_query_test.go index 1e0be1e46e..beecad7322 100644 --- a/services/resourcegroups/handler_query_test.go +++ b/services/resourcegroups/handler_query_test.go @@ -1,7 +1,7 @@ package resourcegroups_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -88,7 +89,7 @@ func TestSearchResources_QueryErrorsShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out, "QueryErrors", "QueryErrors must be omitted when empty") } @@ -137,7 +138,7 @@ func TestSearchResources_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) ids1 := out1["ResourceIdentifiers"].([]any) assert.Len(t, ids1, 3) tok1, _ := out1["NextToken"].(string) @@ -148,7 +149,7 @@ func TestSearchResources_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) ids2 := out2["ResourceIdentifiers"].([]any) assert.Len(t, ids2, 2) assert.Empty(t, out2["NextToken"]) diff --git a/services/resourcegroups/handler_resources_test.go b/services/resourcegroups/handler_resources_test.go index 4e9c1bd287..0191ad94ed 100644 --- a/services/resourcegroups/handler_resources_test.go +++ b/services/resourcegroups/handler_resources_test.go @@ -1,7 +1,7 @@ package resourcegroups_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -154,7 +155,7 @@ func TestGroupResources_InvalidARNReturnsFailed(t *testing.T) { ErrorMessage string `json:"ErrorMessage"` } `json:"Failed"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len( t, @@ -195,7 +196,7 @@ func TestGroupResources_InvalidARNNotAddedToGroup(t *testing.T) { } `json:"Identifier"` } `json:"Resources"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Resources, 1, "only the valid ARN should be in group") assert.Equal(t, "arn:aws:s3:::real-bucket", out.Resources[0].Identifier.ResourceArn) @@ -318,7 +319,7 @@ func TestUngroupResourcesFailures(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) succeeded, _ := out["Succeeded"].([]any) failed, _ := out["Failed"].([]any) @@ -480,14 +481,14 @@ func TestListGroupResources_ResourceIdentifiersDeprecatedField(t *testing.T) { ResourceType string `json:"ResourceType"` } `json:"ResourceIdentifiers"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ResourceIdentifiers, 1) assert.Equal(t, out.Resources[0].Identifier.ResourceArn, out.ResourceIdentifiers[0].ResourceArn) assert.Equal(t, out.Resources[0].Identifier.ResourceType, out.ResourceIdentifiers[0].ResourceType) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.NotContains(t, raw, "QueryErrors", "QueryErrors must be omitted when empty") } @@ -543,7 +544,7 @@ func TestListGroupResources_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) resources1 := out1["Resources"].([]any) assert.Len(t, resources1, 2) token1, _ := out1["NextToken"].(string) @@ -557,7 +558,7 @@ func TestListGroupResources_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) resources2 := out2["Resources"].([]any) assert.Len(t, resources2, 3) assert.Empty(t, out2["NextToken"]) @@ -684,7 +685,7 @@ func TestResourceGroupsHandler_GroupingStatuses_UpdatedAtIsEpochSeconds(t *testi var out struct { GroupingStatuses []map[string]any `json:"GroupingStatuses"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.GroupingStatuses, 1) _, isNumber := out.GroupingStatuses[0]["UpdatedAt"].(float64) assert.True(t, isNumber, "ListGroupingStatuses UpdatedAt must be a JSON number: %s", rec.Body.String()) @@ -713,7 +714,7 @@ func TestListGroupingStatuses_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) statuses1 := out1["GroupingStatuses"].([]any) assert.Len(t, statuses1, 3) tok1, _ := out1["NextToken"].(string) @@ -727,7 +728,7 @@ func TestListGroupingStatuses_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) statuses2 := out2["GroupingStatuses"].([]any) assert.Len(t, statuses2, 2) assert.Empty(t, out2["NextToken"]) diff --git a/services/resourcegroups/handler_tags.go b/services/resourcegroups/handler_tags.go index a22dd6417d..464ee129ca 100644 --- a/services/resourcegroups/handler_tags.go +++ b/services/resourcegroups/handler_tags.go @@ -2,7 +2,7 @@ package resourcegroups import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -38,7 +39,7 @@ func (h *Handler) handleTagRequest(ctx context.Context, c *echo.Context, log *sl var in tagResourceInput - if err = json.Unmarshal(body, &in); err != nil { + if err = json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.handleError(ctx, c, "Tag", errInvalidRequest) } @@ -96,7 +97,7 @@ func (h *Handler) extractUntagKeys(ctx context.Context, c *echo.Context, log *sl } var in untagResourceInput - if err = json.Unmarshal(body, &in); err != nil { + if err = json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, errInvalidRequest } @@ -138,7 +139,7 @@ func (h *Handler) handleResourceTags(ctx context.Context, c *echo.Context) error var in untagResourceInput - if err = json.Unmarshal(body, &in); err != nil { + if err = json.Unmarshal(body, &in, jsonx.V1()); err != nil { return h.handleError(ctx, c, "Untag", errInvalidRequest) } diff --git a/services/resourcegroups/handler_tags_test.go b/services/resourcegroups/handler_tags_test.go index 42b6b055f6..344b1f9f37 100644 --- a/services/resourcegroups/handler_tags_test.go +++ b/services/resourcegroups/handler_tags_test.go @@ -2,7 +2,7 @@ package resourcegroups_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -36,7 +37,7 @@ func TestResourceGroupsHandler_GetTags(t *testing.T) { GroupArn string `json:"GroupArn"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.Group.GroupArn }, @@ -95,7 +96,7 @@ func TestResourceGroupsHandler_Tag(t *testing.T) { GroupArn string `json:"GroupArn"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) groupARN = out.Group.GroupArn } else { groupARN = "arn:aws:resource-groups:us-east-1:000000000000:group/nonexistent" @@ -142,7 +143,7 @@ func TestResourceGroupsHandler_Untag(t *testing.T) { GroupArn string `json:"GroupArn"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) groupARN = out.Group.GroupArn } else { groupARN = "arn:aws:resource-groups:us-east-1:000000000000:group/nonexistent" @@ -170,7 +171,7 @@ func TestUntagViaDeleteVerb(t *testing.T) { // DELETE /resources/{Arn}/tags with JSON body. e := echo.New() - bodyBytes, _ := json.Marshal(map[string]any{"Keys": []string{"env"}}) + bodyBytes, _ := json.Marshal(map[string]any{"Keys": []string{"env"}}, jsonx.V1()) req := httptest.NewRequest( http.MethodDelete, "/resources/"+g.ARN+"/tags", diff --git a/services/resourcegroups/handler_tagsync_test.go b/services/resourcegroups/handler_tagsync_test.go index 4f3ec883a4..d571050bbb 100644 --- a/services/resourcegroups/handler_tagsync_test.go +++ b/services/resourcegroups/handler_tagsync_test.go @@ -1,7 +1,7 @@ package resourcegroups_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -182,7 +183,7 @@ func TestResourceGroupsHandler_GetTagSyncTask(t *testing.T) { var out struct { TaskArn string `json:"TaskArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.TaskArn }, @@ -252,7 +253,7 @@ func TestResourceGroupsHandler_CancelTagSyncTask(t *testing.T) { var out struct { TaskArn string `json:"TaskArn"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.TaskArn }, @@ -315,7 +316,7 @@ func TestCancelTagSyncTaskState(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) taskARN := startOut["TaskArn"].(string) cancelRec := doResourceGroupsRequest( @@ -441,7 +442,7 @@ func TestListTagSyncTasks_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) tasks1 := out1["TagSyncTasks"].([]any) assert.Len(t, tasks1, 2) tok1, _ := out1["NextToken"].(string) @@ -454,7 +455,7 @@ func TestListTagSyncTasks_PaginationViaHandler(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) tasks2 := out2["TagSyncTasks"].([]any) assert.Len(t, tasks2, 2) assert.Empty(t, out2["NextToken"]) @@ -480,13 +481,13 @@ func TestResourceGroupsHandler_TagSyncTask_CreatedAtIsEpochSeconds(t *testing.T) var startOut struct { TaskArn string `json:"TaskArn"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startOut, jsonx.V1())) getRec := doResourceGroupsRequest(t, h, "GetTagSyncTask", map[string]any{"TaskArn": startOut.TaskArn}) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) _, isNumber := getOut["CreatedAt"].(float64) assert.True(t, isNumber, "GetTagSyncTask CreatedAt must be a JSON number: %s", getRec.Body.String()) @@ -496,7 +497,7 @@ func TestResourceGroupsHandler_TagSyncTask_CreatedAtIsEpochSeconds(t *testing.T) var listOut struct { TagSyncTasks []map[string]any `json:"TagSyncTasks"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.TagSyncTasks, 1) _, isNumber = listOut.TagSyncTasks[0]["CreatedAt"].(float64) assert.True(t, isNumber, "ListTagSyncTasks CreatedAt must be a JSON number: %s", listRec.Body.String()) diff --git a/services/resourcegroups/handler_test.go b/services/resourcegroups/handler_test.go index 30d178f8f9..cd50022c83 100644 --- a/services/resourcegroups/handler_test.go +++ b/services/resourcegroups/handler_test.go @@ -2,7 +2,7 @@ package resourcegroups_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -34,7 +35,7 @@ func doResourceGroupsRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -88,7 +89,7 @@ func doResourceTagsRequest( var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) } else { @@ -122,7 +123,7 @@ func doResourceGroupsRESTRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") diff --git a/services/resourcegroups/handler_untag_rejection_test.go b/services/resourcegroups/handler_untag_rejection_test.go index bc6e23e978..498a5683b7 100644 --- a/services/resourcegroups/handler_untag_rejection_test.go +++ b/services/resourcegroups/handler_untag_rejection_test.go @@ -2,7 +2,7 @@ package resourcegroups_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -89,7 +90,7 @@ func TestResourceGroupsHandler_Untag_RejectionNotBypassed(t *testing.T) { GroupArn string `json:"GroupArn"` } `json:"Group"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) groupARN := created.Group.GroupArn untagRec := doResourceTagsRequestRaw(t, h, http.MethodDelete, groupARN, tt.body) @@ -98,7 +99,7 @@ func TestResourceGroupsHandler_Untag_RejectionNotBypassed(t *testing.T) { var resp map[string]any require.NoError( - t, json.Unmarshal(untagRec.Body.Bytes(), &resp), + t, json.Unmarshal(untagRec.Body.Bytes(), &resp, jsonx.V1()), "response body must be a single well-formed JSON document, not two concatenated: %s", untagRec.Body.String(), ) @@ -109,7 +110,7 @@ func TestResourceGroupsHandler_Untag_RejectionNotBypassed(t *testing.T) { var tagsResp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) assert.Equal(t, map[string]string{"env": "prod"}, tagsResp.Tags, "rejected Untag must not remove any tags") }) } diff --git a/services/resourcegroups/persistence.go b/services/resourcegroups/persistence.go index b01d1f8762..661566e9e3 100644 --- a/services/resourcegroups/persistence.go +++ b/services/resourcegroups/persistence.go @@ -2,7 +2,7 @@ package resourcegroups import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -108,7 +108,7 @@ func buildPersistenceDTORegistry() (*store.Registry, *store.Table[groupSnapshot] // strings, or status records), so there is nothing for store.Table to key on, // and each is persisted here directly instead. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` GroupConfigurations map[string]map[string][]GroupConfigurationItem `json:"groupConfigurations"` GroupResources map[string]map[string][]string `json:"groupResources"` GroupingStatuses map[string]map[string][]GroupingStatusItem `json:"groupingStatuses"` @@ -217,7 +217,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreGroups rebuilds the "dirty" groups store.Table from snap's DTO // table, factored out of Restore to keep Restore's own cognitive complexity // low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreGroups(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreGroups(tables map[string]jsontext.Value) error { dtoReg, groupDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("resourcegroups: restore snapshot groups table: %w", err) diff --git a/services/resourcegroups/persistence_test.go b/services/resourcegroups/persistence_test.go index 93b7d2586d..399744c748 100644 --- a/services/resourcegroups/persistence_test.go +++ b/services/resourcegroups/persistence_test.go @@ -2,12 +2,14 @@ package resourcegroups_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/blackbirdworks/gopherstack/services/resourcegroups" ) @@ -295,8 +297,8 @@ func Test_PersistenceFullStateRoundTrip(t *testing.T) { snap := b.Snapshot(t.Context()) require.NotNil(t, snap) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &raw, jsonx.V1())) assert.Contains(t, raw, "version") b2 := resourcegroups.NewInMemoryBackend("123456789012", "us-east-1") diff --git a/services/resourcegroups/query.go b/services/resourcegroups/query.go index 01baa5e124..f4e5cfe62e 100644 --- a/services/resourcegroups/query.go +++ b/services/resourcegroups/query.go @@ -2,10 +2,12 @@ package resourcegroups import ( "context" - "encoding/json" + "encoding/json/v2" "slices" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // arnSplitParts is the number of colon-separated segments in a well-formed AWS ARN. @@ -126,7 +128,7 @@ var arnServiceTypeMap = map[string]string{ //nolint:gochecknoglobals,gosec // st // The special value "AWS::AllSupported" means match all types and returns nil. func parseResourceTypeFilters(queryJSON string) map[string]bool { var tfq tagFilterQuery - if err := json.Unmarshal([]byte(queryJSON), &tfq); err != nil { + if err := json.Unmarshal([]byte(queryJSON), &tfq, jsonx.V1()); err != nil { return nil } diff --git a/services/resourcegroups/query_validation.go b/services/resourcegroups/query_validation.go index bd076b6b91..b3d635615a 100644 --- a/services/resourcegroups/query_validation.go +++ b/services/resourcegroups/query_validation.go @@ -2,9 +2,11 @@ package resourcegroups import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -42,7 +44,7 @@ func validateQueryShape(queryType, query string) error { func validateTagFilterQuery(raw []byte) error { var q tagFilterQuery - if err := json.Unmarshal(raw, &q); err != nil { + if err := json.Unmarshal(raw, &q, jsonx.V1()); err != nil { return fmt.Errorf("%w: ResourceQuery.Query is not a valid TAG_FILTERS_1_0 query: %w", ErrValidation, err) } @@ -62,7 +64,7 @@ func validateTagFilterQuery(raw []byte) error { func validateStackQuery(raw []byte) error { var q cfnQuery - if err := json.Unmarshal(raw, &q); err != nil { + if err := json.Unmarshal(raw, &q, jsonx.V1()); err != nil { return fmt.Errorf( "%w: ResourceQuery.Query is not a valid CLOUDFORMATION_STACK_1_0 query: %w", ErrValidation, err) } diff --git a/services/resourcegroups/source.go b/services/resourcegroups/source.go index 78f0209f8c..fdba608019 100644 --- a/services/resourcegroups/source.go +++ b/services/resourcegroups/source.go @@ -2,10 +2,11 @@ package resourcegroups import ( "context" - "encoding/json" + "encoding/json/v2" "slices" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/cloudformation" "github.com/blackbirdworks/gopherstack/services/resourcegroupstaggingapi" @@ -167,7 +168,7 @@ func typeAllowed(want map[string]bool, resType string) bool { func evalTagFilterQuery(ctx context.Context, src ResourceSource, queryJSON string) []ResourceIdentifier { var q tagFilterQuery - if err := json.Unmarshal([]byte(queryJSON), &q); err != nil { + if err := json.Unmarshal([]byte(queryJSON), &q, jsonx.V1()); err != nil { return nil } @@ -190,7 +191,7 @@ func evalStackQuery( ctx context.Context, src ResourceSource, queryJSON string, ) ([]ResourceIdentifier, []queryErrorWire) { var q cfnQuery - if err := json.Unmarshal([]byte(queryJSON), &q); err != nil { + if err := json.Unmarshal([]byte(queryJSON), &q, jsonx.V1()); err != nil { return nil, nil } diff --git a/services/resourcegroupstaggingapi/compliance.go b/services/resourcegroupstaggingapi/compliance.go index 46dc942cd9..991ed58cc5 100644 --- a/services/resourcegroupstaggingapi/compliance.go +++ b/services/resourcegroupstaggingapi/compliance.go @@ -2,10 +2,11 @@ package resourcegroupstaggingapi import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -189,7 +190,7 @@ type tagPolicyStringListAssign struct { // fabricate an unverified mapping. func requiredTagsFromPolicy(policyJSON string) []RequiredTag { var doc tagPolicyDocument - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return nil } diff --git a/services/resourcegroupstaggingapi/compliance_test.go b/services/resourcegroupstaggingapi/compliance_test.go index 58d086e8f5..48fa5c3d39 100644 --- a/services/resourcegroupstaggingapi/compliance_test.go +++ b/services/resourcegroupstaggingapi/compliance_test.go @@ -2,13 +2,14 @@ package resourcegroupstaggingapi_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroupstaggingapi" ) @@ -184,7 +185,7 @@ func TestGetComplianceSummaryJSONShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["SummaryList"].([]any) require.True(t, ok, "SummaryList should be a JSON array") @@ -312,7 +313,7 @@ func TestListRequiredTagsJSONShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["RequiredTags"].([]any) require.True(t, ok, "RequiredTags should be a JSON array") diff --git a/services/resourcegroupstaggingapi/get_resources_test.go b/services/resourcegroupstaggingapi/get_resources_test.go index ffaedbaa37..aa1a10ecc7 100644 --- a/services/resourcegroupstaggingapi/get_resources_test.go +++ b/services/resourcegroupstaggingapi/get_resources_test.go @@ -2,7 +2,8 @@ package resourcegroupstaggingapi_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroupstaggingapi" ) @@ -668,7 +670,7 @@ func TestGetResources_TagsPerPage_SplitsPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out resourcegroupstaggingapi.GetResourcesOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) require.NotNil(t, out.PaginationToken, "TagsPerPage must force an early page break") assert.LessOrEqual(t, len(out.ResourceTagMappingList), 2, @@ -699,7 +701,7 @@ func TestGetResources_TagsPerPage_OversizedFirstResourceStillReturnedAlone(t *te require.Equal(t, http.StatusOK, rec.Code) var out resourcegroupstaggingapi.GetResourcesOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) // GetResources never splits a single resource and its tags across pages, so the // oversized resource is still returned alone rather than yielding zero results. @@ -729,7 +731,7 @@ func TestGetResources_NoTagsPerPage_Unconstrained(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out resourcegroupstaggingapi.GetResourcesOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.ResourceTagMappingList, 5, "without TagsPerPage all resources fit on one page") assert.Nil(t, out.PaginationToken) @@ -768,7 +770,7 @@ func TestGetResources_ResourceARNList_FiltersToSpecificARNs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["ResourceTagMappingList"].([]any) require.True(t, ok) @@ -809,7 +811,7 @@ func TestGetResources_ResourceARNList_EmptyReturnsAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["ResourceTagMappingList"].([]any) require.True(t, ok) @@ -1097,7 +1099,7 @@ func TestComplianceDetailsFieldName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) list, ok := out["ResourceTagMappingList"].([]any) require.True(t, ok) @@ -1140,7 +1142,7 @@ func TestHandler_GetResources_IncludeComplianceDetails(t *testing.T) { } `json:"ResourceTagMappingList"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &body, jsonx.V1())) require.Len(t, body.ResourceTagMappingList, 1) require.NotNil(t, body.ResourceTagMappingList[0].ComplianceDetails) assert.True(t, body.ResourceTagMappingList[0].ComplianceDetails.ComplianceStatus) diff --git a/services/resourcegroupstaggingapi/handler.go b/services/resourcegroupstaggingapi/handler.go index 87a5ea964e..b3a4697da7 100644 --- a/services/resourcegroupstaggingapi/handler.go +++ b/services/resourcegroupstaggingapi/handler.go @@ -2,7 +2,7 @@ package resourcegroupstaggingapi import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -140,13 +141,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - code := http.StatusInternalServerError errType := "InternalFailure" @@ -166,7 +164,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, ErrPaginationTokenExpired): code = http.StatusBadRequest errType = "PaginationTokenExpiredException" - case errors.As(err, &syntaxErr), errors.As(err, &typeErr): + case jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): // Malformed request bodies are a protocol-level failure, not a modeled // operation error; the AWS JSON 1.1 protocol reports these as // SerializationException (matches services/organizations' convention for @@ -178,7 +176,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err payload, _ := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: err.Error(), - }) + }, jsonx.V1()) c.Response().Header().Set("Content-Type", "application/x-amz-json-1.1") diff --git a/services/resourcegroupstaggingapi/handler_test.go b/services/resourcegroupstaggingapi/handler_test.go index 2ec70fcd56..5f489062f4 100644 --- a/services/resourcegroupstaggingapi/handler_test.go +++ b/services/resourcegroupstaggingapi/handler_test.go @@ -3,7 +3,8 @@ package resourcegroupstaggingapi_test import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/resourcegroupstaggingapi" ) @@ -102,7 +104,7 @@ func doTaggingRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -598,7 +600,7 @@ func TestValidationFailures_UseInvalidParameterException(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, "body: %s", rec.Body.String()) var body map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &body, jsonx.V1())) assert.Equal(t, "InvalidParameterException", body["__type"], "real resourcegroupstaggingapi has no ValidationException shape") assert.NotContains(t, rec.Body.String(), "ValidationException") @@ -631,7 +633,7 @@ func TestMalformedBody_UsesSerializationException(t *testing.T) { assert.Contains(t, rec.Body.String(), "SerializationException") var body map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &body, jsonx.V1())) assert.Equal(t, "SerializationException", body["__type"]) }) } @@ -772,7 +774,7 @@ func TestHandler_ValidationError_HasInvalidParameterExceptionBody(t *testing.T) require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &body, jsonx.V1())) // Real resourcegroupstaggingapi has no ValidationException shape; parameter // validation failures are modeled as InvalidParameterException. assert.Equal(t, "InvalidParameterException", body["__type"]) diff --git a/services/resourcegroupstaggingapi/persistence.go b/services/resourcegroupstaggingapi/persistence.go index cc7c495d56..fd3a56c7a7 100644 --- a/services/resourcegroupstaggingapi/persistence.go +++ b/services/resourcegroupstaggingapi/persistence.go @@ -2,7 +2,7 @@ package resourcegroupstaggingapi import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -98,10 +98,10 @@ func buildPersistenceDTORegistry() (*store.Registry, *store.Table[reportStateSna // resource cache is likewise never persisted -- it is a rebuildable TTL // cache, not part of durable state. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. @@ -198,7 +198,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // store_setup.go's registerAllTables doc) from tables via the ephemeral DTO // registry, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTables(ctx context.Context, tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTables(ctx context.Context, tables map[string]jsontext.Value) error { dtoReg, reportStateDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("resourcegroupstaggingapi: restore snapshot DTO tables: %w", err) diff --git a/services/resourcegroupstaggingapi/report_test.go b/services/resourcegroupstaggingapi/report_test.go index b87022aaa2..ecc47a77a3 100644 --- a/services/resourcegroupstaggingapi/report_test.go +++ b/services/resourcegroupstaggingapi/report_test.go @@ -2,7 +2,7 @@ package resourcegroupstaggingapi_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/resourcegroupstaggingapi" ) @@ -92,7 +93,7 @@ func TestStartReportCreationInput_HasNoS3BucketRegionField(t *testing.T) { // A request body containing S3BucketRegion (as some non-AWS client might send) must // simply be ignored as an unknown field, not silently accepted as a modeled member. - marshaled, err := json.Marshal(resourcegroupstaggingapi.StartReportCreationInput{S3Bucket: "b"}) + marshaled, err := json.Marshal(resourcegroupstaggingapi.StartReportCreationInput{S3Bucket: "b"}, jsonx.V1()) require.NoError(t, err) assert.NotContains(t, string(marshaled), "S3BucketRegion") } @@ -331,7 +332,7 @@ func TestDescribeReportCreationJSONShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "SUCCEEDED", out["Status"]) assert.Equal(t, "s3://bkt/path.csv", out["S3Location"]) diff --git a/services/rolesanywhere/handler.go b/services/rolesanywhere/handler.go index 78edc33d9b..b024dfcfc1 100644 --- a/services/rolesanywhere/handler.go +++ b/services/rolesanywhere/handler.go @@ -2,7 +2,7 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -227,7 +228,7 @@ func (h *Handler) handleREST(c *echo.Context) error { return c.JSON(statusCode, struct{}{}) } - data, jsonErr := json.Marshal(result) + data, jsonErr := json.Marshal(result, jsonx.V1()) if jsonErr != nil { return c.JSON(http.StatusInternalServerError, errBody("InternalFailure", "serialization failed")) } diff --git a/services/rolesanywhere/handler_attribute_mappings.go b/services/rolesanywhere/handler_attribute_mappings.go index 924558c7fd..79ff16ced0 100644 --- a/services/rolesanywhere/handler_attribute_mappings.go +++ b/services/rolesanywhere/handler_attribute_mappings.go @@ -2,10 +2,12 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Attribute mapping handlers ---- @@ -18,7 +20,7 @@ func (h *Handler) handlePutAttributeMapping(ctx context.Context, path string, bo MappingRules []MappingRule `json:"mappingRules"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_attribute_mappings_test.go b/services/rolesanywhere/handler_attribute_mappings_test.go index 9e1b0326bf..4ddba6fbd7 100644 --- a/services/rolesanywhere/handler_attribute_mappings_test.go +++ b/services/rolesanywhere/handler_attribute_mappings_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Attribute Mapping HTTP ---- @@ -38,7 +40,7 @@ func TestHandler_AttributeMapping_CRUD(t *testing.T) { require.Equal(t, http.StatusCreated, recProfile.Code) var profileResp map[string]any - require.NoError(t, json.Unmarshal(recProfile.Body.Bytes(), &profileResp)) + require.NoError(t, json.Unmarshal(recProfile.Body.Bytes(), &profileResp, jsonx.V1())) p := profileResp["profile"].(map[string]any) profileID := p["profileId"].(string) @@ -56,7 +58,7 @@ func TestHandler_AttributeMapping_CRUD(t *testing.T) { assert.Equal(t, tt.wantStatus, recPut.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp, jsonx.V1())) profileInResp := putResp["profile"].(map[string]any) assert.Equal(t, profileID, profileInResp["profileId"]) diff --git a/services/rolesanywhere/handler_crls.go b/services/rolesanywhere/handler_crls.go index 53d9d8f7ef..96123f2d32 100644 --- a/services/rolesanywhere/handler_crls.go +++ b/services/rolesanywhere/handler_crls.go @@ -2,9 +2,11 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- CRL handlers ---- @@ -18,7 +20,7 @@ func (h *Handler) handleImportCrl(ctx context.Context, body []byte) (any, int, e Tags []TagEntry `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -84,7 +86,7 @@ func (h *Handler) handleUpdateCrl(ctx context.Context, path string, body []byte) CrlData []byte `json:"crlData"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_crls_test.go b/services/rolesanywhere/handler_crls_test.go index 2e21d0af4b..3cc129e9bd 100644 --- a/services/rolesanywhere/handler_crls_test.go +++ b/services/rolesanywhere/handler_crls_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- CRL HTTP CRUD ---- @@ -44,7 +46,7 @@ func TestHandler_CRL_CRUD(t *testing.T) { assert.Equal(t, tt.wantCreate, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) crl := createResp["crl"].(map[string]any) id := crl["crlId"].(string) assert.NotEmpty(t, id) @@ -205,8 +207,8 @@ func TestHandler_CRL_DuplicateNameAllowed(t *testing.T) { assert.Equal(t, tt.wantStatus, rec2.Code) var resp1, resp2 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) c1 := resp1["crl"].(map[string]any) c2 := resp2["crl"].(map[string]any) diff --git a/services/rolesanywhere/handler_notification_settings.go b/services/rolesanywhere/handler_notification_settings.go index 88d9266d9f..c79cf80f2d 100644 --- a/services/rolesanywhere/handler_notification_settings.go +++ b/services/rolesanywhere/handler_notification_settings.go @@ -2,8 +2,10 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Notification settings handlers ---- @@ -14,7 +16,7 @@ func (h *Handler) handlePutNotificationSettings(ctx context.Context, body []byte NotificationSettings []NotificationSetting `json:"notificationSettings"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -32,7 +34,7 @@ func (h *Handler) handleResetNotificationSettings(ctx context.Context, body []by NotificationSettingKeys []NotificationSettingKey `json:"notificationSettingKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_notification_settings_test.go b/services/rolesanywhere/handler_notification_settings_test.go index 3b517c5256..4767c8d4d0 100644 --- a/services/rolesanywhere/handler_notification_settings_test.go +++ b/services/rolesanywhere/handler_notification_settings_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Notification Settings HTTP ---- @@ -38,7 +40,7 @@ func TestHandler_NotificationSettings_PutReset(t *testing.T) { require.Equal(t, http.StatusCreated, recTA.Code) var taResp map[string]any - require.NoError(t, json.Unmarshal(recTA.Body.Bytes(), &taResp)) + require.NoError(t, json.Unmarshal(recTA.Body.Bytes(), &taResp, jsonx.V1())) ta := taResp["trustAnchor"].(map[string]any) taID := ta["trustAnchorId"].(string) @@ -52,7 +54,7 @@ func TestHandler_NotificationSettings_PutReset(t *testing.T) { assert.Equal(t, tt.wantStatus, recPut.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp, jsonx.V1())) assert.Contains(t, putResp, "trustAnchor") // Reset notification settings. @@ -91,7 +93,7 @@ func TestHandler_NotificationSettings_ConfiguredBy(t *testing.T) { require.Equal(t, http.StatusCreated, recTA.Code) var taResp map[string]any - require.NoError(t, json.Unmarshal(recTA.Body.Bytes(), &taResp)) + require.NoError(t, json.Unmarshal(recTA.Body.Bytes(), &taResp, jsonx.V1())) ta := taResp["trustAnchor"].(map[string]any) taID := ta["trustAnchorId"].(string) @@ -104,7 +106,7 @@ func TestHandler_NotificationSettings_ConfiguredBy(t *testing.T) { require.Equal(t, http.StatusOK, recPut.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putResp, jsonx.V1())) updatedTA := putResp["trustAnchor"].(map[string]any) settings := updatedTA["notificationSettings"].([]any) diff --git a/services/rolesanywhere/handler_pagination_restart_test.go b/services/rolesanywhere/handler_pagination_restart_test.go index e993867812..2788766812 100644 --- a/services/rolesanywhere/handler_pagination_restart_test.go +++ b/services/rolesanywhere/handler_pagination_restart_test.go @@ -1,12 +1,14 @@ package rolesanywhere_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListTrustAnchors_Pagination_DeletedMidPage proves that deleting @@ -29,7 +31,7 @@ func TestHandler_ListTrustAnchors_Pagination_DeletedMidPage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1, jsonx.V1())) items1, _ := resp1["trustAnchors"].([]any) require.Len(t, items1, 2) @@ -50,7 +52,7 @@ func TestHandler_ListTrustAnchors_Pagination_DeletedMidPage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2, jsonx.V1())) items2, _ := resp2["trustAnchors"].([]any) for _, item := range items2 { @@ -90,7 +92,7 @@ func TestHandler_ListTrustAnchors_Pagination_TiedNamesTotalOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["trustAnchors"].([]any) for _, item := range items { diff --git a/services/rolesanywhere/handler_profiles.go b/services/rolesanywhere/handler_profiles.go index 86dd4071a0..7ccb59b026 100644 --- a/services/rolesanywhere/handler_profiles.go +++ b/services/rolesanywhere/handler_profiles.go @@ -2,9 +2,11 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Profile handlers ---- @@ -22,7 +24,7 @@ func (h *Handler) handleCreateProfile(ctx context.Context, body []byte) (any, in RequireInstanceProperties bool `json:"requireInstanceProperties"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -104,7 +106,7 @@ func (h *Handler) handleUpdateProfile(ctx context.Context, path string, body []b ManagedPolicyArns []string `json:"managedPolicyArns"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_profiles_test.go b/services/rolesanywhere/handler_profiles_test.go index caeb1debbb..6bac1bbd8f 100644 --- a/services/rolesanywhere/handler_profiles_test.go +++ b/services/rolesanywhere/handler_profiles_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Profile HTTP CRUD ---- @@ -43,7 +45,7 @@ func TestHandler_Profile_CRUD(t *testing.T) { assert.Equal(t, tt.wantCreate, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) p := createResp["profile"].(map[string]any) id := p["profileId"].(string) assert.NotEmpty(t, id) @@ -240,8 +242,8 @@ func TestHandler_Profile_DuplicateNameAllowed(t *testing.T) { assert.Equal(t, tt.wantStatus, rec2.Code) var resp1, resp2 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) p1 := resp1["profile"].(map[string]any) p2 := resp2["profile"].(map[string]any) @@ -284,7 +286,7 @@ func TestHandler_CreateProfile_EnabledFieldHonored(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) p := resp["profile"].(map[string]any) assert.Equal(t, tt.wantEnabled, p["enabled"]) }) @@ -307,7 +309,7 @@ func TestHandler_Profile_AcceptRoleSessionName(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) p := resp["profile"].(map[string]any) require.Equal(t, true, p["acceptRoleSessionName"]) @@ -319,7 +321,7 @@ func TestHandler_Profile_AcceptRoleSessionName(t *testing.T) { require.Equal(t, http.StatusOK, recUpdate.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(recUpdate.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(recUpdate.Body.Bytes(), &updateResp, jsonx.V1())) updated := updateResp["profile"].(map[string]any) assert.NotContains(t, updated, "acceptRoleSessionName", "false is the zero value and is omitted, matching requireInstanceProperties' existing convention") @@ -339,7 +341,7 @@ func TestHandler_Profile_CreatedBy(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) p := resp["profile"].(map[string]any) assert.Equal(t, "000000000000", p["createdBy"]) } @@ -360,7 +362,7 @@ func TestHandler_Profile_NoTagsFieldOnResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) p := resp["profile"].(map[string]any) assert.NotContains(t, p, "tags") } @@ -388,7 +390,7 @@ func TestHandler_ListProfiles_PageSizeQueryParam(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["profiles"].([]any) assert.Len(t, items, 1, "pageSize=1 must truncate the page to 1 item") } diff --git a/services/rolesanywhere/handler_tags.go b/services/rolesanywhere/handler_tags.go index e6bb6da809..70f5745808 100644 --- a/services/rolesanywhere/handler_tags.go +++ b/services/rolesanywhere/handler_tags.go @@ -2,9 +2,11 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Tag handlers ---- @@ -15,7 +17,7 @@ func (h *Handler) handleTagResource(ctx context.Context, body []byte) (any, int, Tags []TagEntry `json:"tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -39,7 +41,7 @@ func (h *Handler) handleUntagResource(ctx context.Context, body []byte) (any, in TagKeys []string `json:"tagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_tags_test.go b/services/rolesanywhere/handler_tags_test.go index 1e2b94c184..96ced2460e 100644 --- a/services/rolesanywhere/handler_tags_test.go +++ b/services/rolesanywhere/handler_tags_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rolesanywhere" ) @@ -31,7 +32,7 @@ func createTestTrustAnchor(t *testing.T, h *rolesanywhere.Handler) string { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ta, ok := resp["trustAnchor"].(map[string]any) require.True(t, ok, "response missing trustAnchor: %s", rec.Body.String()) @@ -76,7 +77,7 @@ func TestHandler_Tags_HTTP(t *testing.T) { assert.Equal(t, tt.wantListStatus, recList.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["tags"].([]any) assert.Len(t, tags, 2) @@ -102,7 +103,7 @@ func TestHandler_Tags_HTTP(t *testing.T) { "/ListTagsForResource?resourceArn="+resARN, nil, ) - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listResp, jsonx.V1())) tags2 := listResp["tags"].([]any) assert.Len(t, tags2, 1) }) @@ -169,7 +170,7 @@ func TestHandler_Tags_UnknownResource(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }) } @@ -188,7 +189,7 @@ func TestHandler_Tags_EmptyList(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].([]any) assert.Empty(t, tags) } diff --git a/services/rolesanywhere/handler_test.go b/services/rolesanywhere/handler_test.go index aba4ab88cd..ab479a61eb 100644 --- a/services/rolesanywhere/handler_test.go +++ b/services/rolesanywhere/handler_test.go @@ -11,7 +11,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rolesanywhere" ) @@ -44,7 +45,7 @@ func doREST( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -94,7 +95,7 @@ func TestHandler_Reset(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["trustAnchors"].([]any) assert.Empty(t, items) } @@ -458,7 +459,7 @@ func TestHandler_CreateTrustAnchor_EnabledFalse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ta := resp["trustAnchor"].(map[string]any) assert.Equal(t, tt.wantEnabled, ta["enabled"]) }) @@ -476,14 +477,14 @@ func TestHandler_DeleteTrustAnchor_ReturnsResource(t *testing.T) { require.Equal(t, http.StatusCreated, recCreate.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["trustAnchor"].(map[string]any)["trustAnchorId"].(string) recDelete := doREST(t, h, http.MethodDelete, "/trustanchor/"+id, nil) require.Equal(t, http.StatusOK, recDelete.Code) var deleteResp map[string]any - require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &deleteResp, jsonx.V1())) ta, ok := deleteResp["trustAnchor"].(map[string]any) require.True(t, ok, "DeleteTrustAnchor response must carry the deleted trustAnchor, got: %s", recDelete.Body.String()) @@ -501,14 +502,14 @@ func TestHandler_DeleteProfile_ReturnsResource(t *testing.T) { require.Equal(t, http.StatusCreated, recCreate.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["profile"].(map[string]any)["profileId"].(string) recDelete := doREST(t, h, http.MethodDelete, "/profile/"+id, nil) require.Equal(t, http.StatusOK, recDelete.Code) var deleteResp map[string]any - require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &deleteResp, jsonx.V1())) p, ok := deleteResp["profile"].(map[string]any) require.True(t, ok, "DeleteProfile response must carry the deleted profile, got: %s", recDelete.Body.String()) assert.Equal(t, id, p["profileId"]) @@ -538,7 +539,7 @@ func TestHandler_GetTrustAnchor_ShowsNotificationSettings(t *testing.T) { require.Equal(t, http.StatusCreated, recCreate.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["trustAnchor"].(map[string]any)["trustAnchorId"].(string) recPut := doREST(t, h, http.MethodPatch, "/put-notifications-settings", map[string]any{ @@ -556,7 +557,7 @@ func TestHandler_GetTrustAnchor_ShowsNotificationSettings(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp, jsonx.V1())) ta := getResp["trustAnchor"].(map[string]any) settings, ok := ta["notificationSettings"].([]any) require.True(t, ok, "GetTrustAnchor response must carry notificationSettings, got: %s", recGet.Body.String()) @@ -567,7 +568,7 @@ func TestHandler_GetTrustAnchor_ShowsNotificationSettings(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["trustAnchors"].([]any) require.Len(t, items, 1) listedTA := items[0].(map[string]any) @@ -585,7 +586,7 @@ func TestHandler_GetProfile_ShowsAttributeMappings(t *testing.T) { require.Equal(t, http.StatusCreated, recCreate.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["profile"].(map[string]any)["profileId"].(string) recPut := doREST(t, h, http.MethodPut, "/profiles/"+id+"/mappings", map[string]any{ @@ -601,7 +602,7 @@ func TestHandler_GetProfile_ShowsAttributeMappings(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp, jsonx.V1())) p := getResp["profile"].(map[string]any) mappings, ok := p["attributeMappings"].([]any) require.True(t, ok, "GetProfile response must carry attributeMappings, got: %s", recGet.Body.String()) @@ -612,7 +613,7 @@ func TestHandler_GetProfile_ShowsAttributeMappings(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp, jsonx.V1())) items := listResp["profiles"].([]any) require.Len(t, items, 1) listedProfile := items[0].(map[string]any) diff --git a/services/rolesanywhere/handler_trust_anchors.go b/services/rolesanywhere/handler_trust_anchors.go index 1b7d9c1b4a..7df589eb13 100644 --- a/services/rolesanywhere/handler_trust_anchors.go +++ b/services/rolesanywhere/handler_trust_anchors.go @@ -2,9 +2,11 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Trust Anchor handlers ---- @@ -18,7 +20,7 @@ func (h *Handler) handleCreateTrustAnchor(ctx context.Context, body []byte) (any NotificationSettings []NotificationSetting `json:"notificationSettings"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } @@ -90,7 +92,7 @@ func (h *Handler) handleUpdateTrustAnchor(ctx context.Context, path string, body Name string `json:"name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, 0, ErrValidation } diff --git a/services/rolesanywhere/handler_trust_anchors_test.go b/services/rolesanywhere/handler_trust_anchors_test.go index a85dce9782..7c770115ba 100644 --- a/services/rolesanywhere/handler_trust_anchors_test.go +++ b/services/rolesanywhere/handler_trust_anchors_test.go @@ -2,7 +2,7 @@ package rolesanywhere_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Trust Anchor HTTP CRUD ---- @@ -45,7 +47,7 @@ func TestHandler_TrustAnchor_CRUD(t *testing.T) { assert.Equal(t, tt.wantCreate, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) ta := createResp["trustAnchor"].(map[string]any) id := ta["trustAnchorId"].(string) assert.NotEmpty(t, id) @@ -220,8 +222,8 @@ func TestHandler_TrustAnchor_DuplicateNameAllowed(t *testing.T) { assert.Equal(t, tt.wantStatus, rec2.Code) var resp1, resp2 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) ta1 := resp1["trustAnchor"].(map[string]any) ta2 := resp2["trustAnchor"].(map[string]any) @@ -246,7 +248,7 @@ func TestHandler_TrustAnchor_NoTagsFieldOnResponse(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ta := resp["trustAnchor"].(map[string]any) assert.NotContains(t, ta, "tags") @@ -256,7 +258,7 @@ func TestHandler_TrustAnchor_NoTagsFieldOnResponse(t *testing.T) { require.Equal(t, http.StatusOK, recTags.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(recTags.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["tags"].([]any) require.Len(t, tags, 1) tag := tags[0].(map[string]any) @@ -282,7 +284,7 @@ func TestHandler_CreateTrustAnchor_NotificationSettings(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ta := resp["trustAnchor"].(map[string]any) settings, ok := ta["notificationSettings"].([]any) @@ -323,7 +325,7 @@ func TestHandler_ListTrustAnchors_Pagination(t *testing.T) { rec := doREST(t, h, http.MethodGet, "/trustanchors"+tt.query, nil) assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["trustAnchors"].([]any) assert.Len(t, items, tt.wantCount) }) diff --git a/services/rolesanywhere/persistence.go b/services/rolesanywhere/persistence.go index c24c2d0828..005b2022d1 100644 --- a/services/rolesanywhere/persistence.go +++ b/services/rolesanywhere/persistence.go @@ -2,7 +2,7 @@ package rolesanywhere import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -72,7 +72,7 @@ func buildPersistenceDTORegistry() ( // maps left unconverted by Phase 3.3 (see store_setup.go) and are persisted // here directly, as before. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string][]TagEntry `json:"tags"` AttributeMappings map[string]map[string][]AttributeMapping `json:"attributeMappings"` NotificationSettings map[string]map[string][]NotificationSetting `json:"notificationSettings"` @@ -181,7 +181,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // profiles, crls, subjects) from tables via the ephemeral DTO registry, // factored out of Restore to keep Restore's own cognitive complexity low. // Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, taDTOs, profileDTOs, crlDTOs, subjectDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("rolesanywhere: restore snapshot resource tables: %w", err) diff --git a/services/rolesanywhere/persistence_test.go b/services/rolesanywhere/persistence_test.go index 1e86b77a2d..7d64e0c75a 100644 --- a/services/rolesanywhere/persistence_test.go +++ b/services/rolesanywhere/persistence_test.go @@ -5,11 +5,13 @@ package rolesanywhere //nolint:testpackage // needs unexported Subject.region ac import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestPersistence_SnapshotRestoreRoundTrip proves that a full Snapshot → @@ -247,10 +249,10 @@ func TestPersistence_RestoreVersionMismatch(t *testing.T) { require.NoError(t, err) var snapMap map[string]any - require.NoError(t, json.Unmarshal(donor.Snapshot(ctx), &snapMap)) + require.NoError(t, json.Unmarshal(donor.Snapshot(ctx), &snapMap, jsonx.V1())) tt.mutate(snapMap) - mutatedSnap, err := json.Marshal(snapMap) + mutatedSnap, err := json.Marshal(snapMap, jsonx.V1()) require.NoError(t, err) // The restore target starts with its own, different state so we diff --git a/services/route53/persistence.go b/services/route53/persistence.go index a2ff297afe..3767382c02 100644 --- a/services/route53/persistence.go +++ b/services/route53/persistence.go @@ -2,9 +2,11 @@ package route53 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -51,7 +53,7 @@ func zoneDataSnapshotKey(zs *zoneDataSnapshot) string { return zs.Zone.ID } // newer) build of this backend as though it were the current shape; see // Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TrafficPolicies map[string][]*TrafficPolicy `json:"trafficPolicies,omitempty"` VPCAssociations map[string][]vpcAssociation `json:"vpcAssociations,omitempty"` VPCAssocAuthorizations map[string][]VPCAssociationAuthorization `json:"vpcAssocAuthorizations,omitempty"` @@ -108,7 +110,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Tags: b.tags, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "route53 Snapshot failed", "error", err) @@ -157,7 +159,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // silently produce zero-valued garbage (every entry keying to the same // empty zone ID). zones is restored separately below via its own DTO // registry, so it is excluded here. - cleanTables := make(map[string]json.RawMessage, len(snap.Tables)) + cleanTables := make(map[string]jsontext.Value, len(snap.Tables)) for name, raw := range snap.Tables { if name == "zones" { diff --git a/services/route53recoverycluster/misc_test.go b/services/route53recoverycluster/misc_test.go index 127e6aee29..d9ac0c4011 100644 --- a/services/route53recoverycluster/misc_test.go +++ b/services/route53recoverycluster/misc_test.go @@ -1,7 +1,7 @@ package route53recoverycluster_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdkcheck" cluster "github.com/blackbirdworks/gopherstack/services/route53recoverycluster" ) @@ -96,7 +97,7 @@ func TestErrorBodyShape(t *testing.T) { assert.GreaterOrEqual(t, rec.Code, http.StatusBadRequest) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body["message"]) assert.Equal(t, body["message"], body["Message"]) assert.False( diff --git a/services/route53recoverycluster/ops.go b/services/route53recoverycluster/ops.go index e9b0857b66..fad4437ed4 100644 --- a/services/route53recoverycluster/ops.go +++ b/services/route53recoverycluster/ops.go @@ -2,9 +2,10 @@ package route53recoverycluster import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/restroute" cc "github.com/blackbirdworks/gopherstack/services/route53recoverycontrolconfig" ) @@ -48,7 +49,7 @@ func (h *Handler) dispatch(_ context.Context, action, clusterID string, body []b return nil, err } - return json.Marshal(out) + return json.Marshal(out, jsonx.V1()) } func decode(body []byte, v any) error { @@ -56,7 +57,7 @@ func decode(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return restroute.Validation("request body is not valid JSON for this operation") } diff --git a/services/route53recoverycontrolconfig/misc_test.go b/services/route53recoverycontrolconfig/misc_test.go index 4be704046e..2bb4f6d4ca 100644 --- a/services/route53recoverycontrolconfig/misc_test.go +++ b/services/route53recoverycontrolconfig/misc_test.go @@ -2,7 +2,7 @@ package route53recoverycontrolconfig_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdkcheck" cc "github.com/blackbirdworks/gopherstack/services/route53recoverycontrolconfig" ) @@ -156,7 +157,7 @@ func TestErrorBodyShape(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Header().Get("X-Amzn-Errortype")) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body["message"]) assert.Equal(t, body["message"], body["Message"]) assert.False( diff --git a/services/route53recoverycontrolconfig/persistence.go b/services/route53recoverycontrolconfig/persistence.go index 865a6b511d..853018e5ff 100644 --- a/services/route53recoverycontrolconfig/persistence.go +++ b/services/route53recoverycontrolconfig/persistence.go @@ -2,7 +2,7 @@ package route53recoverycontrolconfig import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -13,9 +13,9 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Tokens map[string]string `json:"tokens,omitempty"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Tokens map[string]string `json:"tokens,omitempty"` + Version int `json:"version"` } // Snapshot serialises the backend state. It implements persistence.Persistable. diff --git a/services/route53recoveryreadiness/misc_test.go b/services/route53recoveryreadiness/misc_test.go index 50b1aa7d2f..b77c0257b2 100644 --- a/services/route53recoveryreadiness/misc_test.go +++ b/services/route53recoveryreadiness/misc_test.go @@ -1,7 +1,7 @@ package route53recoveryreadiness_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdkcheck" rr "github.com/blackbirdworks/gopherstack/services/route53recoveryreadiness" ) @@ -174,7 +175,7 @@ func TestErrorBodyShape(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Header().Get("X-Amzn-Errortype")) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.NotEmpty(t, body["message"]) assert.Equal(t, body["message"], body["Message"]) assert.False(t, strings.HasPrefix(body["message"], tt.wantCode)) diff --git a/services/route53recoveryreadiness/persistence.go b/services/route53recoveryreadiness/persistence.go index 169f339698..403d50fe68 100644 --- a/services/route53recoveryreadiness/persistence.go +++ b/services/route53recoveryreadiness/persistence.go @@ -2,7 +2,7 @@ package route53recoveryreadiness import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -13,8 +13,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. It implements persistence.Persistable. diff --git a/services/route53resolver/configs_test.go b/services/route53resolver/configs_test.go index fabb04c2c4..4257c9bd29 100644 --- a/services/route53resolver/configs_test.go +++ b/services/route53resolver/configs_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -90,7 +91,7 @@ func TestResolverDnssecConfig_StatusValues(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["ResolverDNSSECConfig"].(map[string]any) assert.Equal(t, tt.wantStatus, cfg["ValidationStatus"]) } @@ -108,7 +109,7 @@ func TestFirewallConfig_NoArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["FirewallConfig"].(map[string]any) // AWS does not return Arn for FirewallConfig. @@ -629,7 +630,7 @@ func TestListFirewallConfigs_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListFirewallConfigs", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) cfgs, _ := out["FirewallConfigs"].([]any) assert.Len(t, cfgs, tt.wantLen) if tt.wantNextToken { @@ -681,7 +682,7 @@ func TestListResolverConfigs_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverConfigs", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) cfgs, _ := out["ResolverConfigs"].([]any) assert.Len(t, cfgs, tt.wantLen) if tt.wantNextToken { @@ -733,7 +734,7 @@ func TestListResolverDnssecConfigs_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverDnssecConfigs", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) cfgs, _ := out["ResolverDnssecConfigs"].([]any) assert.Len(t, cfgs, tt.wantLen) if tt.wantNextToken { @@ -764,7 +765,7 @@ func TestResolverDnssecConfig_ValidationField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) cfg, ok := out["ResolverDNSSECConfig"].(map[string]any) require.True(t, ok, "ResolverDNSSECConfig must be present") diff --git a/services/route53resolver/dns_querylog.go b/services/route53resolver/dns_querylog.go index e33858d796..de280ec157 100644 --- a/services/route53resolver/dns_querylog.go +++ b/services/route53resolver/dns_querylog.go @@ -2,13 +2,14 @@ package route53resolver import ( "context" - "encoding/json" + "encoding/json/v2" "net" "strings" "time" "github.com/miekg/dns" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" cwlogsbackend "github.com/blackbirdworks/gopherstack/services/cloudwatchlogs" ) @@ -152,7 +153,7 @@ func (b *InMemoryBackend) queryLogJSON(e queryLogEntry) string { }) } - out, _ := json.Marshal(rec) + out, _ := json.Marshal(rec, jsonx.V1()) return string(out) } diff --git a/services/route53resolver/dns_resolver_log_destinations_test.go b/services/route53resolver/dns_resolver_log_destinations_test.go index fbd2b0a11b..a6ba049b1a 100644 --- a/services/route53resolver/dns_resolver_log_destinations_test.go +++ b/services/route53resolver/dns_resolver_log_destinations_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "regexp" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" ) @@ -147,7 +148,7 @@ func TestDNSResolverQueryLogS3Destination(t *testing.T) { for _, l := range lines { var rec map[string]any - require.NoError(t, json.Unmarshal([]byte(l), &rec)) + require.NoError(t, json.Unmarshal([]byte(l), &rec, jsonx.V1())) assert.Equal(t, vpc1, rec["vpc_id"]) names = append(names, rec["query_name"].(string)) } @@ -205,7 +206,7 @@ func TestDNSResolverQueryLogFirehoseDestination(t *testing.T) { assert.True(t, bytes.HasSuffix(rec.data, []byte("\n"))) var body map[string]any - require.NoError(t, json.Unmarshal(rec.data, &body)) + require.NoError(t, json.Unmarshal(rec.data, &body, jsonx.V1())) assert.Equal(t, "q.example.", body["query_name"]) assert.Equal(t, vpc1, body["vpc_id"]) }) diff --git a/services/route53resolver/dns_resolver_log_test.go b/services/route53resolver/dns_resolver_log_test.go index cb5f2f5a29..02523d24ab 100644 --- a/services/route53resolver/dns_resolver_log_test.go +++ b/services/route53resolver/dns_resolver_log_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "net" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ec2backend "github.com/blackbirdworks/gopherstack/services/ec2" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -117,7 +118,7 @@ func TestDNSResolverQueryLogging(t *testing.T) { } var rec map[string]any - require.NoError(t, json.Unmarshal([]byte(msgs[0]), &rec)) + require.NoError(t, json.Unmarshal([]byte(msgs[0]), &rec, jsonx.V1())) assert.Equal(t, vpc1, rec["vpc_id"]) assert.Equal(t, "q.example.", rec["query_name"]) assert.Equal(t, "A", rec["query_type"]) diff --git a/services/route53resolver/firewall_domain_lists_test.go b/services/route53resolver/firewall_domain_lists_test.go index 6c8d7373aa..5ca48a369b 100644 --- a/services/route53resolver/firewall_domain_lists_test.go +++ b/services/route53resolver/firewall_domain_lists_test.go @@ -1,13 +1,14 @@ package route53resolver_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -22,7 +23,7 @@ func TestFirewallDomainList_ManagedOwnerName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dl := resp["FirewallDomainList"].(map[string]any) // ManagedOwnerName should be empty for user-created domain lists. @@ -78,7 +79,7 @@ func TestCreateFirewallDomainList(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dl, ok := resp["FirewallDomainList"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, dl["Name"]) @@ -108,7 +109,7 @@ func TestDeleteFirewallDomainList(t *testing.T) { rec := doRequest(t, h, "CreateFirewallDomainList", map[string]any{"Name": "dl-del"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["FirewallDomainList"].(map[string]any)["Id"].(string) }, @@ -149,7 +150,7 @@ func TestDeleteFirewallDomainList(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dl, ok := resp["FirewallDomainList"].(map[string]any) require.True(t, ok) assert.Equal(t, id, dl["Id"]) @@ -170,7 +171,7 @@ func TestListFirewallDomains_Pagination(t *testing.T) { dlRec := doRequest(t, h, "CreateFirewallDomainList", map[string]any{"Name": "dl-paginate"}) require.Equal(t, http.StatusOK, dlRec.Code) var dlOut map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlOut)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlOut, jsonx.V1())) dlID := dlOut["FirewallDomainList"].(map[string]any)["Id"].(string) domains := []string{"example.com", "foo.com", "bar.com", "baz.com"} @@ -207,7 +208,7 @@ func TestListFirewallDomains_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListFirewallDomains", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) domList, _ := out["Domains"].([]any) assert.Len(t, domList, tt.wantLen) if tt.wantNextToken { @@ -296,7 +297,7 @@ func TestListFirewallDomainLists_NoOverwideLeak(t *testing.T) { var raw struct { FirewallDomainLists []map[string]any `json:"FirewallDomainLists"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.FirewallDomainLists, 1) item := raw.FirewallDomainLists[0] diff --git a/services/route53resolver/firewall_rule_groups_test.go b/services/route53resolver/firewall_rule_groups_test.go index 849495e23b..756f1e89c6 100644 --- a/services/route53resolver/firewall_rule_groups_test.go +++ b/services/route53resolver/firewall_rule_groups_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -24,7 +25,7 @@ func TestFirewallRuleGroup_ShareStatusAndTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rg := resp["FirewallRuleGroup"].(map[string]any) assert.Equal(t, "NOT_SHARED", rg["ShareStatus"]) @@ -80,7 +81,7 @@ func TestFirewallRuleGroupAssociation_MutationProtection(t *testing.T) { ) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocBody := map[string]any{ @@ -98,7 +99,7 @@ func TestFirewallRuleGroupAssociation_MutationProtection(t *testing.T) { if tt.wantAssocCode == http.StatusOK && tt.tryDisassociate { var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) assocID := assocResp["FirewallRuleGroupAssociation"].(map[string]any)["Id"].(string) disassocRec := doRequest(t, h, "DisassociateFirewallRuleGroup", map[string]any{ @@ -139,7 +140,7 @@ func TestUpdateFirewallRuleGroupAssociation_MutationProtection(t *testing.T) { ) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateFirewallRuleGroup", map[string]any{ @@ -150,7 +151,7 @@ func TestUpdateFirewallRuleGroupAssociation_MutationProtection(t *testing.T) { }) require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) assocID := assocResp["FirewallRuleGroupAssociation"].(map[string]any)["Id"].(string) rec := doRequest(t, h, "UpdateFirewallRuleGroupAssociation", map[string]any{ @@ -161,7 +162,7 @@ func TestUpdateFirewallRuleGroupAssociation_MutationProtection(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc := resp["FirewallRuleGroupAssociation"].(map[string]any) assert.Equal(t, tt.mutationProtection, assoc["MutationProtection"]) } @@ -184,7 +185,7 @@ func TestUpdateFirewallRuleGroupAssociation_RejectedMutationProtectionLeavesStat grpRec := doRequest(t, h, "CreateFirewallRuleGroup", map[string]any{"Name": "upd-mp-partial-grp"}) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateFirewallRuleGroup", map[string]any{ @@ -195,7 +196,7 @@ func TestUpdateFirewallRuleGroupAssociation_RejectedMutationProtectionLeavesStat }) require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) assocID := assocResp["FirewallRuleGroupAssociation"].(map[string]any)["Id"].(string) updateRec := doRequest(t, h, "UpdateFirewallRuleGroupAssociation", map[string]any{ @@ -211,7 +212,7 @@ func TestUpdateFirewallRuleGroupAssociation_RejectedMutationProtectionLeavesStat }) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assoc := getResp["FirewallRuleGroupAssociation"].(map[string]any) assert.Equal(t, "original-name", assoc["Name"]) assert.InDelta(t, float64(100), assoc["Priority"], 0) @@ -227,7 +228,7 @@ func TestFirewallRuleGroupAssociation_CreatorAndTimestamps(t *testing.T) { grpRec := doRequest(t, h, "CreateFirewallRuleGroup", map[string]any{"Name": "assoc-ts-grp"}) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateFirewallRuleGroup", map[string]any{ @@ -240,7 +241,7 @@ func TestFirewallRuleGroupAssociation_CreatorAndTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, assocRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &resp, jsonx.V1())) assoc := resp["FirewallRuleGroupAssociation"].(map[string]any) assert.Equal(t, "req-assoc-ts-1", assoc["CreatorRequestId"]) @@ -314,7 +315,7 @@ func TestListFirewallRuleGroups_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, _ := resp["FirewallRuleGroups"].([]any) assert.Len(t, groups, tt.wantLen) nextToken, _ := resp["NextToken"].(string) @@ -362,7 +363,7 @@ func TestCreateFirewallRuleGroup(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) grp, ok := resp["FirewallRuleGroup"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, grp["Name"]) @@ -393,7 +394,7 @@ func TestAssociateFirewallRuleGroup(t *testing.T) { rec := doRequest(t, h, "CreateFirewallRuleGroup", map[string]any{"Name": "grp-assoc"}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["FirewallRuleGroup"].(map[string]any)["Id"].(string) }, @@ -449,7 +450,7 @@ func TestAssociateFirewallRuleGroup(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc, ok := resp["FirewallRuleGroupAssociation"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantStatus, assoc["Status"]) @@ -511,7 +512,7 @@ func TestListFirewallRuleGroupAssociations_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, grpRec.Code) var grpOut map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpOut)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpOut, jsonx.V1())) grpID := grpOut["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateFirewallRuleGroup", map[string]any{ @@ -548,7 +549,7 @@ func TestListFirewallRuleGroupAssociations_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListFirewallRuleGroupAssociations", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assocs, _ := out["FirewallRuleGroupAssociations"].([]any) assert.Len(t, assocs, tt.wantLen) if tt.wantNextToken { @@ -575,7 +576,7 @@ func TestListFirewallRuleGroupAssociations_PriorityStatusFilters(t *testing.T) { }) require.Equal(t, http.StatusOK, grpRec.Code) var grpOut map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpOut)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpOut, jsonx.V1())) grpID := grpOut["FirewallRuleGroup"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateFirewallRuleGroup", map[string]any{ @@ -603,7 +604,7 @@ func TestListFirewallRuleGroupAssociations_PriorityStatusFilters(t *testing.T) { listRec := doRequest(t, h, "ListFirewallRuleGroupAssociations", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assocs, _ := out["FirewallRuleGroupAssociations"].([]any) assert.Len(t, assocs, tt.wantLen) }) diff --git a/services/route53resolver/firewall_rules_test.go b/services/route53resolver/firewall_rules_test.go index b59d118db8..6eb64cd7d8 100644 --- a/services/route53resolver/firewall_rules_test.go +++ b/services/route53resolver/firewall_rules_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -135,7 +136,7 @@ func TestFirewallRule_BlockOverrideFields(t *testing.T) { ) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec := doRequest( @@ -146,7 +147,7 @@ func TestFirewallRule_BlockOverrideFields(t *testing.T) { ) require.Equal(t, http.StatusOK, dlRec.Code) var dlResp map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp, jsonx.V1())) dlID := dlResp["FirewallDomainList"].(map[string]any)["Id"].(string) rec := doRequest(t, h, "CreateFirewallRule", tt.body(groupID, dlID)) @@ -154,7 +155,7 @@ func TestFirewallRule_BlockOverrideFields(t *testing.T) { if tt.wantCode == http.StatusOK && tt.checkFn != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["FirewallRule"].(map[string]any) tt.checkFn(t, rule) } @@ -215,13 +216,13 @@ func TestUpdateFirewallRule_ExtendedFields(t *testing.T) { ) require.Equal(t, http.StatusOK, grpRec2.Code) var grpR2 map[string]any - require.NoError(t, json.Unmarshal(grpRec2.Body.Bytes(), &grpR2)) + require.NoError(t, json.Unmarshal(grpRec2.Body.Bytes(), &grpR2, jsonx.V1())) gID := grpR2["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec2 := doRequest(t, h2, "CreateFirewallDomainList", map[string]any{"Name": "upd-dl"}) require.Equal(t, http.StatusOK, dlRec2.Code) var dlR2 map[string]any - require.NoError(t, json.Unmarshal(dlRec2.Body.Bytes(), &dlR2)) + require.NoError(t, json.Unmarshal(dlRec2.Body.Bytes(), &dlR2, jsonx.V1())) dID := dlR2["FirewallDomainList"].(map[string]any)["Id"].(string) createRec2 := doRequest(t, h2, "CreateFirewallRule", map[string]any{ @@ -240,7 +241,7 @@ func TestUpdateFirewallRule_ExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["FirewallRule"].(map[string]any) tt.checkFn(t, rule) }) @@ -257,13 +258,13 @@ func TestFirewallRule_MetadataFields(t *testing.T) { grpRec := doRequest(t, h, "CreateFirewallRuleGroup", map[string]any{"Name": "meta-grp"}) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec := doRequest(t, h, "CreateFirewallDomainList", map[string]any{"Name": "meta-dl"}) require.Equal(t, http.StatusOK, dlRec.Code) var dlResp map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp, jsonx.V1())) dlID := dlResp["FirewallDomainList"].(map[string]any)["Id"].(string) createRec := doRequest(t, h, "CreateFirewallRule", map[string]any{ @@ -277,7 +278,7 @@ func TestFirewallRule_MetadataFields(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["FirewallRule"].(map[string]any) assert.Equal(t, "req-fw-rule-1", rule["CreatorRequestId"]) @@ -347,7 +348,7 @@ func TestFirewallRule_BlockResponseStoredOnCreate(t *testing.T) { ) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec := doRequest( @@ -358,7 +359,7 @@ func TestFirewallRule_BlockResponseStoredOnCreate(t *testing.T) { ) require.Equal(t, http.StatusOK, dlRec.Code) var dlResp map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp, jsonx.V1())) dlID := dlResp["FirewallDomainList"].(map[string]any)["Id"].(string) body := map[string]any{ @@ -377,7 +378,7 @@ func TestFirewallRule_BlockResponseStoredOnCreate(t *testing.T) { if tt.wantCode == http.StatusOK && tt.blockResponse != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["FirewallRule"].(map[string]any) assert.Equal(t, tt.blockResponse, rule["BlockResponse"]) } @@ -414,7 +415,7 @@ func TestCreateFirewallRule_ActionValidation(t *testing.T) { require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) grpID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) rec := doRequest(t, h, "CreateFirewallRule", map[string]any{ @@ -447,13 +448,13 @@ func TestCreateFirewallRule_NoIdOrArnInOutput(t *testing.T) { require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) grpID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec := doRequest(t, h, "CreateFirewallDomainList", map[string]any{"Name": "id-test-dl"}) require.Equal(t, http.StatusOK, dlRec.Code) var dlResp map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp, jsonx.V1())) dlID := dlResp["FirewallDomainList"].(map[string]any)["Id"].(string) ruleRec := doRequest(t, h, "CreateFirewallRule", map[string]any{ @@ -466,7 +467,7 @@ func TestCreateFirewallRule_NoIdOrArnInOutput(t *testing.T) { require.Equal(t, http.StatusOK, ruleRec.Code) var ruleResp map[string]any - require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp)) + require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp, jsonx.V1())) rule, ok := ruleResp["FirewallRule"].(map[string]any) require.True(t, ok) _, hasID := rule["Id"] @@ -488,7 +489,7 @@ func TestCreateFirewallRule_IncrementsRuleCount(t *testing.T) { require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) grp := grpResp["FirewallRuleGroup"].(map[string]any) grpID := grp["Id"].(string) assert.InDelta(t, 0, grp["RuleCount"], 0) @@ -526,13 +527,13 @@ func TestCreateFirewallRule(t *testing.T) { grpRec := doRequest(t, h, "CreateFirewallRuleGroup", map[string]any{"Name": "grp-rule"}) require.Equal(t, http.StatusOK, grpRec.Code) var grpResp map[string]any - require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp)) + require.NoError(t, json.Unmarshal(grpRec.Body.Bytes(), &grpResp, jsonx.V1())) groupID := grpResp["FirewallRuleGroup"].(map[string]any)["Id"].(string) dlRec := doRequest(t, h, "CreateFirewallDomainList", map[string]any{"Name": "dl-for-rule"}) require.Equal(t, http.StatusOK, dlRec.Code) var dlResp map[string]any - require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp)) + require.NoError(t, json.Unmarshal(dlRec.Body.Bytes(), &dlResp, jsonx.V1())) dlID := dlResp["FirewallDomainList"].(map[string]any)["Id"].(string) return groupID, dlID @@ -600,7 +601,7 @@ func TestCreateFirewallRule(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule, ok := resp["FirewallRule"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantAction, rule["Action"]) diff --git a/services/route53resolver/handler.go b/services/route53resolver/handler.go index ecf37d0fa3..54743e838b 100644 --- a/services/route53resolver/handler.go +++ b/services/route53resolver/handler.go @@ -2,7 +2,7 @@ package route53resolver import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/collections" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -121,7 +122,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } var req extractResolverResourceInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.Name } @@ -153,14 +154,11 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { msg := wireMessage(err) - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrNotFound): payload := errorPayload("ResourceNotFoundException", msg) @@ -188,7 +186,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err // FirewallRule ops model ValidationException (see ErrBatchValidation) -- // left untyped rather than guessing which family reached this path. case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{keyMessageField: msg}) default: // InternalServiceErrorException is modeled on all 72 operations @@ -309,7 +307,7 @@ func updateSimpleConfig[TConfig, TOutput any]( // errorPayload carries the message under both casings: schema-driven SDK deserializers // (route53resolver v1.53+) only match the modeled member name "Message". func errorPayload(errType, msg string) []byte { - payload, _ := json.Marshal(map[string]string{"__type": errType, "message": msg, "Message": msg}) + payload, _ := json.Marshal(map[string]string{"__type": errType, "message": msg, "Message": msg}, jsonx.V1()) return payload } diff --git a/services/route53resolver/handler_error_type_internal_test.go b/services/route53resolver/handler_error_type_internal_test.go index 9c8958e356..9925628592 100644 --- a/services/route53resolver/handler_error_type_internal_test.go +++ b/services/route53resolver/handler_error_type_internal_test.go @@ -2,7 +2,7 @@ package route53resolver import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -43,6 +44,6 @@ func TestHandleError_GenericErrorSurfacesInternalServiceErrorException(t *testin assert.Equal(t, http.StatusInternalServerError, rec.Code) var payload service.JSONErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload, jsonx.V1())) assert.Equal(t, "InternalServiceErrorException", payload.Type) } diff --git a/services/route53resolver/handler_firewall_rules.go b/services/route53resolver/handler_firewall_rules.go index eba373348d..2cfc82b74d 100644 --- a/services/route53resolver/handler_firewall_rules.go +++ b/services/route53resolver/handler_firewall_rules.go @@ -2,7 +2,7 @@ package route53resolver import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "sort" @@ -40,9 +40,9 @@ type dnsThreatProtectionRuleTypeConfigInput struct { type firewallRuleTypeInput struct { DNSThreatProtection *dnsThreatProtectionRuleTypeConfigInput `json:"DnsThreatProtection,omitempty"` - FirewallAdvancedContentCategory json.RawMessage `json:"FirewallAdvancedContentCategory,omitempty"` - FirewallAdvancedThreatCategory json.RawMessage `json:"FirewallAdvancedThreatCategory,omitempty"` - PartnerThreatProtection json.RawMessage `json:"PartnerThreatProtection,omitempty"` + FirewallAdvancedContentCategory jsontext.Value `json:"FirewallAdvancedContentCategory,omitempty"` + FirewallAdvancedThreatCategory jsontext.Value `json:"FirewallAdvancedThreatCategory,omitempty"` + PartnerThreatProtection jsontext.Value `json:"PartnerThreatProtection,omitempty"` } // resolveFirewallRuleType validates a FirewallRuleType tagged union (exactly diff --git a/services/route53resolver/handler_test.go b/services/route53resolver/handler_test.go index 39ef2b21bd..ac3844ac73 100644 --- a/services/route53resolver/handler_test.go +++ b/services/route53resolver/handler_test.go @@ -2,7 +2,8 @@ package route53resolver_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -34,7 +36,7 @@ func doRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -309,7 +311,7 @@ func TestHandlerReset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, ok := resp["ResolverEndpoints"].([]any) require.True(t, ok) assert.Empty(t, endpoints) @@ -326,7 +328,7 @@ func decodeJSON(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &m, jsonx.V1())) return m } diff --git a/services/route53resolver/outpost_resolvers_test.go b/services/route53resolver/outpost_resolvers_test.go index 374137af65..987ccb105b 100644 --- a/services/route53resolver/outpost_resolvers_test.go +++ b/services/route53resolver/outpost_resolvers_test.go @@ -1,13 +1,15 @@ package route53resolver_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOutpostResolverCRUD(t *testing.T) { @@ -143,7 +145,7 @@ func TestCreateOutpostResolver_DefaultInstanceCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resolver, ok := resp["OutpostResolver"].(map[string]any) require.True(t, ok) assert.InDelta(t, 4, resolver["InstanceCount"], 0) @@ -213,7 +215,7 @@ func TestCreateOutpostResolver(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) r, ok := resp["OutpostResolver"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, r["Name"]) @@ -336,7 +338,7 @@ func TestListOutpostResolvers_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListOutpostResolvers", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) resolvers, _ := out["OutpostResolvers"].([]any) assert.Len(t, resolvers, tt.wantLen) if tt.wantNextToken { @@ -389,7 +391,7 @@ func TestListOutpostResolvers_OutpostArnFilter(t *testing.T) { listRec := doRequest(t, h, "ListOutpostResolvers", map[string]any{"OutpostArn": tt.outpostArn}) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) resolvers, _ := out["OutpostResolvers"].([]any) assert.Len(t, resolvers, tt.wantLen) }) diff --git a/services/route53resolver/persistence.go b/services/route53resolver/persistence.go index f83005c724..2684925934 100644 --- a/services/route53resolver/persistence.go +++ b/services/route53resolver/persistence.go @@ -2,9 +2,11 @@ package route53resolver import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -21,7 +23,7 @@ import ( const route53resolverSnapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string][]svcTags.KV `json:"tags"` FirewallRuleGroupPolicies map[string]map[string]string `json:"firewallRuleGroupPolicies"` QueryLogConfigPolicies map[string]map[string]string `json:"queryLogConfigPolicies"` @@ -59,7 +61,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "route53resolver: failed to snapshot backend", "error", err) diff --git a/services/route53resolver/persistence_test.go b/services/route53resolver/persistence_test.go index 084af0dcd5..55267d80e7 100644 --- a/services/route53resolver/persistence_test.go +++ b/services/route53resolver/persistence_test.go @@ -2,13 +2,14 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" svcTags "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -322,7 +323,7 @@ func TestPersistenceRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, epRec.Code) var epResp map[string]any - require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp)) + require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp, jsonx.V1())) ep := epResp["ResolverEndpoint"].(map[string]any) epARN := ep["Arn"].(string) @@ -347,7 +348,7 @@ func TestPersistenceRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) assert.Len(t, tags, 1) } diff --git a/services/route53resolver/query_log_associations_test.go b/services/route53resolver/query_log_associations_test.go index 381741bac6..92ddda93bd 100644 --- a/services/route53resolver/query_log_associations_test.go +++ b/services/route53resolver/query_log_associations_test.go @@ -1,7 +1,7 @@ package route53resolver_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -23,7 +24,7 @@ func TestResolverQueryLogConfigAssociation_Fields(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cfgID := createResp["ResolverQueryLogConfig"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateResolverQueryLogConfig", map[string]any{ @@ -33,7 +34,7 @@ func TestResolverQueryLogConfigAssociation_Fields(t *testing.T) { require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) assoc := assocResp["ResolverQueryLogConfigAssociation"].(map[string]any) assert.NotEmpty(t, assoc["Id"]) @@ -163,7 +164,7 @@ func TestAssociateResolverQueryLogConfig(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ResolverQueryLogConfig"].(map[string]any)["Id"].(string) }, @@ -214,7 +215,7 @@ func TestAssociateResolverQueryLogConfig(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc, ok := resp["ResolverQueryLogConfigAssociation"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantStatus, assoc["Status"]) @@ -286,7 +287,7 @@ func TestListResolverQueryLogConfigAssociations_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, cfgRec.Code) var cfgOut map[string]any - require.NoError(t, json.Unmarshal(cfgRec.Body.Bytes(), &cfgOut)) + require.NoError(t, json.Unmarshal(cfgRec.Body.Bytes(), &cfgOut, jsonx.V1())) cfgID := cfgOut["ResolverQueryLogConfig"].(map[string]any)["Id"].(string) for i := range 3 { @@ -323,7 +324,7 @@ func TestListResolverQueryLogConfigAssociations_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverQueryLogConfigAssociations", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assocs, _ := out["ResolverQueryLogConfigAssociations"].([]any) assert.Len(t, assocs, tt.wantLen) if tt.wantNextToken { @@ -348,7 +349,7 @@ func TestListResolverQueryLogConfigAssociations_Filters(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ResolverQueryLogConfig"].(map[string]any)["Id"].(string) } @@ -413,7 +414,7 @@ func TestListResolverQueryLogConfigAssociations_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assocs, _ := resp["ResolverQueryLogConfigAssociations"].([]any) gotResIDs := make([]string, len(assocs)) for i, a := range assocs { @@ -567,6 +568,6 @@ func TestListResolverQueryLogConfigAssociations_UnknownFilterNameRejected(t *tes require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } diff --git a/services/route53resolver/query_log_configs_test.go b/services/route53resolver/query_log_configs_test.go index 28387dbdbb..b02b5b6229 100644 --- a/services/route53resolver/query_log_configs_test.go +++ b/services/route53resolver/query_log_configs_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -25,7 +26,7 @@ func TestResolverQueryLogConfig_AssociationCount(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cfg := createResp["ResolverQueryLogConfig"].(map[string]any) cfgID := cfg["Id"].(string) assert.EqualValues(t, 0, cfg["AssociationCount"]) @@ -55,7 +56,7 @@ func TestResolverQueryLogConfig_AssociationCount(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) cfg = getResp["ResolverQueryLogConfig"].(map[string]any) assert.EqualValues(t, 2, cfg["AssociationCount"]) } @@ -133,7 +134,7 @@ func TestResolverQueryLogConfig_AssociationCountDecrement(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) cfgID := createResp["ResolverQueryLogConfig"].(map[string]any)["Id"].(string) // Associate. @@ -160,7 +161,7 @@ func TestResolverQueryLogConfig_AssociationCountDecrement(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) cfg := getResp["ResolverQueryLogConfig"].(map[string]any) assert.EqualValues(t, 0, cfg["AssociationCount"]) } @@ -410,7 +411,7 @@ func TestCreateResolverQueryLogConfig(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfg, ok := resp["ResolverQueryLogConfig"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, cfg["Name"]) @@ -465,7 +466,7 @@ func TestListResolverQueryLogConfigs_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverQueryLogConfigs", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) cfgs, _ := out["ResolverQueryLogConfigs"].([]any) assert.Len(t, cfgs, tt.wantLen) if tt.wantNextToken { @@ -538,7 +539,7 @@ func TestListResolverQueryLogConfigs_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfgs, _ := resp["ResolverQueryLogConfigs"].([]any) gotNames := make([]string, len(cfgs)) for i, c := range cfgs { @@ -698,6 +699,6 @@ func TestListResolverQueryLogConfigs_UnknownFilterNameRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } diff --git a/services/route53resolver/resolver_endpoints_test.go b/services/route53resolver/resolver_endpoints_test.go index 1cfe201b57..a449e75abf 100644 --- a/services/route53resolver/resolver_endpoints_test.go +++ b/services/route53resolver/resolver_endpoints_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -77,7 +78,7 @@ func TestResolverEndpoint_IPv6IPAddress(t *testing.T) { if tt.wantCode == http.StatusOK && tt.checkFn != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) _, hasIPs := ep["IpAddresses"] assert.False(t, hasIPs, "ResolverEndpoint response must not carry an IpAddresses field") @@ -88,7 +89,7 @@ func TestResolverEndpoint_IPv6IPAddress(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ips, _ := listResp["IpAddresses"].([]any) tt.checkFn(t, ips) } @@ -148,7 +149,7 @@ func TestResolverEndpoint_Protocols(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) assert.Equal(t, tt.wantProtocols, ep["Protocols"]) } @@ -196,7 +197,7 @@ func TestResolverEndpoint_OutpostFields(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) if outpostArn, ok := tt.body["OutpostArn"]; ok { @@ -222,7 +223,7 @@ func TestResolverEndpoint_CreatorRequestIdAndTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) assert.Equal(t, "req-abc-123", ep["CreatorRequestId"]) @@ -265,7 +266,7 @@ func TestResolverEndpoint_TypeValidation(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) if tt.epType == "" { assert.Equal(t, "IPV4", ep["ResolverEndpointType"]) @@ -334,7 +335,7 @@ func TestUpdateResolverEndpoint_Extended(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) epID := createResp["ResolverEndpoint"].(map[string]any)["Id"].(string) tt.updateBody["ResolverEndpointId"] = epID @@ -343,7 +344,7 @@ func TestUpdateResolverEndpoint_Extended(t *testing.T) { if tt.wantCode == http.StatusOK && tt.checkFn != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) tt.checkFn(t, ep) } @@ -367,7 +368,7 @@ func TestUpdateResolverEndpoint_RejectedTypeLeavesNameUnchanged(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) epID := createResp["ResolverEndpoint"].(map[string]any)["Id"].(string) updateRec := doRequest(t, h, "UpdateResolverEndpoint", map[string]any{ @@ -380,7 +381,7 @@ func TestUpdateResolverEndpoint_RejectedTypeLeavesNameUnchanged(t *testing.T) { getRec := doRequest(t, h, "GetResolverEndpoint", map[string]any{"ResolverEndpointId": epID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ep := getResp["ResolverEndpoint"].(map[string]any) assert.Equal(t, "orig-ep-partial", ep["Name"]) } @@ -615,7 +616,7 @@ func TestAssociateResolverEndpointIpAddress_IPv6(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) epID := createResp["ResolverEndpoint"].(map[string]any)["Id"].(string) assocRec := doRequest(t, h, "AssociateResolverEndpointIpAddress", map[string]any{ @@ -626,7 +627,7 @@ func TestAssociateResolverEndpointIpAddress_IPv6(t *testing.T) { if tt.wantCode == http.StatusOK && tt.checkFn != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) _, hasIPs := ep["IpAddresses"] assert.False(t, hasIPs, "ResolverEndpoint response must not carry an IpAddresses field") @@ -636,7 +637,7 @@ func TestAssociateResolverEndpointIpAddress_IPv6(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ips, _ := listResp["IpAddresses"].([]any) tt.checkFn(t, ips) } @@ -985,7 +986,7 @@ func TestCreateResolverEndpoint_HostVPCIdAndSecurityGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep, ok := resp["ResolverEndpoint"].(map[string]any) require.True(t, ok) assert.Equal(t, "vpc-abc123", ep["HostVPCId"]) @@ -1009,7 +1010,7 @@ func TestListResolverEndpoints_SortedByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, ok := resp["ResolverEndpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 3) @@ -1044,7 +1045,7 @@ func TestCreateResolverEndpoint_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep := resp["ResolverEndpoint"].(map[string]any) epARN := ep["Arn"].(string) @@ -1052,7 +1053,7 @@ func TestCreateResolverEndpoint_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) require.True(t, ok) assert.Len(t, tags, 1) @@ -1084,7 +1085,7 @@ func TestAssociateResolverEndpointIPAddress_Success(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ep := createResp["ResolverEndpoint"].(map[string]any) epID := ep["Id"].(string) @@ -1098,7 +1099,7 @@ func TestAssociateResolverEndpointIPAddress_Success(t *testing.T) { require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) updatedEP := assocResp["ResolverEndpoint"].(map[string]any) _, hasIPs := updatedEP["IpAddresses"] assert.False(t, hasIPs, "ResolverEndpoint response must not carry an IpAddresses field") @@ -1108,7 +1109,7 @@ func TestAssociateResolverEndpointIPAddress_Success(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ips, ok := listResp["IpAddresses"].([]any) require.True(t, ok) assert.Len(t, ips, 1) @@ -1142,7 +1143,7 @@ func TestListResolverEndpoints_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eps, _ := resp["ResolverEndpoints"].([]any) assert.Len(t, eps, tt.wantLen) nextToken, _ := resp["NextToken"].(string) @@ -1166,7 +1167,7 @@ func TestListResolverEndpoints_NextTokenContinuation(t *testing.T) { rec1 := doRequest(t, h, "ListResolverEndpoints", map[string]any{"MaxResults": 2}) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) eps1, _ := resp1["ResolverEndpoints"].([]any) require.Len(t, eps1, 2) nextToken, _ := resp1["NextToken"].(string) @@ -1178,7 +1179,7 @@ func TestListResolverEndpoints_NextTokenContinuation(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) eps2, _ := resp2["ResolverEndpoints"].([]any) assert.NotEmpty(t, eps2) @@ -1227,7 +1228,7 @@ func TestDeleteResolverEndpoint_CascadeDeletesRules(t *testing.T) { require.Equal(t, http.StatusOK, epRec.Code) var epResp map[string]any - require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp)) + require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp, jsonx.V1())) ep := epResp["ResolverEndpoint"].(map[string]any) epID := ep["Id"].(string) @@ -1254,7 +1255,7 @@ func TestDeleteResolverEndpoint_CascadeDeletesRules(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) rules := listResp["ResolverRules"].([]any) assert.Len(t, rules, tt.wantRulesAfter) }) @@ -1306,7 +1307,7 @@ func TestAssociateResolverEndpointIpAddress(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpointID = resp["ResolverEndpoint"].(map[string]any)["Id"].(string) } @@ -1318,7 +1319,7 @@ func TestAssociateResolverEndpointIpAddress(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep, ok := resp["ResolverEndpoint"].(map[string]any) require.True(t, ok) _, hasIPs := ep["IpAddresses"] @@ -1329,7 +1330,7 @@ func TestAssociateResolverEndpointIpAddress(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) ips, _ := listResp["IpAddresses"].([]any) assert.Len(t, ips, tt.wantIPCount) } @@ -1353,7 +1354,7 @@ func TestListResolverEndpointIpAddresses_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) epID := createOut["ResolverEndpoint"].(map[string]any)["Id"].(string) for i := 2; i <= 4; i++ { @@ -1393,7 +1394,7 @@ func TestListResolverEndpointIpAddresses_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverEndpointIpAddresses", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) ips, _ := out["IpAddresses"].([]any) assert.Len(t, ips, tt.wantLen) if tt.wantNextToken { @@ -1419,7 +1420,7 @@ func TestCreateResolverEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ep, ok := resp["ResolverEndpoint"].(map[string]any) require.True(t, ok) assert.Contains(t, ep["Arn"], "arn:aws:route53resolver:") @@ -1437,7 +1438,7 @@ func TestGetResolverEndpoint(t *testing.T) { "Direction": "OUTBOUND", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ep := createResp["ResolverEndpoint"].(map[string]any) id := ep["Id"].(string) @@ -1447,7 +1448,7 @@ func TestGetResolverEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) got := resp["ResolverEndpoint"].(map[string]any) assert.Equal(t, id, got["Id"]) } @@ -1468,7 +1469,7 @@ func TestListResolverEndpoints(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, ok := resp["ResolverEndpoints"].([]any) require.True(t, ok) assert.Len(t, endpoints, 2) @@ -1545,7 +1546,7 @@ func TestListResolverEndpoints_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eps, _ := resp["ResolverEndpoints"].([]any) gotNames := make([]string, len(eps)) for i, e := range eps { @@ -1576,7 +1577,7 @@ func TestListResolverEndpoints_FiltersANDedTogether(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) eps, _ := resp["ResolverEndpoints"].([]any) require.Len(t, eps, 1) assert.Equal(t, "in-vpc2", eps[0].(map[string]any)["Name"]) @@ -1596,7 +1597,7 @@ func TestListResolverEndpoints_UnknownFilterNameRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } @@ -1609,7 +1610,7 @@ func TestDeleteResolverEndpoint(t *testing.T) { "Direction": "INBOUND", }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ep := createResp["ResolverEndpoint"].(map[string]any) id := ep["Id"].(string) diff --git a/services/route53resolver/resolver_rules_test.go b/services/route53resolver/resolver_rules_test.go index e69b3934f1..9fc0729af6 100644 --- a/services/route53resolver/resolver_rules_test.go +++ b/services/route53resolver/resolver_rules_test.go @@ -2,13 +2,14 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -69,7 +70,7 @@ func TestResolverRule_CreatorAndTimestamps(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["ResolverRule"].(map[string]any) assert.Equal(t, "req-rule-001", rule["CreatorRequestId"]) @@ -307,7 +308,7 @@ func TestUpdateResolverRule(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ResolverRule"].(map[string]any)["Id"].(string) }, @@ -445,7 +446,7 @@ func TestListResolverRules_SortedByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, ok := resp["ResolverRules"].([]any) require.True(t, ok) require.Len(t, rules, 2) @@ -469,7 +470,7 @@ func TestDeleteResolverRule_CascadesAssociations(t *testing.T) { require.Equal(t, http.StatusOK, ruleRec.Code) var ruleResp map[string]any - require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp)) + require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp, jsonx.V1())) ruleID := ruleResp["ResolverRule"].(map[string]any)["Id"].(string) // Associate with a VPC. @@ -531,7 +532,7 @@ func TestListResolverRules_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, _ := resp["ResolverRules"].([]any) assert.Len(t, rules, tt.wantLen) nextToken, _ := resp["NextToken"].(string) @@ -585,7 +586,7 @@ func TestCreateResolverRule_EndpointValidation(t *testing.T) { require.Equal(t, http.StatusOK, epRec.Code) var epResp map[string]any - require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp)) + require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp, jsonx.V1())) ep := epResp["ResolverEndpoint"].(map[string]any) endpointID = ep["Id"].(string) } @@ -615,7 +616,7 @@ func TestCreateResolverRule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule, ok := resp["ResolverRule"].(map[string]any) require.True(t, ok) assert.Contains(t, rule["Arn"], "arn:aws:route53resolver:") @@ -681,7 +682,7 @@ func TestListResolverRules(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, ok := resp["ResolverRules"].([]any) require.True(t, ok) assert.Len(t, rules, 2) @@ -748,7 +749,7 @@ func TestListResolverRules_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, _ := resp["ResolverRules"].([]any) gotNames := make([]string, len(rules)) for i, r := range rules { @@ -775,7 +776,7 @@ func TestListResolverRules_UnknownFilterNameRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } @@ -790,7 +791,7 @@ func TestDeleteResolverRule(t *testing.T) { "TargetIps": []map[string]any{{"Ip": "10.0.0.1", "Port": 53}}, }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rule := createResp["ResolverRule"].(map[string]any) id := rule["Id"].(string) @@ -811,7 +812,7 @@ func TestGetResolverRule(t *testing.T) { "TargetIps": []map[string]any{{"Ip": "10.0.0.1", "Port": 53}}, }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rule := createResp["ResolverRule"].(map[string]any) id := rule["Id"].(string) @@ -821,7 +822,7 @@ func TestGetResolverRule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) got, ok := resp["ResolverRule"].(map[string]any) require.True(t, ok) assert.Equal(t, id, got["Id"]) diff --git a/services/route53resolver/rule_associations_test.go b/services/route53resolver/rule_associations_test.go index c06ab7ed85..ad6399c3c6 100644 --- a/services/route53resolver/rule_associations_test.go +++ b/services/route53resolver/rule_associations_test.go @@ -1,7 +1,7 @@ package route53resolver_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -174,7 +175,7 @@ func TestAssociateResolverRule(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["ResolverRule"].(map[string]any)["Id"].(string) }, @@ -226,7 +227,7 @@ func TestAssociateResolverRule(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assoc, ok := resp["ResolverRuleAssociation"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantStatus, assoc["Status"]) @@ -254,7 +255,7 @@ func TestListResolverRuleAssociations_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, ruleRec.Code) var ruleOut map[string]any - require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleOut)) + require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleOut, jsonx.V1())) ruleID := ruleOut["ResolverRule"].(map[string]any)["Id"].(string) for i := range 3 { @@ -292,7 +293,7 @@ func TestListResolverRuleAssociations_Pagination(t *testing.T) { listRec := doRequest(t, h, "ListResolverRuleAssociations", tt.body) require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) assocs, _ := out["ResolverRuleAssociations"].([]any) assert.Len(t, assocs, tt.wantLen) if tt.wantNextToken { @@ -318,7 +319,7 @@ func TestListResolverRuleAssociations_Filters(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ResolverRule"].(map[string]any)["Id"].(string) } @@ -390,7 +391,7 @@ func TestListResolverRuleAssociations_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assocs, _ := resp["ResolverRuleAssociations"].([]any) gotNames := make([]string, len(assocs)) for i, a := range assocs { @@ -413,6 +414,6 @@ func TestListResolverRuleAssociations_UnknownFilterNameRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } diff --git a/services/route53resolver/store_test.go b/services/route53resolver/store_test.go index 7ad5af5bda..f78decb005 100644 --- a/services/route53resolver/store_test.go +++ b/services/route53resolver/store_test.go @@ -2,7 +2,7 @@ package route53resolver_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/route53resolver" ) @@ -29,7 +30,7 @@ func TestFullCRUDLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, epRec.Code) var epResp map[string]any - require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp)) + require.NoError(t, json.Unmarshal(epRec.Body.Bytes(), &epResp, jsonx.V1())) ep := epResp["ResolverEndpoint"].(map[string]any) epID := ep["Id"].(string) assert.NotEmpty(t, ep["CreationTime"]) @@ -48,7 +49,7 @@ func TestFullCRUDLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, ruleRec.Code) var ruleResp map[string]any - require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp)) + require.NoError(t, json.Unmarshal(ruleRec.Body.Bytes(), &ruleResp, jsonx.V1())) rule := ruleResp["ResolverRule"].(map[string]any) ruleID := rule["Id"].(string) assert.Equal(t, "req-rule-lifecycle", rule["CreatorRequestId"]) @@ -61,7 +62,7 @@ func TestFullCRUDLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, qlcRec.Code) var qlcResp map[string]any - require.NoError(t, json.Unmarshal(qlcRec.Body.Bytes(), &qlcResp)) + require.NoError(t, json.Unmarshal(qlcRec.Body.Bytes(), &qlcResp, jsonx.V1())) qlc := qlcResp["ResolverQueryLogConfig"].(map[string]any) qlcID := qlc["Id"].(string) assert.EqualValues(t, 0, qlc["AssociationCount"]) @@ -79,7 +80,7 @@ func TestFullCRUDLifecycle(t *testing.T) { ) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.EqualValues(t, 1, getResp["ResolverQueryLogConfig"].(map[string]any)["AssociationCount"]) // 5. Firewall rule group with timestamps. @@ -89,7 +90,7 @@ func TestFullCRUDLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, frgRec.Code) var frgResp map[string]any - require.NoError(t, json.Unmarshal(frgRec.Body.Bytes(), &frgResp)) + require.NoError(t, json.Unmarshal(frgRec.Body.Bytes(), &frgResp, jsonx.V1())) frg := frgResp["FirewallRuleGroup"].(map[string]any) frgID := frg["Id"].(string) assert.Equal(t, "NOT_SHARED", frg["ShareStatus"]) diff --git a/services/route53resolver/tags_test.go b/services/route53resolver/tags_test.go index 339a806bab..ff85d1dbbc 100644 --- a/services/route53resolver/tags_test.go +++ b/services/route53resolver/tags_test.go @@ -1,12 +1,14 @@ package route53resolver_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTagResource(t *testing.T) { @@ -72,7 +74,7 @@ func TestListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ep := createResp["ResolverEndpoint"].(map[string]any) epARN := ep["Arn"].(string) @@ -90,7 +92,7 @@ func TestListTagsForResource(t *testing.T) { assert.Equal(t, tt.wantCode, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].([]any) assert.Len(t, tags, tt.wantTags) }) @@ -115,7 +117,7 @@ func TestListTagsForResource_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) epARN := createResp["ResolverEndpoint"].(map[string]any)["Arn"].(string) tagRec := doRequest(t, h, "TagResource", map[string]any{ @@ -134,7 +136,7 @@ func TestListTagsForResource_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, page1Rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1, jsonx.V1())) page1Tags := page1["Tags"].([]any) assert.Len(t, page1Tags, 2) nextToken, ok := page1["NextToken"].(string) @@ -148,7 +150,7 @@ func TestListTagsForResource_Pagination(t *testing.T) { }) require.Equal(t, http.StatusOK, page2Rec.Code) var page2 map[string]any - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2, jsonx.V1())) page2Tags := page2["Tags"].([]any) assert.Len(t, page2Tags, 1) _, hasNext := page2["NextToken"] @@ -192,7 +194,7 @@ func TestUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ep := createResp["ResolverEndpoint"].(map[string]any) epARN := ep["Arn"].(string) @@ -220,7 +222,7 @@ func TestUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].([]any) assert.Len(t, tags, tt.wantTags) }) diff --git a/services/rum/events.go b/services/rum/events.go index 88d70ec2fd..c0c3af5ef8 100644 --- a/services/rum/events.go +++ b/services/rum/events.go @@ -3,12 +3,14 @@ package rum import ( "cmp" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/restroute" @@ -79,11 +81,11 @@ func validateEvents(in *PutEventsInput) error { return invalid("RumEvents[%d] requires id, type and a timestamp", i) } - if !json.Valid([]byte(e.Details)) { + if !jsontext.Value([]byte(e.Details)).IsValid(jsonx.V1()) { return invalid("RumEvents[%d].details must be valid JSON", i) } - if e.Metadata != "" && !json.Valid([]byte(e.Metadata)) { + if e.Metadata != "" && !jsontext.Value([]byte(e.Metadata)).IsValid(jsonx.V1()) { return invalid("RumEvents[%d].metadata must be valid JSON", i) } } @@ -98,7 +100,7 @@ func decodeJSON(s string) any { var v any - _ = json.Unmarshal([]byte(s), &v) + _ = json.Unmarshal([]byte(s), &v, jsonx.V1()) return v } @@ -202,7 +204,7 @@ func (b *InMemoryBackend) storeEvents(in *PutEventsInput) (delivery, error) { doc := eventDoc(m, in, e) - raw, merr := json.Marshal(doc) + raw, merr := json.Marshal(doc, jsonx.V1()) if merr != nil { return delivery{}, internalError("could not encode event %s", e.ID) } diff --git a/services/rum/metrics.go b/services/rum/metrics.go index c19985d6b6..15e5fea410 100644 --- a/services/rum/metrics.go +++ b/services/rum/metrics.go @@ -3,13 +3,14 @@ package rum import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "errors" "slices" "strings" "github.com/google/uuid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/restroute" ) @@ -184,7 +185,7 @@ func (b *InMemoryBackend) ListRumMetricsDestinations( func jsonObjectOf(s string) (map[string]any, error) { var out map[string]any - if err := json.Unmarshal([]byte(s), &out); err != nil || out == nil { + if err := json.Unmarshal([]byte(s), &out, jsonx.V1()); err != nil || out == nil { return nil, invalid("EventPattern must be a JSON object") } diff --git a/services/rum/pattern.go b/services/rum/pattern.go index dcbf6f5110..c9da1400e6 100644 --- a/services/rum/pattern.go +++ b/services/rum/pattern.go @@ -1,13 +1,14 @@ package rum import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/eventpattern" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // matchPattern reports whether doc satisfies an EventBridge-style event pattern. @@ -176,7 +177,7 @@ func pathString(doc map[string]any, path string) (string, bool) { func parseObject(s string) (map[string]any, error) { var out map[string]any - if err := json.Unmarshal([]byte(s), &out); err != nil || out == nil { + if err := json.Unmarshal([]byte(s), &out, jsonx.V1()); err != nil || out == nil { return nil, fmt.Errorf("%w: %q", errNotObject, s) } diff --git a/services/rum/pattern_test.go b/services/rum/pattern_test.go index 3692f0295b..e3569071f0 100644 --- a/services/rum/pattern_test.go +++ b/services/rum/pattern_test.go @@ -1,12 +1,13 @@ package rum_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/rum" ) @@ -42,14 +43,14 @@ func TestMatchPattern(t *testing.T) { } var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(event), &doc)) + require.NoError(t, json.Unmarshal([]byte(event), &doc, jsonx.V1())) for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() var pattern map[string]any - require.NoError(t, json.Unmarshal([]byte(tc.pattern), &pattern)) + require.NoError(t, json.Unmarshal([]byte(tc.pattern), &pattern, jsonx.V1())) assert.Equal(t, tc.want, rum.MatchPattern(pattern, doc)) }) } diff --git a/services/rum/persistence.go b/services/rum/persistence.go index a44c7f3227..0d0e27f1e3 100644 --- a/services/rum/persistence.go +++ b/services/rum/persistence.go @@ -2,7 +2,7 @@ package rum import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,11 +12,11 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Seq int64 `json:"seq"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Seq int64 `json:"seq"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/rum/policies.go b/services/rum/policies.go index 6af42d3703..e62550a6a8 100644 --- a/services/rum/policies.go +++ b/services/rum/policies.go @@ -2,9 +2,11 @@ package rum import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutResourcePolicy attaches or replaces the resource policy of an app monitor. @@ -16,10 +18,10 @@ func (b *InMemoryBackend) PutResourcePolicy( } var parsed struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if doc == "" || json.Unmarshal([]byte(doc), &parsed) != nil || len(parsed.Statement) == 0 { + if doc == "" || json.Unmarshal([]byte(doc), &parsed, jsonx.V1()) != nil || len(parsed.Statement) == 0 { return "", "", malformedPolicy("PolicyDocument must be a JSON policy with a Statement") } diff --git a/services/rum/realclient_helpers_test.go b/services/rum/realclient_helpers_test.go index 8feffb1fdc..d1b3d58bce 100644 --- a/services/rum/realclient_helpers_test.go +++ b/services/rum/realclient_helpers_test.go @@ -2,7 +2,7 @@ package rum_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net" "net/http" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/services/rum" ) @@ -250,7 +251,7 @@ func mustJSON(t *testing.T, s string) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal([]byte(s), &m)) + require.NoError(t, json.Unmarshal([]byte(s), &m, jsonx.V1())) return m } diff --git a/services/s3/authz.go b/services/s3/authz.go index 7787606535..6082f8ccac 100644 --- a/services/s3/authz.go +++ b/services/s3/authz.go @@ -2,11 +2,12 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Data-plane access control. This file evaluates the stored bucket policy + ACL @@ -109,7 +110,7 @@ func (h *S3Handler) evaluateBucketPolicy( } var doc bucketPolicyDocument - if uErr := json.Unmarshal([]byte(arn.Canonical(policyJSON)), &doc); uErr != nil { + if uErr := json.Unmarshal([]byte(arn.Canonical(policyJSON)), &doc, jsonx.V1()); uErr != nil { return effectDefault } diff --git a/services/s3/bucket_ops_acl_policy.go b/services/s3/bucket_ops_acl_policy.go index f4418ed9b6..06f2d2662f 100644 --- a/services/s3/bucket_ops_acl_policy.go +++ b/services/s3/bucket_ops_acl_policy.go @@ -2,7 +2,8 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "errors" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validCannedACLs is the complete set of canned ACL strings that AWS S3 accepts @@ -153,7 +155,7 @@ func (h *S3Handler) putBucketPolicy( // Real S3 rejects a policy body that isn't valid JSON with 400 // MalformedPolicy before ever persisting it. - if !json.Valid(body) { + if !jsontext.Value(body).IsValid(jsonx.V1()) { WriteError(ctx, w, r, ErrMalformedPolicy) return @@ -501,7 +503,7 @@ func (h *S3Handler) handleGetBucketPolicyStatus( // contains an Allow statement that grants s3:GetObject to the wildcard principal "*". func policyGrantsPublicGetObject(policyJSON string) bool { var doc policyDoc - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return false } diff --git a/services/s3/bucket_policy_validation.go b/services/s3/bucket_policy_validation.go index 27ebc6ecc2..90c19f72f3 100644 --- a/services/s3/bucket_policy_validation.go +++ b/services/s3/bucket_policy_validation.go @@ -2,7 +2,11 @@ package s3 import ( "bytes" - "encoding/json" + "encoding/json/v2" + + "encoding/json/jsontext" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validateBucketPolicyDocument checks a PutBucketPolicy request body against the @@ -20,8 +24,8 @@ import ( // else *ErrorResponse{Code: "MalformedPolicy"} describing the first violation // (real S3's fail-fast behavior). func validateBucketPolicyDocument(body []byte) *ErrorResponse { - var top map[string]json.RawMessage - if err := json.Unmarshal(body, &top); err != nil { + var top map[string]jsontext.Value + if err := json.Unmarshal(body, &top, jsonx.V1()); err != nil { // json.Valid() already gated non-JSON bodies before this is called; // this branch covers valid-JSON-but-not-an-object bodies (e.g. a bare // JSON array, string, or number), which real S3 also rejects. @@ -61,14 +65,14 @@ func validateBucketPolicyDocument(body []byte) *ErrorResponse { // validatePolicyVersion validates the optional top-level Version element. AWS // accepts a policy with no Version at all (version_block is marked `?` in the // grammar), but if present it must be exactly "2008-10-17" or "2012-10-17". -func validatePolicyVersion(top map[string]json.RawMessage) *ErrorResponse { +func validatePolicyVersion(top map[string]jsontext.Value) *ErrorResponse { raw, ok := top["Version"] if !ok { return nil } var version string - if err := json.Unmarshal(raw, &version); err != nil { + if err := json.Unmarshal(raw, &version, jsonx.V1()); err != nil { return &ErrorResponse{ Code: errMalformedPolicy, Message: "The policy must contain a valid version string.", @@ -88,7 +92,7 @@ func validatePolicyVersion(top map[string]json.RawMessage) *ErrorResponse { // decodePolicyStatements decodes the "Statement" value, which per the IAM // grammar convention may be either a single statement object or a JSON array // of statement objects. -func decodePolicyStatements(raw json.RawMessage) ([]map[string]json.RawMessage, *ErrorResponse) { +func decodePolicyStatements(raw jsontext.Value) ([]map[string]jsontext.Value, *ErrorResponse) { trimmed := bytes.TrimSpace(raw) shapeErr := &ErrorResponse{ Code: errMalformedPolicy, @@ -100,27 +104,27 @@ func decodePolicyStatements(raw json.RawMessage) ([]map[string]json.RawMessage, } if trimmed[0] == '[' { - var arr []map[string]json.RawMessage - if err := json.Unmarshal(trimmed, &arr); err != nil { + var arr []map[string]jsontext.Value + if err := json.Unmarshal(trimmed, &arr, jsonx.V1()); err != nil { return nil, shapeErr } return arr, nil } - var single map[string]json.RawMessage - if err := json.Unmarshal(trimmed, &single); err != nil { + var single map[string]jsontext.Value + if err := json.Unmarshal(trimmed, &single, jsonx.V1()); err != nil { return nil, shapeErr } - return []map[string]json.RawMessage{single}, nil + return []map[string]jsontext.Value{single}, nil } // validatePolicyStatementShape validates a single statement object against // the required grammar elements for a resource-based (bucket) // policy: Effect, Principal/NotPrincipal, Action/NotAction, and // Resource/NotResource. -func validatePolicyStatementShape(stmt map[string]json.RawMessage) *ErrorResponse { +func validatePolicyStatementShape(stmt map[string]jsontext.Value) *ErrorResponse { if effectErr := validatePolicyEffect(stmt); effectErr != nil { return effectErr } @@ -150,14 +154,14 @@ func validatePolicyStatementShape(stmt map[string]json.RawMessage) *ErrorRespons } // validatePolicyEffect validates the required Effect element of a statement. -func validatePolicyEffect(stmt map[string]json.RawMessage) *ErrorResponse { +func validatePolicyEffect(stmt map[string]jsontext.Value) *ErrorResponse { effectRaw, ok := stmt["Effect"] if !ok { return &ErrorResponse{Code: errMalformedPolicy, Message: "Missing required field Effect"} } var effect string - if err := json.Unmarshal(effectRaw, &effect); err != nil || (effect != "Allow" && effect != "Deny") { + if err := json.Unmarshal(effectRaw, &effect, jsonx.V1()); err != nil || (effect != "Allow" && effect != "Deny") { return &ErrorResponse{ Code: errMalformedPolicy, Message: "Invalid effect: " + string(effectRaw), diff --git a/services/s3/express_session.go b/services/s3/express_session.go index 11582ee7f0..4c39c08fed 100644 --- a/services/s3/express_session.go +++ b/services/s3/express_session.go @@ -5,7 +5,7 @@ import ( "crypto/rand" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -13,6 +13,8 @@ import ( "time" "github.com/aws/aws-sdk-go-v2/service/s3/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // s3ExpressSessionTTL matches real S3 Express One Zone: CreateSession @@ -254,7 +256,7 @@ func validateSessionEncryptionContext(encoded, bucket string) error { } var kv map[string]string - if err = json.Unmarshal(raw, &kv); err != nil { + if err = json.Unmarshal(raw, &kv, jsonx.V1()); err != nil { return fmt.Errorf("%w: encryption context must be a JSON object of strings", ErrInvalidArgument) } diff --git a/services/s3/golden_perf_test.go b/services/s3/golden_perf_test.go index 5d0548b539..cc8b47a66d 100644 --- a/services/s3/golden_perf_test.go +++ b/services/s3/golden_perf_test.go @@ -11,7 +11,8 @@ package s3_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/xml" "net/http" "net/http/httptest" @@ -23,6 +24,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -35,9 +37,9 @@ func goldenPath(name string) string { func checkGolden(t *testing.T, name string, got []byte) { t.Helper() - var buf bytes.Buffer - require.NoError(t, json.Indent(&buf, got, "", " ")) - pretty := buf.Bytes() + indented := jsontext.Value(bytes.Clone(got)) + require.NoError(t, indented.Indent(jsonx.V1(), jsontext.WithIndent(" "))) + pretty := []byte(indented) path := goldenPath(name) if os.Getenv("S3_GOLDEN_UPDATE") != "" { @@ -79,7 +81,7 @@ func snapshotResponse(rec *httptest.ResponseRecorder, headerNames []string) gold func marshalGolden(t *testing.T, v any) []byte { t.Helper() - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return data diff --git a/services/s3/notification.go b/services/s3/notification.go index 583cb795e5..54678cb210 100644 --- a/services/s3/notification.go +++ b/services/s3/notification.go @@ -2,7 +2,7 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/v2" "encoding/xml" "fmt" "strings" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // notificationConfiguration mirrors the AWS S3 XML notification configuration @@ -204,7 +205,7 @@ func buildS3EventPayload( } envelope := map[string]any{"Records": []s3EventRecord{record}} - b, err := json.Marshal(envelope) + b, err := json.Marshal(envelope, jsonx.V1()) if err != nil { return "", err } @@ -249,7 +250,7 @@ func buildEventBridgeDetail(bucket, key, etag, reason string, size int64) (strin Reason: reason, } - b, err := json.Marshal(detail) + b, err := json.Marshal(detail, jsonx.V1()) if err != nil { return "", err } diff --git a/services/s3/object_lambda.go b/services/s3/object_lambda.go index d9c5d136ba..a705247ae9 100644 --- a/services/s3/object_lambda.go +++ b/services/s3/object_lambda.go @@ -2,7 +2,7 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -191,7 +192,7 @@ func (h *S3Handler) handleObjectLambdaGetObject( OutputRoute: "gopherstack", OutputToken: token, } - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { h.pendingObjectLambdaRequests.Delete(token) WriteError(ctx, w, r, err) diff --git a/services/s3/object_lambda_features_test.go b/services/s3/object_lambda_features_test.go index be8abb4878..582adff6c8 100644 --- a/services/s3/object_lambda_features_test.go +++ b/services/s3/object_lambda_features_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -118,7 +119,7 @@ func TestObjectLambdaEventUserIdentity(t *testing.T) { select { case p := <-rec.payload: - require.NoError(t, json.Unmarshal(p, &event)) + require.NoError(t, json.Unmarshal(p, &event, jsonx.V1())) case <-t.Context().Done(): t.Fatal("lambda not invoked") } diff --git a/services/s3/object_lambda_head_list.go b/services/s3/object_lambda_head_list.go index dd74360ca2..c6612da5e4 100644 --- a/services/s3/object_lambda_head_list.go +++ b/services/s3/object_lambda_head_list.go @@ -2,7 +2,7 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -137,7 +138,7 @@ func (h *S3Handler) invokeObjectLambda(ctx context.Context, lambdaARN string, ev return nil, ErrNoSuchKey } - payload, err := json.Marshal(ev) + payload, err := json.Marshal(ev, jsonx.V1()) if err != nil { return nil, err } @@ -200,7 +201,7 @@ func (h *S3Handler) callObjectLambda( var res objectLambdaResult out, err := h.invokeObjectLambda(ctx, route.lambdaARN, ev) - if err != nil || json.Unmarshal(out, &res) != nil || res.StatusCode == 0 || + if err != nil || json.Unmarshal(out, &res, jsonx.V1()) != nil || res.StatusCode == 0 || (res.ListResultXML != "" && res.ListBucketResult != nil) { WriteError(ctx, w, r, errObjectLambdaFailed) diff --git a/services/s3/object_lambda_head_list_test.go b/services/s3/object_lambda_head_list_test.go index 0adf068c16..6579bb6d41 100644 --- a/services/s3/object_lambda_head_list_test.go +++ b/services/s3/object_lambda_head_list_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -23,7 +24,7 @@ type recordingObjectLambda struct { func (l *recordingObjectLambda) InvokeFunction(_ context.Context, _, _ string, payload []byte) ([]byte, int, error) { var ev map[string]any - _ = json.Unmarshal(payload, &ev) + _ = json.Unmarshal(payload, &ev, jsonx.V1()) l.mu.Lock() l.events = append(l.events, ev) diff --git a/services/s3/object_lambda_test.go b/services/s3/object_lambda_test.go index a65e2c99d7..d631e2019c 100644 --- a/services/s3/object_lambda_test.go +++ b/services/s3/object_lambda_test.go @@ -2,7 +2,7 @@ package s3_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -42,7 +43,7 @@ func (l *staticObjectLambda) InvokeFunction( OutputToken string `json:"outputToken"` } `json:"getObjectContext"` } - if err := json.Unmarshal(payload, &event); err != nil { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err != nil { return nil, 0, err } diff --git a/services/s3/persistence.go b/services/s3/persistence.go index 6a3c554175..f7b7757461 100644 --- a/services/s3/persistence.go +++ b/services/s3/persistence.go @@ -2,11 +2,13 @@ package s3 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" @@ -32,10 +34,10 @@ const s3SnapshotVersion = 1 // unexported, so encoding/json already skips them. Restore re-initialises those // skipped fields via reinitBucketMutexes/reinitUploadMutexes below. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Tags map[string][]types.Tag `json:"tags"` - DefaultRegion string `json:"defaultRegion"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Tags map[string][]types.Tag `json:"tags"` + DefaultRegion string `json:"defaultRegion"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -175,7 +177,7 @@ func (bucket *StoredBucket) MarshalJSON() ([]byte, error) { bucket.mu.RLock("Snapshot") defer bucket.mu.RUnlock() - return json.Marshal((*storedBucketAlias)(bucket)) + return json.Marshal((*storedBucketAlias)(bucket), jsonx.V1()) } // storedObjectAlias avoids infinite recursion from StoredObject.MarshalJSON. @@ -187,7 +189,7 @@ func (obj *StoredObject) MarshalJSON() ([]byte, error) { obj.mu.RLock("Snapshot") defer obj.mu.RUnlock() - return json.Marshal((*storedObjectAlias)(obj)) + return json.Marshal((*storedObjectAlias)(obj), jsonx.V1()) } // storedMultipartUploadAlias avoids infinite recursion from @@ -200,7 +202,7 @@ func (u *StoredMultipartUpload) MarshalJSON() ([]byte, error) { u.mu.RLock("Snapshot") defer u.mu.RUnlock() - return json.Marshal((*storedMultipartUploadAlias)(u)) + return json.Marshal((*storedMultipartUploadAlias)(u), jsonx.V1()) } // Snapshot implements persistence.Persistable by delegating to the backend. diff --git a/services/s3/realclient_bucket_config_and_object_ops_test.go b/services/s3/realclient_bucket_config_and_object_ops_test.go index 0f07fdb6cf..c87b14972f 100644 --- a/services/s3/realclient_bucket_config_and_object_ops_test.go +++ b/services/s3/realclient_bucket_config_and_object_ops_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "crypto/sha256" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -1136,7 +1137,7 @@ func (l *typedWriteGetObjectResponseLambda) InvokeFunction( OutputToken string `json:"outputToken"` } `json:"getObjectContext"` } - if err := json.Unmarshal(payload, &event); err != nil { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err != nil { l.setResult(true, err) return nil, 0, err diff --git a/services/s3/select_csv.go b/services/s3/select_csv.go index c810961071..7cf29a2716 100644 --- a/services/s3/select_csv.go +++ b/services/s3/select_csv.go @@ -3,11 +3,13 @@ package s3 import ( "bytes" "encoding/csv" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errMissingSQLColumn = errors.New("MissingSQLColumn") @@ -205,7 +207,7 @@ func serializeCSVRowsAsJSON(rows []map[string]string, jsonOut *selectJSONOutput) var buf bytes.Buffer for _, row := range rows { - jsonBytes, err := json.Marshal(mapStringToAny(row)) + jsonBytes, err := json.Marshal(mapStringToAny(row), jsonx.V1()) if err != nil { return nil, err } diff --git a/services/s3/select_json.go b/services/s3/select_json.go index e226781c65..64bdaea521 100644 --- a/services/s3/select_json.go +++ b/services/s3/select_json.go @@ -3,10 +3,12 @@ package s3 import ( "bufio" "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "io" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // evaluateJSONQuery reads JSON from data, applies the SQL query, and streams results. @@ -52,7 +54,7 @@ func evaluateJSONLinesQuery( } var row map[string]any - if err := json.Unmarshal(line, &row); err != nil { + if err := json.Unmarshal(line, &row, jsonx.V1()); err != nil { return 0, fmt.Errorf("parsing JSON line: %w", err) } @@ -95,7 +97,7 @@ func evaluateJSONDocumentQuery( req *selectRequest, ) (int64, error) { var doc any - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &doc, jsonx.V1()); err != nil { return 0, fmt.Errorf("parsing JSON document: %w", err) } @@ -192,7 +194,7 @@ func serializeJSONRows(rows []map[string]any, jsonOut *selectJSONOutput) ([]byte var buf bytes.Buffer for _, row := range rows { - jsonBytes, err := json.Marshal(row) + jsonBytes, err := json.Marshal(row, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/s3control/handler_access_points.go b/services/s3control/handler_access_points.go index e8707a74eb..7caac15787 100644 --- a/services/s3control/handler_access_points.go +++ b/services/s3control/handler_access_points.go @@ -1,7 +1,7 @@ package s3control import ( - "encoding/json" + "encoding/json/jsontext" "encoding/xml" "net/http" "slices" @@ -9,6 +9,8 @@ import ( "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -421,7 +423,7 @@ func (h *Handler) handlePutAccessPointPolicy(c *echo.Context) error { return writeXMLErrorCode(c, http.StatusBadRequest, "MalformedXML", "invalid request body") } - if !json.Valid([]byte(body.Policy)) { + if !jsontext.Value([]byte(body.Policy)).IsValid(jsonx.V1()) { return writeXMLErrorCode(c, http.StatusBadRequest, "MalformedPolicy", "Policy is not valid JSON") } diff --git a/services/s3control/persistence.go b/services/s3control/persistence.go index a18e4984bb..56cbf29618 100644 --- a/services/s3control/persistence.go +++ b/services/s3control/persistence.go @@ -2,10 +2,12 @@ package s3control import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -72,7 +74,7 @@ func newDirtyDTORegistry() ( // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` // batch2 additions (raw, non-*T maps -- never converted to store.Table) BucketReplication map[string]string `json:"bucketReplication"` StorageLensConfigs map[string]string `json:"storageLensConfigs"` @@ -153,7 +155,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NextID: b.nextID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "s3control: failed to snapshot backend", "error", err) @@ -341,7 +343,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreDirtyTablesLocked restores the "dirty" tables (mrapRequests, // accessPointPABs) from tables via the ephemeral DTO registry, converting // each DTO back to its live type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, mrapDTOs, pabDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/s3files/file_systems.go b/services/s3files/file_systems.go index 27464785e8..fe0651a5e2 100644 --- a/services/s3files/file_systems.go +++ b/services/s3files/file_systems.go @@ -2,7 +2,8 @@ package s3files import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" @@ -10,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -287,8 +289,8 @@ func validatePolicy(policy string) error { return validation("Policy must be between 1 and 20480 bytes") } - var doc map[string]json.RawMessage - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + var doc map[string]jsontext.Value + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return validation("Policy is not a valid JSON document") } diff --git a/services/s3files/handler.go b/services/s3files/handler.go index 759ea63e04..5c7e92cfe4 100644 --- a/services/s3files/handler.go +++ b/services/s3files/handler.go @@ -2,7 +2,7 @@ package s3files import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -285,7 +286,7 @@ func decode(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validation("malformed request body") } diff --git a/services/s3files/persistence.go b/services/s3files/persistence.go index 213c401a5a..adc74ec5d9 100644 --- a/services/s3files/persistence.go +++ b/services/s3files/persistence.go @@ -2,7 +2,7 @@ package s3files import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -13,10 +13,10 @@ import ( const s3filesSnapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/s3tables/handler.go b/services/s3tables/handler.go index 17839dc3f9..ef785435a9 100644 --- a/services/s3tables/handler.go +++ b/services/s3tables/handler.go @@ -2,7 +2,7 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -530,7 +531,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ "message": msg, - }) + }, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) diff --git a/services/s3tables/handler_namespaces.go b/services/s3tables/handler_namespaces.go index 0425243eb2..d47716290b 100644 --- a/services/s3tables/handler_namespaces.go +++ b/services/s3tables/handler_namespaces.go @@ -2,10 +2,11 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -23,7 +24,7 @@ func (h *Handler) handleCreateNamespace(ctx context.Context, r *http.Request, bo bucketARN := segs[1] var req createNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -42,7 +43,7 @@ func (h *Handler) handleCreateNamespace(ctx context.Context, r *http.Request, bo return json.Marshal(map[string]any{ keyNamespace: ns.Namespace, keyTableBucketARN: ns.TableBucketARN, - }) + }, jsonx.V1()) } func (h *Handler) handleGetNamespace(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -73,7 +74,7 @@ func (h *Handler) handleGetNamespace(ctx context.Context, r *http.Request, _ []b keyCreatedBy: ns.CreatedBy, keyOwnerAccountID: ns.OwnerAccountID, keyNamespaceID: ns.NamespaceID, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteNamespace(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -143,5 +144,5 @@ func (h *Handler) handleListNamespaces(ctx context.Context, r *http.Request, _ [ resp[keyContinuationToken] = pg.Next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/s3tables/handler_namespaces_test.go b/services/s3tables/handler_namespaces_test.go index 08b4f54d45..73bbd59550 100644 --- a/services/s3tables/handler_namespaces_test.go +++ b/services/s3tables/handler_namespaces_test.go @@ -1,13 +1,15 @@ package s3tables_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Namespace_CRUD(t *testing.T) { @@ -153,7 +155,7 @@ func TestHandler_ListNamespacesIncludesTableBucketId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) namespaces, ok := out["namespaces"].([]any) require.True(t, ok) diff --git a/services/s3tables/handler_replication.go b/services/s3tables/handler_replication.go index 04c9a74988..1aba17cb07 100644 --- a/services/s3tables/handler_replication.go +++ b/services/s3tables/handler_replication.go @@ -2,10 +2,11 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -60,7 +61,7 @@ func (h *Handler) handleGetTableRecordExpirationConfiguration( return json.Marshal(map[string]any{ keyConfiguration: configuration, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableReplication(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -83,7 +84,7 @@ func (h *Handler) handleGetTableReplication(ctx context.Context, r *http.Request "rules": cfg.Rules, }, keyVersionToken: cfg.VersionToken, - }) + }, jsonx.V1()) } // putTableReplicationRequest is the request body for PutTableReplication. @@ -100,7 +101,7 @@ func (h *Handler) handlePutTableReplication(ctx context.Context, r *http.Request versionToken := r.URL.Query().Get(keyVersionToken) var req putTableReplicationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +118,7 @@ func (h *Handler) handlePutTableReplication(ctx context.Context, r *http.Request return json.Marshal(map[string]any{ keyStatusField: replicationStatusCompleted, keyVersionToken: cfg.VersionToken, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableReplicationStatus(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -149,7 +150,7 @@ func (h *Handler) handleGetTableReplicationStatus(ctx context.Context, r *http.R return json.Marshal(map[string]any{ "sourceTableArn": tableArn, "destinations": destinations, - }) + }, jsonx.V1()) } // putTableRecordExpirationRequest is the request body for PutTableRecordExpirationConfiguration. @@ -168,7 +169,7 @@ func (h *Handler) handlePutTableRecordExpirationConfiguration( } var req putTableRecordExpirationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -225,5 +226,5 @@ func (h *Handler) handleGetTableRecordExpirationJobStatus( return json.Marshal(map[string]any{ keyStatusField: status, - }) + }, jsonx.V1()) } diff --git a/services/s3tables/handler_table_bucket_config_test.go b/services/s3tables/handler_table_bucket_config_test.go index 906f19a6bf..1c6bd2730c 100644 --- a/services/s3tables/handler_table_bucket_config_test.go +++ b/services/s3tables/handler_table_bucket_config_test.go @@ -1,7 +1,7 @@ package s3tables_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3tables" ) @@ -227,7 +228,7 @@ func TestHandler_ListTableBucketsIncludesType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) buckets, ok := out["tableBuckets"].([]any) require.True(t, ok) @@ -274,7 +275,7 @@ func TestHandler_GetTableBucketReplication_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) _, hasTopLevelDests := out["destinations"] _, hasFabricatedWrapper := out["replicationConfiguration"] diff --git a/services/s3tables/handler_table_buckets.go b/services/s3tables/handler_table_buckets.go index fd6e08c0b6..4f2bcd7b67 100644 --- a/services/s3tables/handler_table_buckets.go +++ b/services/s3tables/handler_table_buckets.go @@ -2,11 +2,12 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -23,7 +24,7 @@ type createTableBucketRequest struct { func (h *Handler) handleCreateTableBucket(ctx context.Context, _ *http.Request, body []byte) ([]byte, error) { var req createTableBucketRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -45,7 +46,7 @@ func (h *Handler) handleCreateTableBucket(ctx context.Context, _ *http.Request, return json.Marshal(map[string]string{ keyArn: tb.ARN, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableBucket(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -71,7 +72,7 @@ func (h *Handler) handleGetTableBucket(ctx context.Context, r *http.Request, _ [ keyCreatedAt: tb.CreatedAt.UTC().Format("2006-01-02T15:04:05.999Z"), keyType: bucketTypeCustomer, keyTableBucketID: tb.BucketID, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteTableBucket(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -137,7 +138,7 @@ func (h *Handler) handleListTableBuckets(ctx context.Context, r *http.Request, _ resp[keyContinuationToken] = pg.Next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleGetTableBucketMaintenanceConfiguration( @@ -167,7 +168,7 @@ func (h *Handler) handleGetTableBucketMaintenanceConfiguration( return json.Marshal(map[string]any{ keyTableBucketARN: bucketARN, keyConfiguration: cfg, - }) + }, jsonx.V1()) } // putTableBucketMaintenanceRequest is the request body for PutTableBucketMaintenanceConfiguration. @@ -189,7 +190,7 @@ func (h *Handler) handlePutTableBucketMaintenanceConfiguration( maintenanceType := segs[3] var req putTableBucketMaintenanceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -244,7 +245,7 @@ func (h *Handler) handleGetTableBucketEncryption(ctx context.Context, r *http.Re return json.Marshal(map[string]any{ "encryptionConfiguration": tb.Encryption, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteTableBucketEncryption(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -292,7 +293,7 @@ func (h *Handler) handleGetTableBucketMetricsConfiguration( resp["id"] = configID } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleDeleteTableBucketMetricsConfiguration( @@ -347,7 +348,7 @@ func (h *Handler) handleGetTableBucketStorageClass(ctx context.Context, r *http. "storageClassConfiguration": map[string]any{ "storageClass": sc, }, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableBucketReplication(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -370,7 +371,7 @@ func (h *Handler) handleGetTableBucketReplication(ctx context.Context, r *http.R "rules": cfg.Rules, }, keyVersionToken: cfg.VersionToken, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteTableBucketReplication(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -409,7 +410,7 @@ func (h *Handler) handleGetTableBucketPolicy(ctx context.Context, r *http.Reques return json.Marshal(map[string]string{ "resourcePolicy": policy, - }) + }, jsonx.V1()) } // putTableBucketPolicyRequest is the request body for PutTableBucketPolicy. @@ -426,7 +427,7 @@ func (h *Handler) handlePutTableBucketPolicy(ctx context.Context, r *http.Reques bucketARN := segs[1] var req putTableBucketPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -476,7 +477,7 @@ func (h *Handler) handlePutTableBucketEncryption(ctx context.Context, r *http.Re bucketARN := segs[1] var req putTableBucketEncryptionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -530,7 +531,7 @@ func (h *Handler) handlePutTableBucketStorageClass(ctx context.Context, r *http. bucketARN := segs[1] var req putTableBucketStorageClassRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -563,7 +564,7 @@ func (h *Handler) handlePutTableBucketReplication(ctx context.Context, r *http.R versionToken := r.URL.Query().Get(keyVersionToken) var req putTableBucketReplicationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -580,5 +581,5 @@ func (h *Handler) handlePutTableBucketReplication(ctx context.Context, r *http.R return json.Marshal(map[string]any{ keyStatusField: replicationStatusCompleted, keyVersionToken: cfg.VersionToken, - }) + }, jsonx.V1()) } diff --git a/services/s3tables/handler_table_maintenance_test.go b/services/s3tables/handler_table_maintenance_test.go index 21e030bb96..841f74e807 100644 --- a/services/s3tables/handler_table_maintenance_test.go +++ b/services/s3tables/handler_table_maintenance_test.go @@ -1,13 +1,16 @@ package s3tables_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/url" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_MaintenanceConfiguration(t *testing.T) { @@ -329,12 +332,12 @@ func TestHandler_MetadataLocationVsUpdate(t *testing.T) { updateBody, err := json.Marshal(map[string]string{ "metadataLocation": tt.metaLocation, "versionToken": table["versionToken"].(string), - }) + }, jsonx.V1()) require.NoError(t, err) rec := doS3TablesRequest(t, h, http.MethodPut, "/tables/"+encodedARN+"/ns1/t1/metadata-location", - json.RawMessage(updateBody)) + jsontext.Value(updateBody)) require.Equal(t, http.StatusOK, rec.Code) rec2 := doS3TablesRequest(t, h, http.MethodGet, diff --git a/services/s3tables/handler_tables.go b/services/s3tables/handler_tables.go index 95ddfd2166..8c96c648c3 100644 --- a/services/s3tables/handler_tables.go +++ b/services/s3tables/handler_tables.go @@ -2,10 +2,11 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -48,7 +49,7 @@ func (h *Handler) handleGetTableMaintenanceJobStatus( return json.Marshal(map[string]any{ keyTableARN: table.ARN, keyStatusField: jobStatus, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableMetadataLocation(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -73,7 +74,7 @@ func (h *Handler) handleGetTableMetadataLocation(ctx context.Context, r *http.Re keyVersionToken: table.VersionToken, "warehouseLocation": table.WarehouseLocation, keyMetadataLocation: table.MetadataLocation, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableEncryption(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -96,7 +97,7 @@ func (h *Handler) handleGetTableEncryption(ctx context.Context, r *http.Request, return json.Marshal(map[string]any{ "encryptionConfiguration": encCfg, - }) + }, jsonx.V1()) } // createTableRequest is the request body for CreateTable. @@ -123,7 +124,7 @@ func (h *Handler) handleCreateTable(ctx context.Context, r *http.Request, body [ nsName := segs[2] var req createTableRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -155,7 +156,7 @@ func (h *Handler) handleCreateTable(ctx context.Context, r *http.Request, body [ return json.Marshal(map[string]string{ keyTableARN: table.ARN, keyVersionToken: table.VersionToken, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTable(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -211,7 +212,7 @@ func (h *Handler) handleGetTable(ctx context.Context, r *http.Request, _ []byte) keyCreatedBy: table.OwnerAccountID, "modifiedBy": table.OwnerAccountID, keyOwnerAccountID: table.OwnerAccountID, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteTable(ctx context.Context, r *http.Request, _ []byte) ([]byte, error) { @@ -286,7 +287,7 @@ func (h *Handler) handleListTables(ctx context.Context, r *http.Request, _ []byt resp[keyContinuationToken] = pg.Next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // renameTableRequest is the request body for RenameTable. @@ -307,7 +308,7 @@ func (h *Handler) handleRenameTable(ctx context.Context, r *http.Request, body [ name := segs[3] var req renameTableRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -353,7 +354,7 @@ func (h *Handler) handleUpdateTableMetadataLocation(ctx context.Context, r *http name := segs[3] var req updateTableMetadataLocationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -387,7 +388,7 @@ func (h *Handler) handleUpdateTableMetadataLocation(ctx context.Context, r *http keyNamespace: table.Namespace, keyVersionToken: table.VersionToken, keyMetadataLocation: table.MetadataLocation, - }) + }, jsonx.V1()) } func (h *Handler) handleGetTableMaintenanceConfiguration( @@ -419,7 +420,7 @@ func (h *Handler) handleGetTableMaintenanceConfiguration( return json.Marshal(map[string]any{ keyTableARN: tableARN, keyConfiguration: cfg, - }) + }, jsonx.V1()) } // putTableMaintenanceRequest is the request body for PutTableMaintenanceConfiguration. @@ -443,7 +444,7 @@ func (h *Handler) handlePutTableMaintenanceConfiguration( maintenanceType := segs[5] var req putTableMaintenanceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -500,7 +501,7 @@ func (h *Handler) handleGetTablePolicy(ctx context.Context, r *http.Request, _ [ return json.Marshal(map[string]string{ "resourcePolicy": policy, - }) + }, jsonx.V1()) } // putTablePolicyRequest is the request body for PutTablePolicy. @@ -519,7 +520,7 @@ func (h *Handler) handlePutTablePolicy(ctx context.Context, r *http.Request, bod name := segs[3] var req putTablePolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -579,5 +580,5 @@ func (h *Handler) handleGetTableStorageClass(ctx context.Context, r *http.Reques "storageClassConfiguration": map[string]any{ "storageClass": sc, }, - }) + }, jsonx.V1()) } diff --git a/services/s3tables/handler_tables_test.go b/services/s3tables/handler_tables_test.go index 04fe46da34..880a2b86ed 100644 --- a/services/s3tables/handler_tables_test.go +++ b/services/s3tables/handler_tables_test.go @@ -1,7 +1,7 @@ package s3tables_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/url" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Table_Create(t *testing.T) { @@ -518,7 +520,7 @@ func TestHandler_ListTablesResponseUsesLowercaseArns(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tables, ok := out["tables"].([]any) require.True(t, ok) diff --git a/services/s3tables/handler_tags.go b/services/s3tables/handler_tags.go index e9e5b3276e..551bf60571 100644 --- a/services/s3tables/handler_tags.go +++ b/services/s3tables/handler_tags.go @@ -2,10 +2,11 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -27,7 +28,7 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, r *http.Request return json.Marshal(map[string]any{ "tags": tags, - }) + }, jsonx.V1()) } // tagResourceRequest is the request body for TagResource. @@ -44,7 +45,7 @@ func (h *Handler) handleTagResource(ctx context.Context, r *http.Request, body [ resourceArn := segs[1] var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/s3tables/handler_test.go b/services/s3tables/handler_test.go index 40f8002b1a..861b966b0b 100644 --- a/services/s3tables/handler_test.go +++ b/services/s3tables/handler_test.go @@ -2,7 +2,8 @@ package s3tables_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/s3tables" ) @@ -46,7 +48,7 @@ func doS3TablesRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -68,7 +70,7 @@ func parseResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var result map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&result)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &result, jsonx.V1())) return result } diff --git a/services/s3tables/metadata_writer.go b/services/s3tables/metadata_writer.go index 4e4ab31f52..25933b799c 100644 --- a/services/s3tables/metadata_writer.go +++ b/services/s3tables/metadata_writer.go @@ -2,7 +2,7 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "time" @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/iceberg" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -181,7 +182,7 @@ func buildIcebergTableMetadata(t *Table, now time.Time) (*iceberg.TableMetadata, return nil, err } - orderJSON, err := json.Marshal(order) + orderJSON, err := json.Marshal(order, jsonx.V1()) if err != nil { return nil, err } @@ -203,10 +204,10 @@ func buildIcebergTableMetadata(t *Table, now time.Time) (*iceberg.TableMetadata, } func roundTrip(in map[string]any, out any) error { - b, err := json.Marshal(in) + b, err := json.Marshal(in, jsonx.V1()) if err != nil { return err } - return json.Unmarshal(b, out) + return json.Unmarshal(b, out, jsonx.V1()) } diff --git a/services/s3tables/multi_region_test.go b/services/s3tables/multi_region_test.go index cd88cdb18f..f7963aeed4 100644 --- a/services/s3tables/multi_region_test.go +++ b/services/s3tables/multi_region_test.go @@ -2,7 +2,8 @@ package s3tables_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/s3tables" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/s3tables/persistence.go b/services/s3tables/persistence.go index a166d4f092..fc4ddf8946 100644 --- a/services/s3tables/persistence.go +++ b/services/s3tables/persistence.go @@ -2,7 +2,7 @@ package s3tables import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -87,7 +87,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // against decoding a snapshot from an incompatible (older or newer) build // of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -198,7 +198,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold every mutex. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { if err := b.registry.RestoreAll(tables); err != nil { return fmt.Errorf("s3tables: restore clean tables: %w", err) } diff --git a/services/s3tables/validation.go b/services/s3tables/validation.go index 5ec7dda3bd..bed100b367 100644 --- a/services/s3tables/validation.go +++ b/services/s3tables/validation.go @@ -1,10 +1,12 @@ package s3tables import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Real S3 Tables naming rules, confirmed against @@ -133,7 +135,7 @@ func validateNamespaceParts(namespace []string) error { // validatePolicyDocument rejects a resource policy that is not a JSON object. func validatePolicyDocument(policy string) error { var doc map[string]any - if err := json.Unmarshal([]byte(policy), &doc); err != nil || doc == nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil || doc == nil { return fmt.Errorf("%w: resourcePolicy must be a valid JSON policy document", errInvalidRequest) } diff --git a/services/s3vectors/buckets.go b/services/s3vectors/buckets.go index 333cb8e18e..116733a8e5 100644 --- a/services/s3vectors/buckets.go +++ b/services/s3vectors/buckets.go @@ -2,11 +2,12 @@ package s3vectors import ( "cmp" - "encoding/json" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -238,7 +239,7 @@ func (b *InMemoryBackend) putBucketPolicy(sc scope, in *putPolicyIn) (any, error return nil, invalid("policy is required") } - if err := json.Unmarshal([]byte(in.Policy), &doc); err != nil || doc.Statement == nil { + if err := json.Unmarshal([]byte(in.Policy), &doc, jsonx.V1()); err != nil || doc.Statement == nil { return nil, invalid("policy must be a JSON policy document with a Statement") } diff --git a/services/s3vectors/models.go b/services/s3vectors/models.go index bbcd2594ac..5c4728a1f2 100644 --- a/services/s3vectors/models.go +++ b/services/s3vectors/models.go @@ -3,10 +3,12 @@ package s3vectors import ( "encoding/base64" "encoding/binary" - "encoding/json" + "encoding/json/v2" "errors" "math" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // f32s stores a vector as float32 and serialises as base64 little-endian bytes, 4 bytes per dimension. @@ -23,13 +25,13 @@ func (v f32s) MarshalJSON() ([]byte, error) { binary.LittleEndian.PutUint32(buf[bytesPerFloat*i:], math.Float32bits(f)) } - return json.Marshal(base64.StdEncoding.EncodeToString(buf)) + return json.Marshal(base64.StdEncoding.EncodeToString(buf), jsonx.V1()) } // UnmarshalJSON implements json.Unmarshaler. func (v *f32s) UnmarshalJSON(data []byte) error { var s string - if err := json.Unmarshal(data, &s); err != nil { + if err := json.Unmarshal(data, &s, jsonx.V1()); err != nil { return err } diff --git a/services/s3vectors/persistence.go b/services/s3vectors/persistence.go index cc414f37ba..a045579e09 100644 --- a/services/s3vectors/persistence.go +++ b/services/s3vectors/persistence.go @@ -2,7 +2,7 @@ package s3vectors import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/s3vectors/vectors.go b/services/s3vectors/vectors.go index 9920f587bc..f304d79f14 100644 --- a/services/s3vectors/vectors.go +++ b/services/s3vectors/vectors.go @@ -1,11 +1,12 @@ package s3vectors import ( - "encoding/json" + "encoding/json/v2" "hash/fnv" "maps" "slices" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -51,7 +52,7 @@ func validateMetadata(ix *Index, meta any) (map[string]any, error) { return nil, err } - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) total += len(k) + len(raw) if ix.filterable(k) { @@ -136,7 +137,7 @@ func (b *InMemoryBackend) putVectors(sc scope, in *putVectorsIn) (any, error) { } func itoa(i int) string { - b, _ := json.Marshal(i) + b, _ := json.Marshal(i, jsonx.V1()) return string(b) } diff --git a/services/sagemaker/ai_benchmark_jobs.go b/services/sagemaker/ai_benchmark_jobs.go index cbbe082e70..81634fc004 100644 --- a/services/sagemaker/ai_benchmark_jobs.go +++ b/services/sagemaker/ai_benchmark_jobs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -49,9 +51,9 @@ type AIBenchmarkJob struct { AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` RoleArn string `json:"RoleArn"` FailureReason string `json:"FailureReason,omitempty"` - BenchmarkTarget json.RawMessage `json:"BenchmarkTarget"` - OutputConfig json.RawMessage `json:"OutputConfig"` - NetworkConfig json.RawMessage `json:"NetworkConfig,omitempty"` + BenchmarkTarget jsontext.Value `json:"BenchmarkTarget"` + OutputConfig jsontext.Value `json:"OutputConfig"` + NetworkConfig jsontext.Value `json:"NetworkConfig,omitempty"` } // MarshalJSON emits CreationTime/StartTime/EndTime as AWS awsjson1.1 @@ -75,7 +77,7 @@ func (j *AIBenchmarkJob) MarshalJSON() ([]byte, error) { StartTime: epochSecondsPtr(j.StartTime), EndTime: epochSecondsPtr(j.EndTime), Tags: toTagObjects(j.Tags), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [AIBenchmarkJob.MarshalJSON], used by @@ -90,7 +92,7 @@ func (j *AIBenchmarkJob) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -104,9 +106,9 @@ func (j *AIBenchmarkJob) UnmarshalJSON(data []byte) error { func cloneAIBenchmarkJob(j *AIBenchmarkJob) *AIBenchmarkJob { cp := *j cp.Tags = maps.Clone(j.Tags) - cp.BenchmarkTarget = append(json.RawMessage(nil), j.BenchmarkTarget...) - cp.OutputConfig = append(json.RawMessage(nil), j.OutputConfig...) - cp.NetworkConfig = append(json.RawMessage(nil), j.NetworkConfig...) + cp.BenchmarkTarget = append(jsontext.Value(nil), j.BenchmarkTarget...) + cp.OutputConfig = append(jsontext.Value(nil), j.OutputConfig...) + cp.NetworkConfig = append(jsontext.Value(nil), j.NetworkConfig...) if j.StartTime != nil { t := *j.StartTime @@ -152,9 +154,9 @@ type CreateAIBenchmarkJobOptions struct { AIBenchmarkJobName string AIWorkloadConfigIdentifier string RoleArn string - BenchmarkTarget json.RawMessage - OutputConfig json.RawMessage - NetworkConfig json.RawMessage + BenchmarkTarget jsontext.Value + OutputConfig jsontext.Value + NetworkConfig jsontext.Value } // CreateAIBenchmarkJob creates an AI benchmark job and schedules its diff --git a/services/sagemaker/ai_recommendation_jobs.go b/services/sagemaker/ai_recommendation_jobs.go index f7dc069dfd..503eff4eec 100644 --- a/services/sagemaker/ai_recommendation_jobs.go +++ b/services/sagemaker/ai_recommendation_jobs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -53,12 +55,12 @@ type AIRecommendationJob struct { AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` RoleArn string `json:"RoleArn"` FailureReason string `json:"FailureReason,omitempty"` - ModelSource json.RawMessage `json:"ModelSource"` - OutputConfig json.RawMessage `json:"OutputConfig"` - PerformanceTarget json.RawMessage `json:"PerformanceTarget,omitempty"` - ComputeSpec json.RawMessage `json:"ComputeSpec,omitempty"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification,omitempty"` - AdapterSource json.RawMessage `json:"AdapterSource,omitempty"` + ModelSource jsontext.Value `json:"ModelSource"` + OutputConfig jsontext.Value `json:"OutputConfig"` + PerformanceTarget jsontext.Value `json:"PerformanceTarget,omitempty"` + ComputeSpec jsontext.Value `json:"ComputeSpec,omitempty"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification,omitempty"` + AdapterSource jsontext.Value `json:"AdapterSource,omitempty"` } // MarshalJSON emits CreationTime/StartTime/EndTime as AWS awsjson1.1 @@ -83,7 +85,7 @@ func (j *AIRecommendationJob) MarshalJSON() ([]byte, error) { StartTime: epochSecondsPtr(j.StartTime), EndTime: epochSecondsPtr(j.EndTime), Tags: toTagObjects(j.Tags), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [AIRecommendationJob.MarshalJSON], used by @@ -98,7 +100,7 @@ func (j *AIRecommendationJob) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -112,12 +114,12 @@ func (j *AIRecommendationJob) UnmarshalJSON(data []byte) error { func cloneAIRecommendationJob(j *AIRecommendationJob) *AIRecommendationJob { cp := *j cp.Tags = maps.Clone(j.Tags) - cp.ModelSource = append(json.RawMessage(nil), j.ModelSource...) - cp.OutputConfig = append(json.RawMessage(nil), j.OutputConfig...) - cp.PerformanceTarget = append(json.RawMessage(nil), j.PerformanceTarget...) - cp.ComputeSpec = append(json.RawMessage(nil), j.ComputeSpec...) - cp.InferenceSpecification = append(json.RawMessage(nil), j.InferenceSpecification...) - cp.AdapterSource = append(json.RawMessage(nil), j.AdapterSource...) + cp.ModelSource = append(jsontext.Value(nil), j.ModelSource...) + cp.OutputConfig = append(jsontext.Value(nil), j.OutputConfig...) + cp.PerformanceTarget = append(jsontext.Value(nil), j.PerformanceTarget...) + cp.ComputeSpec = append(jsontext.Value(nil), j.ComputeSpec...) + cp.InferenceSpecification = append(jsontext.Value(nil), j.InferenceSpecification...) + cp.AdapterSource = append(jsontext.Value(nil), j.AdapterSource...) if j.StartTime != nil { t := *j.StartTime @@ -169,12 +171,12 @@ type CreateAIRecommendationJobOptions struct { AIRecommendationJobName string AIWorkloadConfigIdentifier string RoleArn string - ModelSource json.RawMessage - OutputConfig json.RawMessage - PerformanceTarget json.RawMessage - ComputeSpec json.RawMessage - InferenceSpecification json.RawMessage - AdapterSource json.RawMessage + ModelSource jsontext.Value + OutputConfig jsontext.Value + PerformanceTarget jsontext.Value + ComputeSpec jsontext.Value + InferenceSpecification jsontext.Value + AdapterSource jsontext.Value } // CreateAIRecommendationJob creates an AI recommendation job and schedules diff --git a/services/sagemaker/ai_workload_configs.go b/services/sagemaker/ai_workload_configs.go index 1b03a81c39..44fa4c4c1e 100644 --- a/services/sagemaker/ai_workload_configs.go +++ b/services/sagemaker/ai_workload_configs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -49,8 +51,8 @@ type AIWorkloadConfig struct { Tags map[string]string `json:"Tags,omitempty"` AIWorkloadConfigName string `json:"AIWorkloadConfigName"` AIWorkloadConfigArn string `json:"AIWorkloadConfigArn"` - WorkloadSpec json.RawMessage `json:"AIWorkloadConfigs,omitempty"` - DatasetConfig json.RawMessage `json:"DatasetConfig,omitempty"` + WorkloadSpec jsontext.Value `json:"AIWorkloadConfigs,omitempty"` + DatasetConfig jsontext.Value `json:"DatasetConfig,omitempty"` } // MarshalJSON emits CreationTime as an AWS awsjson1.1 epoch-seconds number @@ -69,7 +71,7 @@ func (c *AIWorkloadConfig) MarshalJSON() ([]byte, error) { alias: (*alias)(c), Tags: toTagObjects(c.Tags), CreationTime: epochSeconds(c.CreationTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [AIWorkloadConfig.MarshalJSON], used by @@ -82,7 +84,7 @@ func (c *AIWorkloadConfig) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(c)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -94,8 +96,8 @@ func (c *AIWorkloadConfig) UnmarshalJSON(data []byte) error { func cloneAIWorkloadConfig(c *AIWorkloadConfig) *AIWorkloadConfig { cp := *c cp.Tags = maps.Clone(c.Tags) - cp.WorkloadSpec = append(json.RawMessage(nil), c.WorkloadSpec...) - cp.DatasetConfig = append(json.RawMessage(nil), c.DatasetConfig...) + cp.WorkloadSpec = append(jsontext.Value(nil), c.WorkloadSpec...) + cp.DatasetConfig = append(jsontext.Value(nil), c.DatasetConfig...) return &cp } @@ -170,8 +172,8 @@ func (b *InMemoryBackend) aiWorkloadConfigARNIndexStoreRO(r string) map[string]s type CreateAIWorkloadConfigOptions struct { Tags map[string]string AIWorkloadConfigName string - WorkloadSpec json.RawMessage - DatasetConfig json.RawMessage + WorkloadSpec jsontext.Value + DatasetConfig jsontext.Value } // CreateAIWorkloadConfig creates an AI workload configuration. diff --git a/services/sagemaker/algorithms.go b/services/sagemaker/algorithms.go index 519f2870c4..9aba6255aa 100644 --- a/services/sagemaker/algorithms.go +++ b/services/sagemaker/algorithms.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -22,9 +22,9 @@ type CreateAlgorithmOptions struct { Tags map[string]string AlgorithmName string AlgorithmDescription string - TrainingSpecification json.RawMessage - InferenceSpecification json.RawMessage - ValidationSpecification json.RawMessage + TrainingSpecification jsontext.Value + InferenceSpecification jsontext.Value + ValidationSpecification jsontext.Value CertifyForMarketplace bool } diff --git a/services/sagemaker/app_image_configs.go b/services/sagemaker/app_image_configs.go index b6cf47d81e..624e139573 100644 --- a/services/sagemaker/app_image_configs.go +++ b/services/sagemaker/app_image_configs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrAppImageConfigNotFound is returned when an app image config does not exist. @@ -32,17 +34,17 @@ type AppImageConfig struct { Tags map[string]string `json:"Tags,omitempty"` AppImageConfigName string `json:"AppImageConfigName"` AppImageConfigArn string `json:"AppImageConfigArn"` - KernelGatewayImageConfig json.RawMessage `json:"KernelGatewayImageConfig,omitempty"` - JupyterLabAppImageConfig json.RawMessage `json:"JupyterLabAppImageConfig,omitempty"` - CodeEditorAppImageConfig json.RawMessage `json:"CodeEditorAppImageConfig,omitempty"` + KernelGatewayImageConfig jsontext.Value `json:"KernelGatewayImageConfig,omitempty"` + JupyterLabAppImageConfig jsontext.Value `json:"JupyterLabAppImageConfig,omitempty"` + CodeEditorAppImageConfig jsontext.Value `json:"CodeEditorAppImageConfig,omitempty"` } func cloneAppImageConfig(a *AppImageConfig) *AppImageConfig { cp := *a cp.Tags = maps.Clone(a.Tags) - cp.KernelGatewayImageConfig = append(json.RawMessage(nil), a.KernelGatewayImageConfig...) - cp.JupyterLabAppImageConfig = append(json.RawMessage(nil), a.JupyterLabAppImageConfig...) - cp.CodeEditorAppImageConfig = append(json.RawMessage(nil), a.CodeEditorAppImageConfig...) + cp.KernelGatewayImageConfig = append(jsontext.Value(nil), a.KernelGatewayImageConfig...) + cp.JupyterLabAppImageConfig = append(jsontext.Value(nil), a.JupyterLabAppImageConfig...) + cp.CodeEditorAppImageConfig = append(jsontext.Value(nil), a.CodeEditorAppImageConfig...) return &cp } @@ -61,7 +63,7 @@ func (a *AppImageConfig) MarshalJSON() ([]byte, error) { alias: (*alias)(a), CreationTime: epochSeconds(a.CreationTime), LastModifiedTime: epochSeconds(a.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [AppImageConfig.MarshalJSON], read by @@ -75,7 +77,7 @@ func (a *AppImageConfig) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(a)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -90,9 +92,9 @@ func (a *AppImageConfig) UnmarshalJSON(data []byte) error { type CreateAppImageConfigOptions struct { AppImageConfigName string Tags map[string]string - KernelGatewayImageConfig json.RawMessage - JupyterLabAppImageConfig json.RawMessage - CodeEditorAppImageConfig json.RawMessage + KernelGatewayImageConfig jsontext.Value + JupyterLabAppImageConfig jsontext.Value + CodeEditorAppImageConfig jsontext.Value } // CreateAppImageConfig creates an app image config. @@ -154,9 +156,9 @@ func (b *InMemoryBackend) DescribeAppImageConfig(ctx context.Context, name strin // an image's kernel/container/filesystem config had every field silently // dropped -- the same "accept and drop" bug class as UpdateTrainingJob. type UpdateAppImageConfigOptions struct { - KernelGatewayImageConfig json.RawMessage - JupyterLabAppImageConfig json.RawMessage - CodeEditorAppImageConfig json.RawMessage + KernelGatewayImageConfig jsontext.Value + JupyterLabAppImageConfig jsontext.Value + CodeEditorAppImageConfig jsontext.Value } // UpdateAppImageConfig updates an app image config's kernel/container diff --git a/services/sagemaker/apps.go b/services/sagemaker/apps.go index 7af757fe6f..434272f753 100644 --- a/services/sagemaker/apps.go +++ b/services/sagemaker/apps.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -50,14 +50,14 @@ type App struct { AppName string `json:"AppName"` AppArn string `json:"AppArn"` Status string `json:"Status"` - ResourceSpec json.RawMessage `json:"ResourceSpec,omitempty"` + ResourceSpec jsontext.Value `json:"ResourceSpec,omitempty"` RecoveryMode bool `json:"RecoveryMode,omitempty"` } func cloneApp(a *App) *App { cp := *a cp.Tags = maps.Clone(a.Tags) - cp.ResourceSpec = append(json.RawMessage(nil), a.ResourceSpec...) + cp.ResourceSpec = append(jsontext.Value(nil), a.ResourceSpec...) return &cp } @@ -65,7 +65,7 @@ func cloneApp(a *App) *App { // CreateAppOptions bundles CreateApp's optional fields. type CreateAppOptions struct { SpaceName string - ResourceSpec json.RawMessage + ResourceSpec jsontext.Value RecoveryMode bool } diff --git a/services/sagemaker/automl.go b/services/sagemaker/automl.go index 069e3b042f..1163878579 100644 --- a/services/sagemaker/automl.go +++ b/services/sagemaker/automl.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -100,17 +102,17 @@ type AutoMLJob struct { AutoMLJobName string `json:"AutoMLJobName"` InputDataConfig []AutoMLChannel `json:"InputDataConfig"` AutoMLJobInputDataConfig []AutoMLJobChannel `json:"AutoMLJobInputDataConfig,omitempty"` - AutoMLProblemTypeConfig json.RawMessage `json:"AutoMLProblemTypeConfig,omitempty"` + AutoMLProblemTypeConfig jsontext.Value `json:"AutoMLProblemTypeConfig,omitempty"` } // AutoMLV1Options holds the V1 CreateAutoMLJob members that only round-trip // through DescribeAutoMLJob; none of them drives a simulated training run. type AutoMLV1Options struct { - ProblemType string `json:"ProblemType,omitempty"` - Mode string `json:"Mode,omitempty"` - CandidateGenerationConfig json.RawMessage `json:"CandidateGenerationConfig,omitempty"` - CompletionCriteria json.RawMessage `json:"CompletionCriteria,omitempty"` - GenerateCandidateDefinitionsOnly bool `json:"GenerateCandidateDefinitionsOnly,omitempty"` + ProblemType string `json:"ProblemType,omitempty"` + Mode string `json:"Mode,omitempty"` + CandidateGenerationConfig jsontext.Value `json:"CandidateGenerationConfig,omitempty"` + CompletionCriteria jsontext.Value `json:"CompletionCriteria,omitempty"` + GenerateCandidateDefinitionsOnly bool `json:"GenerateCandidateDefinitionsOnly,omitempty"` } // SetAutoMLJobV1Options stores the V1-only round-trip members on a created job. @@ -157,13 +159,13 @@ func cloneAutoMLJob(j *AutoMLJob) *AutoMLJob { if j.V1Options != nil { v1 := *j.V1Options - v1.CandidateGenerationConfig = append(json.RawMessage(nil), v1.CandidateGenerationConfig...) - v1.CompletionCriteria = append(json.RawMessage(nil), v1.CompletionCriteria...) + v1.CandidateGenerationConfig = append(jsontext.Value(nil), v1.CandidateGenerationConfig...) + v1.CompletionCriteria = append(jsontext.Value(nil), v1.CompletionCriteria...) cp.V1Options = &v1 } if j.AutoMLProblemTypeConfig != nil { - cp.AutoMLProblemTypeConfig = append(json.RawMessage{}, j.AutoMLProblemTypeConfig...) + cp.AutoMLProblemTypeConfig = append(jsontext.Value{}, j.AutoMLProblemTypeConfig...) } if j.AutoMLComputeConfig != nil { @@ -205,7 +207,7 @@ func (j *AutoMLJob) MarshalJSON() ([]byte, error) { EndTime: epochSecondsPtr(j.EndTime), CreationTime: epochSeconds(j.CreationTime), LastModifiedTime: epochSeconds(j.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [AutoMLJob.MarshalJSON], read by @@ -220,7 +222,7 @@ func (j *AutoMLJob) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/automl_search.go b/services/sagemaker/automl_search.go index 8500ec2765..540c7eedf3 100644 --- a/services/sagemaker/automl_search.go +++ b/services/sagemaker/automl_search.go @@ -2,13 +2,15 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Resource-type / property-name string constants shared by Search, @@ -69,7 +71,7 @@ func (c *AutoMLCandidate) MarshalJSON() ([]byte, error) { alias: (*alias)(c), CreationTime: epochSeconds(c.CreationTime), LastModifiedTime: epochSeconds(c.LastModifiedTime), - }) + }, jsonx.V1()) } // generateAutoMLCandidates deterministically derives a set of candidates for @@ -361,13 +363,13 @@ func matchesSearchFilter(flat map[string]any, f SearchFilter) bool { // []map[string]any via a JSON round trip, so its fields can be addressed by // the JSON dotted path a NestedFilters entry documents. func toObjectList(raw any) ([]map[string]any, bool) { - b, marshalErr := json.Marshal(raw) + b, marshalErr := json.Marshal(raw, jsonx.V1()) if marshalErr != nil { return nil, false } var items []map[string]any - if unmarshalErr := json.Unmarshal(b, &items); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(b, &items, jsonx.V1()); unmarshalErr != nil { return nil, false } diff --git a/services/sagemaker/automl_v2.go b/services/sagemaker/automl_v2.go index a5765d30bf..fb08f0c0ce 100644 --- a/services/sagemaker/automl_v2.go +++ b/services/sagemaker/automl_v2.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AutoMLJobChannel is a named input source for CreateAutoMLJobV2/ @@ -63,13 +66,13 @@ var automlProblemTypeConfigNames = map[string]string{ // automlProblemTypeConfigName derives AutoMLProblemTypeConfigName from the // single member key present in an opaque AutoMLProblemTypeConfig payload. -func automlProblemTypeConfigName(raw json.RawMessage) string { +func automlProblemTypeConfigName(raw jsontext.Value) string { if len(raw) == 0 { return "" } - var members map[string]json.RawMessage - if err := json.Unmarshal(raw, &members); err != nil { + var members map[string]jsontext.Value + if err := json.Unmarshal(raw, &members, jsonx.V1()); err != nil { return "" } @@ -85,7 +88,7 @@ func automlProblemTypeConfigName(raw json.RawMessage) string { // AutoMLJobV2Extras bundles the CreateAutoMLJobV2-only fields set after the // shared CreateAutoMLJob record is created. type AutoMLJobV2Extras struct { - AutoMLProblemTypeConfig json.RawMessage + AutoMLProblemTypeConfig jsontext.Value OutputDataConfig *AutoMLOutputDataConfig AutoMLJobObjective *AutoMLJobObjective AutoMLComputeConfig *AutoMLComputeConfig @@ -113,7 +116,7 @@ func (b *InMemoryBackend) SetAutoMLJobV2Extras(ctx context.Context, name string, j.AutoMLJobInputDataConfig = append([]AutoMLJobChannel(nil), extras.AutoMLJobInputDataConfig...) if extras.AutoMLProblemTypeConfig != nil { - j.AutoMLProblemTypeConfig = append(json.RawMessage(nil), extras.AutoMLProblemTypeConfig...) + j.AutoMLProblemTypeConfig = append(jsontext.Value(nil), extras.AutoMLProblemTypeConfig...) } if extras.OutputDataConfig != nil { diff --git a/services/sagemaker/code_repositories.go b/services/sagemaker/code_repositories.go index 12babd9ec7..8453584687 100644 --- a/services/sagemaker/code_repositories.go +++ b/services/sagemaker/code_repositories.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrCodeRepositoryNotFound is returned when a code repository does not exist. @@ -55,7 +56,7 @@ func (r *CodeRepository) MarshalJSON() ([]byte, error) { alias: (*alias)(r), CreationTime: epochSeconds(r.CreationTime), LastModifiedTime: epochSeconds(r.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [CodeRepository.MarshalJSON], read by @@ -69,7 +70,7 @@ func (r *CodeRepository) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(r)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/compilation_jobs.go b/services/sagemaker/compilation_jobs.go index 9367943c1e..ca56d54625 100644 --- a/services/sagemaker/compilation_jobs.go +++ b/services/sagemaker/compilation_jobs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -87,9 +89,9 @@ type CompilationJob struct { // DescribeCompilationJobOutput (api_op_DescribeCompilationJob.go:67-69) // even though it is legitimately empty for every non-failed job -- the // common case. - FailureReason string `json:"FailureReason"` - ModelPackageVersionArn string `json:"ModelPackageVersionArn,omitempty"` - VpcConfig json.RawMessage `json:"VpcConfig,omitempty"` + FailureReason string `json:"FailureReason"` + ModelPackageVersionArn string `json:"ModelPackageVersionArn,omitempty"` + VpcConfig jsontext.Value `json:"VpcConfig,omitempty"` } func cloneCompilationJob(j *CompilationJob) *CompilationJob { @@ -121,7 +123,7 @@ func cloneCompilationJob(j *CompilationJob) *CompilationJob { cp.ModelArtifacts = &ma } - cp.VpcConfig = append(json.RawMessage(nil), j.VpcConfig...) + cp.VpcConfig = append(jsontext.Value(nil), j.VpcConfig...) if j.CompilationStartTime != nil { t := *j.CompilationStartTime @@ -155,7 +157,7 @@ func (j *CompilationJob) MarshalJSON() ([]byte, error) { LastModifiedTime: epochSeconds(j.LastModifiedTime), CompilationStartTime: epochSecondsPtr(j.CompilationStartTime), CompilationEndTime: epochSecondsPtr(j.CompilationEndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [CompilationJob.MarshalJSON], read by @@ -171,7 +173,7 @@ func (j *CompilationJob) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -431,7 +433,7 @@ func (b *InMemoryBackend) SetCompilationJobExtras( outputConfig *CompilationOutputConfig, stoppingCondition *StoppingCondition, modelPackageVersionArn string, - vpcConfig json.RawMessage, + vpcConfig jsontext.Value, ) error { b.mu.Lock("SetCompilationJobExtras") defer b.mu.Unlock() diff --git a/services/sagemaker/device_fleets.go b/services/sagemaker/device_fleets.go index 1abc1a9e37..cccb94320a 100644 --- a/services/sagemaker/device_fleets.go +++ b/services/sagemaker/device_fleets.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -76,7 +77,7 @@ func (f *DeviceFleet) MarshalJSON() ([]byte, error) { alias: (*alias)(f), CreationTime: epochSeconds(f.CreationTime), LastModifiedTime: epochSeconds(f.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [DeviceFleet.MarshalJSON], read by @@ -90,7 +91,7 @@ func (f *DeviceFleet) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(f)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -379,7 +380,7 @@ func (d *Device) MarshalJSON() ([]byte, error) { alias: (*alias)(d), RegistrationTime: epochSeconds(d.RegistrationTime), LastModifiedTime: epochSeconds(d.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [Device.MarshalJSON], read by @@ -393,7 +394,7 @@ func (d *Device) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(d)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/domains.go b/services/sagemaker/domains.go index 0e56cc0652..38b4360ae5 100644 --- a/services/sagemaker/domains.go +++ b/services/sagemaker/domains.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -43,18 +43,18 @@ type Domain struct { VpcID string `json:"VpcId,omitempty"` TagPropagation string `json:"TagPropagation,omitempty"` SubnetIDs []string `json:"SubnetIds,omitempty"` - DefaultUserSettings json.RawMessage `json:"DefaultUserSettings,omitempty"` - DefaultSpaceSettings json.RawMessage `json:"DefaultSpaceSettings,omitempty"` - DomainSettings json.RawMessage `json:"DomainSettings,omitempty"` + DefaultUserSettings jsontext.Value `json:"DefaultUserSettings,omitempty"` + DefaultSpaceSettings jsontext.Value `json:"DefaultSpaceSettings,omitempty"` + DomainSettings jsontext.Value `json:"DomainSettings,omitempty"` } func cloneDomain(d *Domain) *Domain { cp := *d cp.Tags = maps.Clone(d.Tags) cp.SubnetIDs = append([]string(nil), d.SubnetIDs...) - cp.DefaultUserSettings = append(json.RawMessage(nil), d.DefaultUserSettings...) - cp.DefaultSpaceSettings = append(json.RawMessage(nil), d.DefaultSpaceSettings...) - cp.DomainSettings = append(json.RawMessage(nil), d.DomainSettings...) + cp.DefaultUserSettings = append(jsontext.Value(nil), d.DefaultUserSettings...) + cp.DefaultSpaceSettings = append(jsontext.Value(nil), d.DefaultSpaceSettings...) + cp.DomainSettings = append(jsontext.Value(nil), d.DomainSettings...) return &cp } @@ -72,9 +72,9 @@ type CreateDomainOptions struct { VpcID string TagPropagation string SubnetIDs []string - DefaultUserSettings json.RawMessage - DefaultSpaceSettings json.RawMessage - DomainSettings json.RawMessage + DefaultUserSettings jsontext.Value + DefaultSpaceSettings jsontext.Value + DomainSettings jsontext.Value } // CreateDomain creates a new SageMaker Studio domain. @@ -198,9 +198,9 @@ type UpdateDomainOptions struct { TagPropagation string VpcID string SubnetIDs []string - DefaultUserSettings json.RawMessage - DefaultSpaceSettings json.RawMessage - DomainSettingsForUpdate json.RawMessage + DefaultUserSettings jsontext.Value + DefaultSpaceSettings jsontext.Value + DomainSettingsForUpdate jsontext.Value } // applyUpdateDomainOptions overwrites d's overridable fields with any diff --git a/services/sagemaker/edge_deployment.go b/services/sagemaker/edge_deployment.go index 61441d1d36..d9388eb908 100644 --- a/services/sagemaker/edge_deployment.go +++ b/services/sagemaker/edge_deployment.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -43,20 +43,20 @@ type EdgeDeploymentModelConfig struct { // device selection and deployment configs are stored opaquely (as submitted) // since this emulator does not act on them beyond round-tripping. type EdgeDeploymentStage struct { - StageName string `json:"StageName"` - DeploymentStatus string `json:"DeploymentStatus"` - DeviceSelectionConfig json.RawMessage `json:"DeviceSelectionConfig,omitempty"` - DeploymentConfig json.RawMessage `json:"DeploymentConfig,omitempty"` + StageName string `json:"StageName"` + DeploymentStatus string `json:"DeploymentStatus"` + DeviceSelectionConfig jsontext.Value `json:"DeviceSelectionConfig,omitempty"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig,omitempty"` } func cloneEdgeDeploymentStage(s *EdgeDeploymentStage) *EdgeDeploymentStage { cp := *s if s.DeviceSelectionConfig != nil { - cp.DeviceSelectionConfig = append(json.RawMessage(nil), s.DeviceSelectionConfig...) + cp.DeviceSelectionConfig = append(jsontext.Value(nil), s.DeviceSelectionConfig...) } if s.DeploymentConfig != nil { - cp.DeploymentConfig = append(json.RawMessage(nil), s.DeploymentConfig...) + cp.DeploymentConfig = append(jsontext.Value(nil), s.DeploymentConfig...) } return &cp @@ -96,8 +96,8 @@ func cloneEdgeDeploymentPlan(p *EdgeDeploymentPlan) *EdgeDeploymentPlan { // CreateEdgeDeploymentPlan or CreateEdgeDeploymentStage. type EdgeDeploymentStageInput struct { StageName string - DeviceSelectionConfig json.RawMessage - DeploymentConfig json.RawMessage + DeviceSelectionConfig jsontext.Value + DeploymentConfig jsontext.Value } func buildEdgeDeploymentStages(inputs []EdgeDeploymentStageInput) []*EdgeDeploymentStage { diff --git a/services/sagemaker/endpoint_configs.go b/services/sagemaker/endpoint_configs.go index 1e83328fa2..9fb6ddadb6 100644 --- a/services/sagemaker/endpoint_configs.go +++ b/services/sagemaker/endpoint_configs.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -143,7 +143,7 @@ func (b *InMemoryBackend) SetEndpointConfigExtras( kmsKeyID string, shadowProductionVariants []ProductionVariant, enableNetworkIsolation bool, - explainerConfig, metricsConfig json.RawMessage, + explainerConfig, metricsConfig jsontext.Value, ) error { b.mu.Lock("SetEndpointConfigExtras") defer b.mu.Unlock() @@ -177,11 +177,11 @@ func (b *InMemoryBackend) SetEndpointConfigExtras( ec.EnableNetworkIsolation = enableNetworkIsolation if len(explainerConfig) > 0 { - ec.ExplainerConfig = append(json.RawMessage(nil), explainerConfig...) + ec.ExplainerConfig = append(jsontext.Value(nil), explainerConfig...) } if len(metricsConfig) > 0 { - ec.MetricsConfig = append(json.RawMessage(nil), metricsConfig...) + ec.MetricsConfig = append(jsontext.Value(nil), metricsConfig...) } if len(shadowProductionVariants) > 0 { diff --git a/services/sagemaker/endpoints.go b/services/sagemaker/endpoints.go index 35f6bf0395..1eb2edd2a3 100644 --- a/services/sagemaker/endpoints.go +++ b/services/sagemaker/endpoints.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "slices" @@ -40,7 +40,7 @@ type Endpoint struct { FailureReason string `json:"FailureReason,omitempty"` ProductionVariants []ProductionVariantSummary `json:"ProductionVariants,omitempty"` ShadowProductionVariants []ProductionVariantSummary `json:"ShadowProductionVariants,omitempty"` - DeploymentConfig json.RawMessage `json:"DeploymentConfig,omitempty"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig,omitempty"` } // ProductionVariantStatus describes the current deployment stage of a @@ -172,7 +172,7 @@ type CreateEndpointOptions struct { Tags map[string]string Name string EndpointConfigName string - DeploymentConfig json.RawMessage + DeploymentConfig jsontext.Value } // CreateEndpoint creates a new SageMaker endpoint. @@ -379,7 +379,7 @@ func (b *InMemoryBackend) finishEndpointDelete(region, name string) { // DataCaptureConfig is endpoint-wide here, not per-variant, so that // VariantPropertyType value has no effect to exclude. type UpdateEndpointOptions struct { - DeploymentConfig json.RawMessage + DeploymentConfig jsontext.Value EndpointConfigName string ExcludeRetainedVariantProperties []string RetainAllVariantProperties bool diff --git a/services/sagemaker/engine_async_test.go b/services/sagemaker/engine_async_test.go index c9c950688f..797cf4c414 100644 --- a/services/sagemaker/engine_async_test.go +++ b/services/sagemaker/engine_async_test.go @@ -1,13 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -137,7 +138,7 @@ func TestEngineAsync_ResultsAndNotifications(t *testing.T) { assert.Contains(t, string(data), tt.wantOutValue) var note map[string]any - require.NoError(t, json.Unmarshal([]byte(rig.sns.messages(tt.wantTopic)[0]), ¬e)) + require.NoError(t, json.Unmarshal([]byte(rig.sns.messages(tt.wantTopic)[0]), ¬e, jsonx.V1())) assert.Equal(t, tt.wantStatus, note["invocationStatus"]) assert.Equal(t, tt.req.InferenceID, note["inferenceId"]) assert.Equal(t, "us-east-1", note["awsRegion"]) diff --git a/services/sagemaker/engine_files.go b/services/sagemaker/engine_files.go index 8c1d08278f..0f3527e7fe 100644 --- a/services/sagemaker/engine_files.go +++ b/services/sagemaker/engine_files.go @@ -5,7 +5,8 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "path/filepath" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -254,8 +257,8 @@ func stageManifest(ctx context.Context, store ObjectStore, bucket, key, dir stri return fmt.Errorf("read manifest s3://%s/%s: %w", bucket, key, err) } - var entries []json.RawMessage - if err = json.Unmarshal(raw, &entries); err != nil { + var entries []jsontext.Value + if err = json.Unmarshal(raw, &entries, jsonx.V1()); err != nil { return fmt.Errorf("parse manifest s3://%s/%s: %w", bucket, key, err) } @@ -263,14 +266,14 @@ func stageManifest(ctx context.Context, store ObjectStore, bucket, key, dir stri for i, ent := range entries { var obj map[string]string - if json.Unmarshal(ent, &obj) == nil { + if json.Unmarshal(ent, &obj, jsonx.V1()) == nil { prefix = obj[manifestPrefixK] continue } var name string - if err = json.Unmarshal(ent, &name); err != nil || prefix == "" { + if err = json.Unmarshal(ent, &name, jsonx.V1()); err != nil || prefix == "" { return fmt.Errorf("manifest s3://%s/%s: entry %d: %w", bucket, key, i, errManifestEntry) } @@ -397,7 +400,7 @@ func getS3URI(ctx context.Context, store ObjectStore, uri string) ([]byte, error // writeJSONFile marshals v to dir/name. func writeJSONFile(dir, name string, v any) error { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Errorf("marshal %s: %w", name, err) } diff --git a/services/sagemaker/engine_invoke.go b/services/sagemaker/engine_invoke.go index 71a2f49a3f..c060f98b43 100644 --- a/services/sagemaker/engine_invoke.go +++ b/services/sagemaker/engine_invoke.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -12,6 +12,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxResponseBytes = 25 << 20 @@ -315,7 +316,7 @@ func (b *InMemoryBackend) notifyAsync( msg["eventSource"] = "aws:sagemaker" msg["eventName"] = "InferenceResult" - data, err := json.Marshal(msg) + data, err := json.Marshal(msg, jsonx.V1()) if err != nil { return } diff --git a/services/sagemaker/engine_jobs_test.go b/services/sagemaker/engine_jobs_test.go index 95310d7c70..c0dbec6043 100644 --- a/services/sagemaker/engine_jobs_test.go +++ b/services/sagemaker/engine_jobs_test.go @@ -5,7 +5,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "os" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -215,11 +216,17 @@ func TestEngineTraining_ConfigFilesAndStop(t *testing.T) { cfg := filepath.Join(mountSource(spec, "/opt/ml/input"), "config") var res map[string]any - require.NoError(t, json.Unmarshal([]byte(readFileOrEmpty(filepath.Join(cfg, "resourceconfig.json"))), &res)) + require.NoError( + t, + json.Unmarshal([]byte(readFileOrEmpty(filepath.Join(cfg, "resourceconfig.json"))), &res, jsonx.V1()), + ) assert.Equal(t, "algo-1", res["current_host"]) var chans map[string]map[string]string - require.NoError(t, json.Unmarshal([]byte(readFileOrEmpty(filepath.Join(cfg, "inputdataconfig.json"))), &chans)) + require.NoError( + t, + json.Unmarshal([]byte(readFileOrEmpty(filepath.Join(cfg, "inputdataconfig.json"))), &chans, jsonx.V1()), + ) assert.Equal(t, "text/csv", chans["train"]["ContentType"]) assert.Equal(t, "File", chans["train"]["TrainingInputMode"]) diff --git a/services/sagemaker/engine_training.go b/services/sagemaker/engine_training.go index 1a4572e393..cbeb40ee24 100644 --- a/services/sagemaker/engine_training.go +++ b/services/sagemaker/engine_training.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "os" @@ -11,6 +11,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -94,7 +95,7 @@ func (b *InMemoryBackend) trainingImage(region string, spec AlgorithmSpecificati TrainingImage string `json:"TrainingImage"` } - if err := json.Unmarshal(alg.TrainingSpecification, &ts); err != nil || ts.TrainingImage == "" { + if err := json.Unmarshal(alg.TrainingSpecification, &ts, jsonx.V1()); err != nil || ts.TrainingImage == "" { return "", fmt.Errorf("%w: %s", errAlgorithmNoImage, spec.AlgorithmName) } diff --git a/services/sagemaker/flow_definitions.go b/services/sagemaker/flow_definitions.go index 6db6dcec01..67359b2a6c 100644 --- a/services/sagemaker/flow_definitions.go +++ b/services/sagemaker/flow_definitions.go @@ -2,13 +2,14 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrFlowDefinitionNotFound is returned when a flow definition does not exist. @@ -128,7 +129,7 @@ func (f *FlowDefinition) MarshalJSON() ([]byte, error) { } } - return json.Marshal(aux) + return json.Marshal(aux, jsonx.V1()) } // UnmarshalJSON is the inverse of [FlowDefinition.MarshalJSON], read by @@ -145,7 +146,7 @@ func (f *FlowDefinition) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(f)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/handler.go b/services/sagemaker/handler.go index 267516f8eb..850a15de22 100644 --- a/services/sagemaker/handler.go +++ b/services/sagemaker/handler.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -908,7 +909,7 @@ func writeInternalServerError(c *echo.Context) error { payload, err := json.Marshal(map[string]string{ keyTypeField: "InternalFailure", keyMessageField: "internal server error", - }) + }, jsonx.V1()) if err != nil { return err } @@ -925,15 +926,12 @@ func wireMessage(err error) string { } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrInvalidParameter): payload, _ := json.Marshal(map[string]string{ keyTypeField: errValidationException, keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrResourceNotFound): @@ -945,14 +943,14 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: "ResourceNotFound", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, awserr.ErrNotFound): payload, _ := json.Marshal(map[string]string{ keyTypeField: errValidationException, keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, ErrConflictException): @@ -964,21 +962,21 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: "ConflictException", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, awserr.ErrConflict): payload, _ := json.Marshal(map[string]string{ keyTypeField: "ResourceInUse", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) - case errors.Is(err, errInvalidRequest), errors.As(err, &syntaxErr), errors.As(err, &typeErr): + case errors.Is(err, errInvalidRequest), jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): payload, _ := json.Marshal(map[string]string{ keyTypeField: errValidationException, keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) case errors.Is(err, errUnknownAction): @@ -1072,5 +1070,5 @@ func listResp(key string, items []map[string]any, nextToken string) ([]byte, err resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_ai_benchmark_jobs.go b/services/sagemaker/handler_ai_benchmark_jobs.go index 4f47d79673..088b23c7d1 100644 --- a/services/sagemaker/handler_ai_benchmark_jobs.go +++ b/services/sagemaker/handler_ai_benchmark_jobs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aiBenchmarkJobOpsSupported returns the operations handled by @@ -51,19 +54,19 @@ func (h *Handler) dispatchAIBenchmarkJobOps( // createAIBenchmarkJobInput mirrors CreateAIBenchmarkJobInput // (api_op_CreateAIBenchmarkJob.go:33-73). type createAIBenchmarkJobInput struct { - BenchmarkTarget json.RawMessage `json:"BenchmarkTarget"` - OutputConfig json.RawMessage `json:"OutputConfig"` - NetworkConfig json.RawMessage `json:"NetworkConfig"` - AIBenchmarkJobName string `json:"AIBenchmarkJobName"` - AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` - RoleArn string `json:"RoleArn"` - Tags []tagObject `json:"Tags"` + BenchmarkTarget jsontext.Value `json:"BenchmarkTarget"` + OutputConfig jsontext.Value `json:"OutputConfig"` + NetworkConfig jsontext.Value `json:"NetworkConfig"` + AIBenchmarkJobName string `json:"AIBenchmarkJobName"` + AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` + RoleArn string `json:"RoleArn"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateAIBenchmarkJob(ctx context.Context, body []byte) ([]byte, error) { var req createAIBenchmarkJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -84,7 +87,7 @@ func (h *Handler) handleCreateAIBenchmarkJob(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]string{keyAIBenchmarkJobArn: j.AIBenchmarkJobArn}) + return json.Marshal(map[string]string{keyAIBenchmarkJobArn: j.AIBenchmarkJobArn}, jsonx.V1()) } // describeAIBenchmarkJobInput mirrors DescribeAIBenchmarkJobInput @@ -96,7 +99,7 @@ type describeAIBenchmarkJobInput struct { func (h *Handler) handleDescribeAIBenchmarkJob(ctx context.Context, body []byte) ([]byte, error) { var req describeAIBenchmarkJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -109,7 +112,7 @@ func (h *Handler) handleDescribeAIBenchmarkJob(ctx context.Context, body []byte) return nil, err } - return json.Marshal(j) + return json.Marshal(j, jsonx.V1()) } // deleteAIBenchmarkJobInput mirrors DeleteAIBenchmarkJobInput @@ -121,7 +124,7 @@ type deleteAIBenchmarkJobInput struct { func (h *Handler) handleDeleteAIBenchmarkJob(ctx context.Context, body []byte) ([]byte, error) { var req deleteAIBenchmarkJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -134,7 +137,7 @@ func (h *Handler) handleDeleteAIBenchmarkJob(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]string{keyAIBenchmarkJobArn: jobARN}) + return json.Marshal(map[string]string{keyAIBenchmarkJobArn: jobARN}, jsonx.V1()) } // stopAIBenchmarkJobInput mirrors StopAIBenchmarkJobInput @@ -146,7 +149,7 @@ type stopAIBenchmarkJobInput struct { func (h *Handler) handleStopAIBenchmarkJob(ctx context.Context, body []byte) ([]byte, error) { var req stopAIBenchmarkJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -159,7 +162,7 @@ func (h *Handler) handleStopAIBenchmarkJob(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]string{keyAIBenchmarkJobArn: jobARN}) + return json.Marshal(map[string]string{keyAIBenchmarkJobArn: jobARN}, jsonx.V1()) } // listAIBenchmarkJobsInput mirrors ListAIBenchmarkJobsInput @@ -178,7 +181,7 @@ type listAIBenchmarkJobsInput struct { func (h *Handler) handleListAIBenchmarkJobs(ctx context.Context, body []byte) ([]byte, error) { var req listAIBenchmarkJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_ai_recommendation_jobs.go b/services/sagemaker/handler_ai_recommendation_jobs.go index 6c4114078b..af4b151d5a 100644 --- a/services/sagemaker/handler_ai_recommendation_jobs.go +++ b/services/sagemaker/handler_ai_recommendation_jobs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aiRecommendationJobOpsSupported returns the operations handled by @@ -51,23 +54,23 @@ func (h *Handler) dispatchAIRecommendationJobOps( // createAIRecommendationJobInput mirrors CreateAIRecommendationJobInput // (api_op_CreateAIRecommendationJob.go:30-90). type createAIRecommendationJobInput struct { - ModelSource json.RawMessage `json:"ModelSource"` - OutputConfig json.RawMessage `json:"OutputConfig"` - PerformanceTarget json.RawMessage `json:"PerformanceTarget"` - ComputeSpec json.RawMessage `json:"ComputeSpec"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification"` - AdapterSource json.RawMessage `json:"AdapterSource"` - OptimizeModel *bool `json:"OptimizeModel"` - AIRecommendationJobName string `json:"AIRecommendationJobName"` - AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` - RoleArn string `json:"RoleArn"` - Tags []tagObject `json:"Tags"` + ModelSource jsontext.Value `json:"ModelSource"` + OutputConfig jsontext.Value `json:"OutputConfig"` + PerformanceTarget jsontext.Value `json:"PerformanceTarget"` + ComputeSpec jsontext.Value `json:"ComputeSpec"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification"` + AdapterSource jsontext.Value `json:"AdapterSource"` + OptimizeModel *bool `json:"OptimizeModel"` + AIRecommendationJobName string `json:"AIRecommendationJobName"` + AIWorkloadConfigIdentifier string `json:"AIWorkloadConfigIdentifier"` + RoleArn string `json:"RoleArn"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateAIRecommendationJob(ctx context.Context, body []byte) ([]byte, error) { var req createAIRecommendationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -92,7 +95,7 @@ func (h *Handler) handleCreateAIRecommendationJob(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]string{keyAIRecommendationJobArn: j.AIRecommendationJobArn}) + return json.Marshal(map[string]string{keyAIRecommendationJobArn: j.AIRecommendationJobArn}, jsonx.V1()) } // describeAIRecommendationJobInput mirrors DescribeAIRecommendationJobInput @@ -104,7 +107,7 @@ type describeAIRecommendationJobInput struct { func (h *Handler) handleDescribeAIRecommendationJob(ctx context.Context, body []byte) ([]byte, error) { var req describeAIRecommendationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +120,7 @@ func (h *Handler) handleDescribeAIRecommendationJob(ctx context.Context, body [] return nil, err } - return json.Marshal(j) + return json.Marshal(j, jsonx.V1()) } // deleteAIRecommendationJobInput mirrors DeleteAIRecommendationJobInput @@ -129,7 +132,7 @@ type deleteAIRecommendationJobInput struct { func (h *Handler) handleDeleteAIRecommendationJob(ctx context.Context, body []byte) ([]byte, error) { var req deleteAIRecommendationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -142,7 +145,7 @@ func (h *Handler) handleDeleteAIRecommendationJob(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]string{keyAIRecommendationJobArn: jobARN}) + return json.Marshal(map[string]string{keyAIRecommendationJobArn: jobARN}, jsonx.V1()) } // stopAIRecommendationJobInput mirrors StopAIRecommendationJobInput @@ -154,7 +157,7 @@ type stopAIRecommendationJobInput struct { func (h *Handler) handleStopAIRecommendationJob(ctx context.Context, body []byte) ([]byte, error) { var req stopAIRecommendationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -167,7 +170,7 @@ func (h *Handler) handleStopAIRecommendationJob(ctx context.Context, body []byte return nil, err } - return json.Marshal(map[string]string{keyAIRecommendationJobArn: jobARN}) + return json.Marshal(map[string]string{keyAIRecommendationJobArn: jobARN}, jsonx.V1()) } // listAIRecommendationJobsInput mirrors ListAIRecommendationJobsInput @@ -186,7 +189,7 @@ type listAIRecommendationJobsInput struct { func (h *Handler) handleListAIRecommendationJobs(ctx context.Context, body []byte) ([]byte, error) { var req listAIRecommendationJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_ai_workload_configs.go b/services/sagemaker/handler_ai_workload_configs.go index 17ea80b720..2fa8fe35c1 100644 --- a/services/sagemaker/handler_ai_workload_configs.go +++ b/services/sagemaker/handler_ai_workload_configs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aiWorkloadConfigOpsSupported returns the operations handled by @@ -44,16 +47,16 @@ func (h *Handler) dispatchAIWorkloadConfigOps( } type createAIWorkloadConfigRequest struct { - WorkloadSpec json.RawMessage `json:"AIWorkloadConfigs"` - DatasetConfig json.RawMessage `json:"DatasetConfig"` - AIWorkloadConfigName string `json:"AIWorkloadConfigName"` - Tags []tagObject `json:"Tags"` + WorkloadSpec jsontext.Value `json:"AIWorkloadConfigs"` + DatasetConfig jsontext.Value `json:"DatasetConfig"` + AIWorkloadConfigName string `json:"AIWorkloadConfigName"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateAIWorkloadConfig(ctx context.Context, body []byte) ([]byte, error) { var req createAIWorkloadConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -71,7 +74,7 @@ func (h *Handler) handleCreateAIWorkloadConfig(ctx context.Context, body []byte) return nil, err } - return json.Marshal(map[string]string{keyAIWorkloadConfigArn: c.AIWorkloadConfigArn}) + return json.Marshal(map[string]string{keyAIWorkloadConfigArn: c.AIWorkloadConfigArn}, jsonx.V1()) } type describeAIWorkloadConfigRequest struct { @@ -81,7 +84,7 @@ type describeAIWorkloadConfigRequest struct { func (h *Handler) handleDescribeAIWorkloadConfig(ctx context.Context, body []byte) ([]byte, error) { var req describeAIWorkloadConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -94,7 +97,7 @@ func (h *Handler) handleDescribeAIWorkloadConfig(ctx context.Context, body []byt return nil, err } - return json.Marshal(c) + return json.Marshal(c, jsonx.V1()) } type deleteAIWorkloadConfigRequest struct { @@ -104,7 +107,7 @@ type deleteAIWorkloadConfigRequest struct { func (h *Handler) handleDeleteAIWorkloadConfig(ctx context.Context, body []byte) ([]byte, error) { var req deleteAIWorkloadConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +120,7 @@ func (h *Handler) handleDeleteAIWorkloadConfig(ctx context.Context, body []byte) return nil, err } - return json.Marshal(map[string]string{keyAIWorkloadConfigArn: configARN}) + return json.Marshal(map[string]string{keyAIWorkloadConfigArn: configARN}, jsonx.V1()) } type listAIWorkloadConfigsRequest struct { @@ -133,7 +136,7 @@ type listAIWorkloadConfigsRequest struct { func (h *Handler) handleListAIWorkloadConfigs(ctx context.Context, body []byte) ([]byte, error) { var req listAIWorkloadConfigsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_algorithms.go b/services/sagemaker/handler_algorithms.go index 50a8ea696a..31eb49941a 100644 --- a/services/sagemaker/handler_algorithms.go +++ b/services/sagemaker/handler_algorithms.go @@ -2,26 +2,28 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) // createAlgorithmRequest is the request body for CreateAlgorithm. type createAlgorithmRequest struct { - TrainingSpecification json.RawMessage `json:"TrainingSpecification,omitempty"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification,omitempty"` - ValidationSpecification json.RawMessage `json:"ValidationSpecification,omitempty"` - AlgorithmName string `json:"AlgorithmName"` - AlgorithmDescription string `json:"AlgorithmDescription,omitempty"` - Tags []tagObject `json:"Tags"` - CertifyForMarketplace bool `json:"CertifyForMarketplace,omitempty"` + TrainingSpecification jsontext.Value `json:"TrainingSpecification,omitempty"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification,omitempty"` + ValidationSpecification jsontext.Value `json:"ValidationSpecification,omitempty"` + AlgorithmName string `json:"AlgorithmName"` + AlgorithmDescription string `json:"AlgorithmDescription,omitempty"` + Tags []tagObject `json:"Tags"` + CertifyForMarketplace bool `json:"CertifyForMarketplace,omitempty"` } func (h *Handler) handleCreateAlgorithm(ctx context.Context, body []byte) ([]byte, error) { var req createAlgorithmRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -55,7 +57,7 @@ func (h *Handler) handleCreateAlgorithm(ctx context.Context, body []byte) ([]byt log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created algorithm", "name", al.AlgorithmName) - return json.Marshal(map[string]string{"AlgorithmArn": al.AlgorithmArn}) + return json.Marshal(map[string]string{"AlgorithmArn": al.AlgorithmArn}, jsonx.V1()) } // describeAlgorithmResponse is the response body for DescribeAlgorithm. @@ -71,9 +73,9 @@ type describeAlgorithmResponse struct { AlgorithmDescription string `json:"AlgorithmDescription,omitempty"` ProductID string `json:"ProductId,omitempty"` AlgorithmStatusDetails AlgorithmStatusDetails `json:"AlgorithmStatusDetails"` - TrainingSpecification json.RawMessage `json:"TrainingSpecification"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification,omitempty"` - ValidationSpecification json.RawMessage `json:"ValidationSpecification,omitempty"` + TrainingSpecification jsontext.Value `json:"TrainingSpecification"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification,omitempty"` + ValidationSpecification jsontext.Value `json:"ValidationSpecification,omitempty"` CreationTime float64 `json:"CreationTime"` CertifyForMarketplace bool `json:"CertifyForMarketplace,omitempty"` } @@ -86,7 +88,7 @@ type describeAlgorithmRequest struct { func (h *Handler) handleDescribeAlgorithm(ctx context.Context, body []byte) ([]byte, error) { var req describeAlgorithmRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -111,7 +113,7 @@ func (h *Handler) handleDescribeAlgorithm(ctx context.Context, body []byte) ([]b TrainingSpecification: al.TrainingSpecification, InferenceSpecification: al.InferenceSpecification, ValidationSpecification: al.ValidationSpecification, - }) + }, jsonx.V1()) } // deleteAlgorithmRequest is the request body for DeleteAlgorithm. @@ -122,7 +124,7 @@ type deleteAlgorithmRequest struct { func (h *Handler) handleDeleteAlgorithm(ctx context.Context, body []byte) error { var req deleteAlgorithmRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -136,7 +138,7 @@ func (h *Handler) handleDeleteAlgorithm(ctx context.Context, body []byte) error func (h *Handler) handleListAlgorithms(ctx context.Context, body []byte) ([]byte, error) { var req nameTimeListRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_algorithms_test.go b/services/sagemaker/handler_algorithms_test.go index 1cb68c2ce7..d1a996bc73 100644 --- a/services/sagemaker/handler_algorithms_test.go +++ b/services/sagemaker/handler_algorithms_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -52,7 +53,7 @@ func TestHandler_DescribeAlgorithm(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-algo", resp["AlgorithmName"]) assert.Equal(t, "a description", resp["AlgorithmDescription"]) assert.Equal(t, "Completed", resp["AlgorithmStatus"]) @@ -114,7 +115,7 @@ func TestHandler_ListAlgorithms(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["AlgorithmSummaryList"]) doSageMakerRequest(t, h, "CreateAlgorithm", map[string]any{ @@ -129,7 +130,7 @@ func TestHandler_ListAlgorithms(t *testing.T) { rec = doSageMakerRequest(t, h, "ListAlgorithms", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["AlgorithmSummaryList"].([]any) require.True(t, ok) assert.Len(t, list, 2) @@ -196,7 +197,7 @@ func TestCreateAlgorithm_TagsPresent(t *testing.T) { if tt.wantTags { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arnStr := resp["AlgorithmArn"] require.NotEmpty(t, arnStr) @@ -272,7 +273,7 @@ func TestHandler_CreateAlgorithm(t *testing.T) { if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["AlgorithmArn"], "arn:aws:sagemaker") assert.Contains(t, resp["AlgorithmArn"], "algorithm/") } @@ -303,7 +304,7 @@ func TestHandler_ListAlgorithms_FilterSort(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["AlgorithmSummaryList"].([]any) require.True(t, ok) @@ -357,6 +358,6 @@ func TestHandler_ListAlgorithms_CreationTimeAfterExclusive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["AlgorithmSummaryList"]) } diff --git a/services/sagemaker/handler_app_image_configs.go b/services/sagemaker/handler_app_image_configs.go index 246193557c..9895958987 100644 --- a/services/sagemaker/handler_app_image_configs.go +++ b/services/sagemaker/handler_app_image_configs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -13,17 +16,17 @@ import ( // createAppImageConfigInput mirrors CreateAppImageConfigInput // (api_op_CreateAppImageConfig.go:28-52). type createAppImageConfigInput struct { - AppImageConfigName string `json:"AppImageConfigName"` - KernelGatewayImageConfig json.RawMessage `json:"KernelGatewayImageConfig"` - JupyterLabAppImageConfig json.RawMessage `json:"JupyterLabAppImageConfig"` - CodeEditorAppImageConfig json.RawMessage `json:"CodeEditorAppImageConfig"` - Tags []tagObject `json:"Tags"` + AppImageConfigName string `json:"AppImageConfigName"` + KernelGatewayImageConfig jsontext.Value `json:"KernelGatewayImageConfig"` + JupyterLabAppImageConfig jsontext.Value `json:"JupyterLabAppImageConfig"` + CodeEditorAppImageConfig jsontext.Value `json:"CodeEditorAppImageConfig"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateAppImageConfig(ctx context.Context, body []byte) ([]byte, error) { var req createAppImageConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -42,7 +45,7 @@ func (h *Handler) handleCreateAppImageConfig(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]any{keyAppImageConfigArn: result.AppImageConfigArn}) + return json.Marshal(map[string]any{keyAppImageConfigArn: result.AppImageConfigArn}, jsonx.V1()) } // describeAppImageConfigInput mirrors DescribeAppImageConfigInput @@ -54,7 +57,7 @@ type describeAppImageConfigInput struct { func (h *Handler) handleDescribeAppImageConfig(ctx context.Context, body []byte) ([]byte, error) { var req describeAppImageConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -67,7 +70,7 @@ func (h *Handler) handleDescribeAppImageConfig(ctx context.Context, body []byte) return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteAppImageConfigInput mirrors DeleteAppImageConfigInput @@ -79,7 +82,7 @@ type deleteAppImageConfigInput struct { func (h *Handler) handleDeleteAppImageConfig(ctx context.Context, body []byte) error { var req deleteAppImageConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -93,16 +96,16 @@ func (h *Handler) handleDeleteAppImageConfig(ctx context.Context, body []byte) e // updateAppImageConfigInput mirrors UpdateAppImageConfigInput // (api_op_UpdateAppImageConfig.go:28-45). type updateAppImageConfigInput struct { - AppImageConfigName string `json:"AppImageConfigName"` - KernelGatewayImageConfig json.RawMessage `json:"KernelGatewayImageConfig"` - JupyterLabAppImageConfig json.RawMessage `json:"JupyterLabAppImageConfig"` - CodeEditorAppImageConfig json.RawMessage `json:"CodeEditorAppImageConfig"` + AppImageConfigName string `json:"AppImageConfigName"` + KernelGatewayImageConfig jsontext.Value `json:"KernelGatewayImageConfig"` + JupyterLabAppImageConfig jsontext.Value `json:"JupyterLabAppImageConfig"` + CodeEditorAppImageConfig jsontext.Value `json:"CodeEditorAppImageConfig"` } func (h *Handler) handleUpdateAppImageConfig(ctx context.Context, body []byte) ([]byte, error) { var req updateAppImageConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -119,7 +122,7 @@ func (h *Handler) handleUpdateAppImageConfig(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]any{keyAppImageConfigArn: result.AppImageConfigArn}) + return json.Marshal(map[string]any{keyAppImageConfigArn: result.AppImageConfigArn}, jsonx.V1()) } // listAppImageConfigsInput mirrors ListAppImageConfigsInput @@ -139,7 +142,7 @@ type listAppImageConfigsInput struct { func (h *Handler) handleListAppImageConfigs(ctx context.Context, body []byte) ([]byte, error) { var req listAppImageConfigsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_app_image_configs_test.go b/services/sagemaker/handler_app_image_configs_test.go index 084dd70e97..5289ff9ca8 100644 --- a/services/sagemaker/handler_app_image_configs_test.go +++ b/services/sagemaker/handler_app_image_configs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAppImageConfig(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_CreateAppImageConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["AppImageConfigArn"], "my-config") } @@ -37,7 +39,7 @@ func TestHandler_DescribeAppImageConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "aic-1", resp["AppImageConfigName"]) } @@ -64,7 +66,7 @@ func TestHandler_UpdateAppImageConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["AppImageConfigArn"], "aic-upd") } @@ -81,7 +83,7 @@ func TestHandler_ListAppImageConfigs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["AppImageConfigs"]) doSageMakerRequest(t, h, "CreateAppImageConfig", map[string]any{ @@ -89,7 +91,7 @@ func TestHandler_ListAppImageConfigs(t *testing.T) { }) rec = doSageMakerRequest(t, h, "ListAppImageConfigs", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs := resp["AppImageConfigs"].([]any) assert.Len(t, configs, 1) c := configs[0].(map[string]any) diff --git a/services/sagemaker/handler_apps.go b/services/sagemaker/handler_apps.go index ec378570c0..21c935d782 100644 --- a/services/sagemaker/handler_apps.go +++ b/services/sagemaker/handler_apps.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -16,20 +18,20 @@ import ( // wire-field-audit tooling that only inspects named types can see it — see // gopherstack-oc9v). type createAppInput struct { - DomainID string `json:"DomainId"` - UserProfileName string `json:"UserProfileName"` - SpaceName string `json:"SpaceName"` - AppType string `json:"AppType"` - AppName string `json:"AppName"` - ResourceSpec json.RawMessage `json:"ResourceSpec"` - Tags []tagObject `json:"Tags"` - RecoveryMode bool `json:"RecoveryMode"` + DomainID string `json:"DomainId"` + UserProfileName string `json:"UserProfileName"` + SpaceName string `json:"SpaceName"` + AppType string `json:"AppType"` + AppName string `json:"AppName"` + ResourceSpec jsontext.Value `json:"ResourceSpec"` + Tags []tagObject `json:"Tags"` + RecoveryMode bool `json:"RecoveryMode"` } func (h *Handler) handleCreateApp(ctx context.Context, body []byte) ([]byte, error) { var req createAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -81,7 +83,7 @@ func (h *Handler) handleCreateApp(ctx context.Context, body []byte) ([]byte, err logger.Load(ctx).InfoContext(ctx, "sagemaker: created app", "name", a.AppName) - return json.Marshal(map[string]string{keyAppArn: a.AppArn}) + return json.Marshal(map[string]string{keyAppArn: a.AppArn}, jsonx.V1()) } type describeAppInput struct { @@ -95,7 +97,7 @@ type describeAppInput struct { func (h *Handler) handleDescribeApp(ctx context.Context, body []byte) ([]byte, error) { var req describeAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -138,7 +140,7 @@ func (h *Handler) handleDescribeApp(ctx context.Context, body []byte) ([]byte, e resp["ResourceSpec"] = a.ResourceSpec } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type appSummary struct { @@ -165,7 +167,7 @@ type listAppsInput struct { func (h *Handler) handleListApps(ctx context.Context, body []byte) ([]byte, error) { var req listAppsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +203,7 @@ func (h *Handler) handleListApps(ctx context.Context, body []byte) ([]byte, erro resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type deleteAppInput struct { @@ -215,7 +217,7 @@ type deleteAppInput struct { func (h *Handler) handleDeleteApp(ctx context.Context, body []byte) error { var req deleteAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_apps_test.go b/services/sagemaker/handler_apps_test.go index 5ee8a3f949..234be4e334 100644 --- a/services/sagemaker/handler_apps_test.go +++ b/services/sagemaker/handler_apps_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AppLifecycle(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_AppLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) recUser := doSageMakerRequest(t, h, "CreateUserProfile", map[string]any{ @@ -43,7 +45,7 @@ func TestHandler_AppLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["AppArn"]) // Describe app. @@ -60,7 +62,7 @@ func TestHandler_AppLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["Apps"].([]any), 1) // Delete app. @@ -117,7 +119,7 @@ func TestHandler_CreateApp_SpaceOwned(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) doSageMakerRequest(t, h, "CreateSpace", map[string]any{"DomainId": domainID, "SpaceName": "app-space"}) @@ -142,7 +144,7 @@ func TestHandler_CreateApp_SpaceOwned(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "app-space", descOut["SpaceName"]) assert.Nil(t, descOut["UserProfileName"]) @@ -167,7 +169,7 @@ func TestHandler_ListApps_FiltersAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) doSageMakerRequest(t, h, "CreateUserProfile", map[string]any{"DomainId": domainID, "UserProfileName": "u1"}) @@ -198,7 +200,7 @@ func TestHandler_ListApps_FiltersAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Apps"].([]any), 2) }) @@ -212,7 +214,7 @@ func TestHandler_ListApps_FiltersAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Apps"].([]any), 1, "MaxResults must cap the page, not just be parsed and ignored") assert.NotEmpty(t, out["NextToken"]) }) diff --git a/services/sagemaker/handler_automl.go b/services/sagemaker/handler_automl.go index 9b3537f11d..d4e17f6f61 100644 --- a/services/sagemaker/handler_automl.go +++ b/services/sagemaker/handler_automl.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -16,8 +19,8 @@ type autoMLJobConfigRequest struct { DataSplitConfig *AutoMLDataSplitConfig `json:"DataSplitConfig,omitempty"` SecurityConfig *AutoMLSecurityConfig `json:"SecurityConfig,omitempty"` Mode string `json:"Mode,omitempty"` - CandidateGenerationConfig json.RawMessage `json:"CandidateGenerationConfig,omitempty"` - CompletionCriteria json.RawMessage `json:"CompletionCriteria,omitempty"` + CandidateGenerationConfig jsontext.Value `json:"CandidateGenerationConfig,omitempty"` + CompletionCriteria jsontext.Value `json:"CompletionCriteria,omitempty"` } type createAutoMLJobRequest struct { @@ -37,7 +40,7 @@ type createAutoMLJobRequest struct { func (h *Handler) handleCreateAutoMLJob(ctx context.Context, body []byte) ([]byte, error) { var req createAutoMLJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -97,7 +100,7 @@ func (h *Handler) handleCreateAutoMLJob(ctx context.Context, body []byte) ([]byt return nil, optErr } - return json.Marshal(map[string]any{keyAutoMLJobArn: result.AutoMLJobArn}) + return json.Marshal(map[string]any{keyAutoMLJobArn: result.AutoMLJobArn}, jsonx.V1()) } type describeAutoMLJobRequest struct { @@ -107,7 +110,7 @@ type describeAutoMLJobRequest struct { func (h *Handler) handleDescribeAutoMLJob(ctx context.Context, body []byte) ([]byte, error) { var req describeAutoMLJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -154,7 +157,7 @@ func (h *Handler) handleDescribeAutoMLJob(ctx context.Context, body []byte) ([]b describeAutoMLV1Config(resp, j) - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type stopAutoMLJobRequest struct { @@ -164,7 +167,7 @@ type stopAutoMLJobRequest struct { func (h *Handler) handleStopAutoMLJob(ctx context.Context, body []byte) error { var req stopAutoMLJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -191,7 +194,7 @@ type listAutoMLJobsRequest struct { func (h *Handler) handleListAutoMLJobs(ctx context.Context, body []byte) ([]byte, error) { var req listAutoMLJobsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -228,7 +231,7 @@ func (h *Handler) handleListAutoMLJobs(ctx context.Context, body []byte) ([]byte return json.Marshal(map[string]any{ "AutoMLJobSummaries": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } func describeAutoMLV1Config(resp map[string]any, j *AutoMLJob) { diff --git a/services/sagemaker/handler_automl_search.go b/services/sagemaker/handler_automl_search.go index 89c691b69b..19aa3060c2 100644 --- a/services/sagemaker/handler_automl_search.go +++ b/services/sagemaker/handler_automl_search.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // automlSearchExtOpsSupported returns the real stateful operations @@ -66,7 +68,7 @@ type listCandidatesForAutoMLJobInput struct { func (h *Handler) handleListCandidatesForAutoMLJob(ctx context.Context, body []byte) ([]byte, error) { var req listCandidatesForAutoMLJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -88,7 +90,7 @@ func (h *Handler) handleListCandidatesForAutoMLJob(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]any{"Candidates": candidates, keyNextToken: next}) + return json.Marshal(map[string]any{"Candidates": candidates, keyNextToken: next}, jsonx.V1()) } // searchInput mirrors SearchInput (api_op_Search.go:27-63). VisibilityConditions @@ -108,7 +110,7 @@ type searchInput struct { func (h *Handler) handleSearch(ctx context.Context, body []byte) ([]byte, error) { var req searchInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -133,7 +135,7 @@ func (h *Handler) handleSearch(ctx context.Context, body []byte) ([]byte, error) "Results": results, "TotalHits": map[string]any{"Value": total, "Relation": "EqualTo"}, keyNextToken: next, - }) + }, jsonx.V1()) } // listModelMetadataInput mirrors ListModelMetadataInput @@ -149,7 +151,7 @@ type listModelMetadataInput struct { func (h *Handler) handleListModelMetadata(body []byte) ([]byte, error) { var req listModelMetadataInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -166,7 +168,7 @@ func (h *Handler) handleListModelMetadata(body []byte) ([]byte, error) { }) } - return json.Marshal(map[string]any{"ModelMetadataSummaries": summaries, keyNextToken: next}) + return json.Marshal(map[string]any{"ModelMetadataSummaries": summaries, keyNextToken: next}, jsonx.V1()) } // getSearchSuggestionsInput mirrors GetSearchSuggestionsInput @@ -183,7 +185,7 @@ type getSearchSuggestionsInput struct { func (h *Handler) handleGetSearchSuggestions(_ context.Context, body []byte) ([]byte, error) { var req getSearchSuggestionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +203,7 @@ func (h *Handler) handleGetSearchSuggestions(_ context.Context, body []byte) ([] suggestions = append(suggestions, map[string]string{"PropertyName": n}) } - return json.Marshal(map[string]any{"PropertyNameSuggestions": suggestions}) + return json.Marshal(map[string]any{"PropertyNameSuggestions": suggestions}, jsonx.V1()) } // getScalingConfigurationRecommendationInput mirrors @@ -220,7 +222,7 @@ type getScalingConfigurationRecommendationInput struct { func (h *Handler) handleGetScalingConfigurationRecommendation(ctx context.Context, body []byte) ([]byte, error) { var req getScalingConfigurationRecommendationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -256,5 +258,5 @@ func (h *Handler) handleGetScalingConfigurationRecommendation(ctx context.Contex resp["ScalingPolicyObjective"] = req.ScalingPolicyObjective } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_automl_search_test.go b/services/sagemaker/handler_automl_search_test.go index 585954efb8..41489b6aa0 100644 --- a/services/sagemaker/handler_automl_search_test.go +++ b/services/sagemaker/handler_automl_search_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListCandidatesForAutoMLJob(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_ListCandidatesForAutoMLJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) candidates, ok := resp["Candidates"].([]any) require.True(t, ok) assert.Len(t, candidates, 3) @@ -76,7 +78,7 @@ func TestHandler_Search_TrainingJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["Results"].([]any) require.True(t, ok) require.Len(t, results, 1) @@ -98,7 +100,7 @@ func TestHandler_Search_TrainingJob(t *testing.T) { }) var noMatchResp map[string]any - require.NoError(t, json.Unmarshal(noMatchRec.Body.Bytes(), &noMatchResp)) + require.NoError(t, json.Unmarshal(noMatchRec.Body.Bytes(), &noMatchResp, jsonx.V1())) assert.Empty(t, noMatchResp["Results"]) } @@ -122,7 +124,7 @@ func TestHandler_ListModelMetadata(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["ModelMetadataSummaries"].([]any) require.True(t, ok) assert.NotEmpty(t, summaries) @@ -134,7 +136,7 @@ func TestHandler_ListModelMetadata(t *testing.T) { }) var filteredResp map[string]any - require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp)) + require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp, jsonx.V1())) filtered, ok := filteredResp["ModelMetadataSummaries"].([]any) require.True(t, ok) require.Len(t, filtered, 1) @@ -157,7 +159,7 @@ func TestHandler_GetSearchSuggestions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) suggestions, ok := resp["PropertyNameSuggestions"].([]any) require.True(t, ok) require.Len(t, suggestions, 1) @@ -185,7 +187,7 @@ func TestHandler_GetScalingConfigurationRecommendation(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InEpsilon(t, float64(60), resp["TargetCpuUtilizationPerCore"], 0.001) dyn, ok := resp["DynamicScalingConfiguration"].(map[string]any) require.True(t, ok) @@ -220,7 +222,7 @@ func TestHandler_ListCandidatesForAutoMLJob_SortByStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) candidates := resp["Candidates"].([]any) require.Len(t, candidates, 3) diff --git a/services/sagemaker/handler_automl_test.go b/services/sagemaker/handler_automl_test.go index a843e27cfc..2b0c5fb9fb 100644 --- a/services/sagemaker/handler_automl_test.go +++ b/services/sagemaker/handler_automl_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // autoMLJobRequestBody returns a minimal-but-complete CreateAutoMLJob request @@ -46,7 +48,7 @@ func TestHandler_CreateAutoMLJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["AutoMLJobArn"], "my-job") } @@ -106,7 +108,7 @@ func TestHandler_CreateAutoMLJob_InputDataConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) channels, ok := descResp["InputDataConfig"].([]any) require.True(t, ok, "DescribeAutoMLJob must always emit InputDataConfig (required field)") @@ -134,7 +136,7 @@ func TestHandler_DescribeAutoMLJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "job-1", resp["AutoMLJobName"]) assert.Equal(t, "InProgress", resp["AutoMLJobStatus"]) } @@ -154,7 +156,7 @@ func TestHandler_DescribeAutoMLJob_ModelDeployConfigRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deployConfig, ok := resp["ModelDeployConfig"].(map[string]any) require.True(t, ok, "DescribeAutoMLJob must return ModelDeployConfig when set") @@ -221,7 +223,7 @@ func TestHandler_StopAutoMLJob(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeAutoMLJob", map[string]any{"AutoMLJobName": "job-stop"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Stopping", resp["AutoMLJobStatus"]) } @@ -237,7 +239,7 @@ func TestHandler_ListAutoMLJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["AutoMLJobSummaries"].([]any) assert.Len(t, items, 2) } @@ -257,7 +259,7 @@ func TestHandler_ListAutoMLJobs_FilterSort(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["AutoMLJobSummaries"].([]any) require.Len(t, items, 1) assert.Equal(t, "job-alpha", items[0].(map[string]any)["AutoMLJobName"]) @@ -267,7 +269,7 @@ func TestHandler_ListAutoMLJobs_FilterSort(t *testing.T) { "SortOrder": "Descending", }) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items = resp["AutoMLJobSummaries"].([]any) require.Len(t, items, 2) assert.Equal(t, "job-beta", items[0].(map[string]any)["AutoMLJobName"]) @@ -308,7 +310,7 @@ func TestAutoMLJob_OutputDataConfigRoundtrip(t *testing.T) { MetricName string `json:"MetricName"` } `json:"AutoMLJobObjective"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.OutputDataConfig, "OutputDataConfig must be returned by DescribeAutoMLJob") assert.Equal(t, "s3://my-bucket/automl-output/", out.OutputDataConfig.S3OutputPath) @@ -334,7 +336,7 @@ func TestAutoMLJob_InitialStatus_InProgress(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InProgress", resp["AutoMLJobStatus"]) } diff --git a/services/sagemaker/handler_automl_v2.go b/services/sagemaker/handler_automl_v2.go index a34aa13582..d821e63324 100644 --- a/services/sagemaker/handler_automl_v2.go +++ b/services/sagemaker/handler_automl_v2.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -23,7 +26,7 @@ import ( // enforced. type createAutoMLJobV2Input struct { Tags []tagObject `json:"Tags"` - AutoMLProblemTypeConfig json.RawMessage `json:"AutoMLProblemTypeConfig"` + AutoMLProblemTypeConfig jsontext.Value `json:"AutoMLProblemTypeConfig"` OutputDataConfig *AutoMLOutputDataConfig `json:"OutputDataConfig"` AutoMLJobObjective *AutoMLJobObjective `json:"AutoMLJobObjective"` AutoMLComputeConfig *AutoMLComputeConfig `json:"AutoMLComputeConfig"` @@ -38,7 +41,7 @@ type createAutoMLJobV2Input struct { func (h *Handler) handleCreateAutoMLJobV2(ctx context.Context, body []byte) ([]byte, error) { var req createAutoMLJobV2Input - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -85,7 +88,7 @@ func (h *Handler) handleCreateAutoMLJobV2(ctx context.Context, body []byte) ([]b return nil, extErr } - return json.Marshal(map[string]any{keyAutoMLJobArn: result.AutoMLJobArn}) + return json.Marshal(map[string]any{keyAutoMLJobArn: result.AutoMLJobArn}, jsonx.V1()) } // describeAutoMLJobV2Input mirrors DescribeAutoMLJobV2Input @@ -98,7 +101,7 @@ type describeAutoMLJobV2Input struct { func (h *Handler) handleDescribeAutoMLJobV2(ctx context.Context, body []byte) ([]byte, error) { var req describeAutoMLJobV2Input - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -163,5 +166,5 @@ func (h *Handler) handleDescribeAutoMLJobV2(ctx context.Context, body []byte) ([ resp["ModelDeployConfig"] = j.ModelDeployConfig } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_automl_v2_test.go b/services/sagemaker/handler_automl_v2_test.go index d99d7d6a04..5a18c11dcb 100644 --- a/services/sagemaker/handler_automl_v2_test.go +++ b/services/sagemaker/handler_automl_v2_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAutoMLJobV2_RoundTrip(t *testing.T) { @@ -61,7 +63,7 @@ func TestHandler_CreateAutoMLJobV2_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) channels, ok := out["AutoMLJobInputDataConfig"].([]any) require.True(t, ok, "DescribeAutoMLJobV2 must always emit AutoMLJobInputDataConfig") @@ -170,7 +172,7 @@ func TestHandler_DescribeAutoMLJobV1_OmitsV2Fields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) _, hasV2Field := out["AutoMLJobInputDataConfig"] assert.False(t, hasV2Field, "DescribeAutoMLJob (V1) must not emit the V2-only AutoMLJobInputDataConfig field") diff --git a/services/sagemaker/handler_cluster.go b/services/sagemaker/handler_cluster.go index aa6bad0f67..4effff5279 100644 --- a/services/sagemaker/handler_cluster.go +++ b/services/sagemaker/handler_cluster.go @@ -2,10 +2,12 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -109,7 +111,7 @@ type startClusterHealthCheckInput struct { func (h *Handler) handleStartClusterHealthCheck(ctx context.Context, body []byte) ([]byte, error) { var req startClusterHealthCheckInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -126,7 +128,7 @@ func (h *Handler) handleStartClusterHealthCheck(ctx context.Context, body []byte return nil, err } - return json.Marshal(map[string]string{keyClusterArn: clusterARN}) + return json.Marshal(map[string]string{keyClusterArn: clusterARN}, jsonx.V1()) } // clusterInstanceGroupRequest is the wire shape for a @@ -310,7 +312,7 @@ type createClusterRequest struct { func (h *Handler) handleCreateCluster(ctx context.Context, body []byte) ([]byte, error) { var req createClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -338,7 +340,7 @@ func (h *Handler) handleCreateCluster(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: created cluster", "name", c.ClusterName) - return json.Marshal(map[string]string{keyClusterArn: c.ClusterArn}) + return json.Marshal(map[string]string{keyClusterArn: c.ClusterArn}, jsonx.V1()) } // clusterAutoScalingStatusInService is the (only) status this emulator @@ -396,7 +398,7 @@ func (h *Handler) describeClusterResponse(c *Cluster) []byte { resp["RestrictedInstanceGroupsConfig"] = rc } - b, _ := json.Marshal(resp) + b, _ := json.Marshal(resp, jsonx.V1()) return b } @@ -410,7 +412,7 @@ type describeClusterInput struct { func (h *Handler) handleDescribeCluster(ctx context.Context, body []byte) ([]byte, error) { var req describeClusterInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -451,7 +453,7 @@ type listClustersInput struct { func (h *Handler) handleListClusters(ctx context.Context, body []byte) ([]byte, error) { var req listClustersInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -480,7 +482,7 @@ func (h *Handler) handleListClusters(ctx context.Context, body []byte) ([]byte, resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteClusterInput is DeleteCluster's request shape @@ -492,7 +494,7 @@ type deleteClusterInput struct { func (h *Handler) handleDeleteCluster(ctx context.Context, body []byte) ([]byte, error) { var req deleteClusterInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -507,7 +509,7 @@ func (h *Handler) handleDeleteCluster(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted cluster", "name", req.ClusterName) - return json.Marshal(map[string]string{keyClusterArn: clusterArn}) + return json.Marshal(map[string]string{keyClusterArn: clusterArn}, jsonx.V1()) } // updateClusterRequest is the request body for UpdateCluster. @@ -527,7 +529,7 @@ type updateClusterRequest struct { func (h *Handler) handleUpdateCluster(ctx context.Context, body []byte) ([]byte, error) { var req updateClusterRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -554,7 +556,7 @@ func (h *Handler) handleUpdateCluster(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: updated cluster", "name", c.ClusterName) - return json.Marshal(map[string]string{keyClusterArn: c.ClusterArn}) + return json.Marshal(map[string]string{keyClusterArn: c.ClusterArn}, jsonx.V1()) } // updateClusterSoftwareInstanceGroupRequest is the wire shape for one entry @@ -569,7 +571,7 @@ type updateClusterSoftwareInstanceGroupRequest struct { // updateClusterSoftwareInput mirrors api_op_UpdateClusterSoftware.go:28-63; // DeploymentConfig/ImageId are decoded but have no state to act on. type updateClusterSoftwareInput struct { - DeploymentConfig json.RawMessage `json:"DeploymentConfig"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig"` ClusterName string `json:"ClusterName"` ImageID string `json:"ImageId"` InstanceGroups []updateClusterSoftwareInstanceGroupRequest `json:"InstanceGroups"` @@ -578,7 +580,7 @@ type updateClusterSoftwareInput struct { func (h *Handler) handleUpdateClusterSoftware(ctx context.Context, body []byte) ([]byte, error) { var req updateClusterSoftwareInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -599,7 +601,7 @@ func (h *Handler) handleUpdateClusterSoftware(ctx context.Context, body []byte) logger.Load(ctx). InfoContext(ctx, "sagemaker: updated cluster software", "name", req.ClusterName) - return json.Marshal(map[string]string{keyClusterArn: clusterArn}) + return json.Marshal(map[string]string{keyClusterArn: clusterArn}, jsonx.V1()) } // clusterInstanceStatusDetails mirrors the AWS ClusterInstanceStatusDetails shape. @@ -661,7 +663,7 @@ type describeClusterNodeInput struct { func (h *Handler) handleDescribeClusterNode(ctx context.Context, body []byte) ([]byte, error) { var req describeClusterNodeInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -678,7 +680,7 @@ func (h *Handler) handleDescribeClusterNode(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(map[string]any{"NodeDetails": toClusterNodeDetails(n)}) + return json.Marshal(map[string]any{"NodeDetails": toClusterNodeDetails(n)}, jsonx.V1()) } // clusterNodeSummary is the wire shape for a ListClusterNodes entry @@ -720,7 +722,7 @@ type listClusterNodesInput struct { func (h *Handler) handleListClusterNodes(ctx context.Context, body []byte) ([]byte, error) { var req listClusterNodesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -760,7 +762,7 @@ func (h *Handler) handleListClusterNodes(ctx context.Context, body []byte) ([]by resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // describeClusterEventInput is DescribeClusterEvent's request shape @@ -773,7 +775,7 @@ type describeClusterEventInput struct { func (h *Handler) handleDescribeClusterEvent(ctx context.Context, body []byte) ([]byte, error) { var req describeClusterEventInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -791,7 +793,7 @@ func (h *Handler) handleDescribeClusterEvent(ctx context.Context, body []byte) ( // Unreachable in practice: DescribeClusterEvent always errors above because // this emulator never generates cluster events. - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // listClusterEventsInput is ListClusterEvents' request shape @@ -816,7 +818,7 @@ type listClusterEventsInput struct { func (h *Handler) handleListClusterEvents(ctx context.Context, body []byte) ([]byte, error) { var req listClusterEventsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -828,7 +830,7 @@ func (h *Handler) handleListClusterEvents(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{"Events": []any{}}) + return json.Marshal(map[string]any{"Events": []any{}}, jsonx.V1()) } // detachClusterNodeVolumeRequest is the request body for DetachClusterNodeVolume. @@ -840,7 +842,7 @@ type detachClusterNodeVolumeRequest struct { func (h *Handler) handleDetachClusterNodeVolume(ctx context.Context, body []byte) ([]byte, error) { var req detachClusterNodeVolumeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -861,7 +863,7 @@ func (h *Handler) handleDetachClusterNodeVolume(ctx context.Context, body []byte "DeviceName": dv.DeviceName, keyStatus: dv.Status, "AttachTime": epochSeconds(dv.AttachTime), - }) + }, jsonx.V1()) } // attachClusterNodeVolumeRequest is the request body for @@ -882,7 +884,7 @@ type attachClusterNodeNetworkInterfaceRequest struct { func (h *Handler) handleAttachClusterNodeNetworkInterface(ctx context.Context, body []byte) ([]byte, error) { var req attachClusterNodeNetworkInterfaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -904,12 +906,12 @@ func (h *Handler) handleAttachClusterNodeNetworkInterface(ctx context.Context, b keyNodeID: out.NodeID, "NetworkInterfaceId": out.NetworkInterfaceID, "AttachmentId": out.AttachmentID, - }) + }, jsonx.V1()) } func (h *Handler) handleAttachClusterNodeVolume(ctx context.Context, body []byte) ([]byte, error) { var req attachClusterNodeVolumeRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -942,7 +944,7 @@ func (h *Handler) handleAttachClusterNodeVolume(ctx context.Context, body []byte "DeviceName": av.DeviceName, keyStatus: av.Status, "AttachTime": epochSeconds(av.AttachTime), - }) + }, jsonx.V1()) } // batchAddClusterNodesError mirrors types.BatchAddClusterNodesError @@ -1058,7 +1060,7 @@ type batchAddClusterNodesRequest struct { func (h *Handler) handleBatchAddClusterNodes(ctx context.Context, body []byte) ([]byte, error) { var req batchAddClusterNodesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1104,7 +1106,7 @@ func (h *Handler) handleBatchAddClusterNodes(ctx context.Context, body []byte) ( return json.Marshal(map[string]any{ keyFailed: failedResults, statusSuccessful: successResults, - }) + }, jsonx.V1()) } // batchNodeIDsRequest is the request body shared by BatchDeleteClusterNodes, @@ -1213,7 +1215,7 @@ func (h *Handler) handleBatchNodeIDsOp( buildFailedLogical func(nodeLogicalID string) any, ) ([]byte, error) { var req batchNodeIDsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1246,7 +1248,7 @@ func (h *Handler) handleBatchNodeIDsOp( resp["SuccessfulNodeLogicalIds"] = successfulLogicalResults } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleBatchDeleteClusterNodes(ctx context.Context, body []byte) ([]byte, error) { diff --git a/services/sagemaker/handler_cluster_test.go b/services/sagemaker/handler_cluster_test.go index 899c52fb9a..b930244ed8 100644 --- a/services/sagemaker/handler_cluster_test.go +++ b/services/sagemaker/handler_cluster_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -38,7 +39,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) clusterArn := createResp["ClusterArn"] assert.Contains(t, clusterArn, "arn:aws:sagemaker") assert.Contains(t, clusterArn, "cluster/my-cluster") @@ -48,7 +49,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-cluster", descResp["ClusterName"]) assert.Equal(t, "InService", descResp["ClusterStatus"]) assert.Equal(t, "Automatic", descResp["NodeRecovery"]) @@ -70,7 +71,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["ClusterSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -80,7 +81,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var nodesResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodesResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodesResp, jsonx.V1())) nodeSummaries, ok := nodesResp["ClusterNodeSummaries"].([]any) require.True(t, ok) require.Len(t, nodeSummaries, 2) @@ -102,7 +103,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var nodeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodeResp, jsonx.V1())) nodeDetails, ok := nodeResp["NodeDetails"].(map[string]any) require.True(t, ok) assert.Equal(t, nodeID, nodeDetails["InstanceId"]) @@ -121,7 +122,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeCluster", map[string]any{"ClusterName": "my-cluster"}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "None", descResp["NodeRecovery"]) groups, ok = descResp["InstanceGroups"].([]any) require.True(t, ok) @@ -134,7 +135,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var softwareResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &softwareResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &softwareResp, jsonx.V1())) assert.Equal(t, clusterArn, softwareResp["ClusterArn"]) // ListClusterEvents / DescribeClusterEvent: correct empty shape, no events ever exist. @@ -142,7 +143,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var eventsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &eventsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &eventsResp, jsonx.V1())) events, ok := eventsResp["Events"].([]any) require.True(t, ok) assert.Empty(t, events) @@ -162,7 +163,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var attachResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &attachResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &attachResp, jsonx.V1())) assert.Equal(t, clusterArn, attachResp["ClusterArn"]) assert.Equal(t, nodeID, attachResp["NodeId"]) assert.Equal(t, "vol-123", attachResp["VolumeId"]) @@ -178,7 +179,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var detachResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detachResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &detachResp, jsonx.V1())) assert.Equal(t, clusterArn, detachResp["ClusterArn"]) assert.Equal(t, nodeID, detachResp["NodeId"]) assert.Equal(t, "vol-123", detachResp["VolumeId"]) @@ -198,7 +199,7 @@ func TestHandler_ClusterLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp, jsonx.V1())) assert.Equal(t, clusterArn, deleteResp["ClusterArn"]) // Verify deleted. @@ -225,7 +226,7 @@ func TestHandler_CreateCluster_ClusterRoleAndVpcConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "arn:aws:iam::000000000000:role/HyperPodClusterRole", descResp["ClusterRole"]) vpcConfig, ok := descResp["VpcConfig"].(map[string]any) @@ -309,7 +310,7 @@ func TestHandler_UpdateCluster_DeleteInstanceGroup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["InstanceGroups"].([]any) require.True(t, ok) assert.Len(t, groups, 1) @@ -318,7 +319,7 @@ func TestHandler_UpdateCluster_DeleteInstanceGroup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var nodesResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodesResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &nodesResp, jsonx.V1())) nodes, ok := nodesResp["ClusterNodeSummaries"].([]any) require.True(t, ok) assert.Len(t, nodes, 2) // only group-a's 2 nodes remain @@ -427,7 +428,7 @@ func TestHandler_ListClusters_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["ClusterSummaries"].([]any) require.True(t, ok) @@ -463,7 +464,7 @@ func TestHandler_ListClusters_NameContains(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["ClusterSummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 1) @@ -672,7 +673,7 @@ func TestHandler_AttachClusterNodeVolume(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ClusterArn"], "arn:aws:sagemaker") assert.Equal(t, "node-1", resp["NodeId"]) assert.Equal(t, "vol-1", resp["VolumeId"]) @@ -761,7 +762,7 @@ func TestHandler_BatchAddClusterNodes(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "ClusterArn") assert.IsType(t, []any{}, resp["Failed"]) assert.IsType(t, []any{}, resp["Successful"]) @@ -844,7 +845,7 @@ func TestHandler_BatchDeleteClusterNodes(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "ClusterArn") assert.Contains(t, resp, "Failed") assert.Contains(t, resp, "Successful") @@ -924,7 +925,7 @@ func TestHandler_BatchRebootClusterNodes(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "ClusterArn") assert.Contains(t, resp, "Failed") assert.Contains(t, resp, "Successful") @@ -1004,7 +1005,7 @@ func TestHandler_BatchReplaceClusterNodes(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "ClusterArn") assert.Contains(t, resp, "Failed") assert.Contains(t, resp, "Successful") @@ -1154,7 +1155,7 @@ func TestHandler_CreateCluster_OrchestratorValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) }) } @@ -1433,7 +1434,7 @@ func TestHandler_CreateCluster_InstanceStorageConfigUnion_Validation(t *testing. require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) }) } @@ -1489,7 +1490,7 @@ func TestHandler_CreateCluster_RestrictedInstanceGroupsConfig_Validation(t *test require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) }) } @@ -1548,7 +1549,7 @@ func TestHandler_StartClusterHealthCheck(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ClusterArn"], "hc-cluster") } }) diff --git a/services/sagemaker/handler_code_repositories.go b/services/sagemaker/handler_code_repositories.go index cafe8f86e3..8e756a614f 100644 --- a/services/sagemaker/handler_code_repositories.go +++ b/services/sagemaker/handler_code_repositories.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -21,7 +23,7 @@ type createCodeRepositoryInput struct { func (h *Handler) handleCreateCodeRepository(ctx context.Context, body []byte) ([]byte, error) { var req createCodeRepositoryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -34,7 +36,7 @@ func (h *Handler) handleCreateCodeRepository(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]any{keyCodeRepositoryArn: result.CodeRepositoryArn}) + return json.Marshal(map[string]any{keyCodeRepositoryArn: result.CodeRepositoryArn}, jsonx.V1()) } // describeCodeRepositoryInput mirrors DescribeCodeRepositoryInput @@ -46,7 +48,7 @@ type describeCodeRepositoryInput struct { func (h *Handler) handleDescribeCodeRepository(ctx context.Context, body []byte) ([]byte, error) { var req describeCodeRepositoryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -59,7 +61,7 @@ func (h *Handler) handleDescribeCodeRepository(ctx context.Context, body []byte) return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // updateCodeRepositoryInput mirrors UpdateCodeRepositoryInput @@ -76,7 +78,7 @@ type updateCodeRepositoryInput struct { func (h *Handler) handleUpdateCodeRepository(ctx context.Context, body []byte) ([]byte, error) { var req updateCodeRepositoryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -89,7 +91,7 @@ func (h *Handler) handleUpdateCodeRepository(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]any{keyCodeRepositoryArn: result.CodeRepositoryArn}) + return json.Marshal(map[string]any{keyCodeRepositoryArn: result.CodeRepositoryArn}, jsonx.V1()) } // deleteCodeRepositoryInput mirrors DeleteCodeRepositoryInput @@ -101,7 +103,7 @@ type deleteCodeRepositoryInput struct { func (h *Handler) handleDeleteCodeRepository(ctx context.Context, body []byte) error { var req deleteCodeRepositoryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -129,7 +131,7 @@ type listCodeRepositoriesInput struct { func (h *Handler) handleListCodeRepositories(ctx context.Context, body []byte) ([]byte, error) { var req listCodeRepositoriesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -162,5 +164,5 @@ func (h *Handler) handleListCodeRepositories(ctx context.Context, body []byte) ( return json.Marshal(map[string]any{ "CodeRepositorySummaryList": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } diff --git a/services/sagemaker/handler_code_repositories_test.go b/services/sagemaker/handler_code_repositories_test.go index d4d5e93c17..b56247354a 100644 --- a/services/sagemaker/handler_code_repositories_test.go +++ b/services/sagemaker/handler_code_repositories_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateCodeRepository(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_CreateCodeRepository(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["CodeRepositoryArn"], "my-repo") } @@ -73,7 +75,7 @@ func TestHandler_DescribeCodeRepository(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "repo-1", resp["CodeRepositoryName"]) // The awsjson1.1 protocol encodes timestamps as epoch-seconds numbers, not @@ -108,12 +110,12 @@ func TestHandler_UpdateCodeRepository(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["CodeRepositoryArn"], "repo-upd") descRec := doSageMakerRequest(t, h, "DescribeCodeRepository", map[string]any{"CodeRepositoryName": "repo-upd"}) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) gitConfig, ok := descResp["GitConfig"].(map[string]any) require.True(t, ok, "GitConfig must be present") assert.Equal(t, "https://github.com/test/repo", gitConfig["RepositoryUrl"], @@ -155,7 +157,7 @@ func TestHandler_ListCodeRepositories(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["CodeRepositorySummaryList"].([]any) assert.Len(t, items, 2) } diff --git a/services/sagemaker/handler_compilation_jobs.go b/services/sagemaker/handler_compilation_jobs.go index 3bd98e551f..2a79925690 100644 --- a/services/sagemaker/handler_compilation_jobs.go +++ b/services/sagemaker/handler_compilation_jobs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -19,14 +22,14 @@ type createCompilationJobInput struct { CompilationJobName string `json:"CompilationJobName"` RoleArn string `json:"RoleArn"` ModelPackageVersionArn string `json:"ModelPackageVersionArn"` - VpcConfig json.RawMessage `json:"VpcConfig"` + VpcConfig jsontext.Value `json:"VpcConfig"` Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateCompilationJob(ctx context.Context, body []byte) ([]byte, error) { var req createCompilationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -75,7 +78,7 @@ func (h *Handler) handleCreateCompilationJob(ctx context.Context, body []byte) ( return nil, extErr } - return json.Marshal(map[string]any{"CompilationJobArn": result.CompilationJobArn}) + return json.Marshal(map[string]any{"CompilationJobArn": result.CompilationJobArn}, jsonx.V1()) } // describeCompilationJobInput mirrors DescribeCompilationJobInput @@ -87,7 +90,7 @@ type describeCompilationJobInput struct { func (h *Handler) handleDescribeCompilationJob(ctx context.Context, body []byte) ([]byte, error) { var req describeCompilationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -100,7 +103,7 @@ func (h *Handler) handleDescribeCompilationJob(ctx context.Context, body []byte) return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteCompilationJobInput mirrors DeleteCompilationJobInput @@ -112,7 +115,7 @@ type deleteCompilationJobInput struct { func (h *Handler) handleDeleteCompilationJob(ctx context.Context, body []byte) error { var req deleteCompilationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -132,7 +135,7 @@ type stopCompilationJobInput struct { func (h *Handler) handleStopCompilationJob(ctx context.Context, body []byte) error { var req stopCompilationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -161,7 +164,7 @@ type listCompilationJobsInput struct { func (h *Handler) handleListCompilationJobs(ctx context.Context, body []byte) ([]byte, error) { var req listCompilationJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -214,7 +217,7 @@ func (h *Handler) handleListCompilationJobs(ctx context.Context, body []byte) ([ return json.Marshal(map[string]any{ "CompilationJobSummaries": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } func compilationTargetPlatform(j *CompilationJob) *CompilationTargetPlatform { diff --git a/services/sagemaker/handler_compilation_jobs_test.go b/services/sagemaker/handler_compilation_jobs_test.go index d55ccad4ac..91ae896818 100644 --- a/services/sagemaker/handler_compilation_jobs_test.go +++ b/services/sagemaker/handler_compilation_jobs_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -32,7 +33,7 @@ func TestHandler_CreateCompilationJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["CompilationJobArn"], "my-compile") } @@ -103,7 +104,7 @@ func TestHandler_DescribeCompilationJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "cj-1", resp["CompilationJobName"]) assert.Equal(t, "INPROGRESS", resp["CompilationJobStatus"]) } @@ -138,14 +139,14 @@ func TestHandler_StopCompilationJob(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeCompilationJob", map[string]any{"CompilationJobName": "cj-stop"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "STOPPING", resp["CompilationJobStatus"]) time.Sleep(time.Second) synctest.Wait() rec = doSageMakerRequest(t, h, "DescribeCompilationJob", map[string]any{"CompilationJobName": "cj-stop"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "STOPPED", resp["CompilationJobStatus"]) }) } @@ -174,7 +175,7 @@ func TestHandler_ListCompilationJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["CompilationJobSummaries"].([]any) assert.Len(t, items, 2) } @@ -264,7 +265,7 @@ func TestCompilationJob_HandlerCapturesInputOutputConfig(t *testing.T) { MaxRuntimeInSeconds int32 `json:"MaxRuntimeInSeconds"` } `json:"StoppingCondition"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) require.NotNil(t, out.InputConfig, "InputConfig must be returned by DescribeCompilationJob") assert.Equal(t, "s3://bucket/model.tar.gz", out.InputConfig.S3Uri) @@ -301,7 +302,7 @@ func TestCompilationJob_InitialStatus_InProgress(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "INPROGRESS", resp["CompilationJobStatus"]) } @@ -384,7 +385,7 @@ func TestHandler_AIBenchmarkJobLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["AIBenchmarkJobArn"], "bench-2") rec = doSageMakerRequest( @@ -396,7 +397,7 @@ func TestHandler_AIBenchmarkJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "InProgress", descResp["AIBenchmarkJobStatus"]) assert.Equal(t, "wc-1", descResp["AIWorkloadConfigIdentifier"]) assert.NotEmpty(t, descResp["BenchmarkTarget"]) @@ -411,7 +412,7 @@ func TestHandler_AIBenchmarkJobLifecycle(t *testing.T) { "DescribeAIBenchmarkJob", map[string]any{"AIBenchmarkJobName": "bench-2"}, ) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopping", descResp["AIBenchmarkJobStatus"]) }, }, @@ -472,7 +473,7 @@ func TestHandler_AIBenchmarkJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["AIBenchmarkJobs"].([]any) require.Len(t, items, 1) item := items[0].(map[string]any) diff --git a/services/sagemaker/handler_device_fleets.go b/services/sagemaker/handler_device_fleets.go index eb35eb3dec..de5df622aa 100644 --- a/services/sagemaker/handler_device_fleets.go +++ b/services/sagemaker/handler_device_fleets.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -45,7 +47,7 @@ type createDeviceFleetInput struct { func (h *Handler) handleCreateDeviceFleet(ctx context.Context, body []byte) ([]byte, error) { var req createDeviceFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -71,7 +73,7 @@ func (h *Handler) handleCreateDeviceFleet(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type describeDeviceFleetInput struct { @@ -81,7 +83,7 @@ type describeDeviceFleetInput struct { func (h *Handler) handleDescribeDeviceFleet(ctx context.Context, body []byte) ([]byte, error) { var req describeDeviceFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -90,7 +92,7 @@ func (h *Handler) handleDescribeDeviceFleet(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(f) + return json.Marshal(f, jsonx.V1()) } // listDeviceFleetsInput is ListDeviceFleets' request shape @@ -110,7 +112,7 @@ type listDeviceFleetsInput struct { func (h *Handler) handleListDeviceFleets(ctx context.Context, body []byte) ([]byte, error) { var req listDeviceFleetsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -150,7 +152,7 @@ type updateDeviceFleetInput struct { func (h *Handler) handleUpdateDeviceFleet(ctx context.Context, body []byte) ([]byte, error) { var req updateDeviceFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -165,7 +167,7 @@ func (h *Handler) handleUpdateDeviceFleet(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type deleteDeviceFleetInput struct { @@ -175,7 +177,7 @@ type deleteDeviceFleetInput struct { func (h *Handler) handleDeleteDeviceFleet(ctx context.Context, body []byte) ([]byte, error) { var req deleteDeviceFleetInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -183,7 +185,7 @@ func (h *Handler) handleDeleteDeviceFleet(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -213,7 +215,7 @@ type registerDevicesInput struct { func (h *Handler) handleRegisterDevices(ctx context.Context, body []byte) ([]byte, error) { var req registerDevicesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -226,7 +228,7 @@ func (h *Handler) handleRegisterDevices(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type deregisterDevicesInput struct { @@ -237,7 +239,7 @@ type deregisterDevicesInput struct { func (h *Handler) handleDeregisterDevices(ctx context.Context, body []byte) ([]byte, error) { var req deregisterDevicesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -245,7 +247,7 @@ func (h *Handler) handleDeregisterDevices(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // describeDeviceInput is DescribeDevice's request shape @@ -262,7 +264,7 @@ type describeDeviceInput struct { func (h *Handler) handleDescribeDevice(ctx context.Context, body []byte) ([]byte, error) { var req describeDeviceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -271,7 +273,7 @@ func (h *Handler) handleDescribeDevice(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(d) + return json.Marshal(d, jsonx.V1()) } // listDevicesInput is ListDevices' request shape (api_op_ListDevices.go: @@ -288,7 +290,7 @@ type listDevicesInput struct { func (h *Handler) handleListDevices(ctx context.Context, body []byte) ([]byte, error) { var req listDevicesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } diff --git a/services/sagemaker/handler_device_fleets_test.go b/services/sagemaker/handler_device_fleets_test.go index 85363f5f82..58d7e11531 100644 --- a/services/sagemaker/handler_device_fleets_test.go +++ b/services/sagemaker/handler_device_fleets_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DeviceFleetLifecycle(t *testing.T) { @@ -30,7 +32,7 @@ func TestHandler_DeviceFleetLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Empty(t, createResp) // Describe @@ -40,7 +42,7 @@ func TestHandler_DeviceFleetLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-fleet", descResp["DeviceFleetName"]) assert.Equal(t, "test fleet", descResp["Description"]) assert.Contains(t, descResp["DeviceFleetArn"], "my-fleet") @@ -56,7 +58,7 @@ func TestHandler_DeviceFleetLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["DeviceFleetSummaries"].([]any) assert.Len(t, summaries, 1) @@ -73,7 +75,7 @@ func TestHandler_DeviceFleetLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeDeviceFleet", map[string]any{ "DeviceFleetName": "my-fleet", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "updated description", descResp["Description"]) outCfg, ok = descResp["OutputConfig"].(map[string]any) require.True(t, ok) @@ -151,7 +153,7 @@ func TestHandler_DeviceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "dev-1", descResp["DeviceName"]) assert.Equal(t, "fleet-a", descResp["DeviceFleetName"]) @@ -162,7 +164,7 @@ func TestHandler_DeviceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["DeviceSummaries"].([]any) assert.Len(t, summaries, 2) @@ -175,7 +177,7 @@ func TestHandler_DeviceLifecycle(t *testing.T) { // Verify only one remains rec = doSageMakerRequest(t, h, "ListDevices", map[string]any{"DeviceFleetName": "fleet-a"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries = listResp["DeviceSummaries"].([]any) assert.Len(t, summaries, 1) } @@ -207,7 +209,7 @@ func TestHandler_ListDevices_NoFleetFilter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["DeviceSummaries"].([]any) assert.Len(t, summaries, 2) } diff --git a/services/sagemaker/handler_domains.go b/services/sagemaker/handler_domains.go index 78d735f2cd..7d4a0af87e 100644 --- a/services/sagemaker/handler_domains.go +++ b/services/sagemaker/handler_domains.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -18,26 +20,26 @@ import ( // are carried as opaque json.RawMessage passthrough per this file's // established convention (algorithms.go, ai_workload_configs.go). type createDomainInput struct { - DomainName string `json:"DomainName"` - AuthMode string `json:"AuthMode"` - AppNetworkAccessType string `json:"AppNetworkAccessType"` - AppSecurityGroupManagement string `json:"AppSecurityGroupManagement"` - HomeEfsFileSystemCreation string `json:"HomeEfsFileSystemCreation"` - KmsKeyID string `json:"KmsKeyId"` - HomeEfsFileSystemKmsKeyID string `json:"HomeEfsFileSystemKmsKeyId"` - VpcID string `json:"VpcId"` - TagPropagation string `json:"TagPropagation"` - SubnetIDs []string `json:"SubnetIds"` - DefaultUserSettings json.RawMessage `json:"DefaultUserSettings"` - DefaultSpaceSettings json.RawMessage `json:"DefaultSpaceSettings"` - DomainSettings json.RawMessage `json:"DomainSettings"` - Tags []tagObject `json:"Tags"` + DomainName string `json:"DomainName"` + AuthMode string `json:"AuthMode"` + AppNetworkAccessType string `json:"AppNetworkAccessType"` + AppSecurityGroupManagement string `json:"AppSecurityGroupManagement"` + HomeEfsFileSystemCreation string `json:"HomeEfsFileSystemCreation"` + KmsKeyID string `json:"KmsKeyId"` + HomeEfsFileSystemKmsKeyID string `json:"HomeEfsFileSystemKmsKeyId"` + VpcID string `json:"VpcId"` + TagPropagation string `json:"TagPropagation"` + SubnetIDs []string `json:"SubnetIds"` + DefaultUserSettings jsontext.Value `json:"DefaultUserSettings"` + DefaultSpaceSettings jsontext.Value `json:"DefaultSpaceSettings"` + DomainSettings jsontext.Value `json:"DomainSettings"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateDomain(ctx context.Context, body []byte) ([]byte, error) { var req createDomainInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -76,7 +78,7 @@ func (h *Handler) handleCreateDomain(ctx context.Context, body []byte) ([]byte, InfoContext(ctx, "sagemaker: created domain", "name", d.DomainName, "id", d.DomainID) return json.Marshal( - map[string]string{keyDomainArn: d.DomainArn, keyDomainID: d.DomainID, keyURL: d.URL}, + map[string]string{keyDomainArn: d.DomainArn, keyDomainID: d.DomainID, keyURL: d.URL}, jsonx.V1(), ) } @@ -87,7 +89,7 @@ type describeDomainInput struct { func (h *Handler) handleDescribeDomain(ctx context.Context, body []byte) ([]byte, error) { var req describeDomainInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -155,7 +157,7 @@ func (h *Handler) handleDescribeDomain(ctx context.Context, body []byte) ([]byte resp["DomainSettings"] = d.DomainSettings } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type domainSummary struct { @@ -174,7 +176,7 @@ type listDomainsInput struct { func (h *Handler) handleListDomains(ctx context.Context, body []byte) ([]byte, error) { var req listDomainsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -196,7 +198,7 @@ func (h *Handler) handleListDomains(ctx context.Context, body []byte) ([]byte, e resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type deleteDomainInput struct { @@ -209,7 +211,7 @@ type deleteDomainInput struct { func (h *Handler) handleDeleteDomain(ctx context.Context, body []byte) error { var req deleteDomainInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -237,22 +239,22 @@ func (h *Handler) handleDeleteDomain(ctx context.Context, body []byte) error { // distinct, separately-shaped type from Create's DomainSettings, so it gets // its own opaque passthrough field on the wire. type updateDomainInput struct { - DomainID string `json:"DomainId"` - AppNetworkAccessType string `json:"AppNetworkAccessType"` - AppSecurityGroupManagement string `json:"AppSecurityGroupManagement"` - HomeEfsFileSystemCreation string `json:"HomeEfsFileSystemCreation"` - TagPropagation string `json:"TagPropagation"` - VpcID string `json:"VpcId"` - SubnetIDs []string `json:"SubnetIds"` - DefaultUserSettings json.RawMessage `json:"DefaultUserSettings"` - DefaultSpaceSettings json.RawMessage `json:"DefaultSpaceSettings"` - DomainSettingsForUpdate json.RawMessage `json:"DomainSettingsForUpdate"` + DomainID string `json:"DomainId"` + AppNetworkAccessType string `json:"AppNetworkAccessType"` + AppSecurityGroupManagement string `json:"AppSecurityGroupManagement"` + HomeEfsFileSystemCreation string `json:"HomeEfsFileSystemCreation"` + TagPropagation string `json:"TagPropagation"` + VpcID string `json:"VpcId"` + SubnetIDs []string `json:"SubnetIds"` + DefaultUserSettings jsontext.Value `json:"DefaultUserSettings"` + DefaultSpaceSettings jsontext.Value `json:"DefaultSpaceSettings"` + DomainSettingsForUpdate jsontext.Value `json:"DomainSettingsForUpdate"` } func (h *Handler) handleUpdateDomain(ctx context.Context, body []byte) ([]byte, error) { var req updateDomainInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -281,5 +283,5 @@ func (h *Handler) handleUpdateDomain(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: updated domain", "id", req.DomainID) - return json.Marshal(map[string]string{keyDomainArn: d.DomainArn}) + return json.Marshal(map[string]string{keyDomainArn: d.DomainArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_domains_test.go b/services/sagemaker/handler_domains_test.go index 824e38851e..72347207cf 100644 --- a/services/sagemaker/handler_domains_test.go +++ b/services/sagemaker/handler_domains_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateDomain(t *testing.T) { @@ -50,7 +52,7 @@ func TestHandler_CreateDomain(t *testing.T) { if tt.wantID { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["DomainId"]) assert.NotEmpty(t, resp["Url"]) } @@ -76,7 +78,7 @@ func TestHandler_DomainLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) domainID := createOut["DomainId"].(string) // Describe domain by ID — the fields accepted on Create must round-trip, @@ -86,7 +88,7 @@ func TestHandler_DomainLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "test-domain", descOut["DomainName"]) assert.Equal(t, "VpcOnly", descOut["AppNetworkAccessType"]) assert.Equal(t, "vpc-1", descOut["VpcId"]) @@ -98,7 +100,7 @@ func TestHandler_DomainLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["Domains"].([]any), 1) // Update domain — a partial update must apply the overridable field @@ -113,7 +115,7 @@ func TestHandler_DomainLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDescAfterUpdate.Code) var descAfterUpdate map[string]any - require.NoError(t, json.Unmarshal(recDescAfterUpdate.Body.Bytes(), &descAfterUpdate)) + require.NoError(t, json.Unmarshal(recDescAfterUpdate.Body.Bytes(), &descAfterUpdate, jsonx.V1())) assert.Equal(t, "PublicInternetOnly", descAfterUpdate["AppNetworkAccessType"]) assert.Equal(t, "vpc-1", descAfterUpdate["VpcId"]) @@ -170,7 +172,7 @@ func TestHandler_ListDomains_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Domains"].([]any), 2, "MaxResults must cap the page, not just be parsed and ignored") assert.NotEmpty(t, out["NextToken"]) } diff --git a/services/sagemaker/handler_edge_deployment.go b/services/sagemaker/handler_edge_deployment.go index 106ea8028b..13d33a486a 100644 --- a/services/sagemaker/handler_edge_deployment.go +++ b/services/sagemaker/handler_edge_deployment.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // edge deployment operation name constants. @@ -105,9 +108,9 @@ func (h *Handler) dispatchEdgeDeploymentOps( // --------------------------------------------------------------------------- type edgeDeploymentStageRequest struct { - StageName string `json:"StageName"` - DeviceSelectionConfig json.RawMessage `json:"DeviceSelectionConfig,omitempty"` - DeploymentConfig json.RawMessage `json:"DeploymentConfig,omitempty"` + StageName string `json:"StageName"` + DeviceSelectionConfig jsontext.Value `json:"DeviceSelectionConfig,omitempty"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig,omitempty"` } func toEdgeDeploymentStageInputs(reqs []edgeDeploymentStageRequest) []EdgeDeploymentStageInput { @@ -133,7 +136,7 @@ type createEdgeDeploymentPlanInput struct { func (h *Handler) handleCreateEdgeDeploymentPlan(ctx context.Context, body []byte) ([]byte, error) { var req createEdgeDeploymentPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -156,7 +159,7 @@ func (h *Handler) handleCreateEdgeDeploymentPlan(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]string{keyEdgeDeploymentPlanArn: p.EdgeDeploymentPlanArn}) + return json.Marshal(map[string]string{keyEdgeDeploymentPlanArn: p.EdgeDeploymentPlanArn}, jsonx.V1()) } // stageStatusSummary builds the DeploymentStageStatusSummary shape for a @@ -193,7 +196,7 @@ type describeEdgeDeploymentPlanInput struct { func (h *Handler) handleDescribeEdgeDeploymentPlan(ctx context.Context, body []byte) ([]byte, error) { var req describeEdgeDeploymentPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -234,7 +237,7 @@ func (h *Handler) handleDescribeEdgeDeploymentPlan(ctx context.Context, body []b resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type deleteEdgeDeploymentPlanInput struct { @@ -244,7 +247,7 @@ type deleteEdgeDeploymentPlanInput struct { func (h *Handler) handleDeleteEdgeDeploymentPlan(ctx context.Context, body []byte) error { var req deleteEdgeDeploymentPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -267,7 +270,7 @@ type listEdgeDeploymentPlansInput struct { func (h *Handler) handleListEdgeDeploymentPlans(ctx context.Context, body []byte) ([]byte, error) { var req listEdgeDeploymentPlansInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -313,7 +316,7 @@ type createEdgeDeploymentStageInput struct { func (h *Handler) handleCreateEdgeDeploymentStage(ctx context.Context, body []byte) error { var req createEdgeDeploymentStageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -328,7 +331,7 @@ type deleteEdgeDeploymentStageInput struct { func (h *Handler) handleDeleteEdgeDeploymentStage(ctx context.Context, body []byte) error { var req deleteEdgeDeploymentStageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -343,7 +346,7 @@ type startEdgeDeploymentStageInput struct { func (h *Handler) handleStartEdgeDeploymentStage(ctx context.Context, body []byte) error { var req startEdgeDeploymentStageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -358,7 +361,7 @@ type stopEdgeDeploymentStageInput struct { func (h *Handler) handleStopEdgeDeploymentStage(ctx context.Context, body []byte) error { var req stopEdgeDeploymentStageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -376,7 +379,7 @@ type getDeviceFleetReportInput struct { func (h *Handler) handleGetDeviceFleetReport(ctx context.Context, body []byte) ([]byte, error) { var req getDeviceFleetReportInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -410,7 +413,7 @@ func (h *Handler) handleGetDeviceFleetReport(ctx context.Context, body []byte) ( resp["OutputConfig"] = outputConfig } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type listStageDevicesInput struct { @@ -424,7 +427,7 @@ type listStageDevicesInput struct { func (h *Handler) handleListStageDevices(ctx context.Context, body []byte) ([]byte, error) { var req listStageDevicesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -470,7 +473,7 @@ type updateDevicesInput struct { func (h *Handler) handleUpdateDevices(ctx context.Context, body []byte) error { var req updateDevicesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_edge_deployment_test.go b/services/sagemaker/handler_edge_deployment_test.go index 08f133fd23..34f47447e9 100644 --- a/services/sagemaker/handler_edge_deployment_test.go +++ b/services/sagemaker/handler_edge_deployment_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -49,7 +50,7 @@ func TestHandler_EdgeDeploymentPlanLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["EdgeDeploymentPlanArn"], "my-plan") // Describe @@ -59,7 +60,7 @@ func TestHandler_EdgeDeploymentPlanLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-plan", descResp["EdgeDeploymentPlanName"]) assert.Equal(t, "plan-fleet", descResp["DeviceFleetName"]) @@ -91,7 +92,7 @@ func TestHandler_EdgeDeploymentPlanLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["EdgeDeploymentPlanSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -181,7 +182,7 @@ func TestHandler_EdgeDeploymentStageLifecycle(t *testing.T) { "EdgeDeploymentPlanName": "stage-plan", }) var resp map[string]any - require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(descResp.Body.Bytes(), &resp, jsonx.V1())) return resp } @@ -293,7 +294,7 @@ func TestHandler_GetDeviceFleetReport(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "report-fleet", resp["DeviceFleetName"]) assert.Contains(t, resp["DeviceFleetArn"], "report-fleet") assert.Equal(t, "fleet for reports", resp["Description"]) @@ -353,7 +354,7 @@ func TestHandler_ListStageDevices(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["DeviceDeploymentSummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 2) @@ -422,7 +423,7 @@ func TestHandler_UpdateDevices(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "updated", resp["Description"]) assert.Equal(t, "thing-1", resp["IotThingName"]) } @@ -478,7 +479,7 @@ func TestHandler_EdgeDeploymentPlan_SnapshotRestore(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["EdgeDeploymentPlanSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, tt.wantCount) @@ -493,7 +494,7 @@ func TestHandler_EdgeDeploymentPlan_SnapshotRestore(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "persist-fleet", descResp["DeviceFleetName"]) stages, ok := descResp["Stages"].([]any) diff --git a/services/sagemaker/handler_edge_packaging_jobs.go b/services/sagemaker/handler_edge_packaging_jobs.go index 279fc8da47..5232504cc2 100644 --- a/services/sagemaker/handler_edge_packaging_jobs.go +++ b/services/sagemaker/handler_edge_packaging_jobs.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -34,7 +36,7 @@ type createEdgePackagingJobRequest struct { func (h *Handler) handleCreateEdgePackagingJob(ctx context.Context, body []byte) ([]byte, error) { var req createEdgePackagingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -81,7 +83,7 @@ func (h *Handler) handleCreateEdgePackagingJob(ctx context.Context, body []byte) return nil, err } - return json.Marshal(map[string]string{keyEdgePackagingJobArn: j.EdgePackagingJobArn}) + return json.Marshal(map[string]string{keyEdgePackagingJobArn: j.EdgePackagingJobArn}, jsonx.V1()) } type describeEdgePackagingJobRequest struct { @@ -91,7 +93,7 @@ type describeEdgePackagingJobRequest struct { func (h *Handler) handleDescribeEdgePackagingJob(ctx context.Context, body []byte) ([]byte, error) { var req describeEdgePackagingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -137,7 +139,7 @@ func (h *Handler) handleDescribeEdgePackagingJob(ctx context.Context, body []byt resp["ModelArtifact"] = j.ModelArtifact } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type stopEdgePackagingJobRequest struct { @@ -147,7 +149,7 @@ type stopEdgePackagingJobRequest struct { func (h *Handler) handleStopEdgePackagingJob(ctx context.Context, body []byte) error { var req stopEdgePackagingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -186,7 +188,7 @@ type listEdgePackagingJobsRequest struct { func (h *Handler) handleListEdgePackagingJobs(ctx context.Context, body []byte) ([]byte, error) { var req listEdgePackagingJobsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -222,5 +224,5 @@ func (h *Handler) handleListEdgePackagingJobs(ctx context.Context, body []byte) resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_edge_packaging_jobs_test.go b/services/sagemaker/handler_edge_packaging_jobs_test.go index 14a71cabfa..852f960061 100644 --- a/services/sagemaker/handler_edge_packaging_jobs_test.go +++ b/services/sagemaker/handler_edge_packaging_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_DescribeEdgePackagingJob_FailureReasonNotOnWire proves @@ -28,7 +30,7 @@ func TestHandler_DescribeEdgePackagingJob_FailureReasonNotOnWire(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) _, present := out["FailureReason"] assert.False(t, present, "FailureReason is not a real DescribeEdgePackagingJobOutput field") } @@ -55,7 +57,7 @@ func TestHandler_EdgePackagingJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["EdgePackagingJobArn"], "my-edge-job") // Describe @@ -65,7 +67,7 @@ func TestHandler_EdgePackagingJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-edge-job", descResp["EdgePackagingJobName"]) assert.Equal(t, "my-model", descResp["ModelName"]) assert.Equal(t, "1.0", descResp["ModelVersion"]) @@ -80,7 +82,7 @@ func TestHandler_EdgePackagingJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["EdgePackagingJobSummaries"].([]any) assert.Len(t, summaries, 1) @@ -95,7 +97,7 @@ func TestHandler_EdgePackagingJobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeEdgePackagingJob", map[string]any{ "EdgePackagingJobName": "my-edge-job", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "STOPPING", descResp["EdgePackagingJobStatus"]) // Confirm it actually reaches STOPPED. @@ -105,7 +107,7 @@ func TestHandler_EdgePackagingJobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeEdgePackagingJob", map[string]any{ "EdgePackagingJobName": "my-edge-job", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "STOPPED", descResp["EdgePackagingJobStatus"]) }) } @@ -137,7 +139,7 @@ func TestHandler_EdgePackagingJob_ReachesCompleted(t *testing.T) { "EdgePackagingJobName": "edge-job-completes", }) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "COMPLETED", out["EdgePackagingJobStatus"]) assert.Equal(t, "s3://bucket/edge-out/model.tar.gz", out["ModelArtifact"], "ModelArtifact must be derived from OutputConfig.S3OutputLocation once the job completes") @@ -227,7 +229,7 @@ func TestHandler_ListEdgePackagingJobs_StatusFilter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["EdgePackagingJobSummaries"].([]any) assert.Len(t, summaries, 1) } @@ -248,7 +250,7 @@ func TestHandler_ListEdgePackagingJobs_FilterSort(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["EdgePackagingJobSummaries"].([]any)) // SortBy=NAME, SortOrder=Descending reverses this backend's ascending @@ -258,7 +260,7 @@ func TestHandler_ListEdgePackagingJobs_FilterSort(t *testing.T) { "SortOrder": "Descending", }) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["EdgePackagingJobSummaries"].([]any) require.Len(t, summaries, 2) assert.Equal(t, "job-beta", summaries[0].(map[string]any)["EdgePackagingJobName"]) @@ -316,7 +318,7 @@ func TestHandler_JobLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["JobArn"], "job-2") rec = doSageMakerRequest(t, h, "DescribeJob", map[string]any{ @@ -325,7 +327,7 @@ func TestHandler_JobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "InProgress", descResp["JobStatus"]) assert.Equal(t, "AgentRFT", descResp["JobCategory"]) assert.NotEmpty(t, descResp["SecondaryStatusTransitions"]) @@ -346,7 +348,7 @@ func TestHandler_JobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeJob", map[string]any{ "JobCategory": "AgentRFT", "JobName": "job-2", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopping", descResp["JobStatus"]) }, }, @@ -403,7 +405,7 @@ func TestHandler_JobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["JobSummaries"].([]any) require.Len(t, items, 1) assert.Equal(t, "job-cat-a", items[0].(map[string]any)["JobName"]) @@ -440,7 +442,7 @@ func TestHandler_JobSchemaVersionLookups(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "1.0", resp["JobConfigSchemaVersion"]) assert.NotEmpty(t, resp["JobConfigSchema"]) }, @@ -472,7 +474,7 @@ func TestHandler_JobSchemaVersionLookups(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["JobConfigSchemas"].([]any) require.Len(t, items, 1) assert.Equal(t, "1.0", items[0].(map[string]any)["JobConfigSchemaVersion"]) diff --git a/services/sagemaker/handler_endpoint_configs.go b/services/sagemaker/handler_endpoint_configs.go index 97f293fe29..5229554264 100644 --- a/services/sagemaker/handler_endpoint_configs.go +++ b/services/sagemaker/handler_endpoint_configs.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -13,8 +15,8 @@ type createEndpointConfigRequest struct { DataCaptureConfig *DataCaptureConfig `json:"DataCaptureConfig,omitempty"` AsyncInferenceConfig *AsyncInferenceConfig `json:"AsyncInferenceConfig,omitempty"` VpcConfig *VpcConfig `json:"VpcConfig,omitempty"` - ExplainerConfig json.RawMessage `json:"ExplainerConfig,omitempty"` - MetricsConfig json.RawMessage `json:"MetricsConfig,omitempty"` + ExplainerConfig jsontext.Value `json:"ExplainerConfig,omitempty"` + MetricsConfig jsontext.Value `json:"MetricsConfig,omitempty"` EndpointConfigName string `json:"EndpointConfigName"` ExecutionRoleArn string `json:"ExecutionRoleArn,omitempty"` KmsKeyID string `json:"KmsKeyId,omitempty"` @@ -36,8 +38,8 @@ type describeEndpointConfigResponse struct { DataCaptureConfig *DataCaptureConfig `json:"DataCaptureConfig,omitempty"` AsyncInferenceConfig *AsyncInferenceConfig `json:"AsyncInferenceConfig,omitempty"` VpcConfig *VpcConfig `json:"VpcConfig,omitempty"` - ExplainerConfig json.RawMessage `json:"ExplainerConfig,omitempty"` - MetricsConfig json.RawMessage `json:"MetricsConfig,omitempty"` + ExplainerConfig jsontext.Value `json:"ExplainerConfig,omitempty"` + MetricsConfig jsontext.Value `json:"MetricsConfig,omitempty"` EndpointConfigArn string `json:"EndpointConfigArn"` EndpointConfigName string `json:"EndpointConfigName"` ExecutionRoleArn string `json:"ExecutionRoleArn,omitempty"` @@ -50,7 +52,7 @@ type describeEndpointConfigResponse struct { func (h *Handler) handleCreateEndpointConfig(ctx context.Context, body []byte) ([]byte, error) { var req createEndpointConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -91,7 +93,7 @@ func (h *Handler) handleCreateEndpointConfig(ctx context.Context, body []byte) ( log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created endpoint config", "name", ec.EndpointConfigName) - return json.Marshal(map[string]string{"EndpointConfigArn": ec.EndpointConfigARN}) + return json.Marshal(map[string]string{"EndpointConfigArn": ec.EndpointConfigARN}, jsonx.V1()) } // describeEndpointConfigRequest is the request body for DescribeEndpointConfig. @@ -102,7 +104,7 @@ type describeEndpointConfigRequest struct { func (h *Handler) handleDescribeEndpointConfig(ctx context.Context, body []byte) ([]byte, error) { var req describeEndpointConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -139,13 +141,17 @@ func (h *Handler) handleDescribeEndpointConfig(ctx context.Context, body []byte) resp.ShadowProductionVariants = nil } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } -func (h *Handler) handleListEndpointConfigs(ctx context.Context, body []byte) ([]byte, error) { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleListEndpointConfigs( + ctx context.Context, + body []byte, +) ([]byte, error) { var req nameTimeListRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -171,7 +177,7 @@ func (h *Handler) handleListEndpointConfigs(ctx context.Context, body []byte) ([ resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteEndpointConfigRequest is the request body for DeleteEndpointConfig. @@ -182,7 +188,7 @@ type deleteEndpointConfigRequest struct { func (h *Handler) handleDeleteEndpointConfig(ctx context.Context, body []byte) error { var req deleteEndpointConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_endpoint_configs_test.go b/services/sagemaker/handler_endpoint_configs_test.go index 38307aa447..749c714761 100644 --- a/services/sagemaker/handler_endpoint_configs_test.go +++ b/services/sagemaker/handler_endpoint_configs_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -60,7 +61,7 @@ func TestHandler_CreateEndpointConfig(t *testing.T) { if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["EndpointConfigArn"], "arn:aws:sagemaker") } }) @@ -111,7 +112,7 @@ func TestHandler_DescribeEndpointConfig(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["EndpointConfigName"]) } }) @@ -250,7 +251,7 @@ func TestHandler_ListEndpointConfigs(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs, ok := resp["EndpointConfigs"].([]any) require.True(t, ok) @@ -281,7 +282,7 @@ func TestHandler_Tags_EndpointConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].([]any) require.True(t, ok) @@ -334,7 +335,7 @@ func TestHandler_ListEndpointConfigsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs, configsOK := resp["EndpointConfigs"].([]any) require.True(t, configsOK) @@ -355,7 +356,7 @@ func TestHandler_ListEndpointConfigsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) configs2, configs2OK := resp2["EndpointConfigs"].([]any) require.True(t, configs2OK) @@ -444,7 +445,7 @@ func TestCreateEndpointConfig_RequiresProductionVariants(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) msg, _ := errResp["message"].(string) assert.Contains(t, msg, "ProductionVariant", "error message must mention ProductionVariant") @@ -525,7 +526,7 @@ func TestHandler_ListEndpointConfigs_FilterSort(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["EndpointConfigs"].([]any) require.True(t, ok) @@ -597,7 +598,7 @@ func TestHandler_ListEndpointConfigs_CreationTimeAfterInclusive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs, ok := resp["EndpointConfigs"].([]any) require.True(t, ok) require.Len(t, configs, 1) diff --git a/services/sagemaker/handler_endpoints.go b/services/sagemaker/handler_endpoints.go index fde3085066..78b733b463 100644 --- a/services/sagemaker/handler_endpoints.go +++ b/services/sagemaker/handler_endpoints.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -14,15 +16,15 @@ import ( // createEndpointInput mirrors CreateEndpointInput (api_op_CreateEndpoint.go:1-49). type createEndpointInput struct { - DeploymentConfig json.RawMessage `json:"DeploymentConfig"` - EndpointName string `json:"EndpointName"` - EndpointConfigName string `json:"EndpointConfigName"` - Tags []tagObject `json:"Tags"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig"` + EndpointName string `json:"EndpointName"` + EndpointConfigName string `json:"EndpointConfigName"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateEndpointFSM(ctx context.Context, body []byte) ([]byte, error) { var req createEndpointInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.EndpointName == "" { @@ -48,24 +50,24 @@ func (h *Handler) handleCreateEndpointFSM(ctx context.Context, body []byte) ([]b log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created endpoint (FSM)", "name", ep.EndpointName) - return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}) + return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}, jsonx.V1()) } // updateEndpointInput mirrors UpdateEndpointInput (api_op_UpdateEndpoint.go:1-56). type updateEndpointInput struct { - DeploymentConfig json.RawMessage `json:"DeploymentConfig"` - EndpointConfigName string `json:"EndpointConfigName"` - EndpointName string `json:"EndpointName"` - ExcludeRetainedVariantProperties []string `json:"ExcludeRetainedVariantProperties"` - RetainAllVariantProperties bool `json:"RetainAllVariantProperties"` - RetainDeploymentConfig bool `json:"RetainDeploymentConfig"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig"` + EndpointConfigName string `json:"EndpointConfigName"` + EndpointName string `json:"EndpointName"` + ExcludeRetainedVariantProperties []string `json:"ExcludeRetainedVariantProperties"` + RetainAllVariantProperties bool `json:"RetainAllVariantProperties"` + RetainDeploymentConfig bool `json:"RetainDeploymentConfig"` } // handleUpdateEndpointFSM replaces the basic UpdateEndpoint handler with one // that properly drives Updating → InService via the lifecycle simulator. func (h *Handler) handleUpdateEndpointFSM(ctx context.Context, body []byte) ([]byte, error) { var req updateEndpointInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.EndpointName == "" { @@ -86,7 +88,7 @@ func (h *Handler) handleUpdateEndpointFSM(ctx context.Context, body []byte) ([]b log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: updated endpoint (FSM)", "name", ep.EndpointName) - return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}) + return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -105,7 +107,7 @@ func (h *Handler) handleUpdateEndpointWeightsAndCapacitiesFull( body []byte, ) ([]byte, error) { var req updateEndpointWeightsAndCapacitiesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.EndpointName == "" { @@ -129,7 +131,7 @@ func (h *Handler) handleUpdateEndpointWeightsAndCapacitiesFull( req.EndpointName, ) - return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}) + return json.Marshal(map[string]string{keyEndpointArn: ep.EndpointArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -199,7 +201,7 @@ func endpointResponseMap(ep *Endpoint) map[string]any { func (h *Handler) handleDescribeEndpoint(ctx context.Context, body []byte) ([]byte, error) { var req describeEndpointInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -212,7 +214,7 @@ func (h *Handler) handleDescribeEndpoint(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(endpointResponseMap(ep)) + return json.Marshal(endpointResponseMap(ep), jsonx.V1()) } type endpointSummary struct { @@ -240,7 +242,7 @@ type listEndpointsInput struct { func (h *Handler) handleListEndpoints(ctx context.Context, body []byte) ([]byte, error) { var req listEndpointsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -273,7 +275,7 @@ func (h *Handler) handleListEndpoints(ctx context.Context, body []byte) ([]byte, resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteEndpointInput mirrors DeleteEndpointInput (api_op_DeleteEndpoint.go:29-37). @@ -284,7 +286,7 @@ type deleteEndpointInput struct { func (h *Handler) handleDeleteEndpoint(ctx context.Context, body []byte) error { var req deleteEndpointInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_endpoints_test.go b/services/sagemaker/handler_endpoints_test.go index 8f3c1622bb..f838980949 100644 --- a/services/sagemaker/handler_endpoints_test.go +++ b/services/sagemaker/handler_endpoints_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DescribeEndpoint_FullResponse(t *testing.T) { @@ -44,7 +46,7 @@ func TestHandler_DescribeEndpoint_FullResponse(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "ep1", descResp["EndpointName"]) assert.NotEmpty(t, descResp["EndpointArn"]) // Status is Creating initially (FSM transitions async) @@ -94,7 +96,7 @@ func TestHandler_DescribeEndpoint_DataCaptureAndAsyncInferenceConfig(t *testing. require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dcc, ok := resp["DataCaptureConfig"].(map[string]any) require.True(t, ok, "DataCaptureConfig must be surfaced from the active EndpointConfig") @@ -137,7 +139,7 @@ func TestHandler_CreateEndpoint_DeploymentConfigEchoedOnDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) lastDeploymentConfig, ok := resp["LastDeploymentConfig"].(map[string]any) require.True(t, ok, "LastDeploymentConfig must echo the DeploymentConfig sent to Create/UpdateEndpoint") @@ -186,7 +188,7 @@ func TestHandler_UpdateEndpoint_RetainAllVariantProperties(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeEndpoint", map[string]any{"EndpointName": "ep-retain"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) variant := resp["ProductionVariants"].([]any)[0].(map[string]any) assert.InDelta(t, float64(5), variant["DesiredInstanceCount"], 0) assert.InDelta(t, 9.0, variant["DesiredWeight"], 0.0001) @@ -207,7 +209,7 @@ func TestHandler_UpdateEndpoint_RetainAllVariantProperties(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeEndpoint", map[string]any{"EndpointName": "ep-retain-true"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) variant := resp["ProductionVariants"].([]any)[0].(map[string]any) assert.InDelta(t, float64(1), variant["DesiredInstanceCount"], 0) assert.InDelta(t, 1.0, variant["DesiredWeight"], 0.0001) @@ -229,7 +231,7 @@ func TestHandler_UpdateEndpoint_RetainAllVariantProperties(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeEndpoint", map[string]any{"EndpointName": "ep-retain-exclude"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) variant := resp["ProductionVariants"].([]any)[0].(map[string]any) // Excluded, so takes the new config's value instead of retaining. assert.InDelta(t, float64(5), variant["DesiredInstanceCount"], 0) @@ -298,7 +300,7 @@ func TestHandler_ListEndpoints_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, _ := resp["Endpoints"].([]any) assert.Len(t, endpoints, tc.wantCount) }) @@ -329,7 +331,7 @@ func TestHandler_ListEndpoints_SortByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, ok := resp["Endpoints"].([]any) require.True(t, ok) require.Len(t, endpoints, 2) @@ -348,7 +350,7 @@ func TestHandler_CreateEndpoint_UnknownEndpointConfig(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) rec = doSageMakerRequest(t, h, "DescribeEndpoint", map[string]any{ @@ -389,7 +391,7 @@ func TestHandler_DescribeEndpoint_EventuallyInService(t *testing.T) { rec := doSageMakerRequest(t, h, "DescribeEndpoint", map[string]any{ "EndpointName": "ep2", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) }) require.Equal(t, "InService", descResp["EndpointStatus"]) @@ -434,7 +436,7 @@ func TestHandler_EndpointLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["EndpointArn"]) // Describe endpoint. @@ -451,7 +453,7 @@ func TestHandler_EndpointLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["Endpoints"].([]any), 1) // Update endpoint. diff --git a/services/sagemaker/handler_experiments.go b/services/sagemaker/handler_experiments.go index b2534a1e6d..c16dab54c0 100644 --- a/services/sagemaker/handler_experiments.go +++ b/services/sagemaker/handler_experiments.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -24,7 +25,7 @@ type createExperimentInput struct { func (h *Handler) handleCreateExperiment(ctx context.Context, body []byte) ([]byte, error) { var req createExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -41,7 +42,7 @@ func (h *Handler) handleCreateExperiment(ctx context.Context, body []byte) ([]by logger.Load(ctx).InfoContext(ctx, "sagemaker: created experiment", "name", e.ExperimentName) - return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}) + return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}, jsonx.V1()) } // describeExperimentInput mirrors DescribeExperimentInput @@ -53,7 +54,7 @@ type describeExperimentInput struct { func (h *Handler) handleDescribeExperiment(ctx context.Context, body []byte) ([]byte, error) { var req describeExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -85,7 +86,7 @@ func (h *Handler) handleDescribeExperiment(ctx context.Context, body []byte) ([] resp["LastModifiedBy"] = e.LastModifiedBy } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type experimentSummary struct { @@ -110,7 +111,7 @@ type listExperimentsInput struct { func (h *Handler) handleListExperiments(ctx context.Context, body []byte) ([]byte, error) { var req listExperimentsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -138,7 +139,7 @@ func (h *Handler) handleListExperiments(ctx context.Context, body []byte) ([]byt resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteExperimentInput mirrors DeleteExperimentInput @@ -150,7 +151,7 @@ type deleteExperimentInput struct { func (h *Handler) handleDeleteExperiment(ctx context.Context, body []byte) ([]byte, error) { var req deleteExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -165,7 +166,7 @@ func (h *Handler) handleDeleteExperiment(ctx context.Context, body []byte) ([]by logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted experiment", "name", req.ExperimentName) - return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}) + return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -186,7 +187,7 @@ type updateExperimentInput struct { func (h *Handler) handleUpdateExperiment(ctx context.Context, body []byte) ([]byte, error) { var req updateExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,5 +202,5 @@ func (h *Handler) handleUpdateExperiment(ctx context.Context, body []byte) ([]by logger.Load(ctx).InfoContext(ctx, "sagemaker: updated experiment", "name", e.ExperimentName) - return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}) + return json.Marshal(map[string]string{keyExperimentArn: e.ExperimentArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_experiments_test.go b/services/sagemaker/handler_experiments_test.go index 84a75405ce..7981a6f2a5 100644 --- a/services/sagemaker/handler_experiments_test.go +++ b/services/sagemaker/handler_experiments_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ExperimentLifecycle(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_ExperimentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["ExperimentArn"]) // Describe experiment. @@ -39,7 +41,7 @@ func TestHandler_ExperimentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["ExperimentSummaries"].([]any), 1) // Delete experiment. @@ -114,7 +116,7 @@ func TestHandler_UpdateExperiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["ExperimentArn"]) // Describe returns updated fields @@ -122,7 +124,7 @@ func TestHandler_UpdateExperiment(t *testing.T) { "ExperimentName": "my-exp", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "My Experiment", descResp["DisplayName"]) assert.Equal(t, "A test experiment", descResp["Description"]) } @@ -152,7 +154,7 @@ func TestHandler_Tags_Experiment(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) expARN := createResp["ExperimentArn"] require.NotEmpty(t, expARN) @@ -162,7 +164,7 @@ func TestHandler_Tags_Experiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "project", tags[0].(map[string]any)["Key"]) @@ -186,7 +188,7 @@ func TestHandler_CreateExperiment_DisplayNameAndDescription(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Friendly Name", descResp["DisplayName"]) assert.Equal(t, "An experiment created with a display name", descResp["Description"]) @@ -194,7 +196,7 @@ func TestHandler_CreateExperiment_DisplayNameAndDescription(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["ExperimentSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "Friendly Name", summaries[0].(map[string]any)["DisplayName"]) diff --git a/services/sagemaker/handler_feature_groups.go b/services/sagemaker/handler_feature_groups.go index dcd850c718..b3008e8acd 100644 --- a/services/sagemaker/handler_feature_groups.go +++ b/services/sagemaker/handler_feature_groups.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -33,7 +34,7 @@ type createFeatureGroupInput struct { func (h *Handler) handleCreateFeatureGroup(ctx context.Context, body []byte) ([]byte, error) { var req createFeatureGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -72,7 +73,7 @@ func (h *Handler) handleCreateFeatureGroup(ctx context.Context, body []byte) ([] logger.Load(ctx). InfoContext(ctx, "sagemaker: created feature group", "name", fg.FeatureGroupName) - return json.Marshal(map[string]string{keyFeatureGroupArn: fg.FeatureGroupArn}) + return json.Marshal(map[string]string{keyFeatureGroupArn: fg.FeatureGroupArn}, jsonx.V1()) } // describeFeatureGroupInput mirrors DescribeFeatureGroupInput @@ -89,7 +90,7 @@ type describeFeatureGroupInput struct { func (h *Handler) handleDescribeFeatureGroup(ctx context.Context, body []byte) ([]byte, error) { var req describeFeatureGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -146,7 +147,7 @@ func (h *Handler) handleDescribeFeatureGroup(ctx context.Context, body []byte) ( resp["LastUpdateStatus"] = fg.LastUpdateStatus } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type featureGroupSummary struct { @@ -175,7 +176,7 @@ type listFeatureGroupsInput struct { func (h *Handler) handleListFeatureGroups(ctx context.Context, body []byte) ([]byte, error) { var req listFeatureGroupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -206,7 +207,7 @@ func (h *Handler) handleListFeatureGroups(ctx context.Context, body []byte) ([]b resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteFeatureGroupInput mirrors DeleteFeatureGroupInput @@ -218,7 +219,7 @@ type deleteFeatureGroupInput struct { func (h *Handler) handleDeleteFeatureGroup(ctx context.Context, body []byte) error { var req deleteFeatureGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -254,7 +255,7 @@ type updateFeatureGroupInput struct { func (h *Handler) handleUpdateFeatureGroup(ctx context.Context, body []byte) ([]byte, error) { var req updateFeatureGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -273,5 +274,5 @@ func (h *Handler) handleUpdateFeatureGroup(ctx context.Context, body []byte) ([] logger.Load(ctx).InfoContext(ctx, "sagemaker: updated feature group", "name", fg.FeatureGroupName) - return json.Marshal(map[string]string{keyFeatureGroupArn: fg.FeatureGroupArn}) + return json.Marshal(map[string]string{keyFeatureGroupArn: fg.FeatureGroupArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_feature_groups_stores_test.go b/services/sagemaker/handler_feature_groups_stores_test.go index 6b3c0f8729..e8945fb515 100644 --- a/services/sagemaker/handler_feature_groups_stores_test.go +++ b/services/sagemaker/handler_feature_groups_stores_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateFeatureGroup_StoreConfigsRoundTrip(t *testing.T) { @@ -49,7 +51,7 @@ func TestHandler_CreateFeatureGroup_StoreConfigsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) onlineCfg, ok := out["OnlineStoreConfig"].(map[string]any) require.True(t, ok, "OnlineStoreConfig must round-trip") @@ -96,7 +98,7 @@ func TestHandler_CreateFeatureGroup_NoStoreConfigsOmitsFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) _, hasOnline := out["OnlineStoreConfig"] _, hasOffline := out["OfflineStoreConfig"] diff --git a/services/sagemaker/handler_feature_groups_test.go b/services/sagemaker/handler_feature_groups_test.go index 75eae57f28..7e572763bf 100644 --- a/services/sagemaker/handler_feature_groups_test.go +++ b/services/sagemaker/handler_feature_groups_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_FeatureGroupLifecycle(t *testing.T) { @@ -34,7 +36,7 @@ func TestHandler_FeatureGroupLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code, recCreate.Body.String()) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["FeatureGroupArn"]) // Describe feature group. @@ -48,7 +50,7 @@ func TestHandler_FeatureGroupLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["FeatureGroupSummaries"].([]any), 1) // Delete feature group. @@ -125,7 +127,7 @@ func TestHandler_UpdateFeatureGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["FeatureGroupArn"]) // Describe should show 2 features now @@ -135,7 +137,7 @@ func TestHandler_UpdateFeatureGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) defs := descResp["FeatureDefinitions"].([]any) assert.Len(t, defs, 2) } @@ -170,7 +172,7 @@ func TestHandler_Tags_FeatureGroup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) fgARN := createResp["FeatureGroupArn"] require.NotEmpty(t, fgARN) @@ -181,7 +183,7 @@ func TestHandler_Tags_FeatureGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "env", tags[0].(map[string]any)["Key"]) @@ -210,7 +212,7 @@ func TestHandler_CreateFeatureGroup_RoleArnAndDescription(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "arn:aws:iam::000000000000:role/FeatureStoreRole", descResp["RoleArn"]) assert.Equal(t, "A feature group with a role", descResp["Description"]) } diff --git a/services/sagemaker/handler_feature_metadata.go b/services/sagemaker/handler_feature_metadata.go index cffbd96b02..f6ae578c25 100644 --- a/services/sagemaker/handler_feature_metadata.go +++ b/services/sagemaker/handler_feature_metadata.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -52,7 +54,7 @@ type describeFeatureMetadataInput struct { func (h *Handler) handleDescribeFeatureMetadata(ctx context.Context, body []byte) ([]byte, error) { var req describeFeatureMetadataInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -101,7 +103,7 @@ func (h *Handler) handleDescribeFeatureMetadata(ctx context.Context, body []byte resp["Parameters"] = featureParametersToWire(meta.Parameters) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateFeatureMetadataInput mirrors UpdateFeatureMetadataInput @@ -123,7 +125,7 @@ type updateFeatureMetadataInput struct { func (h *Handler) handleUpdateFeatureMetadata(ctx context.Context, body []byte) ([]byte, error) { var req updateFeatureMetadataInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -161,7 +163,7 @@ func (h *Handler) handleUpdateFeatureMetadata(ctx context.Context, body []byte) return nil, updateErr } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // featureExistsInGroup reports whether featureName is one of fg's defined features. diff --git a/services/sagemaker/handler_feature_metadata_test.go b/services/sagemaker/handler_feature_metadata_test.go index 1be34e3fa0..c58c87ef01 100644 --- a/services/sagemaker/handler_feature_metadata_test.go +++ b/services/sagemaker/handler_feature_metadata_test.go @@ -2,13 +2,14 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -45,7 +46,7 @@ func TestHandler_UpdateAndDescribeFeatureMetadata(t *testing.T) { assert.Equal(t, http.StatusOK, recDescribe.Code) var describeOut map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &describeOut)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &describeOut, jsonx.V1())) assert.Equal(t, "the record id", describeOut["Description"]) assert.Equal(t, "id", describeOut["FeatureName"]) assert.Equal(t, "Integral", describeOut["FeatureType"]) @@ -90,7 +91,7 @@ func TestHandler_UpdateFeatureMetadata_ParameterRemovals(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) params, ok := out["Parameters"].([]any) require.True(t, ok) @@ -124,7 +125,7 @@ func TestHandler_DescribeFeatureMetadata_LastModifiedTimeDefaultsToGroupCreation require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, out["CreationTime"], out["LastModifiedTime"], "a never-updated feature's LastModifiedTime falls back to the group's CreationTime") diff --git a/services/sagemaker/handler_flow_definitions.go b/services/sagemaker/handler_flow_definitions.go index 2210e8b723..ec92feda9a 100644 --- a/services/sagemaker/handler_flow_definitions.go +++ b/services/sagemaker/handler_flow_definitions.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -48,7 +50,7 @@ type createFlowDefinitionInput struct { func (h *Handler) handleCreateFlowDefinition(ctx context.Context, body []byte) ([]byte, error) { var req createFlowDefinitionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -97,7 +99,7 @@ func (h *Handler) handleCreateFlowDefinition(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(map[string]any{keyFlowDefinitionArn: result.FlowDefinitionArn}) + return json.Marshal(map[string]any{keyFlowDefinitionArn: result.FlowDefinitionArn}, jsonx.V1()) } // describeFlowDefinitionInput mirrors DescribeFlowDefinitionInput @@ -109,7 +111,7 @@ type describeFlowDefinitionInput struct { func (h *Handler) handleDescribeFlowDefinition(ctx context.Context, body []byte) ([]byte, error) { var req describeFlowDefinitionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -122,7 +124,7 @@ func (h *Handler) handleDescribeFlowDefinition(ctx context.Context, body []byte) return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteFlowDefinitionInput mirrors DeleteFlowDefinitionInput @@ -134,7 +136,7 @@ type deleteFlowDefinitionInput struct { func (h *Handler) handleDeleteFlowDefinition(ctx context.Context, body []byte) error { var req deleteFlowDefinitionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -163,7 +165,7 @@ type listFlowDefinitionsInput struct { func (h *Handler) handleListFlowDefinitions(ctx context.Context, body []byte) ([]byte, error) { var req listFlowDefinitionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_flow_definitions_test.go b/services/sagemaker/handler_flow_definitions_test.go index 4b57454038..a844285745 100644 --- a/services/sagemaker/handler_flow_definitions_test.go +++ b/services/sagemaker/handler_flow_definitions_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func createFlowDefinitionRequest(name string) map[string]any { @@ -29,7 +31,7 @@ func TestHandler_CreateFlowDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["FlowDefinitionArn"], "my-flow") } @@ -77,7 +79,7 @@ func TestHandler_DescribeFlowDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "flow-1", resp["FlowDefinitionName"]) } @@ -152,13 +154,13 @@ func TestHandler_ListFlowDefinitions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["FlowDefinitionSummaries"]) doSageMakerRequest(t, h, "CreateFlowDefinition", createFlowDefinitionRequest("my-flow")) rec = doSageMakerRequest(t, h, "ListFlowDefinitions", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) defs := resp["FlowDefinitionSummaries"].([]any) assert.Len(t, defs, 1) d := defs[0].(map[string]any) diff --git a/services/sagemaker/handler_hp_tuning_jobs.go b/services/sagemaker/handler_hp_tuning_jobs.go index 005d212c7f..6717d846f0 100644 --- a/services/sagemaker/handler_hp_tuning_jobs.go +++ b/services/sagemaker/handler_hp_tuning_jobs.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -30,13 +32,13 @@ type hpResourceLimitsRequest struct { // one Unmarshal pass — encoding/json drops both silently on that ambiguity — // hence the second decode.) type createHPTuningJobRequest struct { - HyperParameterTuningJobName string `json:"HyperParameterTuningJobName"` - HyperParameterTuningJobConfig json.RawMessage `json:"HyperParameterTuningJobConfig"` - Autotune json.RawMessage `json:"Autotune"` - WarmStartConfig json.RawMessage `json:"WarmStartConfig"` - TrainingJobDefinition json.RawMessage `json:"TrainingJobDefinition"` - TrainingJobDefinitions json.RawMessage `json:"TrainingJobDefinitions"` - Tags []tagObject `json:"Tags"` + HyperParameterTuningJobName string `json:"HyperParameterTuningJobName"` + HyperParameterTuningJobConfig jsontext.Value `json:"HyperParameterTuningJobConfig"` + Autotune jsontext.Value `json:"Autotune"` + WarmStartConfig jsontext.Value `json:"WarmStartConfig"` + TrainingJobDefinition jsontext.Value `json:"TrainingJobDefinition"` + TrainingJobDefinitions jsontext.Value `json:"TrainingJobDefinitions"` + Tags []tagObject `json:"Tags"` } // hpTuningJobConfigStrategyAndLimits mirrors the Strategy/ResourceLimits @@ -51,7 +53,7 @@ func (h *Handler) handleCreateHyperParameterTuningJob( body []byte, ) ([]byte, error) { var req createHPTuningJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -64,7 +66,7 @@ func (h *Handler) handleCreateHyperParameterTuningJob( } var config hpTuningJobConfigStrategyAndLimits - if err := json.Unmarshal(req.HyperParameterTuningJobConfig, &config); err != nil { + if err := json.Unmarshal(req.HyperParameterTuningJobConfig, &config, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -93,7 +95,7 @@ func (h *Handler) handleCreateHyperParameterTuningJob( log.InfoContext(ctx, "sagemaker: created HP tuning job", "name", j.HyperParameterTuningJobName) return json.Marshal( - map[string]string{"HyperParameterTuningJobArn": j.HyperParameterTuningJobArn}, + map[string]string{"HyperParameterTuningJobArn": j.HyperParameterTuningJobArn}, jsonx.V1(), ) } @@ -109,7 +111,7 @@ func (h *Handler) handleDescribeHyperParameterTuningJob( ) ([]byte, error) { var req describeHPTuningJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -153,7 +155,7 @@ func (h *Handler) handleDescribeHyperParameterTuningJob( resp["HyperParameterTuningEndTime"] = epochSeconds(*j.HyperParameterTuningEndTime) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type hpTuningJobSummary struct { @@ -187,7 +189,7 @@ type listHPTuningJobsInput struct { func (h *Handler) handleListHyperParameterTuningJobs(ctx context.Context, body []byte) ([]byte, error) { var req listHPTuningJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -228,7 +230,7 @@ func (h *Handler) handleListHyperParameterTuningJobs(ctx context.Context, body [ resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // stopHPTuningJobInput mirrors StopHyperParameterTuningJobInput @@ -240,7 +242,7 @@ type stopHPTuningJobInput struct { func (h *Handler) handleStopHyperParameterTuningJob(ctx context.Context, body []byte) error { var req stopHPTuningJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -272,7 +274,7 @@ type deleteHPTuningJobInput struct { func (h *Handler) handleDeleteHyperParameterTuningJob(ctx context.Context, body []byte) error { var req deleteHPTuningJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -328,7 +330,7 @@ func (h *Handler) handleListTrainingJobsForHyperParameterTuningJob( ) ([]byte, error) { var req listTrainingJobsForHPTuningJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -371,5 +373,5 @@ func (h *Handler) handleListTrainingJobsForHyperParameterTuningJob( resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_hp_tuning_jobs_test.go b/services/sagemaker/handler_hp_tuning_jobs_test.go index d84ac73093..441f013ed6 100644 --- a/services/sagemaker/handler_hp_tuning_jobs_test.go +++ b/services/sagemaker/handler_hp_tuning_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -93,7 +94,7 @@ func TestHandler_ListTrainingJobsForHyperParameterTuningJob(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TrainingJobSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, tt.wantSummaries) @@ -125,7 +126,7 @@ func TestHandler_DescribeHyperParameterTuningJob_WireShape(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Real AWS nests Strategy/ResourceLimits inside HyperParameterTuningJobConfig // rather than emitting a flat top-level Strategy field — a client reading @@ -179,7 +180,7 @@ func TestHandler_ListHyperParameterTuningJobs_WireShape(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["HyperParameterTuningJobSummaries"].([]any) require.True(t, ok) @@ -217,7 +218,7 @@ func TestHandler_HyperParameterTuningJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["HyperParameterTuningJobArn"]) // Describe. @@ -231,7 +232,7 @@ func TestHandler_HyperParameterTuningJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["HyperParameterTuningJobSummaries"].([]any), 1) // Stop. @@ -279,7 +280,7 @@ func TestHandler_StopHyperParameterTuningJob_ReachesStopped(t *testing.T) { "HyperParameterTuningJobName": "hpt-stop-reaches", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopping", descResp["HyperParameterTuningJobStatus"]) time.Sleep(time.Second) @@ -288,7 +289,7 @@ func TestHandler_StopHyperParameterTuningJob_ReachesStopped(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeHyperParameterTuningJob", map[string]any{ "HyperParameterTuningJobName": "hpt-stop-reaches", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopped", descResp["HyperParameterTuningJobStatus"]) }) } @@ -319,7 +320,7 @@ func TestHandler_CreateHyperParameterTuningJob_ReachesCompleted(t *testing.T) { "HyperParameterTuningJobName": "hpt-create-reaches-completed", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "InProgress", descResp["HyperParameterTuningJobStatus"]) time.Sleep(time.Second) @@ -328,7 +329,7 @@ func TestHandler_CreateHyperParameterTuningJob_ReachesCompleted(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeHyperParameterTuningJob", map[string]any{ "HyperParameterTuningJobName": "hpt-create-reaches-completed", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Completed", descResp["HyperParameterTuningJobStatus"]) }) } diff --git a/services/sagemaker/handler_hub.go b/services/sagemaker/handler_hub.go index a3e05e6831..d2c73bfb51 100644 --- a/services/sagemaker/handler_hub.go +++ b/services/sagemaker/handler_hub.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -155,7 +156,7 @@ type createHubInput struct { func (h *Handler) handleCreateHub(ctx context.Context, body []byte) ([]byte, error) { var req createHubInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -182,7 +183,7 @@ func (h *Handler) handleCreateHub(ctx context.Context, body []byte) ([]byte, err logger.Load(ctx).InfoContext(ctx, "sagemaker: created hub", "name", hub.HubName) - return json.Marshal(map[string]string{keyHubArn: hub.HubArn}) + return json.Marshal(map[string]string{keyHubArn: hub.HubArn}, jsonx.V1()) } func hubToDescribeResponse(hub *Hub) map[string]any { @@ -226,7 +227,7 @@ type describeHubInput struct { func (h *Handler) handleDescribeHub(ctx context.Context, body []byte) ([]byte, error) { var req describeHubInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -239,7 +240,7 @@ func (h *Handler) handleDescribeHub(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(hubToDescribeResponse(hub)) + return json.Marshal(hubToDescribeResponse(hub), jsonx.V1()) } type hubInfoSummary struct { @@ -270,7 +271,7 @@ type listHubsInput struct { func (h *Handler) handleListHubs(ctx context.Context, body []byte) ([]byte, error) { var req listHubsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -305,7 +306,7 @@ func (h *Handler) handleListHubs(ctx context.Context, body []byte) ([]byte, erro resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateHubInput is the UpdateHub request shape (named, not inline — see @@ -320,7 +321,7 @@ type updateHubInput struct { func (h *Handler) handleUpdateHub(ctx context.Context, body []byte) ([]byte, error) { var req updateHubInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -339,7 +340,7 @@ func (h *Handler) handleUpdateHub(ctx context.Context, body []byte) ([]byte, err return nil, err } - return json.Marshal(map[string]string{keyHubArn: hub.HubArn}) + return json.Marshal(map[string]string{keyHubArn: hub.HubArn}, jsonx.V1()) } // deleteHubInput is the DeleteHub request shape (named, not inline — see @@ -351,7 +352,7 @@ type deleteHubInput struct { func (h *Handler) handleDeleteHub(ctx context.Context, body []byte) error { var req deleteHubInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -393,7 +394,7 @@ type importHubContentInput struct { func (h *Handler) handleImportHubContent(ctx context.Context, body []byte) ([]byte, error) { var req importHubContentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -423,7 +424,7 @@ func (h *Handler) handleImportHubContent(ctx context.Context, body []byte) ([]by logger.Load(ctx).InfoContext(ctx, "sagemaker: imported hub content", "hub", hc.HubName, "name", hc.HubContentName, "version", hc.HubContentVersion) - return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}) + return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}, jsonx.V1()) } // requireImportHubContentFields returns the name of the first missing required @@ -535,7 +536,7 @@ type describeHubContentInput struct { func (h *Handler) handleDescribeHubContent(ctx context.Context, body []byte) ([]byte, error) { var req describeHubContentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -552,7 +553,7 @@ func (h *Handler) handleDescribeHubContent(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(hubContentToDescribeResponse(hc)) + return json.Marshal(hubContentToDescribeResponse(hc), jsonx.V1()) } type hubContentInfoSummary struct { @@ -613,7 +614,7 @@ type listHubContentsInput struct { func (h *Handler) handleListHubContents(ctx context.Context, body []byte) ([]byte, error) { var req listHubContentsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -642,7 +643,7 @@ func (h *Handler) handleListHubContents(ctx context.Context, body []byte) ([]byt resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listHubContentVersionsInput is the ListHubContentVersions request shape @@ -665,7 +666,7 @@ type listHubContentVersionsInput struct { func (h *Handler) handleListHubContentVersions(ctx context.Context, body []byte) ([]byte, error) { var req listHubContentVersionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -698,7 +699,7 @@ func (h *Handler) handleListHubContentVersions(ctx context.Context, body []byte) resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteHubContentInput is the DeleteHubContent request shape (named, not @@ -714,7 +715,7 @@ type deleteHubContentInput struct { func (h *Handler) handleDeleteHubContent(ctx context.Context, body []byte) error { var req deleteHubContentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -750,7 +751,7 @@ type createHubContentReferenceInput struct { func (h *Handler) handleCreateHubContentReference(ctx context.Context, body []byte) ([]byte, error) { var req createHubContentReferenceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -768,7 +769,7 @@ func (h *Handler) handleCreateHubContentReference(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}) + return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}, jsonx.V1()) } // deleteHubContentReferenceInput is the DeleteHubContentReference request @@ -783,7 +784,7 @@ type deleteHubContentReferenceInput struct { func (h *Handler) handleDeleteHubContentReference(ctx context.Context, body []byte) error { var req deleteHubContentReferenceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -814,7 +815,7 @@ type updateHubContentInput struct { func (h *Handler) handleUpdateHubContent(ctx context.Context, body []byte) ([]byte, error) { var req updateHubContentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -838,7 +839,7 @@ func (h *Handler) handleUpdateHubContent(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}) + return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}, jsonx.V1()) } // updateHubContentReferenceInput is the UpdateHubContentReference request @@ -854,7 +855,7 @@ type updateHubContentReferenceInput struct { func (h *Handler) handleUpdateHubContentReference(ctx context.Context, body []byte) ([]byte, error) { var req updateHubContentReferenceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -871,7 +872,7 @@ func (h *Handler) handleUpdateHubContentReference(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}) + return json.Marshal(map[string]string{keyHubArn: hc.HubArn, keyHubContentArn: hc.HubContentArn}, jsonx.V1()) } // presignedURLAccessConfigJSON is the wire shape of PresignedUrlAccessConfig @@ -898,7 +899,7 @@ type createHubContentPresignedURLsInput struct { func (h *Handler) handleCreateHubContentPresignedURLs(ctx context.Context, body []byte) ([]byte, error) { var req createHubContentPresignedURLsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -938,5 +939,5 @@ func (h *Handler) handleCreateHubContentPresignedURLs(ctx context.Context, body resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_hub_test.go b/services/sagemaker/handler_hub_test.go index 83b52c0540..f743f6a524 100644 --- a/services/sagemaker/handler_hub_test.go +++ b/services/sagemaker/handler_hub_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -55,7 +57,7 @@ func TestHandler_CreateHub(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["HubArn"], "hub/my-hub") } }) @@ -95,7 +97,7 @@ func TestHandler_HubLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "lifecycle-hub", desc["HubName"]) assert.Equal(t, "InService", desc["HubStatus"]) assert.Equal(t, "initial description", desc["HubDescription"]) @@ -117,7 +119,7 @@ func TestHandler_HubLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc2.Code) var desc2 map[string]any - require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &desc2)) + require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &desc2, jsonx.V1())) assert.Equal(t, "updated description", desc2["HubDescription"]) // Untouched field should be preserved. assert.Equal(t, "Lifecycle Hub", desc2["HubDisplayName"]) @@ -164,7 +166,7 @@ func TestHandler_ListHubs(t *testing.T) { var resp struct { HubSummaries []map[string]any `json:"HubSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.HubSummaries, 3) // NameContains filter. @@ -174,7 +176,7 @@ func TestHandler_ListHubs(t *testing.T) { var filtered struct { HubSummaries []map[string]any `json:"HubSummaries"` } - require.NoError(t, json.Unmarshal(recFiltered.Body.Bytes(), &filtered)) + require.NoError(t, json.Unmarshal(recFiltered.Body.Bytes(), &filtered, jsonx.V1())) require.Len(t, filtered.HubSummaries, 1) assert.Equal(t, "beta-hub", filtered.HubSummaries[0]["HubName"]) } @@ -363,7 +365,7 @@ func TestHandler_ImportHubContent(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["HubArn"]) assert.Contains(t, resp["HubContentArn"], "hub-content/content-hub/Model/my-model") } @@ -428,7 +430,7 @@ func TestHandler_DescribeHubContent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-model", resp["HubContentName"]) assert.Equal(t, "2.0.0", resp["HubContentVersion"]) assert.Equal(t, "Model", resp["HubContentType"]) @@ -486,7 +488,7 @@ func TestHandler_HubContent_ListsAndVersions(t *testing.T) { var listResp struct { HubContentSummaries []map[string]any `json:"HubContentSummaries"` } - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.HubContentSummaries, 2) // ListHubContentVersions returns every version of one named content. @@ -498,7 +500,7 @@ func TestHandler_HubContent_ListsAndVersions(t *testing.T) { var versionsResp struct { HubContentSummaries []map[string]any `json:"HubContentSummaries"` } - require.NoError(t, json.Unmarshal(recVersions.Body.Bytes(), &versionsResp)) + require.NoError(t, json.Unmarshal(recVersions.Body.Bytes(), &versionsResp, jsonx.V1())) require.Len(t, versionsResp.HubContentSummaries, 3) } @@ -735,7 +737,7 @@ func TestHandler_HubContentReference_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recCreate.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createResp, jsonx.V1())) assert.NotEmpty(t, createResp["HubArn"]) assert.Contains(t, createResp["HubContentArn"], "hub-content/ref-hub/ModelReference/huggingface-model") @@ -746,7 +748,7 @@ func TestHandler_HubContentReference_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var desc map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, publicArn, desc["SageMakerPublicHubContentArn"]) // Duplicate reference is rejected. @@ -805,7 +807,7 @@ func TestHandler_CreateHubContentPresignedUrls(t *testing.T) { var resp struct { AuthorizedURLConfigs []map[string]string `json:"AuthorizedUrlConfigs"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.AuthorizedURLConfigs, 1) assert.Contains(t, resp.AuthorizedURLConfigs[0]["Url"], "https://") assert.NotEmpty(t, resp.AuthorizedURLConfigs[0]["LocalPath"]) @@ -919,7 +921,7 @@ func TestHandler_UpdateHubContent(t *testing.T) { "HubName": "my-hub2", "HubContentName": "model-a", "HubContentType": "Model", }) var out map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "new description", out["HubContentDescription"]) } @@ -942,7 +944,7 @@ func TestHandler_UpdateHubContentReference(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["HubContentArn"]) } diff --git a/services/sagemaker/handler_human_task_ui.go b/services/sagemaker/handler_human_task_ui.go index 9d6a3b1b70..7bccf8a7d4 100644 --- a/services/sagemaker/handler_human_task_ui.go +++ b/services/sagemaker/handler_human_task_ui.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -25,7 +27,7 @@ type createHumanTaskUIInput struct { func (h *Handler) handleCreateHumanTaskUI(ctx context.Context, body []byte) ([]byte, error) { var req createHumanTaskUIInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -44,7 +46,7 @@ func (h *Handler) handleCreateHumanTaskUI(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{keyHumanTaskUIArn: result.HumanTaskUIArn}) + return json.Marshal(map[string]any{keyHumanTaskUIArn: result.HumanTaskUIArn}, jsonx.V1()) } // describeHumanTaskUIInput mirrors DescribeHumanTaskUiInput @@ -56,7 +58,7 @@ type describeHumanTaskUIInput struct { func (h *Handler) handleDescribeHumanTaskUI(ctx context.Context, body []byte) ([]byte, error) { var req describeHumanTaskUIInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -69,7 +71,7 @@ func (h *Handler) handleDescribeHumanTaskUI(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteHumanTaskUIInput mirrors DeleteHumanTaskUiInput @@ -81,7 +83,7 @@ type deleteHumanTaskUIInput struct { func (h *Handler) handleDeleteHumanTaskUI(ctx context.Context, body []byte) error { var req deleteHumanTaskUIInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -107,7 +109,7 @@ type listHumanTaskUIsInput struct { func (h *Handler) handleListHumanTaskUIs(ctx context.Context, body []byte) ([]byte, error) { var req listHumanTaskUIsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_human_task_ui_test.go b/services/sagemaker/handler_human_task_ui_test.go index 61f6e890f1..ad63e71695 100644 --- a/services/sagemaker/handler_human_task_ui_test.go +++ b/services/sagemaker/handler_human_task_ui_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateHumanTaskUI(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_CreateHumanTaskUI(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["HumanTaskUiArn"], "my-ui") } @@ -61,7 +63,7 @@ func TestHandler_DescribeHumanTaskUI(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ui-1", resp["HumanTaskUiName"]) } @@ -110,7 +112,7 @@ func TestHandler_ListHumanTaskUIs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["HumanTaskUiSummaries"]) doSageMakerRequest(t, h, "CreateHumanTaskUi", map[string]any{ @@ -119,7 +121,7 @@ func TestHandler_ListHumanTaskUIs(t *testing.T) { }) rec = doSageMakerRequest(t, h, "ListHumanTaskUis", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) uis := resp["HumanTaskUiSummaries"].([]any) assert.Len(t, uis, 1) u := uis[0].(map[string]any) diff --git a/services/sagemaker/handler_hyperpod_scheduling.go b/services/sagemaker/handler_hyperpod_scheduling.go index 289e90b251..e205007dff 100644 --- a/services/sagemaker/handler_hyperpod_scheduling.go +++ b/services/sagemaker/handler_hyperpod_scheduling.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -21,7 +23,7 @@ type createClusterSchedulerConfigInput struct { func (h *Handler) handleCreateClusterSchedulerConfig(ctx context.Context, body []byte) ([]byte, error) { var req createClusterSchedulerConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -39,7 +41,7 @@ func (h *Handler) handleCreateClusterSchedulerConfig(ctx context.Context, body [ return json.Marshal(map[string]any{ keyClusterSchedulerConfigArn: c.ClusterSchedulerConfigArn, keyClusterSchedulerConfigID: c.ClusterSchedulerConfigID, - }) + }, jsonx.V1()) } type describeClusterSchedulerConfigInput struct { @@ -50,7 +52,7 @@ type describeClusterSchedulerConfigInput struct { func (h *Handler) handleDescribeClusterSchedulerConfig(ctx context.Context, body []byte) ([]byte, error) { var req describeClusterSchedulerConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -61,7 +63,7 @@ func (h *Handler) handleDescribeClusterSchedulerConfig(ctx context.Context, body return nil, err } - return json.Marshal(c) + return json.Marshal(c, jsonx.V1()) } type listClusterSchedulerConfigsInput struct { @@ -79,7 +81,7 @@ type listClusterSchedulerConfigsInput struct { func (h *Handler) handleListClusterSchedulerConfigs(ctx context.Context, body []byte) ([]byte, error) { var req listClusterSchedulerConfigsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -122,7 +124,7 @@ type updateClusterSchedulerConfigInput struct { func (h *Handler) handleUpdateClusterSchedulerConfig(ctx context.Context, body []byte) ([]byte, error) { var req updateClusterSchedulerConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -142,7 +144,7 @@ func (h *Handler) handleUpdateClusterSchedulerConfig(ctx context.Context, body [ return json.Marshal(map[string]any{ keyClusterSchedulerConfigArn: c.ClusterSchedulerConfigArn, keyClusterSchedulerConfigVersion: c.ClusterSchedulerConfigVersion, - }) + }, jsonx.V1()) } type deleteClusterSchedulerConfigInput struct { @@ -152,7 +154,7 @@ type deleteClusterSchedulerConfigInput struct { func (h *Handler) handleDeleteClusterSchedulerConfig(ctx context.Context, body []byte) ([]byte, error) { var req deleteClusterSchedulerConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -160,7 +162,7 @@ func (h *Handler) handleDeleteClusterSchedulerConfig(ctx context.Context, body [ return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -180,7 +182,7 @@ type createComputeQuotaInput struct { func (h *Handler) handleCreateComputeQuota(ctx context.Context, body []byte) ([]byte, error) { var req createComputeQuotaInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -200,7 +202,7 @@ func (h *Handler) handleCreateComputeQuota(ctx context.Context, body []byte) ([] return json.Marshal(map[string]any{ keyComputeQuotaArn: q.ComputeQuotaArn, keyComputeQuotaID: q.ComputeQuotaID, - }) + }, jsonx.V1()) } type describeComputeQuotaInput struct { @@ -211,7 +213,7 @@ type describeComputeQuotaInput struct { func (h *Handler) handleDescribeComputeQuota(ctx context.Context, body []byte) ([]byte, error) { var req describeComputeQuotaInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -220,7 +222,7 @@ func (h *Handler) handleDescribeComputeQuota(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(q) + return json.Marshal(q, jsonx.V1()) } type listComputeQuotasInput struct { @@ -238,7 +240,7 @@ type listComputeQuotasInput struct { func (h *Handler) handleListComputeQuotas(ctx context.Context, body []byte) ([]byte, error) { var req listComputeQuotasInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -286,7 +288,7 @@ type updateComputeQuotaInput struct { func (h *Handler) handleUpdateComputeQuota(ctx context.Context, body []byte) ([]byte, error) { var req updateComputeQuotaInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -307,7 +309,7 @@ func (h *Handler) handleUpdateComputeQuota(ctx context.Context, body []byte) ([] return json.Marshal(map[string]any{ keyComputeQuotaArn: q.ComputeQuotaArn, keyComputeQuotaVersion: q.ComputeQuotaVersion, - }) + }, jsonx.V1()) } type deleteComputeQuotaInput struct { @@ -317,7 +319,7 @@ type deleteComputeQuotaInput struct { func (h *Handler) handleDeleteComputeQuota(ctx context.Context, body []byte) ([]byte, error) { var req deleteComputeQuotaInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, err } @@ -325,5 +327,5 @@ func (h *Handler) handleDeleteComputeQuota(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/sagemaker/handler_images.go b/services/sagemaker/handler_images.go index 87d4012ce5..55618dda53 100644 --- a/services/sagemaker/handler_images.go +++ b/services/sagemaker/handler_images.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -21,7 +23,7 @@ type createImageInput struct { func (h *Handler) handleCreateImage(ctx context.Context, body []byte) ([]byte, error) { var req createImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -36,7 +38,7 @@ func (h *Handler) handleCreateImage(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]any{"ImageArn": result.ImageArn}) + return json.Marshal(map[string]any{"ImageArn": result.ImageArn}, jsonx.V1()) } type describeImageInput struct { @@ -46,7 +48,7 @@ type describeImageInput struct { func (h *Handler) handleDescribeImage(ctx context.Context, body []byte) ([]byte, error) { var req describeImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -59,7 +61,7 @@ func (h *Handler) handleDescribeImage(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } type deleteImageInput struct { @@ -69,7 +71,7 @@ type deleteImageInput struct { func (h *Handler) handleDeleteImage(ctx context.Context, body []byte) error { var req deleteImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -95,7 +97,7 @@ type listImagesInput struct { func (h *Handler) handleListImages(ctx context.Context, body []byte) ([]byte, error) { var req listImagesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -135,7 +137,7 @@ func (h *Handler) handleListImages(ctx context.Context, body []byte) ([]byte, er return json.Marshal(map[string]any{ "Images": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } type updateImageInput struct { @@ -149,7 +151,7 @@ type updateImageInput struct { func (h *Handler) handleUpdateImage(ctx context.Context, body []byte) ([]byte, error) { var req updateImageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -167,7 +169,7 @@ func (h *Handler) handleUpdateImage(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]any{keyImageArn: result.ImageArn}) + return json.Marshal(map[string]any{keyImageArn: result.ImageArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -191,7 +193,7 @@ type createImageVersionInput struct { func (h *Handler) handleCreateImageVersion(ctx context.Context, body []byte) ([]byte, error) { var req createImageVersionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -214,7 +216,7 @@ func (h *Handler) handleCreateImageVersion(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{"ImageVersionArn": result.ImageVersionArn}) + return json.Marshal(map[string]any{"ImageVersionArn": result.ImageVersionArn}, jsonx.V1()) } type describeImageVersionInput struct { @@ -226,7 +228,7 @@ type describeImageVersionInput struct { func (h *Handler) handleDescribeImageVersion(ctx context.Context, body []byte) ([]byte, error) { var req describeImageVersionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -241,7 +243,7 @@ func (h *Handler) handleDescribeImageVersion(ctx context.Context, body []byte) ( result.ClientToken = "" - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } type deleteImageVersionInput struct { @@ -253,7 +255,7 @@ type deleteImageVersionInput struct { func (h *Handler) handleDeleteImageVersion(ctx context.Context, body []byte) error { var req deleteImageVersionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -279,7 +281,7 @@ type listImageVersionsInput struct { func (h *Handler) handleListImageVersions(ctx context.Context, body []byte) ([]byte, error) { var req listImageVersionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -313,7 +315,7 @@ func (h *Handler) handleListImageVersions(ctx context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "ImageVersions": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } type updateImageVersionInput struct { @@ -334,7 +336,7 @@ type updateImageVersionInput struct { func (h *Handler) handleUpdateImageVersion(ctx context.Context, body []byte) ([]byte, error) { var req updateImageVersionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -358,7 +360,7 @@ func (h *Handler) handleUpdateImageVersion(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{keyImageVersionArn: result.ImageVersionArn}) + return json.Marshal(map[string]any{keyImageVersionArn: result.ImageVersionArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -376,7 +378,7 @@ type listAliasesInput struct { func (h *Handler) handleListAliases(ctx context.Context, body []byte) ([]byte, error) { var req listAliasesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -396,5 +398,5 @@ func (h *Handler) handleListAliases(ctx context.Context, body []byte) ([]byte, e resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_images_test.go b/services/sagemaker/handler_images_test.go index 0f342f0064..c327c2b132 100644 --- a/services/sagemaker/handler_images_test.go +++ b/services/sagemaker/handler_images_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateImage(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_CreateImage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ImageArn"], "my-image") } @@ -40,7 +42,7 @@ func TestHandler_DescribeImage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "img-1", resp["ImageName"]) assert.Equal(t, "CREATED", resp["ImageStatus"]) } @@ -70,7 +72,7 @@ func TestHandler_ListImages(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Images"].([]any) assert.Len(t, items, 2) } @@ -92,7 +94,7 @@ func TestHandler_CreateImageVersion(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ImageVersionArn"], "img-ver") } @@ -110,7 +112,7 @@ func TestHandler_DescribeImageVersion(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InEpsilon(t, float64(1), resp["Version"], 0.001) assert.Equal(t, "CREATED", resp["ImageVersionStatus"]) } @@ -132,7 +134,7 @@ func TestHandler_ListImageVersions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ImageVersions"].([]any) assert.Len(t, items, 2) } @@ -208,7 +210,7 @@ func TestHandler_ListAliases(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) aliases, _ := out["SageMakerImageVersionAliases"].([]any) assert.ElementsMatch(t, []any{"latest", "stable"}, aliases) } @@ -249,7 +251,7 @@ func TestHandler_UpdateImage(t *testing.T) { }) var resp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "updated", resp["Description"]) assert.Equal(t, "My Image", resp["DisplayName"]) @@ -264,7 +266,7 @@ func TestHandler_UpdateImage(t *testing.T) { }) var resp2 map[string]any - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Empty(t, resp2["Description"]) } @@ -307,7 +309,7 @@ func TestHandler_UpdateImageVersion(t *testing.T) { }) var resp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PyTorch 2.0", resp["MLFramework"]) aliases, ok := resp["Aliases"].([]any) require.True(t, ok) @@ -326,7 +328,7 @@ func TestHandler_UpdateImageVersion(t *testing.T) { }) var resp2 map[string]any - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &resp2, jsonx.V1())) aliases2, ok := resp2["Aliases"].([]any) require.True(t, ok) assert.Len(t, aliases2, 1) diff --git a/services/sagemaker/handler_inference_components.go b/services/sagemaker/handler_inference_components.go index 2664af7272..15d04a535d 100644 --- a/services/sagemaker/handler_inference_components.go +++ b/services/sagemaker/handler_inference_components.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -25,7 +28,7 @@ type createInferenceComponentInput struct { func (h *Handler) handleCreateInferenceComponent(ctx context.Context, body []byte) ([]byte, error) { var req createInferenceComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -57,7 +60,7 @@ func (h *Handler) handleCreateInferenceComponent(ctx context.Context, body []byt return json.Marshal(map[string]any{ keyInferenceComponentArn: c.InferenceComponentArn, - }) + }, jsonx.V1()) } // inferenceComponentResponseMap builds the DescribeInferenceComponentOutput @@ -123,7 +126,7 @@ type describeInferenceComponentInput struct { func (h *Handler) handleDescribeInferenceComponent(ctx context.Context, body []byte) ([]byte, error) { var req describeInferenceComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -132,7 +135,7 @@ func (h *Handler) handleDescribeInferenceComponent(ctx context.Context, body []b return nil, err } - return json.Marshal(inferenceComponentResponseMap(c)) + return json.Marshal(inferenceComponentResponseMap(c), jsonx.V1()) } // listInferenceComponentsInput mirrors ListInferenceComponentsInput @@ -155,7 +158,7 @@ type listInferenceComponentsInput struct { func (h *Handler) handleListInferenceComponents(ctx context.Context, body []byte) ([]byte, error) { var req listInferenceComponentsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -200,7 +203,7 @@ func (h *Handler) handleListInferenceComponents(ctx context.Context, body []byte // (api_op_UpdateInferenceComponent.go:24-53). The real op has no VariantName // member — a production variant is fixed at Create time. type updateInferenceComponentInput struct { - DeploymentConfig json.RawMessage `json:"DeploymentConfig"` + DeploymentConfig jsontext.Value `json:"DeploymentConfig"` RuntimeConfig *InferenceComponentRuntimeConfigInput `json:"RuntimeConfig"` InferenceComponentName string `json:"InferenceComponentName"` Specification *InferenceComponentSpecification `json:"Specification"` @@ -210,7 +213,7 @@ type updateInferenceComponentInput struct { func (h *Handler) handleUpdateInferenceComponent(ctx context.Context, body []byte) ([]byte, error) { var req updateInferenceComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -234,7 +237,7 @@ func (h *Handler) handleUpdateInferenceComponent(ctx context.Context, body []byt return json.Marshal(map[string]any{ keyInferenceComponentArn: c.InferenceComponentArn, - }) + }, jsonx.V1()) } // updateInferenceComponentRuntimeConfigInput mirrors @@ -247,7 +250,7 @@ type updateInferenceComponentRuntimeConfigInput struct { func (h *Handler) handleUpdateInferenceComponentRuntimeConfig(ctx context.Context, body []byte) ([]byte, error) { var req updateInferenceComponentRuntimeConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -272,7 +275,7 @@ func (h *Handler) handleUpdateInferenceComponentRuntimeConfig(ctx context.Contex return json.Marshal(map[string]any{ keyInferenceComponentArn: c.InferenceComponentArn, - }) + }, jsonx.V1()) } // deleteInferenceComponentInput mirrors DeleteInferenceComponentInput @@ -284,7 +287,7 @@ type deleteInferenceComponentInput struct { func (h *Handler) handleDeleteInferenceComponent(ctx context.Context, body []byte) ([]byte, error) { var req deleteInferenceComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -292,5 +295,5 @@ func (h *Handler) handleDeleteInferenceComponent(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/sagemaker/handler_inference_components_test.go b/services/sagemaker/handler_inference_components_test.go index 9da5a15ba0..9409304470 100644 --- a/services/sagemaker/handler_inference_components_test.go +++ b/services/sagemaker/handler_inference_components_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_InferenceComponentLifecycle(t *testing.T) { @@ -32,7 +34,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["InferenceComponentArn"], "my-component") // Describe @@ -42,7 +44,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-component", descResp["InferenceComponentName"]) assert.Equal(t, "my-endpoint", descResp["EndpointName"]) assert.Contains(t, descResp["EndpointArn"], "endpoint/my-endpoint") @@ -62,7 +64,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeInferenceComponent", map[string]any{ "InferenceComponentName": "my-component", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "InService", descResp["InferenceComponentStatus"]) runtimeConfig, ok = descResp["RuntimeConfig"].(map[string]any) @@ -74,7 +76,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) components := listResp["InferenceComponents"].([]any) require.Len(t, components, 1) @@ -91,7 +93,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Contains(t, updateResp["InferenceComponentArn"], "my-component") // Update component: DeploymentConfig is opaque and echoed back verbatim @@ -110,7 +112,7 @@ func TestHandler_InferenceComponentLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeInferenceComponent", map[string]any{ "InferenceComponentName": "my-component", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Updating", descResp["InferenceComponentStatus"]) lastDeploymentConfig, ok := descResp["LastDeploymentConfig"].(map[string]any) @@ -203,7 +205,7 @@ func TestHandler_CreateInferenceComponent_SpecificationContainerImage(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) spec, ok := resp["Specification"].(map[string]any) require.True(t, ok) @@ -255,7 +257,7 @@ func TestHandler_ListInferenceComponents_Filters(t *testing.T) { rec := doSageMakerRequest(t, h, "ListInferenceComponents", map[string]any{"StatusEquals": "InService"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) components, _ := resp["InferenceComponents"].([]any) require.Len(t, components, 2) @@ -289,7 +291,7 @@ func TestHandler_ListInferenceComponents_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) components, _ := resp["InferenceComponents"].([]any) assert.Len(t, components, tc.wantCount) }) @@ -314,7 +316,7 @@ func TestHandler_ListInferenceComponents_SortByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) components, ok := resp["InferenceComponents"].([]any) require.True(t, ok) require.Len(t, components, 2) @@ -356,7 +358,7 @@ func TestHandler_ListInferenceComponents_EndpointFilter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) components := resp["InferenceComponents"].([]any) assert.Len(t, components, 1) } diff --git a/services/sagemaker/handler_inference_experiments.go b/services/sagemaker/handler_inference_experiments.go index e9df5afd74..c20181158f 100644 --- a/services/sagemaker/handler_inference_experiments.go +++ b/services/sagemaker/handler_inference_experiments.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -35,7 +37,7 @@ type createInferenceExperimentInput struct { func (h *Handler) handleCreateInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req createInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -69,7 +71,7 @@ func (h *Handler) handleCreateInferenceExperiment(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}) + return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}, jsonx.V1()) } // describeInferenceExperimentInput mirrors DescribeInferenceExperimentInput @@ -81,7 +83,7 @@ type describeInferenceExperimentInput struct { func (h *Handler) handleDescribeInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req describeInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -94,7 +96,7 @@ func (h *Handler) handleDescribeInferenceExperiment(ctx context.Context, body [] return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // stopInferenceExperimentInput mirrors StopInferenceExperimentInput @@ -115,7 +117,7 @@ type stopInferenceExperimentInput struct { func (h *Handler) handleStopInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req stopInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -133,7 +135,7 @@ func (h *Handler) handleStopInferenceExperiment(ctx context.Context, body []byte return nil, err } - return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}) + return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}, jsonx.V1()) } // deleteInferenceExperimentInput mirrors DeleteInferenceExperimentInput @@ -149,7 +151,7 @@ type deleteInferenceExperimentInput struct { func (h *Handler) handleDeleteInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req deleteInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -162,7 +164,7 @@ func (h *Handler) handleDeleteInferenceExperiment(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]any{keyInferenceExperimentArn: arn}) + return json.Marshal(map[string]any{keyInferenceExperimentArn: arn}, jsonx.V1()) } // startInferenceExperimentInput mirrors StartInferenceExperimentInput @@ -174,7 +176,7 @@ type startInferenceExperimentInput struct { func (h *Handler) handleStartInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req startInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -187,7 +189,7 @@ func (h *Handler) handleStartInferenceExperiment(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}) + return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}, jsonx.V1()) } // updateInferenceExperimentInput mirrors UpdateInferenceExperimentInput @@ -205,7 +207,7 @@ type updateInferenceExperimentInput struct { func (h *Handler) handleUpdateInferenceExperiment(ctx context.Context, body []byte) ([]byte, error) { var req updateInferenceExperimentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -224,7 +226,7 @@ func (h *Handler) handleUpdateInferenceExperiment(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}) + return json.Marshal(map[string]any{keyInferenceExperimentArn: result.Arn}, jsonx.V1()) } // listInferenceExperimentsInput mirrors ListInferenceExperimentsInput @@ -249,7 +251,7 @@ type listInferenceExperimentsInput struct { func (h *Handler) handleListInferenceExperiments(ctx context.Context, body []byte) ([]byte, error) { var req listInferenceExperimentsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_inference_experiments_test.go b/services/sagemaker/handler_inference_experiments_test.go index 9a5a9979dd..be75919bc8 100644 --- a/services/sagemaker/handler_inference_experiments_test.go +++ b/services/sagemaker/handler_inference_experiments_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func minimalInferenceExperimentBody(name string) map[string]any { @@ -35,7 +37,7 @@ func TestHandler_CreateInferenceExperiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["InferenceExperimentArn"], "my-exp") } @@ -80,7 +82,7 @@ func TestHandler_DescribeInferenceExperiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "exp-1", resp["Name"]) assert.Contains(t, resp, "EndpointMetadata") assert.Contains(t, resp, "ModelVariants") @@ -99,7 +101,7 @@ func TestHandler_StopInferenceExperiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["InferenceExperimentArn"], "exp-stop") } @@ -113,7 +115,7 @@ func TestHandler_DeleteInferenceExperiment(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["InferenceExperimentArn"], "exp-del") rec = doSageMakerRequest(t, h, "DescribeInferenceExperiment", map[string]any{"Name": "exp-del"}) @@ -141,7 +143,7 @@ func TestHandler_InferenceExperiment_StartAndUpdate(t *testing.T) { recDescribe := doSageMakerRequest(t, h, "DescribeInferenceExperiment", map[string]any{"Name": "inf-exp"}) var out map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "Running", out["Status"]) assert.Equal(t, "updated", out["Description"]) } @@ -174,7 +176,7 @@ func TestHandler_ListInferenceExperiments(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["InferenceExperiments"]) body := minimalInferenceExperimentBody("my-exp") @@ -182,7 +184,7 @@ func TestHandler_ListInferenceExperiments(t *testing.T) { doSageMakerRequest(t, h, "CreateInferenceExperiment", body) rec = doSageMakerRequest(t, h, "ListInferenceExperiments", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) exps := resp["InferenceExperiments"].([]any) assert.Len(t, exps, 1) e := exps[0].(map[string]any) diff --git a/services/sagemaker/handler_inference_recommendations_jobs.go b/services/sagemaker/handler_inference_recommendations_jobs.go index 04de314f37..b5ebcd9343 100644 --- a/services/sagemaker/handler_inference_recommendations_jobs.go +++ b/services/sagemaker/handler_inference_recommendations_jobs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -13,20 +16,20 @@ import ( // createInferenceRecommendationsJobInput mirrors // CreateInferenceRecommendationsJobInput (api_op_CreateInferenceRecommendationsJob.go:1-58). type createInferenceRecommendationsJobInput struct { - JobName string `json:"JobName"` - JobType string `json:"JobType,omitempty"` - JobDescription string `json:"JobDescription,omitempty"` - RoleArn string `json:"RoleArn,omitempty"` - InputConfig json.RawMessage `json:"InputConfig,omitempty"` - OutputConfig json.RawMessage `json:"OutputConfig,omitempty"` - StoppingConditions json.RawMessage `json:"StoppingConditions,omitempty"` - Tags []tagObject `json:"Tags,omitempty"` + JobName string `json:"JobName"` + JobType string `json:"JobType,omitempty"` + JobDescription string `json:"JobDescription,omitempty"` + RoleArn string `json:"RoleArn,omitempty"` + InputConfig jsontext.Value `json:"InputConfig,omitempty"` + OutputConfig jsontext.Value `json:"OutputConfig,omitempty"` + StoppingConditions jsontext.Value `json:"StoppingConditions,omitempty"` + Tags []tagObject `json:"Tags,omitempty"` } func (h *Handler) handleCreateInferenceRecommendationsJob(ctx context.Context, body []byte) ([]byte, error) { var req createInferenceRecommendationsJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -52,7 +55,7 @@ func (h *Handler) handleCreateInferenceRecommendationsJob(ctx context.Context, b return nil, err } - return json.Marshal(map[string]string{keyJobArn: j.JobArn}) + return json.Marshal(map[string]string{keyJobArn: j.JobArn}, jsonx.V1()) } // describeInferenceRecommendationsJobInput mirrors @@ -64,7 +67,7 @@ type describeInferenceRecommendationsJobInput struct { func (h *Handler) handleDescribeInferenceRecommendationsJob(ctx context.Context, body []byte) ([]byte, error) { var req describeInferenceRecommendationsJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -103,7 +106,7 @@ func (h *Handler) handleDescribeInferenceRecommendationsJob(ctx context.Context, resp["StoppingConditions"] = j.StoppingConditions } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // stopInferenceRecommendationsJobInput mirrors @@ -115,7 +118,7 @@ type stopInferenceRecommendationsJobInput struct { func (h *Handler) handleStopInferenceRecommendationsJob(ctx context.Context, body []byte) error { var req stopInferenceRecommendationsJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -157,7 +160,7 @@ type listInferenceRecommendationsJobsInput struct { func (h *Handler) handleListInferenceRecommendationsJobs(ctx context.Context, body []byte) ([]byte, error) { var req listInferenceRecommendationsJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -198,7 +201,7 @@ func (h *Handler) handleListInferenceRecommendationsJobs(ctx context.Context, bo resp[keyNextToken] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listInferenceRecommendationsJobStepsInput mirrors @@ -218,7 +221,7 @@ type listInferenceRecommendationsJobStepsInput struct { func (h *Handler) handleListInferenceRecommendationsJobSteps(ctx context.Context, body []byte) ([]byte, error) { var req listInferenceRecommendationsJobStepsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -230,5 +233,5 @@ func (h *Handler) handleListInferenceRecommendationsJobSteps(ctx context.Context return nil, err } - return json.Marshal(map[string]any{"Steps": []any{}}) + return json.Marshal(map[string]any{"Steps": []any{}}, jsonx.V1()) } diff --git a/services/sagemaker/handler_inference_recommendations_jobs_test.go b/services/sagemaker/handler_inference_recommendations_jobs_test.go index d9498ca705..e282418017 100644 --- a/services/sagemaker/handler_inference_recommendations_jobs_test.go +++ b/services/sagemaker/handler_inference_recommendations_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -12,6 +12,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { @@ -34,7 +36,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["JobArn"], "my-rec-job") // Describe @@ -44,7 +46,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-rec-job", descResp["JobName"]) assert.Equal(t, "IN_PROGRESS", descResp["Status"]) assert.Equal(t, "Default", descResp["JobType"]) @@ -56,7 +58,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["InferenceRecommendationsJobs"].([]any) assert.Len(t, summaries, 1) @@ -67,7 +69,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var stepsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stepsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stepsResp, jsonx.V1())) steps := stepsResp["Steps"].([]any) assert.Empty(t, steps) @@ -80,7 +82,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeInferenceRecommendationsJob", map[string]any{ "JobName": "my-rec-job", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "STOPPING", descResp["Status"]) // Correct immediately after Stop, but an assertion that only checks this @@ -92,7 +94,7 @@ func TestHandler_InferenceRecommendationsJobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeInferenceRecommendationsJob", map[string]any{ "JobName": "my-rec-job", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "STOPPED", descResp["Status"]) }) } @@ -121,7 +123,7 @@ func TestHandler_InferenceRecommendationsJob_ReachesCompleted(t *testing.T) { "JobName": "rec-job-completes", }) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "COMPLETED", out["Status"]) }) } @@ -150,7 +152,7 @@ func TestHandler_CreateInferenceRecommendationsJob_InputConfigRoundTrip(t *testi require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) inputConfig, ok := descResp["InputConfig"].(map[string]any) require.True(t, ok, "DescribeInferenceRecommendationsJob must return the accepted InputConfig") @@ -211,7 +213,7 @@ func TestHandler_CreateInferenceRecommendationsJob_JobTypeDefault(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Default", resp["JobType"]) } @@ -353,7 +355,7 @@ func TestHandler_AIRecommendationJobLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["AIRecommendationJobArn"], "rec-2") rec = doSageMakerRequest(t, h, "DescribeAIRecommendationJob", map[string]any{ @@ -362,7 +364,7 @@ func TestHandler_AIRecommendationJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "InProgress", descResp["AIRecommendationJobStatus"]) assert.Empty(t, descResp["Recommendations"]) }, @@ -385,7 +387,7 @@ func TestHandler_AIRecommendationJobLifecycle(t *testing.T) { "AIRecommendationJobName": "rec-stop", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopping", descResp["AIRecommendationJobStatus"]) }, }, @@ -422,7 +424,7 @@ func TestHandler_AIRecommendationJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["AIRecommendationJobs"].([]any) require.Len(t, items, 1) assert.Equal(t, "rec-list", items[0].(map[string]any)["AIRecommendationJobName"]) diff --git a/services/sagemaker/handler_jobs.go b/services/sagemaker/handler_jobs.go index a32e9cae97..4c70ce6de4 100644 --- a/services/sagemaker/handler_jobs.go +++ b/services/sagemaker/handler_jobs.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // aiAndGenericJobOpsSupported aggregates the operation lists for the four @@ -87,7 +89,7 @@ type createJobInput struct { func (h *Handler) handleCreateJob(ctx context.Context, body []byte) ([]byte, error) { var req createJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -103,7 +105,7 @@ func (h *Handler) handleCreateJob(ctx context.Context, body []byte) ([]byte, err return nil, err } - return json.Marshal(map[string]string{keyJobArn: j.JobArn}) + return json.Marshal(map[string]string{keyJobArn: j.JobArn}, jsonx.V1()) } // describeJobInput mirrors DescribeJobInput (api_op_DescribeJob.go:27-38): @@ -116,7 +118,7 @@ type describeJobInput struct { func (h *Handler) handleDescribeJob(ctx context.Context, body []byte) ([]byte, error) { var req describeJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -139,13 +141,13 @@ func (h *Handler) handleDescribeJob(ctx context.Context, body []byte) ([]byte, e // had any tags at all. Round-trips through Job.MarshalJSON (for its // epoch-seconds time handling) then substitutes the Tags shape. func marshalJobResponse(j *Job) ([]byte, error) { - raw, err := json.Marshal(j) + raw, err := json.Marshal(j, jsonx.V1()) if err != nil { return nil, err } var resp map[string]any - if unmarshalErr := json.Unmarshal(raw, &resp); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(raw, &resp, jsonx.V1()); unmarshalErr != nil { return nil, unmarshalErr } @@ -155,7 +157,7 @@ func marshalJobResponse(j *Job) ([]byte, error) { delete(resp, "Tags") } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteJobInput mirrors DeleteJobInput (api_op_DeleteJob.go:27-38): both @@ -168,7 +170,7 @@ type deleteJobInput struct { func (h *Handler) handleDeleteJob(ctx context.Context, body []byte) error { var req deleteJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -189,7 +191,7 @@ type stopJobInput struct { func (h *Handler) handleStopJob(ctx context.Context, body []byte) error { var req stopJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -222,7 +224,7 @@ type listJobsInput struct { func (h *Handler) handleListJobs(ctx context.Context, body []byte) ([]byte, error) { var req listJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -278,7 +280,7 @@ type describeJobSchemaVersionInput struct { func (h *Handler) handleDescribeJobSchemaVersion(ctx context.Context, body []byte) ([]byte, error) { var req describeJobSchemaVersionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -295,7 +297,7 @@ func (h *Handler) handleDescribeJobSchemaVersion(ctx context.Context, body []byt "JobCategory": req.JobCategory, "JobConfigSchema": info.JobConfigSchema, "JobConfigSchemaVersion": info.JobConfigSchemaVersion, - }) + }, jsonx.V1()) } // listJobSchemaVersionsInput mirrors ListJobSchemaVersionsInput @@ -310,7 +312,7 @@ type listJobSchemaVersionsInput struct { func (h *Handler) handleListJobSchemaVersions(ctx context.Context, body []byte) ([]byte, error) { var req listJobSchemaVersionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_labeling.go b/services/sagemaker/handler_labeling.go index 2dc833e00d..fd56a14517 100644 --- a/services/sagemaker/handler_labeling.go +++ b/services/sagemaker/handler_labeling.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // labelingOpsSupported returns the labeling-job / workteam-subscription @@ -93,7 +95,7 @@ type createLabelingJobRequest struct { func (h *Handler) handleCreateLabelingJob(ctx context.Context, body []byte) ([]byte, error) { var req createLabelingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -137,7 +139,7 @@ func (h *Handler) handleCreateLabelingJob(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{keyLabelingJobArn: result.LabelingJobArn}) + return json.Marshal(map[string]any{keyLabelingJobArn: result.LabelingJobArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -201,7 +203,7 @@ type describeLabelingJobInput struct { func (h *Handler) handleDescribeLabelingJob(ctx context.Context, body []byte) ([]byte, error) { var req describeLabelingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -214,7 +216,7 @@ func (h *Handler) handleDescribeLabelingJob(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(labelingJobResponseMap(result)) + return json.Marshal(labelingJobResponseMap(result), jsonx.V1()) } // --------------------------------------------------------------------------- @@ -229,7 +231,7 @@ type stopLabelingJobInput struct { func (h *Handler) handleStopLabelingJob(ctx context.Context, body []byte) ([]byte, error) { var req stopLabelingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -241,7 +243,7 @@ func (h *Handler) handleStopLabelingJob(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -280,7 +282,7 @@ type listLabelingJobsInput struct { func (h *Handler) handleListLabelingJobs(ctx context.Context, body []byte) ([]byte, error) { var req listLabelingJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -319,7 +321,7 @@ func (h *Handler) handleListLabelingJobs(ctx context.Context, body []byte) ([]by resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -353,7 +355,7 @@ type listLabelingJobsForWorkteamInput struct { func (h *Handler) handleListLabelingJobsForWorkteam(ctx context.Context, body []byte) ([]byte, error) { var req listLabelingJobsForWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -395,7 +397,7 @@ func (h *Handler) handleListLabelingJobsForWorkteam(ctx context.Context, body [] resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -422,11 +424,11 @@ type listSubscribedWorkteamsInput struct { func (h *Handler) handleListSubscribedWorkteams(_ context.Context, body []byte) ([]byte, error) { var req listSubscribedWorkteamsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } - return json.Marshal(map[string]any{"SubscribedWorkteams": []any{}}) + return json.Marshal(map[string]any{"SubscribedWorkteams": []any{}}, jsonx.V1()) } // describeSubscribedWorkteamInput mirrors DescribeSubscribedWorkteamInput @@ -438,7 +440,7 @@ type describeSubscribedWorkteamInput struct { func (h *Handler) handleDescribeSubscribedWorkteam(_ context.Context, body []byte) error { var req describeSubscribedWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_labeling_test.go b/services/sagemaker/handler_labeling_test.go index 9c0ec7027d..92bc73eea0 100644 --- a/services/sagemaker/handler_labeling_test.go +++ b/services/sagemaker/handler_labeling_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -52,7 +54,7 @@ func TestHandler_CreateLabelingJob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["LabelingJobArn"], "labeling-job/job-1") } @@ -73,7 +75,7 @@ func TestHandler_CreateLabelingJob_TagsRoundTripThroughDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].([]any) require.True(t, ok, "DescribeLabelingJob must return Tags") require.Len(t, tags, 1) @@ -135,7 +137,7 @@ func TestHandler_DescribeLabelingJob_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "job-lifecycle", resp["LabelingJobName"]) assert.Equal(t, "Initializing", resp["LabelingJobStatus"]) assert.NotEmpty(t, resp["JobReferenceCode"]) @@ -148,7 +150,7 @@ func TestHandler_DescribeLabelingJob_Lifecycle(t *testing.T) { synctest.Wait() rec = doSageMakerRequest(t, h, "DescribeLabelingJob", map[string]any{"LabelingJobName": "job-lifecycle"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Completed", resp["LabelingJobStatus"]) }) } @@ -162,7 +164,7 @@ func TestHandler_DescribeLabelingJob_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -186,14 +188,14 @@ func TestHandler_StopLabelingJob(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeLabelingJob", map[string]any{"LabelingJobName": "job-stop"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, []any{"Stopping", "Stopped"}, resp["LabelingJobStatus"]) time.Sleep(time.Second) synctest.Wait() rec = doSageMakerRequest(t, h, "DescribeLabelingJob", map[string]any{"LabelingJobName": "job-stop"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Stopped", resp["LabelingJobStatus"]) }) } @@ -229,7 +231,7 @@ func TestHandler_ListLabelingJobs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["LabelingJobSummaryList"].([]any) require.True(t, ok) assert.Len(t, items, 2) @@ -240,7 +242,7 @@ func TestHandler_ListLabelingJobs(t *testing.T) { // NameContains filter. rec = doSageMakerRequest(t, h, "ListLabelingJobs", map[string]any{"NameContains": "list-job-a"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ = resp["LabelingJobSummaryList"].([]any) assert.Len(t, items, 1) } @@ -269,7 +271,7 @@ func TestHandler_ListLabelingJobsForWorkteam(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["LabelingJobSummaryList"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -308,7 +310,7 @@ func TestHandler_ListLabelingJobs_ResponseIncludesInputConfigAndOutput(t *testin require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["LabelingJobSummaryList"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -322,7 +324,7 @@ func TestHandler_ListLabelingJobs_ResponseIncludesInputConfigAndOutput(t *testin synctest.Wait() rec = doSageMakerRequest(t, h, "ListLabelingJobs", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok = resp["LabelingJobSummaryList"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -378,7 +380,7 @@ func TestHandler_ListLabelingJobs_TimeFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["LabelingJobSummaryList"].([]any) assert.Len(t, items, tc.wantCount) }) @@ -429,7 +431,7 @@ func TestHandler_ListLabelingJobs_SortBy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["LabelingJobSummaryList"].([]any) require.True(t, ok) require.Len(t, items, 2) @@ -454,7 +456,7 @@ func TestHandler_ListLabelingJobsForWorkteam_Filters(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) jobReferenceCode, ok := describeResp["JobReferenceCode"].(string) require.True(t, ok) require.NotEmpty(t, jobReferenceCode) @@ -490,7 +492,7 @@ func TestHandler_ListLabelingJobsForWorkteam_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["LabelingJobSummaryList"].([]any) assert.Len(t, items, tc.wantCount) }) @@ -512,7 +514,7 @@ func TestHandler_ListWorkforces(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Workforces"].([]any) require.True(t, ok) require.Len(t, items, 1) @@ -563,7 +565,7 @@ func TestHandler_DeleteWorkforce_ResourceInUse(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceInUse", resp["__type"]) } @@ -591,7 +593,7 @@ func TestHandler_UpdateWorkforce_WithSourceIPConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wf, ok := resp["Workforce"].(map[string]any) require.True(t, ok) @@ -623,7 +625,7 @@ func TestHandler_CreateWorkteam_WithWorkforceName(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeWorkteam", map[string]any{"WorkteamName": "team-link"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wt, ok := resp["Workteam"].(map[string]any) require.True(t, ok) assert.Contains(t, wt["WorkforceArn"], "workforce/wf-link") @@ -662,7 +664,7 @@ func TestHandler_UpdateWorkteam(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wt, ok := resp["Workteam"].(map[string]any) require.True(t, ok) assert.Equal(t, "after", wt["Description"]) @@ -689,7 +691,7 @@ func TestHandler_DeleteWorkteam_ReturnsSuccess(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, true, resp["Success"]) } @@ -706,7 +708,7 @@ func TestHandler_ListSubscribedWorkteams_AlwaysEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["SubscribedWorkteams"].([]any) require.True(t, ok) assert.Empty(t, items) @@ -724,7 +726,7 @@ func TestHandler_DescribeSubscribedWorkteam_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -782,7 +784,7 @@ func TestLabelingFamily_SnapshotRestore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var wtResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wtResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wtResp, jsonx.V1())) wt, ok := wtResp["Workteam"].(map[string]any) require.True(t, ok) assert.Contains(t, wt["WorkforceArn"], "workforce/persist-wf") @@ -791,6 +793,6 @@ func TestLabelingFamily_SnapshotRestore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var jobResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &jobResp, jsonx.V1())) assert.Equal(t, "persist-job", jobResp["LabelingJobName"]) } diff --git a/services/sagemaker/handler_lineage.go b/services/sagemaker/handler_lineage.go index 8127595339..9a1076831a 100644 --- a/services/sagemaker/handler_lineage.go +++ b/services/sagemaker/handler_lineage.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -174,7 +175,7 @@ type createArtifactInput struct { func (h *Handler) handleCreateArtifact(ctx context.Context, body []byte) ([]byte, error) { var req createArtifactInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +202,7 @@ func (h *Handler) handleCreateArtifact(ctx context.Context, body []byte) ([]byte logger.Load(ctx).InfoContext(ctx, "sagemaker: created artifact", "arn", ar.ArtifactArn) - return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}) + return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}, jsonx.V1()) } // describeArtifactInput is the DescribeArtifact request shape (named, not @@ -213,7 +214,7 @@ type describeArtifactInput struct { func (h *Handler) handleDescribeArtifact(ctx context.Context, body []byte) ([]byte, error) { var req describeArtifactInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -245,7 +246,7 @@ func (h *Handler) handleDescribeArtifact(ctx context.Context, body []byte) ([]by resp["MetadataProperties"] = toMetadataProperties(ar.MetadataProperties) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateArtifactInput is the UpdateArtifact request shape (named, not @@ -260,7 +261,7 @@ type updateArtifactInput struct { func (h *Handler) handleUpdateArtifact(ctx context.Context, body []byte) ([]byte, error) { var req updateArtifactInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -275,7 +276,7 @@ func (h *Handler) handleUpdateArtifact(ctx context.Context, body []byte) ([]byte logger.Load(ctx).InfoContext(ctx, "sagemaker: updated artifact", "arn", ar.ArtifactArn) - return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}) + return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}, jsonx.V1()) } // deleteArtifactInput is the DeleteArtifact request shape (named, not @@ -290,7 +291,7 @@ type deleteArtifactInput struct { func (h *Handler) handleDeleteArtifact(ctx context.Context, body []byte) ([]byte, error) { var req deleteArtifactInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -307,7 +308,7 @@ func (h *Handler) handleDeleteArtifact(ctx context.Context, body []byte) ([]byte logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted artifact", "arn", ar.ArtifactArn) - return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}) + return json.Marshal(map[string]string{keyArtifactArn: ar.ArtifactArn}, jsonx.V1()) } type artifactSummary struct { @@ -332,7 +333,7 @@ func marshalSummaryPage[T, S any](key, nextToken string, items []*T, conv func(* resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listArtifactsInput is the ListArtifacts request shape (named, not inline — @@ -351,7 +352,7 @@ type listArtifactsInput struct { func (h *Handler) handleListArtifacts(ctx context.Context, body []byte) ([]byte, error) { var req listArtifactsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -412,7 +413,7 @@ type createContextInput struct { func (h *Handler) handleCreateContext(ctx context.Context, body []byte) ([]byte, error) { var req createContextInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -443,7 +444,7 @@ func (h *Handler) handleCreateContext(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: created context", "name", c.ContextName) - return json.Marshal(map[string]string{keyContextArn: c.ContextArn}) + return json.Marshal(map[string]string{keyContextArn: c.ContextArn}, jsonx.V1()) } // describeContextInput is the DescribeContext request shape (named, not @@ -455,7 +456,7 @@ type describeContextInput struct { func (h *Handler) handleDescribeContext(ctx context.Context, body []byte) ([]byte, error) { var req describeContextInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -484,7 +485,7 @@ func (h *Handler) handleDescribeContext(ctx context.Context, body []byte) ([]byt resp["Properties"] = c.Properties } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateContextInput is the UpdateContext request shape (named, not @@ -499,7 +500,7 @@ type updateContextInput struct { func (h *Handler) handleUpdateContext(ctx context.Context, body []byte) ([]byte, error) { var req updateContextInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -514,7 +515,7 @@ func (h *Handler) handleUpdateContext(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: updated context", "name", c.ContextName) - return json.Marshal(map[string]string{keyContextArn: c.ContextArn}) + return json.Marshal(map[string]string{keyContextArn: c.ContextArn}, jsonx.V1()) } // deleteContextInput is the DeleteContext request shape (named, not @@ -526,7 +527,7 @@ type deleteContextInput struct { func (h *Handler) handleDeleteContext(ctx context.Context, body []byte) ([]byte, error) { var req deleteContextInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -541,7 +542,7 @@ func (h *Handler) handleDeleteContext(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted context", "name", req.ContextName) - return json.Marshal(map[string]string{keyContextArn: c.ContextArn}) + return json.Marshal(map[string]string{keyContextArn: c.ContextArn}, jsonx.V1()) } type contextSummary struct { @@ -569,7 +570,7 @@ type listContextsInput struct { func (h *Handler) handleListContexts(ctx context.Context, body []byte) ([]byte, error) { var req listContextsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -611,7 +612,7 @@ type describeActionInput struct { func (h *Handler) handleDescribeAction(ctx context.Context, body []byte) ([]byte, error) { var req describeActionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -651,7 +652,7 @@ func (h *Handler) handleDescribeAction(ctx context.Context, body []byte) ([]byte resp["MetadataProperties"] = toMetadataProperties(a.MetadataProperties) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateActionInput is the UpdateAction request shape (named, not @@ -667,7 +668,7 @@ type updateActionInput struct { func (h *Handler) handleUpdateAction(ctx context.Context, body []byte) ([]byte, error) { var req updateActionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -684,7 +685,7 @@ func (h *Handler) handleUpdateAction(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: updated action", "name", a.ActionName) - return json.Marshal(map[string]string{keyActionArn: a.ActionArn}) + return json.Marshal(map[string]string{keyActionArn: a.ActionArn}, jsonx.V1()) } // deleteActionInput is the DeleteAction request shape (named, not @@ -696,7 +697,7 @@ type deleteActionInput struct { func (h *Handler) handleDeleteAction(ctx context.Context, body []byte) ([]byte, error) { var req deleteActionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -711,7 +712,7 @@ func (h *Handler) handleDeleteAction(ctx context.Context, body []byte) ([]byte, logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted action", "name", req.ActionName) - return json.Marshal(map[string]string{keyActionArn: a.ActionArn}) + return json.Marshal(map[string]string{keyActionArn: a.ActionArn}, jsonx.V1()) } type actionSummary struct { @@ -740,7 +741,7 @@ type listActionsInput struct { func (h *Handler) handleListActions(ctx context.Context, body []byte) ([]byte, error) { var req listActionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -784,7 +785,7 @@ type deleteAssociationInput struct { func (h *Handler) handleDeleteAssociation(ctx context.Context, body []byte) ([]byte, error) { var req deleteAssociationInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -803,7 +804,7 @@ func (h *Handler) handleDeleteAssociation(ctx context.Context, body []byte) ([]b logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted association", "source", req.SourceArn, "destination", req.DestinationArn) - return json.Marshal(map[string]string{"SourceArn": req.SourceArn, "DestinationArn": req.DestinationArn}) + return json.Marshal(map[string]string{"SourceArn": req.SourceArn, "DestinationArn": req.DestinationArn}, jsonx.V1()) } // associationSummary mirrors types.AssociationSummary (types.go:157-188). @@ -839,7 +840,7 @@ type listAssociationsInput struct { func (h *Handler) handleListAssociations(ctx context.Context, body []byte) ([]byte, error) { var req listAssociationsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -888,7 +889,7 @@ type describeLineageGroupInput struct { func (h *Handler) handleDescribeLineageGroup(ctx context.Context, body []byte) ([]byte, error) { var req describeLineageGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -906,7 +907,7 @@ func (h *Handler) handleDescribeLineageGroup(ctx context.Context, body []byte) ( "LineageGroupName": req.LineageGroupName, keyCreationTime: epochSeconds(createdAt), keyLastModifiedTime: epochSeconds(createdAt), - }) + }, jsonx.V1()) } // listLineageGroupsInput is the ListLineageGroups request shape (named, not @@ -923,7 +924,7 @@ type listLineageGroupsInput struct { func (h *Handler) handleListLineageGroups(ctx context.Context, body []byte) ([]byte, error) { var req listLineageGroupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -951,7 +952,7 @@ func (h *Handler) handleListLineageGroups(ctx context.Context, body []byte) ([]b resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // getLineageGroupPolicyInput is the GetLineageGroupPolicy request shape @@ -963,7 +964,7 @@ type getLineageGroupPolicyInput struct { func (h *Handler) handleGetLineageGroupPolicy(ctx context.Context, body []byte) ([]byte, error) { var req getLineageGroupPolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1007,7 +1008,7 @@ type queryLineageInput struct { func (h *Handler) handleQueryLineage(ctx context.Context, body []byte) ([]byte, error) { var req queryLineageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1056,7 +1057,7 @@ func (h *Handler) handleQueryLineage(ctx context.Context, body []byte) ([]byte, resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // fromQueryFilters converts the wire Filters object to backend QueryLineageFilters. @@ -1087,7 +1088,7 @@ type addAssociationRequest struct { func (h *Handler) handleAddAssociation(ctx context.Context, body []byte) ([]byte, error) { var req addAssociationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1118,7 +1119,7 @@ func (h *Handler) handleAddAssociation(ctx context.Context, body []byte) ([]byte return json.Marshal(map[string]string{ "SourceArn": req.SourceArn, "DestinationArn": req.DestinationArn, - }) + }, jsonx.V1()) } // associateTrialComponentRequest is the request body for AssociateTrialComponent. @@ -1129,7 +1130,7 @@ type associateTrialComponentRequest struct { func (h *Handler) handleAssociateTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req associateTrialComponentRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1153,7 +1154,7 @@ func (h *Handler) handleAssociateTrialComponent(ctx context.Context, body []byte return json.Marshal(map[string]string{ "TrialArn": assoc.TrialArn, "TrialComponentArn": assoc.TrialComponentArn, - }) + }, jsonx.V1()) } // actionSourceRequest is the source for a CreateAction request. @@ -1180,7 +1181,7 @@ type createActionRequest struct { func (h *Handler) handleCreateAction(ctx context.Context, body []byte) ([]byte, error) { var req createActionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -1212,5 +1213,5 @@ func (h *Handler) handleCreateAction(ctx context.Context, body []byte) ([]byte, log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created action", "name", a.ActionName) - return json.Marshal(map[string]string{"ActionArn": a.ActionArn}) + return json.Marshal(map[string]string{"ActionArn": a.ActionArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_lineage_test.go b/services/sagemaker/handler_lineage_test.go index e2875ef159..123c2e8a57 100644 --- a/services/sagemaker/handler_lineage_test.go +++ b/services/sagemaker/handler_lineage_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -34,7 +35,7 @@ func TestHandler_ArtifactLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) artifactArn, _ := createOut["ArtifactArn"].(string) require.NotEmpty(t, artifactArn) assert.Contains(t, artifactArn, "artifact/") @@ -44,7 +45,7 @@ func TestHandler_ArtifactLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "my-artifact", descOut["ArtifactName"]) assert.Equal(t, "DataSet", descOut["ArtifactType"]) assert.NotZero(t, descOut["CreationTime"]) @@ -59,7 +60,7 @@ func TestHandler_ArtifactLifecycle(t *testing.T) { recDesc2 := doSageMakerRequest(t, h, "DescribeArtifact", map[string]any{"ArtifactArn": artifactArn}) var descOut2 map[string]any - require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2, jsonx.V1())) assert.Equal(t, "renamed-artifact", descOut2["ArtifactName"]) props, _ := descOut2["Properties"].(map[string]any) assert.Equal(t, "final", props["stage"]) @@ -70,7 +71,7 @@ func TestHandler_ArtifactLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["ArtifactSummaries"].([]any), 1) // Delete. @@ -79,7 +80,7 @@ func TestHandler_ArtifactLifecycle(t *testing.T) { recList2 := doSageMakerRequest(t, h, "ListArtifacts", map[string]any{}) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2, jsonx.V1())) assert.Empty(t, listOut2["ArtifactSummaries"].([]any)) } @@ -140,7 +141,7 @@ func TestHandler_ListArtifacts_FilterByType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["ArtifactSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "a2", summaries[0].(map[string]any)["ArtifactName"]) @@ -300,7 +301,7 @@ func TestHandler_ContextLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) contextArn, _ := createOut["ContextArn"].(string) require.NotEmpty(t, contextArn) assert.Contains(t, contextArn, "context/my-context") @@ -309,7 +310,7 @@ func TestHandler_ContextLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "Endpoint", descOut["ContextType"]) recUpdate := doSageMakerRequest(t, h, "UpdateContext", map[string]any{ @@ -320,12 +321,12 @@ func TestHandler_ContextLifecycle(t *testing.T) { recDesc2 := doSageMakerRequest(t, h, "DescribeContext", map[string]any{"ContextName": "my-context"}) var descOut2 map[string]any - require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2, jsonx.V1())) assert.Equal(t, "updated", descOut2["Description"]) recList := doSageMakerRequest(t, h, "ListContexts", map[string]any{}) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["ContextSummaries"].([]any), 1) recDelete := doSageMakerRequest(t, h, "DeleteContext", map[string]any{"ContextName": "my-context"}) @@ -428,7 +429,7 @@ func TestHandler_ActionLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "ModelDeployment", descOut["ActionType"]) assert.Equal(t, "InProgress", descOut["Status"]) @@ -440,12 +441,12 @@ func TestHandler_ActionLifecycle(t *testing.T) { recDesc2 := doSageMakerRequest(t, h, "DescribeAction", map[string]any{"ActionName": "my-action"}) var descOut2 map[string]any - require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2)) + require.NoError(t, json.Unmarshal(recDesc2.Body.Bytes(), &descOut2, jsonx.V1())) assert.Equal(t, "Completed", descOut2["Status"]) recList := doSageMakerRequest(t, h, "ListActions", map[string]any{}) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["ActionSummaries"].([]any), 1) recDelete := doSageMakerRequest(t, h, "DeleteAction", map[string]any{"ActionName": "my-action"}) @@ -552,7 +553,7 @@ func TestHandler_AssociationLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) summaries := listOut["AssociationSummaries"].([]any) require.Len(t, summaries, 1) assoc := summaries[0].(map[string]any) @@ -568,7 +569,7 @@ func TestHandler_AssociationLifecycle(t *testing.T) { recList2 := doSageMakerRequest(t, h, "ListAssociations", map[string]any{}) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2, jsonx.V1())) assert.Empty(t, listOut2["AssociationSummaries"].([]any)) } @@ -595,7 +596,7 @@ func TestHandler_ListAssociations_ResolvesEntityNames(t *testing.T) { "Source": map[string]any{"SourceUri": "arn:aws:sagemaker:us-east-1:0:endpoint/e"}, }) var actionOut map[string]any - require.NoError(t, json.Unmarshal(recAction.Body.Bytes(), &actionOut)) + require.NoError(t, json.Unmarshal(recAction.Body.Bytes(), &actionOut, jsonx.V1())) actionArn := actionOut["ActionArn"].(string) recArtifact := doSageMakerRequest(t, h, "CreateArtifact", map[string]any{ @@ -604,7 +605,7 @@ func TestHandler_ListAssociations_ResolvesEntityNames(t *testing.T) { "Source": map[string]any{"SourceUri": "s3://bucket/data"}, }) var artifactOut map[string]any - require.NoError(t, json.Unmarshal(recArtifact.Body.Bytes(), &artifactOut)) + require.NoError(t, json.Unmarshal(recArtifact.Body.Bytes(), &artifactOut, jsonx.V1())) artifactArn := artifactOut["ArtifactArn"].(string) doSageMakerRequest(t, h, "AddAssociation", map[string]any{ @@ -615,7 +616,7 @@ func TestHandler_ListAssociations_ResolvesEntityNames(t *testing.T) { rec := doSageMakerRequest(t, h, "ListAssociations", map[string]any{}) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assoc := out["AssociationSummaries"].([]any)[0].(map[string]any) assert.Equal(t, "src-action", assoc["SourceName"]) assert.Equal(t, "ModelDeployment", assoc["SourceType"]) @@ -656,7 +657,7 @@ func listAssociationSourceArns(t *testing.T, body []byte) []string { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) summaries, _ := out["AssociationSummaries"].([]any) arns := make([]string, 0, len(summaries)) @@ -787,7 +788,7 @@ func TestHandler_ListAssociations_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries, _ := out["AssociationSummaries"].([]any) require.Len(t, summaries, 1, "MaxResults=1 should return exactly one association out of two") @@ -802,7 +803,7 @@ func TestHandler_ListAssociations_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) summaries2, _ := out2["AssociationSummaries"].([]any) require.Len(t, summaries2, 1, "second page should return the remaining association") @@ -844,7 +845,7 @@ func TestHandler_QueryLineage_Traversal(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) vertices := out["Vertices"].([]any) arns := make([]string, 0, len(vertices)) @@ -884,7 +885,7 @@ func TestHandler_QueryLineage_MaxDepth(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) vertices := out["Vertices"].([]any) arns := make([]string, 0, len(vertices)) @@ -917,7 +918,7 @@ func TestHandler_QueryLineage_UnknownArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) vertices := out["Vertices"].([]any) require.Len(t, vertices, 1) assert.Empty(t, vertices[0].(map[string]any)["Type"]) @@ -1042,7 +1043,7 @@ func TestHandler_DescribeLineageGroup(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out["LineageGroupArn"], "lineage-group/sagemaker-default-lineage-group") } @@ -1064,7 +1065,7 @@ func TestHandler_ListLineageGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) summaries := out["LineageGroupSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "sagemaker-default-lineage-group", summaries[0].(map[string]any)["LineageGroupName"]) @@ -1154,7 +1155,7 @@ func createTestContext(t *testing.T, h *sagemaker.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ContextArn"].(string) } @@ -1170,7 +1171,7 @@ func createTestAction(t *testing.T, h *sagemaker.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ActionArn"].(string) } @@ -1186,7 +1187,7 @@ func createTestArtifact(t *testing.T, h *sagemaker.Handler, name string) string require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out["ArtifactArn"].(string) } @@ -1256,7 +1257,7 @@ func TestCreateAction_TagsPresent(t *testing.T) { if tt.wantTags { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arnStr := resp["ActionArn"] require.NotEmpty(t, arnStr) @@ -1265,7 +1266,7 @@ func TestCreateAction_TagsPresent(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) require.Len(t, tags, 1) @@ -1339,7 +1340,7 @@ func TestHandler_AddAssociation(t *testing.T) { if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["SourceArn"], "arn:aws:sagemaker") assert.Contains(t, resp["DestinationArn"], "arn:aws:sagemaker") } @@ -1439,7 +1440,7 @@ func TestHandler_AssociateTrialComponent(t *testing.T) { if tt.wantArns { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["TrialArn"], "arn:aws:sagemaker") assert.Contains(t, resp["TrialComponentArn"], "arn:aws:sagemaker") } @@ -1527,7 +1528,7 @@ func TestHandler_CreateAction(t *testing.T) { if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ActionArn"], "arn:aws:sagemaker") assert.Contains(t, resp["ActionArn"], "action/") } diff --git a/services/sagemaker/handler_list_notebook_filters_test.go b/services/sagemaker/handler_list_notebook_filters_test.go index 70f37bfafe..9f36e748cb 100644 --- a/services/sagemaker/handler_list_notebook_filters_test.go +++ b/services/sagemaker/handler_list_notebook_filters_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListNotebookInstances_Filters verifies that ListNotebookInstances honors @@ -65,7 +67,7 @@ func TestListNotebookInstances_Filters(t *testing.T) { } `json:"NotebookInstances"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) gotNames := make([]string, 0, len(resp.NotebookInstances)) for _, nb := range resp.NotebookInstances { diff --git a/services/sagemaker/handler_mlflow.go b/services/sagemaker/handler_mlflow.go index b69c44da27..262d363dd2 100644 --- a/services/sagemaker/handler_mlflow.go +++ b/services/sagemaker/handler_mlflow.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -28,7 +30,7 @@ type createMlflowTrackingServerInput struct { func (h *Handler) handleCreateMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req createMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -67,7 +69,7 @@ func (h *Handler) handleCreateMlflowTrackingServer(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]any{keyTrackingServerArn: result.TrackingServerArn}) + return json.Marshal(map[string]any{keyTrackingServerArn: result.TrackingServerArn}, jsonx.V1()) } // describeMlflowTrackingServerInput is DescribeMlflowTrackingServer's request @@ -104,7 +106,7 @@ type describeMlflowTrackingServerResponse struct { func (h *Handler) handleDescribeMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req describeMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -135,7 +137,7 @@ func (h *Handler) handleDescribeMlflowTrackingServer(ctx context.Context, body [ S3BucketOwnerVerification: result.S3BucketOwnerVerification, CreationTime: epochSeconds(result.CreationTime), LastModifiedTime: epochSeconds(result.LastModifiedTime), - }) + }, jsonx.V1()) } // deleteMlflowTrackingServerInput is DeleteMlflowTrackingServer's request @@ -147,7 +149,7 @@ type deleteMlflowTrackingServerInput struct { func (h *Handler) handleDeleteMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req deleteMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -160,7 +162,7 @@ func (h *Handler) handleDeleteMlflowTrackingServer(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}) + return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}, jsonx.V1()) } // startMlflowTrackingServerInput is StartMlflowTrackingServer's request shape @@ -172,7 +174,7 @@ type startMlflowTrackingServerInput struct { func (h *Handler) handleStartMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req startMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -185,7 +187,7 @@ func (h *Handler) handleStartMlflowTrackingServer(ctx context.Context, body []by return nil, err } - return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}) + return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}, jsonx.V1()) } // stopMlflowTrackingServerInput is StopMlflowTrackingServer's request shape @@ -197,7 +199,7 @@ type stopMlflowTrackingServerInput struct { func (h *Handler) handleStopMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req stopMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -210,7 +212,7 @@ func (h *Handler) handleStopMlflowTrackingServer(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}) + return json.Marshal(map[string]any{keyTrackingServerArn: arnStr}, jsonx.V1()) } // createPresignedMlflowTrackingServerURLInput is @@ -226,7 +228,7 @@ type createPresignedMlflowTrackingServerURLInput struct { func (h *Handler) handleCreatePresignedMlflowTrackingServerURL(ctx context.Context, body []byte) ([]byte, error) { var req createPresignedMlflowTrackingServerURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -243,7 +245,7 @@ func (h *Handler) handleCreatePresignedMlflowTrackingServerURL(ctx context.Conte return nil, err } - return json.Marshal(map[string]string{keyAuthorizedURL: url}) + return json.Marshal(map[string]string{keyAuthorizedURL: url}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -266,7 +268,7 @@ type createMlflowAppInput struct { func (h *Handler) handleCreateMlflowApp(ctx context.Context, body []byte) ([]byte, error) { var req createMlflowAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -288,7 +290,7 @@ func (h *Handler) handleCreateMlflowApp(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyGenericArn: result.Arn}) + return json.Marshal(map[string]any{keyGenericArn: result.Arn}, jsonx.V1()) } // describeMlflowAppResponse is the response body for DescribeMlflowApp. @@ -319,7 +321,7 @@ type describeMlflowAppInput struct { func (h *Handler) handleDescribeMlflowApp(ctx context.Context, body []byte) ([]byte, error) { var req describeMlflowAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -345,7 +347,7 @@ func (h *Handler) handleDescribeMlflowApp(ctx context.Context, body []byte) ([]b DefaultDomainIDList: result.DefaultDomainIDList, CreationTime: epochSeconds(result.CreationTime), LastModifiedTime: epochSeconds(result.LastModifiedTime), - }) + }, jsonx.V1()) } // deleteMlflowAppInput is DeleteMlflowApp's request shape @@ -357,7 +359,7 @@ type deleteMlflowAppInput struct { func (h *Handler) handleDeleteMlflowApp(ctx context.Context, body []byte) ([]byte, error) { var req deleteMlflowAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -369,7 +371,7 @@ func (h *Handler) handleDeleteMlflowApp(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyGenericArn: req.Arn}) + return json.Marshal(map[string]any{keyGenericArn: req.Arn}, jsonx.V1()) } // updateMlflowAppInput is UpdateMlflowApp's request shape @@ -389,7 +391,7 @@ type updateMlflowAppInput struct { func (h *Handler) handleUpdateMlflowApp(ctx context.Context, body []byte) ([]byte, error) { var req updateMlflowAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -409,7 +411,7 @@ func (h *Handler) handleUpdateMlflowApp(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyGenericArn: result.Arn}) + return json.Marshal(map[string]any{keyGenericArn: result.Arn}, jsonx.V1()) } // listMlflowAppsInput is ListMlflowApps' request shape @@ -430,7 +432,7 @@ type listMlflowAppsInput struct { func (h *Handler) handleListMlflowApps(ctx context.Context, body []byte) ([]byte, error) { var req listMlflowAppsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -474,7 +476,7 @@ type createPresignedMlflowAppURLInput struct { func (h *Handler) handleCreatePresignedMlflowAppURL(ctx context.Context, body []byte) ([]byte, error) { var req createPresignedMlflowAppURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -491,7 +493,7 @@ func (h *Handler) handleCreatePresignedMlflowAppURL(ctx context.Context, body [] return nil, err } - return json.Marshal(map[string]string{keyAuthorizedURL: url}) + return json.Marshal(map[string]string{keyAuthorizedURL: url}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -514,7 +516,7 @@ type listMlflowTrackingServersInput struct { func (h *Handler) handleListMlflowTrackingServers(ctx context.Context, body []byte) ([]byte, error) { var req listMlflowTrackingServersInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -566,7 +568,7 @@ type updateMlflowTrackingServerInput struct { func (h *Handler) handleUpdateMlflowTrackingServer(ctx context.Context, body []byte) ([]byte, error) { var req updateMlflowTrackingServerInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -579,5 +581,5 @@ func (h *Handler) handleUpdateMlflowTrackingServer(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]string{keyTrackingServerArn: s.TrackingServerArn}) + return json.Marshal(map[string]string{keyTrackingServerArn: s.TrackingServerArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_mlflow_test.go b/services/sagemaker/handler_mlflow_test.go index dedecc3731..0bde3ad443 100644 --- a/services/sagemaker/handler_mlflow_test.go +++ b/services/sagemaker/handler_mlflow_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateMlflowTrackingServer(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_CreateMlflowTrackingServer(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["TrackingServerArn"], "my-server") } @@ -38,7 +40,7 @@ func TestHandler_DescribeMlflowTrackingServer(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ts-1", resp["TrackingServerName"]) } @@ -53,14 +55,14 @@ func TestHandler_StartStopMlflowTrackingServer(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.Contains(t, startResp["TrackingServerArn"], "ts-ss") rec = doSageMakerRequest(t, h, "StopMlflowTrackingServer", map[string]any{"TrackingServerName": "ts-ss"}) assert.Equal(t, http.StatusOK, rec.Code) var stopResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &stopResp, jsonx.V1())) assert.Contains(t, stopResp["TrackingServerArn"], "ts-ss") } @@ -120,7 +122,7 @@ func TestHandler_DeleteMlflowTrackingServer(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var deleteResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &deleteResp, jsonx.V1())) assert.Contains(t, deleteResp["TrackingServerArn"], "ts-del") rec = doSageMakerRequest(t, h, "DescribeMlflowTrackingServer", map[string]any{"TrackingServerName": "ts-del"}) @@ -313,7 +315,7 @@ func TestHandler_CreatePresignedMlflowTrackingServerUrl(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["AuthorizedUrl"], "ts-1") } @@ -345,7 +347,7 @@ func TestHandler_MlflowApp_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) appARN := createResp["Arn"] require.Contains(t, appARN, "mlflow-app/app-1") @@ -353,7 +355,7 @@ func TestHandler_MlflowApp_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, describeRec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "app-1", describeResp["Name"]) assert.Equal(t, "Created", describeResp["Status"]) assert.Equal(t, "s3://bucket/path", describeResp["ArtifactStoreUri"]) @@ -365,21 +367,21 @@ func TestHandler_MlflowApp_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, updateRec.Code) describeRec = doSageMakerRequest(t, h, "DescribeMlflowApp", map[string]any{"Arn": appARN}) - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "s3://bucket/new-path", describeResp["ArtifactStoreUri"]) presignRec := doSageMakerRequest(t, h, "CreatePresignedMlflowAppUrl", map[string]any{"Arn": appARN}) assert.Equal(t, http.StatusOK, presignRec.Code) var presignResp map[string]string - require.NoError(t, json.Unmarshal(presignRec.Body.Bytes(), &presignResp)) + require.NoError(t, json.Unmarshal(presignRec.Body.Bytes(), &presignResp, jsonx.V1())) assert.Contains(t, presignResp["AuthorizedUrl"], "app-1") listRec := doSageMakerRequest(t, h, "ListMlflowApps", map[string]any{}) assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["Summaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -388,7 +390,7 @@ func TestHandler_MlflowApp_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, deleteRec.Code) var deleteResp map[string]string - require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp)) + require.NoError(t, json.Unmarshal(deleteRec.Body.Bytes(), &deleteResp, jsonx.V1())) assert.Equal(t, appARN, deleteResp["Arn"]) describeRec = doSageMakerRequest(t, h, "DescribeMlflowApp", map[string]any{"Arn": appARN}) @@ -586,7 +588,7 @@ func TestHandler_ListMlflowTrackingServers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["TrackingServerSummaries"]) // Create one @@ -600,7 +602,7 @@ func TestHandler_ListMlflowTrackingServers(t *testing.T) { rec = doSageMakerRequest(t, h, "ListMlflowTrackingServers", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["TrackingServerSummaries"].([]any) assert.Len(t, summaries, 1) @@ -631,7 +633,7 @@ func TestHandler_UpdateMlflowTrackingServer(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["TrackingServerArn"]) } @@ -663,7 +665,7 @@ func TestHandler_UpdateMlflowTrackingServer_MlflowVersionNotOnWire(t *testing.T) "TrackingServerName": "my-server", }) var resp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal( t, "2.0.0", resp["MlflowVersion"], "MlflowVersion is not a real UpdateMlflowTrackingServerInput field and must not change", diff --git a/services/sagemaker/handler_model_cards.go b/services/sagemaker/handler_model_cards.go index ea0d85e3b0..10dddc50af 100644 --- a/services/sagemaker/handler_model_cards.go +++ b/services/sagemaker/handler_model_cards.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -24,7 +26,7 @@ type createModelCardInput struct { func (h *Handler) handleCreateModelCard(ctx context.Context, body []byte) ([]byte, error) { var req createModelCardInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -43,7 +45,7 @@ func (h *Handler) handleCreateModelCard(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyModelCardArn: result.ModelCardArn}) + return json.Marshal(map[string]any{keyModelCardArn: result.ModelCardArn}, jsonx.V1()) } // describeModelCardInput mirrors DescribeModelCardInput @@ -58,7 +60,7 @@ type describeModelCardInput struct { func (h *Handler) handleDescribeModelCard(ctx context.Context, body []byte) ([]byte, error) { var req describeModelCardInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -71,7 +73,7 @@ func (h *Handler) handleDescribeModelCard(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // updateModelCardInput mirrors UpdateModelCardInput (api_op_UpdateModelCard.go:31-59): @@ -86,7 +88,7 @@ type updateModelCardInput struct { func (h *Handler) handleUpdateModelCard(ctx context.Context, body []byte) ([]byte, error) { var req updateModelCardInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -99,7 +101,7 @@ func (h *Handler) handleUpdateModelCard(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyModelCardArn: result.ModelCardArn}) + return json.Marshal(map[string]any{keyModelCardArn: result.ModelCardArn}, jsonx.V1()) } // deleteModelCardInput mirrors DeleteModelCardInput (api_op_DeleteModelCard.go:27-33): @@ -111,7 +113,7 @@ type deleteModelCardInput struct { func (h *Handler) handleDeleteModelCard(ctx context.Context, body []byte) error { var req deleteModelCardInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -142,7 +144,7 @@ type listModelCardsInput struct { func (h *Handler) handleListModelCards(ctx context.Context, body []byte) ([]byte, error) { var req listModelCardsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -199,7 +201,7 @@ type listModelCardVersionsInput struct { func (h *Handler) handleListModelCardVersions(ctx context.Context, body []byte) ([]byte, error) { var req listModelCardVersionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -224,7 +226,7 @@ func (h *Handler) handleListModelCardVersions(ctx context.Context, body []byte) }) } - return json.Marshal(map[string]any{"ModelCardVersionSummaryList": summaries}) + return json.Marshal(map[string]any{"ModelCardVersionSummaryList": summaries}, jsonx.V1()) } func modelCardVersionMatchesFilter(card *ModelCard, req listModelCardVersionsInput) bool { @@ -262,7 +264,7 @@ type listModelCardExportJobsInput struct { func (h *Handler) handleListModelCardExportJobs(ctx context.Context, body []byte) ([]byte, error) { var req listModelCardExportJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_model_cards_test.go b/services/sagemaker/handler_model_cards_test.go index 2c5f12287f..be1de9e354 100644 --- a/services/sagemaker/handler_model_cards_test.go +++ b/services/sagemaker/handler_model_cards_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateModelCard(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_CreateModelCard(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ModelCardArn"], "my-card") } @@ -51,7 +53,7 @@ func TestHandler_DescribeModelCard(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "card-1", resp["ModelCardName"]) } @@ -70,7 +72,7 @@ func TestHandler_UpdateModelCard(t *testing.T) { // Verify version incremented rec = doSageMakerRequest(t, h, "DescribeModelCard", map[string]any{"ModelCardName": "card-upd"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.InDelta(t, float64(2), resp["ModelCardVersion"], 0) } @@ -101,7 +103,7 @@ func TestHandler_ListModelCards(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["ModelCardSummaries"]) // Create one @@ -113,7 +115,7 @@ func TestHandler_ListModelCards(t *testing.T) { rec = doSageMakerRequest(t, h, "ListModelCards", map[string]any{}) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["ModelCardSummaries"].([]any) assert.Len(t, summaries, 1) @@ -142,7 +144,7 @@ func TestHandler_ListModelCardVersions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions := resp["ModelCardVersionSummaryList"].([]any) assert.Len(t, versions, 1) @@ -178,7 +180,7 @@ func TestHandler_ListModelCardExportJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobs := resp["ModelCardExportJobSummaries"].([]any) assert.Empty(t, jobs) } diff --git a/services/sagemaker/handler_model_packages.go b/services/sagemaker/handler_model_packages.go index ded5c4aa85..d508211da6 100644 --- a/services/sagemaker/handler_model_packages.go +++ b/services/sagemaker/handler_model_packages.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -27,23 +30,23 @@ type createModelPackageInput struct { ModelApprovalStatus string `json:"ModelApprovalStatus"` ModelPackageGroupName string `json:"ModelPackageGroupName"` ModelPackageName string `json:"ModelPackageName"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification"` - ValidationSpecification json.RawMessage `json:"ValidationSpecification"` - DriftCheckBaselines json.RawMessage `json:"DriftCheckBaselines"` - ModelMetrics json.RawMessage `json:"ModelMetrics"` - ModelCard json.RawMessage `json:"ModelCard"` - ModelLifeCycle json.RawMessage `json:"ModelLifeCycle"` - MetadataProperties json.RawMessage `json:"MetadataProperties"` - SecurityConfig json.RawMessage `json:"SecurityConfig"` - AdditionalInferenceSpecifications json.RawMessage `json:"AdditionalInferenceSpecifications"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification"` + ValidationSpecification jsontext.Value `json:"ValidationSpecification"` + DriftCheckBaselines jsontext.Value `json:"DriftCheckBaselines"` + ModelMetrics jsontext.Value `json:"ModelMetrics"` + ModelCard jsontext.Value `json:"ModelCard"` + ModelLifeCycle jsontext.Value `json:"ModelLifeCycle"` + MetadataProperties jsontext.Value `json:"MetadataProperties"` + SecurityConfig jsontext.Value `json:"SecurityConfig"` + AdditionalInferenceSpecifications jsontext.Value `json:"AdditionalInferenceSpecifications"` Tags []tagObject `json:"Tags"` - SourceAlgorithmSpecification json.RawMessage `json:"SourceAlgorithmSpecification"` + SourceAlgorithmSpecification jsontext.Value `json:"SourceAlgorithmSpecification"` } func (h *Handler) handleCreateModelPackage(ctx context.Context, body []byte) ([]byte, error) { var req createModelPackageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -92,7 +95,7 @@ func (h *Handler) handleCreateModelPackage(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{keyModelPackageArn: result.ModelPackageArn}) + return json.Marshal(map[string]any{keyModelPackageArn: result.ModelPackageArn}, jsonx.V1()) } // describeModelPackageInput is the wire request body for @@ -106,7 +109,7 @@ type describeModelPackageInput struct { func (h *Handler) handleDescribeModelPackage(ctx context.Context, body []byte) ([]byte, error) { var req describeModelPackageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -119,7 +122,7 @@ func (h *Handler) handleDescribeModelPackage(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteModelPackageInput is the wire request body for DeleteModelPackage @@ -131,7 +134,7 @@ type deleteModelPackageInput struct { func (h *Handler) handleDeleteModelPackage(ctx context.Context, body []byte) error { var req deleteModelPackageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -160,7 +163,7 @@ type listModelPackagesInput struct { func (h *Handler) handleListModelPackages(ctx context.Context, body []byte) ([]byte, error) { var req listModelPackagesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -205,7 +208,7 @@ func (h *Handler) handleListModelPackages(ctx context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "ModelPackageSummaryList": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -225,7 +228,7 @@ type createModelPackageGroupInput struct { func (h *Handler) handleCreateModelPackageGroup(ctx context.Context, body []byte) ([]byte, error) { var req createModelPackageGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -247,7 +250,7 @@ func (h *Handler) handleCreateModelPackageGroup(ctx context.Context, body []byte return nil, err } - return json.Marshal(map[string]any{keyModelPackageGroupArn: result.ModelPackageGroupArn}) + return json.Marshal(map[string]any{keyModelPackageGroupArn: result.ModelPackageGroupArn}, jsonx.V1()) } // describeModelPackageGroupInput is the wire request body for @@ -260,7 +263,7 @@ type describeModelPackageGroupInput struct { func (h *Handler) handleDescribeModelPackageGroup(ctx context.Context, body []byte) ([]byte, error) { var req describeModelPackageGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -273,7 +276,7 @@ func (h *Handler) handleDescribeModelPackageGroup(ctx context.Context, body []by return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteModelPackageGroupInput is the wire request body for @@ -286,7 +289,7 @@ type deleteModelPackageGroupInput struct { func (h *Handler) handleDeleteModelPackageGroup(ctx context.Context, body []byte) error { var req deleteModelPackageGroupInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -314,7 +317,7 @@ type listModelPackageGroupsInput struct { func (h *Handler) handleListModelPackageGroups(ctx context.Context, body []byte) ([]byte, error) { var req listModelPackageGroupsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -353,7 +356,7 @@ func (h *Handler) handleListModelPackageGroups(ctx context.Context, body []byte) return json.Marshal(map[string]any{ "ModelPackageGroupSummaryList": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } // getModelPackageGroupPolicyInput is the wire request body for @@ -366,7 +369,7 @@ type getModelPackageGroupPolicyInput struct { func (h *Handler) handleGetModelPackageGroupPolicy(ctx context.Context, body []byte) ([]byte, error) { var req getModelPackageGroupPolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -379,7 +382,7 @@ func (h *Handler) handleGetModelPackageGroupPolicy(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]string{"ResourcePolicy": policy}) + return json.Marshal(map[string]string{"ResourcePolicy": policy}, jsonx.V1()) } // putModelPackageGroupPolicyInput is the wire request body for @@ -393,7 +396,7 @@ type putModelPackageGroupPolicyInput struct { func (h *Handler) handlePutModelPackageGroupPolicy(ctx context.Context, body []byte) ([]byte, error) { var req putModelPackageGroupPolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -410,7 +413,7 @@ func (h *Handler) handlePutModelPackageGroupPolicy(ctx context.Context, body []b return nil, err } - return json.Marshal(map[string]string{keyModelPackageGroupArn: g.ModelPackageGroupArn}) + return json.Marshal(map[string]string{keyModelPackageGroupArn: g.ModelPackageGroupArn}, jsonx.V1()) } // deleteModelPackageGroupPolicyInput is the wire request body for @@ -423,7 +426,7 @@ type deleteModelPackageGroupPolicyInput struct { func (h *Handler) handleDeleteModelPackageGroupPolicy(ctx context.Context, body []byte) error { var req deleteModelPackageGroupPolicyInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -452,16 +455,16 @@ type updateModelPackageInput struct { SourceURI string `json:"SourceUri"` CustomerMetadataProperties map[string]string `json:"CustomerMetadataProperties"` CustomerMetadataPropertiesToRemove []string `json:"CustomerMetadataPropertiesToRemove"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification"` - ModelCard json.RawMessage `json:"ModelCard"` - ModelLifeCycle json.RawMessage `json:"ModelLifeCycle"` - AdditionalInferenceSpecificationsToAdd json.RawMessage `json:"AdditionalInferenceSpecificationsToAdd"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification"` + ModelCard jsontext.Value `json:"ModelCard"` + ModelLifeCycle jsontext.Value `json:"ModelLifeCycle"` + AdditionalInferenceSpecificationsToAdd jsontext.Value `json:"AdditionalInferenceSpecificationsToAdd"` } func (h *Handler) handleUpdateModelPackage(ctx context.Context, body []byte) ([]byte, error) { var req updateModelPackageInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -489,7 +492,7 @@ func (h *Handler) handleUpdateModelPackage(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]string{keyModelPackageArn: mp.ModelPackageArn}) + return json.Marshal(map[string]string{keyModelPackageArn: mp.ModelPackageArn}, jsonx.V1()) } // batchDescribeModelPackageRequest is the request body for BatchDescribeModelPackage. @@ -519,7 +522,7 @@ type batchDescribeModelPackageError struct { func (h *Handler) handleBatchDescribeModelPackage(ctx context.Context, body []byte) ([]byte, error) { var req batchDescribeModelPackageRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -551,5 +554,5 @@ func (h *Handler) handleBatchDescribeModelPackage(ctx context.Context, body []by return json.Marshal(map[string]any{ "ModelPackageSummaries": modelPackageMap, "BatchDescribeModelPackageErrorMap": errorsMap, - }) + }, jsonx.V1()) } diff --git a/services/sagemaker/handler_model_packages_test.go b/services/sagemaker/handler_model_packages_test.go index 649850fbae..bfea04fb51 100644 --- a/services/sagemaker/handler_model_packages_test.go +++ b/services/sagemaker/handler_model_packages_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -27,7 +28,7 @@ func TestHandler_CreateModelPackage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ModelPackageArn"], "my-pkg") } @@ -42,7 +43,7 @@ func TestHandler_DescribeModelPackage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "pkg-1", resp["ModelPackageName"]) } @@ -71,7 +72,7 @@ func TestHandler_ListModelPackages(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ModelPackageSummaryList"].([]any) assert.Len(t, items, 2) } @@ -91,7 +92,7 @@ func TestHandler_CreateModelPackageGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ModelPackageGroupArn"], "my-group") } @@ -106,7 +107,7 @@ func TestHandler_DescribeModelPackageGroup(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "grp-1", resp["ModelPackageGroupName"]) assert.Equal(t, "Completed", resp["ModelPackageGroupStatus"]) } @@ -136,7 +137,7 @@ func TestHandler_ListModelPackageGroups(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ModelPackageGroupSummaryList"].([]any) assert.Len(t, items, 2) } @@ -294,7 +295,7 @@ func TestAddTags_ModelPackage(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) tags := resp["Tags"].([]any) require.Len(t, tags, 1) @@ -382,7 +383,7 @@ func TestHandler_ModelPackageGroupPolicy_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recPut.Code) var putOut map[string]any - require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(recPut.Body.Bytes(), &putOut, jsonx.V1())) assert.NotEmpty(t, putOut["ModelPackageGroupArn"]) recGet2 := doSageMakerRequest(t, h, "GetModelPackageGroupPolicy", map[string]any{ @@ -391,7 +392,7 @@ func TestHandler_ModelPackageGroupPolicy_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recGet2.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(recGet2.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(recGet2.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, policy, getOut["ResourcePolicy"]) recDelete := doSageMakerRequest(t, h, "DeleteModelPackageGroupPolicy", map[string]any{ @@ -479,7 +480,7 @@ func TestHandler_UpdateModelPackage(t *testing.T) { }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) pkgArn := createResp["ModelPackageArn"] require.NotEmpty(t, pkgArn) @@ -490,7 +491,7 @@ func TestHandler_UpdateModelPackage(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, pkgArn, resp["ModelPackageArn"]) // Describe and verify approval status @@ -498,7 +499,7 @@ func TestHandler_UpdateModelPackage(t *testing.T) { "ModelPackageName": "my-pkg", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Approved", descResp["ModelApprovalStatus"]) } @@ -593,7 +594,7 @@ func TestHandler_BatchDescribeModelPackage(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ModelPackageSummaries") assert.Contains(t, resp, "BatchDescribeModelPackageErrorMap") } @@ -624,7 +625,7 @@ func TestHandler_BatchDescribeModelPackage_ExistingAndMissing(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["ModelPackageSummaries"].(map[string]any) require.True(t, ok) diff --git a/services/sagemaker/handler_modelcard_export.go b/services/sagemaker/handler_modelcard_export.go index cf31e66fa7..0279690320 100644 --- a/services/sagemaker/handler_modelcard_export.go +++ b/services/sagemaker/handler_modelcard_export.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // modelCardExportOpsSupported returns the real stateful operations @@ -53,7 +55,7 @@ type createModelCardExportJobInput struct { func (h *Handler) handleCreateModelCardExportJob(ctx context.Context, body []byte) ([]byte, error) { var req createModelCardExportJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -68,7 +70,7 @@ func (h *Handler) handleCreateModelCardExportJob(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyModelCardExportJobArn: job.ModelCardExportJobArn}) + return json.Marshal(map[string]any{keyModelCardExportJobArn: job.ModelCardExportJobArn}, jsonx.V1()) } // describeModelCardExportJobInput mirrors DescribeModelCardExportJobInput @@ -81,7 +83,7 @@ type describeModelCardExportJobInput struct { func (h *Handler) handleDescribeModelCardExportJob(ctx context.Context, body []byte) ([]byte, error) { var req describeModelCardExportJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -104,5 +106,5 @@ func (h *Handler) handleDescribeModelCardExportJob(ctx context.Context, body []b keyStatus: job.Status, keyOutputConfig: map[string]any{"S3OutputPath": job.S3OutputPath}, "ExportArtifacts": map[string]any{"S3ExportArtifacts": job.S3ExportArtifacts}, - }) + }, jsonx.V1()) } diff --git a/services/sagemaker/handler_modelcard_export_test.go b/services/sagemaker/handler_modelcard_export_test.go index 30857b0415..47f462f683 100644 --- a/services/sagemaker/handler_modelcard_export_test.go +++ b/services/sagemaker/handler_modelcard_export_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ModelCardExportJob_Lifecycle(t *testing.T) { @@ -27,7 +29,7 @@ func TestHandler_ModelCardExportJob_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) jobArn, _ := createResp["ModelCardExportJobArn"].(string) require.NotEmpty(t, jobArn) @@ -37,7 +39,7 @@ func TestHandler_ModelCardExportJob_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, describeRec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "Completed", describeResp["Status"]) assert.Equal(t, "export-1", describeResp["ModelCardExportJobName"]) assert.Equal(t, "my-card", describeResp["ModelCardName"]) @@ -48,7 +50,7 @@ func TestHandler_ModelCardExportJob_Lifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["ModelCardExportJobSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) diff --git a/services/sagemaker/handler_modelmonitor_test.go b/services/sagemaker/handler_modelmonitor_test.go index 3574c9322d..c7dc988e4f 100644 --- a/services/sagemaker/handler_modelmonitor_test.go +++ b/services/sagemaker/handler_modelmonitor_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -46,7 +47,7 @@ func TestHandler_ListDataQualityJobDefinitions(t *testing.T) { var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.JobDefinitionSummaries, 3) } @@ -67,7 +68,7 @@ func TestHandler_ListDataQualityJobDefinitions_FilterByEndpointName(t *testing.T var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.JobDefinitionSummaries, 2) for _, s := range resp.JobDefinitionSummaries { @@ -94,7 +95,7 @@ func TestHandler_ListDataQualityJobDefinitions_NameContainsAndSort(t *testing.T) var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.JobDefinitionSummaries, 2) assert.Equal(t, "alpha-job", resp.JobDefinitionSummaries[0]["MonitoringJobDefinitionName"]) assert.Equal(t, "alpha-other", resp.JobDefinitionSummaries[1]["MonitoringJobDefinitionName"]) @@ -120,7 +121,7 @@ func TestHandler_ListDataQualityJobDefinitions_Pagination(t *testing.T) { NextToken string `json:"NextToken"` JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page1, jsonx.V1())) require.Len(t, page1.JobDefinitionSummaries, 2) require.NotEmpty(t, page1.NextToken) @@ -134,7 +135,7 @@ func TestHandler_ListDataQualityJobDefinitions_Pagination(t *testing.T) { var page2 struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &page2, jsonx.V1())) assert.Len(t, page2.JobDefinitionSummaries, 1) } @@ -149,7 +150,7 @@ func TestHandler_ListModelBiasJobDefinitions_Empty(t *testing.T) { var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.JobDefinitionSummaries) } @@ -168,7 +169,7 @@ func TestHandler_ListModelQualityJobDefinitions_ReturnsCreated(t *testing.T) { var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.JobDefinitionSummaries, 1) assert.Equal(t, "mq-list-1", resp.JobDefinitionSummaries[0]["MonitoringJobDefinitionName"]) } @@ -188,7 +189,7 @@ func TestHandler_ListModelExplainabilityJobDefinitions_ReturnsCreated(t *testing var resp struct { JobDefinitionSummaries []map[string]any `json:"JobDefinitionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.JobDefinitionSummaries, 1) assert.Equal(t, "me-list-1", resp.JobDefinitionSummaries[0]["MonitoringJobDefinitionName"]) } @@ -230,7 +231,7 @@ func TestHandler_UpdateMonitoringAlert_CreatesThenUpdates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["MonitoringScheduleArn"], "mm-sched") assert.Equal(t, "alert-1", resp["MonitoringAlertName"]) @@ -251,7 +252,7 @@ func TestHandler_UpdateMonitoringAlert_CreatesThenUpdates(t *testing.T) { var listResp struct { MonitoringAlertSummaries []map[string]any `json:"MonitoringAlertSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.MonitoringAlertSummaries, 1) assert.InEpsilon(t, float64(5), listResp.MonitoringAlertSummaries[0]["DatapointsToAlert"], 0) assert.InEpsilon(t, float64(6), listResp.MonitoringAlertSummaries[0]["EvaluationPeriod"], 0) @@ -335,7 +336,7 @@ func TestHandler_ListMonitoringAlerts_MaxResults(t *testing.T) { NextToken string `json:"NextToken"` MonitoringAlertSummaries []map[string]any `json:"MonitoringAlertSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.MonitoringAlertSummaries, 2) assert.NotEmpty(t, resp.NextToken) } @@ -369,7 +370,7 @@ func TestHandler_ListMonitoringAlerts_EmptyForFreshSchedule(t *testing.T) { var resp struct { MonitoringAlertSummaries []map[string]any `json:"MonitoringAlertSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.MonitoringAlertSummaries) } @@ -403,7 +404,7 @@ func TestHandler_ListMonitoringAlertHistory_FiltersByScheduleAndStatus(t *testin var resp struct { MonitoringAlertHistory []map[string]any `json:"MonitoringAlertHistory"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.MonitoringAlertHistory, 1) assert.Equal(t, "sched-1", resp.MonitoringAlertHistory[0]["MonitoringScheduleName"]) assert.Equal(t, "InAlert", resp.MonitoringAlertHistory[0]["AlertStatus"]) @@ -446,7 +447,7 @@ func TestHandler_ListMonitoringAlertHistory_SortByStatus(t *testing.T) { var defaultResp struct { MonitoringAlertHistory []map[string]any `json:"MonitoringAlertHistory"` } - require.NoError(t, json.Unmarshal(defaultRec.Body.Bytes(), &defaultResp)) + require.NoError(t, json.Unmarshal(defaultRec.Body.Bytes(), &defaultResp, jsonx.V1())) require.Len(t, defaultResp.MonitoringAlertHistory, 2) assert.Equal(t, "InAlert", defaultResp.MonitoringAlertHistory[0]["AlertStatus"]) @@ -462,7 +463,7 @@ func TestHandler_ListMonitoringAlertHistory_SortByStatus(t *testing.T) { var statusResp struct { MonitoringAlertHistory []map[string]any `json:"MonitoringAlertHistory"` } - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp, jsonx.V1())) require.Len(t, statusResp.MonitoringAlertHistory, 2) assert.Equal(t, "OK", statusResp.MonitoringAlertHistory[0]["AlertStatus"], "SortBy=Status must be honored") } @@ -501,7 +502,7 @@ func TestHandler_ListMonitoringExecutions_FiltersByScheduleAndStatus(t *testing. var resp struct { MonitoringExecutionSummaries []map[string]any `json:"MonitoringExecutionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.MonitoringExecutionSummaries, 1) assert.Equal(t, "sched-a", resp.MonitoringExecutionSummaries[0]["MonitoringScheduleName"]) assert.Equal(t, "Completed", resp.MonitoringExecutionSummaries[0]["MonitoringExecutionStatus"]) @@ -518,6 +519,6 @@ func TestHandler_ListMonitoringExecutions_EmptyWhenNoneSeeded(t *testing.T) { var resp struct { MonitoringExecutionSummaries []map[string]any `json:"MonitoringExecutionSummaries"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.MonitoringExecutionSummaries) } diff --git a/services/sagemaker/handler_models.go b/services/sagemaker/handler_models.go index 688b77a9be..9ad05cc9be 100644 --- a/services/sagemaker/handler_models.go +++ b/services/sagemaker/handler_models.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -42,7 +43,7 @@ type describeModelResponse struct { func (h *Handler) handleCreateModel(ctx context.Context, body []byte) ([]byte, error) { var req createModelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -96,7 +97,7 @@ func (h *Handler) handleCreateModel(ctx context.Context, body []byte) ([]byte, e log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created model", "name", m.ModelName) - return json.Marshal(map[string]string{"ModelArn": m.ModelARN}) + return json.Marshal(map[string]string{"ModelArn": m.ModelARN}, jsonx.V1()) } // describeModelRequest is the request body for DescribeModel. @@ -107,7 +108,7 @@ type describeModelRequest struct { func (h *Handler) handleDescribeModel(ctx context.Context, body []byte) ([]byte, error) { var req describeModelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -136,13 +137,17 @@ func (h *Handler) handleDescribeModel(ctx context.Context, body []byte) ([]byte, resp.Containers = nil } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } -func (h *Handler) handleListModels(ctx context.Context, body []byte) ([]byte, error) { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleListModels( + ctx context.Context, + body []byte, +) ([]byte, error) { var req nameTimeListRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -168,7 +173,7 @@ func (h *Handler) handleListModels(ctx context.Context, body []byte) ([]byte, er resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteModelRequest is the request body for DeleteModel. @@ -179,7 +184,7 @@ type deleteModelRequest struct { func (h *Handler) handleDeleteModel(ctx context.Context, body []byte) error { var req deleteModelRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_models_test.go b/services/sagemaker/handler_models_test.go index 063a02233a..460e378bf8 100644 --- a/services/sagemaker/handler_models_test.go +++ b/services/sagemaker/handler_models_test.go @@ -3,7 +3,7 @@ package sagemaker_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -62,7 +63,7 @@ func TestHandler_CreateModel(t *testing.T) { if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("not-json") @@ -82,7 +83,7 @@ func TestHandler_CreateModel(t *testing.T) { if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ModelArn"], "arn:aws:sagemaker") assert.Contains(t, resp["ModelArn"], "model/my-model") } @@ -140,7 +141,7 @@ func TestHandler_DescribeModel(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["ModelName"]) } }) @@ -176,7 +177,7 @@ func TestHandler_ListModels(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) models, ok := resp["Models"].([]any) require.True(t, ok) @@ -277,7 +278,7 @@ func TestHandler_ListModelsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) models, modelsOK := resp["Models"].([]any) require.True(t, modelsOK) @@ -298,7 +299,7 @@ func TestHandler_ListModelsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) models2, models2OK := resp2["Models"].([]any) require.True(t, models2OK) @@ -432,7 +433,7 @@ func TestHandler_ListModels_FilterSort(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) list, ok := resp["Models"].([]any) require.True(t, ok) @@ -497,7 +498,7 @@ func TestHandler_ListModels_CreationTimeAfterInclusive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) models, ok := resp["Models"].([]any) require.True(t, ok) require.Len(t, models, 1) diff --git a/services/sagemaker/handler_monitoring.go b/services/sagemaker/handler_monitoring.go index 79d4201acb..f6096905bc 100644 --- a/services/sagemaker/handler_monitoring.go +++ b/services/sagemaker/handler_monitoring.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -21,7 +23,7 @@ type updateMonitoringAlertRequest struct { func (h *Handler) handleUpdateMonitoringAlert(ctx context.Context, body []byte) ([]byte, error) { var req updateMonitoringAlertRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -51,7 +53,7 @@ func (h *Handler) handleUpdateMonitoringAlert(ctx context.Context, body []byte) return json.Marshal(map[string]any{ keyMonitoringScheduleArn: scheduleArn, keyMonitoringAlertName: alert.MonitoringAlertName, - }) + }, jsonx.V1()) } // listMonitoringAlertsRequest is the request body for ListMonitoringAlerts. @@ -64,7 +66,7 @@ type listMonitoringAlertsRequest struct { func (h *Handler) handleListMonitoringAlerts(ctx context.Context, body []byte) ([]byte, error) { var req listMonitoringAlertsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -97,7 +99,7 @@ func (h *Handler) handleListMonitoringAlerts(ctx context.Context, body []byte) ( resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listMonitoringAlertHistoryRequest is the request body for ListMonitoringAlertHistory. @@ -116,7 +118,7 @@ type listMonitoringAlertHistoryRequest struct { func (h *Handler) handleListMonitoringAlertHistory(ctx context.Context, body []byte) ([]byte, error) { var req listMonitoringAlertHistoryRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -148,7 +150,7 @@ func (h *Handler) handleListMonitoringAlertHistory(ctx context.Context, body []b resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listMonitoringExecutionsRequest is the parsed representation of a @@ -192,13 +194,13 @@ func (r listMonitoringExecutionsRequest) toFilter() MonitoringExecutionFilter { func (h *Handler) handleListMonitoringExecutions(ctx context.Context, body []byte) ([]byte, error) { var req listMonitoringExecutionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } items, next := h.Backend.ListMonitoringExecutions(ctx, req.NextToken, req.toFilter()) - return json.Marshal(buildMonitoringExecutionListResponse(items, next)) + return json.Marshal(buildMonitoringExecutionListResponse(items, next), jsonx.V1()) } func buildMonitoringExecutionListResponse(items []*MonitoringExecution, next string) map[string]any { diff --git a/services/sagemaker/handler_monitoring_job_definitions.go b/services/sagemaker/handler_monitoring_job_definitions.go index d655702a7d..0ea8372743 100644 --- a/services/sagemaker/handler_monitoring_job_definitions.go +++ b/services/sagemaker/handler_monitoring_job_definitions.go @@ -2,10 +2,13 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -27,7 +30,7 @@ import ( // JobInput/JobOutputConfig blocks plus the shared JobResources/NetworkConfig/ // StoppingCondition/BaselineConfig) is kept verbatim in Config. type jobDefRequest struct { - Config map[string]json.RawMessage + Config map[string]jsontext.Value Tags map[string]string JobDefinitionName string RoleArn string @@ -38,26 +41,26 @@ type jobDefRequest struct { // wire field name of the type's JobInput block (e.g. "DataQualityJobInput"), // used to derive EndpointName for List filtering/summaries. func parseJobDefRequest(body []byte, jobInputKey string) (jobDefRequest, error) { - var raw map[string]json.RawMessage - if err := json.Unmarshal(body, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(body, &raw, jsonx.V1()); err != nil { return jobDefRequest{}, fmt.Errorf("%w: %w", errInvalidRequest, err) } - req := jobDefRequest{Config: make(map[string]json.RawMessage, len(raw))} + req := jobDefRequest{Config: make(map[string]jsontext.Value, len(raw))} for k, v := range raw { switch k { case keyJobDefinitionName: - if err := json.Unmarshal(v, &req.JobDefinitionName); err != nil { + if err := json.Unmarshal(v, &req.JobDefinitionName, jsonx.V1()); err != nil { return jobDefRequest{}, fmt.Errorf("%w: %w", errInvalidRequest, err) } case "RoleArn": - if err := json.Unmarshal(v, &req.RoleArn); err != nil { + if err := json.Unmarshal(v, &req.RoleArn, jsonx.V1()); err != nil { return jobDefRequest{}, fmt.Errorf("%w: %w", errInvalidRequest, err) } case keyTagsField: var tags []tagObject - if err := json.Unmarshal(v, &tags); err != nil { + if err := json.Unmarshal(v, &tags, jsonx.V1()); err != nil { return jobDefRequest{}, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -83,7 +86,7 @@ func parseJobDefRequest(body []byte, jobInputKey string) (jobDefRequest, error) // AppSpecification/JobInput/JobOutputConfig, whose wire names share the // type's name prefix derived from jobInputKey (e.g. "DataQualityJobInput" -> // "DataQuality"). Returns the decoded JobInput block on success. -func validateJobDefRequest(req jobDefRequest, jobInputKey string) (json.RawMessage, error) { +func validateJobDefRequest(req jobDefRequest, jobInputKey string) (jsontext.Value, error) { if req.JobDefinitionName == "" { return nil, fmt.Errorf("%w: JobDefinitionName is required", errInvalidRequest) } @@ -117,14 +120,14 @@ func validateJobDefRequest(req jobDefRequest, jobInputKey string) (json.RawMessa // extractEndpointName pulls EndpointInput.EndpointName out of a job input // block (DataQualityJobInput / ModelBiasJobInput / ModelQualityJobInput / // ModelExplainabilityJobInput all share this shape). -func extractEndpointName(jobInput json.RawMessage) string { +func extractEndpointName(jobInput jsontext.Value) string { var in struct { EndpointInput *struct { EndpointName string `json:"EndpointName"` } `json:"EndpointInput"` } - if err := json.Unmarshal(jobInput, &in); err != nil || in.EndpointInput == nil { + if err := json.Unmarshal(jobInput, &in, jsonx.V1()); err != nil || in.EndpointInput == nil { return "" } @@ -144,7 +147,7 @@ type jobDefinitionNameInput struct { func parseJobDefinitionName(body []byte) (string, error) { var req jobDefinitionNameInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return "", fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +204,7 @@ type jobDefinitionListInput struct { func parseJobDefinitionListRequest(body []byte) (jobDefListRequest, error) { var req jobDefinitionListInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return jobDefListRequest{}, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -268,7 +271,7 @@ func (h *Handler) handleCreateDataQualityJobDefinition(ctx context.Context, body return nil, err } - return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}) + return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}, jsonx.V1()) } func (h *Handler) handleDescribeDataQualityJobDefinition(ctx context.Context, body []byte) ([]byte, error) { @@ -282,7 +285,7 @@ func (h *Handler) handleDescribeDataQualityJobDefinition(ctx context.Context, bo return nil, err } - return json.Marshal(buildJobDefinitionResponse(result)) + return json.Marshal(buildJobDefinitionResponse(result), jsonx.V1()) } func (h *Handler) handleDeleteDataQualityJobDefinition(ctx context.Context, body []byte) error { @@ -302,7 +305,7 @@ func (h *Handler) handleListDataQualityJobDefinitions(ctx context.Context, body items, next := h.Backend.ListDataQualityJobDefinitions(ctx, req.NextToken, req.Filter) - return json.Marshal(buildJobDefinitionListResponse(items, next)) + return json.Marshal(buildJobDefinitionListResponse(items, next), jsonx.V1()) } // --------------------------------------------------------------------------- @@ -322,7 +325,7 @@ func (h *Handler) handleCreateModelBiasJobDefinition(ctx context.Context, body [ return nil, err } - return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}) + return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}, jsonx.V1()) } func (h *Handler) handleDescribeModelBiasJobDefinition(ctx context.Context, body []byte) ([]byte, error) { @@ -336,7 +339,7 @@ func (h *Handler) handleDescribeModelBiasJobDefinition(ctx context.Context, body return nil, err } - return json.Marshal(buildJobDefinitionResponse(result)) + return json.Marshal(buildJobDefinitionResponse(result), jsonx.V1()) } func (h *Handler) handleDeleteModelBiasJobDefinition(ctx context.Context, body []byte) error { @@ -356,7 +359,7 @@ func (h *Handler) handleListModelBiasJobDefinitions(ctx context.Context, body [] items, next := h.Backend.ListModelBiasJobDefinitions(ctx, req.NextToken, req.Filter) - return json.Marshal(buildJobDefinitionListResponse(items, next)) + return json.Marshal(buildJobDefinitionListResponse(items, next), jsonx.V1()) } // --------------------------------------------------------------------------- @@ -376,7 +379,7 @@ func (h *Handler) handleCreateModelQualityJobDefinition(ctx context.Context, bod return nil, err } - return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}) + return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}, jsonx.V1()) } func (h *Handler) handleDescribeModelQualityJobDefinition(ctx context.Context, body []byte) ([]byte, error) { @@ -390,7 +393,7 @@ func (h *Handler) handleDescribeModelQualityJobDefinition(ctx context.Context, b return nil, err } - return json.Marshal(buildJobDefinitionResponse(result)) + return json.Marshal(buildJobDefinitionResponse(result), jsonx.V1()) } func (h *Handler) handleDeleteModelQualityJobDefinition(ctx context.Context, body []byte) error { @@ -410,7 +413,7 @@ func (h *Handler) handleListModelQualityJobDefinitions(ctx context.Context, body items, next := h.Backend.ListModelQualityJobDefinitions(ctx, req.NextToken, req.Filter) - return json.Marshal(buildJobDefinitionListResponse(items, next)) + return json.Marshal(buildJobDefinitionListResponse(items, next), jsonx.V1()) } // --------------------------------------------------------------------------- @@ -430,7 +433,7 @@ func (h *Handler) handleCreateModelExplainabilityJobDefinition(ctx context.Conte return nil, err } - return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}) + return json.Marshal(map[string]any{keyJobDefinitionArn: result.JobDefinitionArn}, jsonx.V1()) } func (h *Handler) handleDescribeModelExplainabilityJobDefinition(ctx context.Context, body []byte) ([]byte, error) { @@ -444,7 +447,7 @@ func (h *Handler) handleDescribeModelExplainabilityJobDefinition(ctx context.Con return nil, err } - return json.Marshal(buildJobDefinitionResponse(result)) + return json.Marshal(buildJobDefinitionResponse(result), jsonx.V1()) } func (h *Handler) handleDeleteModelExplainabilityJobDefinition(ctx context.Context, body []byte) error { @@ -464,5 +467,5 @@ func (h *Handler) handleListModelExplainabilityJobDefinitions(ctx context.Contex items, next := h.Backend.ListModelExplainabilityJobDefinitions(ctx, req.NextToken, req.Filter) - return json.Marshal(buildJobDefinitionListResponse(items, next)) + return json.Marshal(buildJobDefinitionListResponse(items, next), jsonx.V1()) } diff --git a/services/sagemaker/handler_monitoring_job_definitions_test.go b/services/sagemaker/handler_monitoring_job_definitions_test.go index dadb14c3d1..b1b559ed71 100644 --- a/services/sagemaker/handler_monitoring_job_definitions_test.go +++ b/services/sagemaker/handler_monitoring_job_definitions_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // minimalJobDefinitionFixture builds a structurally valid Create*JobDefinition @@ -41,7 +43,7 @@ func TestHandler_CreateDataQualityJobDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["JobDefinitionArn"], "dq-def-1") } @@ -59,7 +61,7 @@ func TestHandler_DescribeDataQualityJobDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "dq-def-2", resp["JobDefinitionName"]) assert.Equal(t, "arn:aws:iam::000000000000:role/monitor", resp["RoleArn"]) assert.NotNil(t, resp["DataQualityAppSpecification"]) @@ -99,7 +101,7 @@ func TestHandler_CreateDataQualityJobDefinition_DuplicateReturnsResourceInUse(t assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceInUse", resp["__type"]) } @@ -117,7 +119,7 @@ func TestHandler_CreateModelBiasJobDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["JobDefinitionArn"], "mb-def-1") } @@ -153,7 +155,7 @@ func TestHandler_CreateModelQualityJobDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["JobDefinitionArn"], "mq-def-1") } @@ -184,7 +186,7 @@ func TestHandler_CreateModelExplainabilityJobDefinition(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["JobDefinitionArn"], "me-def-1") } diff --git a/services/sagemaker/handler_monitoring_schedules.go b/services/sagemaker/handler_monitoring_schedules.go index 90b5f2f887..a15144978f 100644 --- a/services/sagemaker/handler_monitoring_schedules.go +++ b/services/sagemaker/handler_monitoring_schedules.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -22,7 +24,7 @@ type createMonitoringScheduleInput struct { func (h *Handler) handleCreateMonitoringSchedule(ctx context.Context, body []byte) ([]byte, error) { var req createMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -37,7 +39,7 @@ func (h *Handler) handleCreateMonitoringSchedule(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyMonitoringScheduleArn: result.MonitoringScheduleArn}) + return json.Marshal(map[string]any{keyMonitoringScheduleArn: result.MonitoringScheduleArn}, jsonx.V1()) } // describeMonitoringScheduleInput mirrors DescribeMonitoringScheduleInput @@ -49,7 +51,7 @@ type describeMonitoringScheduleInput struct { func (h *Handler) handleDescribeMonitoringSchedule(ctx context.Context, body []byte) ([]byte, error) { var req describeMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -62,7 +64,7 @@ func (h *Handler) handleDescribeMonitoringSchedule(ctx context.Context, body []b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteMonitoringScheduleInput mirrors DeleteMonitoringScheduleInput @@ -74,7 +76,7 @@ type deleteMonitoringScheduleInput struct { func (h *Handler) handleDeleteMonitoringSchedule(ctx context.Context, body []byte) error { var req deleteMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -94,7 +96,7 @@ type stopMonitoringScheduleInput struct { func (h *Handler) handleStopMonitoringSchedule(ctx context.Context, body []byte) error { var req stopMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -114,7 +116,7 @@ type startMonitoringScheduleInput struct { func (h *Handler) handleStartMonitoringSchedule(ctx context.Context, body []byte) error { var req startMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -135,7 +137,7 @@ type updateMonitoringScheduleInput struct { func (h *Handler) handleUpdateMonitoringSchedule(ctx context.Context, body []byte) ([]byte, error) { var req updateMonitoringScheduleInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -148,7 +150,7 @@ func (h *Handler) handleUpdateMonitoringSchedule(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]any{keyMonitoringScheduleArn: result.MonitoringScheduleArn}) + return json.Marshal(map[string]any{keyMonitoringScheduleArn: result.MonitoringScheduleArn}, jsonx.V1()) } // listMonitoringSchedulesInput mirrors ListMonitoringSchedulesInput @@ -175,7 +177,7 @@ type listMonitoringSchedulesInput struct { func (h *Handler) handleListMonitoringSchedules(ctx context.Context, body []byte) ([]byte, error) { var req listMonitoringSchedulesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -225,5 +227,5 @@ func (h *Handler) handleListMonitoringSchedules(ctx context.Context, body []byte return json.Marshal(map[string]any{ "MonitoringScheduleSummaries": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } diff --git a/services/sagemaker/handler_monitoring_schedules_test.go b/services/sagemaker/handler_monitoring_schedules_test.go index fac766d234..143be00437 100644 --- a/services/sagemaker/handler_monitoring_schedules_test.go +++ b/services/sagemaker/handler_monitoring_schedules_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateMonitoringSchedule(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_CreateMonitoringSchedule(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["MonitoringScheduleArn"], "my-schedule") } @@ -53,7 +55,7 @@ func TestHandler_DescribeMonitoringSchedule(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "sched-1", resp["MonitoringScheduleName"]) assert.Equal(t, "Scheduled", resp["MonitoringScheduleStatus"]) assert.Contains(t, resp, "MonitoringScheduleConfig") @@ -73,7 +75,7 @@ func TestHandler_StopMonitoringSchedule(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeMonitoringSchedule", map[string]any{"MonitoringScheduleName": "sched-stop"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Stopped", resp["MonitoringScheduleStatus"]) } @@ -97,7 +99,7 @@ func TestHandler_StartMonitoringSchedule(t *testing.T) { map[string]any{"MonitoringScheduleName": "sched-start"}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Scheduled", resp["MonitoringScheduleStatus"]) } @@ -135,7 +137,7 @@ func TestHandler_ListMonitoringSchedules(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["MonitoringScheduleSummaries"].([]any) assert.Len(t, items, 2) } diff --git a/services/sagemaker/handler_notebook_instances.go b/services/sagemaker/handler_notebook_instances.go index 61ae26c482..7c0a8ae54c 100644 --- a/services/sagemaker/handler_notebook_instances.go +++ b/services/sagemaker/handler_notebook_instances.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -28,7 +29,7 @@ func (h *Handler) handleCreateNotebookInstanceLifecycleConfig( body []byte, ) ([]byte, error) { var req createNotebookLifecycleConfigRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -62,7 +63,7 @@ func (h *Handler) handleCreateNotebookInstanceLifecycleConfig( log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created notebook lifecycle config", "name", lc.Name) - return json.Marshal(map[string]string{keyNotebookLifecycleConfigArn: lc.ARN}) + return json.Marshal(map[string]string{keyNotebookLifecycleConfigArn: lc.ARN}, jsonx.V1()) } // describeNotebookInstanceLifecycleConfigInput is @@ -77,7 +78,7 @@ func (h *Handler) handleDescribeNotebookInstanceLifecycleConfig( body []byte, ) ([]byte, error) { var req describeNotebookInstanceLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.NotebookInstanceLifecycleConfigName == "" { @@ -111,7 +112,7 @@ func (h *Handler) handleDescribeNotebookInstanceLifecycleConfig( "OnStart": onStart, keyCreationTime: epochSeconds(lc.CreationTime), keyLastModifiedTime: epochSeconds(lc.LastModifiedTime), - }) + }, jsonx.V1()) } // updateNotebookInstanceLifecycleConfigInput is @@ -128,7 +129,7 @@ func (h *Handler) handleUpdateNotebookInstanceLifecycleConfig( body []byte, ) ([]byte, error) { var req updateNotebookInstanceLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.NotebookInstanceLifecycleConfigName == "" { @@ -163,7 +164,7 @@ func (h *Handler) handleUpdateNotebookInstanceLifecycleConfig( return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // deleteNotebookInstanceLifecycleConfigInput is @@ -178,7 +179,7 @@ func (h *Handler) handleDeleteNotebookInstanceLifecycleConfig( body []byte, ) error { var req deleteNotebookInstanceLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.NotebookInstanceLifecycleConfigName == "" { @@ -227,7 +228,7 @@ type listNotebookInstanceLifecycleConfigsInput struct { func (h *Handler) handleListNotebookInstanceLifecycleConfigs(ctx context.Context, body []byte) ([]byte, error) { var req listNotebookInstanceLifecycleConfigsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -258,7 +259,7 @@ func (h *Handler) handleListNotebookInstanceLifecycleConfigs(ctx context.Context resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -302,7 +303,7 @@ func (h *Handler) handleCreateNotebookInstanceFull( body []byte, ) ([]byte, error) { var req createNotebookInstanceFullRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -352,7 +353,7 @@ func (h *Handler) handleCreateNotebookInstanceFull( log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created notebook instance (full)", "name", nb.NotebookInstanceName) - return json.Marshal(map[string]string{keyNotebookInstanceArn: nb.NotebookInstanceArn}) + return json.Marshal(map[string]string{keyNotebookInstanceArn: nb.NotebookInstanceArn}, jsonx.V1()) } // describeNotebookInstanceInput is DescribeNotebookInstance's request shape @@ -367,7 +368,7 @@ func (h *Handler) handleDescribeNotebookInstanceFull( body []byte, ) ([]byte, error) { var req describeNotebookInstanceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.NotebookInstanceName == "" { @@ -390,7 +391,7 @@ func (h *Handler) handleDescribeNotebookInstanceFull( } addNotebookOptionalFields(resp, nb) - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // addNotebookOptionalFields adds every describable optional field. @@ -477,7 +478,7 @@ type updateNotebookInstanceInput struct { // handleUpdateNotebookInstanceFull supports all mutable fields (#1 update coverage). func (h *Handler) handleUpdateNotebookInstanceFull(ctx context.Context, body []byte) error { var req updateNotebookInstanceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.NotebookInstanceName == "" { @@ -558,7 +559,7 @@ type listNotebookInstancesInput struct { func (h *Handler) handleListNotebookInstances(ctx context.Context, body []byte) ([]byte, error) { var req listNotebookInstancesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -599,7 +600,7 @@ func (h *Handler) handleListNotebookInstances(ctx context.Context, body []byte) resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteNotebookInstanceInput is DeleteNotebookInstance's request shape @@ -611,7 +612,7 @@ type deleteNotebookInstanceInput struct { func (h *Handler) handleDeleteNotebookInstance(ctx context.Context, body []byte) error { var req deleteNotebookInstanceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -638,7 +639,7 @@ type startNotebookInstanceInput struct { func (h *Handler) handleStartNotebookInstance(ctx context.Context, body []byte) error { var req startNotebookInstanceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -665,7 +666,7 @@ type stopNotebookInstanceInput struct { func (h *Handler) handleStopNotebookInstance(ctx context.Context, body []byte) error { var req stopNotebookInstanceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -698,7 +699,7 @@ func (h *Handler) handleCreatePresignedNotebookInstanceURL( ) ([]byte, error) { var req createPresignedNotebookInstanceURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -715,5 +716,5 @@ func (h *Handler) handleCreatePresignedNotebookInstanceURL( return nil, err } - return json.Marshal(map[string]string{"AuthorizedUrl": url}) + return json.Marshal(map[string]string{"AuthorizedUrl": url}, jsonx.V1()) } diff --git a/services/sagemaker/handler_notebook_instances_test.go b/services/sagemaker/handler_notebook_instances_test.go index 1ea6b4e57f..12bfaee9ac 100644 --- a/services/sagemaker/handler_notebook_instances_test.go +++ b/services/sagemaker/handler_notebook_instances_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_NotebookInstanceLifecycle(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_NotebookInstanceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["NotebookInstanceArn"]) // Describe. @@ -39,7 +41,7 @@ func TestHandler_NotebookInstanceLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["NotebookInstances"].([]any), 1) // Start. @@ -129,7 +131,7 @@ func TestHandler_NotebookInstance_EventuallyInService(t *testing.T) { assert.Equal(t, http.StatusOK, recDesc.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(recDesc.Body.Bytes(), &descOut, jsonx.V1())) assert.NotEmpty(t, descOut["NotebookInstanceStatus"]) }) } diff --git a/services/sagemaker/handler_optimization_jobs.go b/services/sagemaker/handler_optimization_jobs.go index 5be37defb9..3ace41657d 100644 --- a/services/sagemaker/handler_optimization_jobs.go +++ b/services/sagemaker/handler_optimization_jobs.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -15,10 +18,10 @@ import ( // OutputConfig/VpcConfig are decoded as json.RawMessage — see // OptimizationJob's doc comment in optimization_jobs.go for why. type createOptimizationJobInput struct { - ModelSource json.RawMessage `json:"ModelSource"` - OptimizationConfigs json.RawMessage `json:"OptimizationConfigs"` - OutputConfig json.RawMessage `json:"OutputConfig"` - VpcConfig json.RawMessage `json:"VpcConfig"` + ModelSource jsontext.Value `json:"ModelSource"` + OptimizationConfigs jsontext.Value `json:"OptimizationConfigs"` + OutputConfig jsontext.Value `json:"OutputConfig"` + VpcConfig jsontext.Value `json:"VpcConfig"` StoppingCondition *StoppingCondition `json:"StoppingCondition"` OptimizationEnvironment map[string]string `json:"OptimizationEnvironment"` OptimizationJobName string `json:"OptimizationJobName"` @@ -32,7 +35,7 @@ type createOptimizationJobInput struct { func (h *Handler) handleCreateOptimizationJob(ctx context.Context, body []byte) ([]byte, error) { var req createOptimizationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -58,7 +61,7 @@ func (h *Handler) handleCreateOptimizationJob(ctx context.Context, body []byte) return nil, err } - return json.Marshal(map[string]any{keyOptimizationJobArn: result.OptimizationJobArn}) + return json.Marshal(map[string]any{keyOptimizationJobArn: result.OptimizationJobArn}, jsonx.V1()) } // describeOptimizationJobInput mirrors DescribeOptimizationJobInput @@ -126,7 +129,7 @@ func optimizationJobResponseMap(j *OptimizationJob) map[string]any { func (h *Handler) handleDescribeOptimizationJob(ctx context.Context, body []byte) ([]byte, error) { var req describeOptimizationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -139,7 +142,7 @@ func (h *Handler) handleDescribeOptimizationJob(ctx context.Context, body []byte return nil, err } - return json.Marshal(optimizationJobResponseMap(result)) + return json.Marshal(optimizationJobResponseMap(result), jsonx.V1()) } // deleteOptimizationJobInput mirrors DeleteOptimizationJobInput @@ -151,7 +154,7 @@ type deleteOptimizationJobInput struct { func (h *Handler) handleDeleteOptimizationJob(ctx context.Context, body []byte) error { var req deleteOptimizationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -171,7 +174,7 @@ type stopOptimizationJobInput struct { func (h *Handler) handleStopOptimizationJob(ctx context.Context, body []byte) error { var req stopOptimizationJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -201,7 +204,7 @@ type listOptimizationJobsInput struct { func (h *Handler) handleListOptimizationJobs(ctx context.Context, body []byte) ([]byte, error) { var req listOptimizationJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_optimization_jobs_test.go b/services/sagemaker/handler_optimization_jobs_test.go index 4a15775518..13086f0c4e 100644 --- a/services/sagemaker/handler_optimization_jobs_test.go +++ b/services/sagemaker/handler_optimization_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validOptimizationJobBody returns a CreateOptimizationJob request carrying @@ -50,7 +52,7 @@ func TestHandler_CreateOptimizationJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["OptimizationJobArn"], "my-opt-job") } @@ -96,7 +98,7 @@ func TestHandler_DescribeOptimizationJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "opt-1", resp["OptimizationJobName"]) assert.Equal(t, "ml.g5.2xlarge", resp["DeploymentInstanceType"]) } @@ -177,14 +179,14 @@ func TestHandler_ListOptimizationJobs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["OptimizationJobSummaries"]) // Create and list doSageMakerRequest(t, h, "CreateOptimizationJob", validOptimizationJobBody("my-opt-job")) rec = doSageMakerRequest(t, h, "ListOptimizationJobs", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["OptimizationJobSummaries"].([]any) assert.Len(t, summaries, 1) s := summaries[0].(map[string]any) @@ -296,7 +298,7 @@ func TestHandler_AIWorkloadConfigLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["AIWorkloadConfigArn"], "cfg-1") rec = doSageMakerRequest(t, h, "DescribeAIWorkloadConfig", map[string]any{ @@ -305,7 +307,7 @@ func TestHandler_AIWorkloadConfigLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "cfg-1", descResp["AIWorkloadConfigName"]) assert.NotEmpty(t, descResp["CreationTime"]) }, @@ -336,7 +338,7 @@ func TestHandler_AIWorkloadConfigLifecycle(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ResourceNotFound", out["__type"]) }, }, @@ -371,7 +373,7 @@ func TestHandler_AIWorkloadConfigLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["AIWorkloadConfigs"].([]any) assert.Len(t, items, 1) assert.Equal(t, "cfg-list", items[0].(map[string]any)["AIWorkloadConfigName"]) diff --git a/services/sagemaker/handler_partner_apps.go b/services/sagemaker/handler_partner_apps.go index 50fcdc7823..c47f961a6c 100644 --- a/services/sagemaker/handler_partner_apps.go +++ b/services/sagemaker/handler_partner_apps.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -23,7 +26,7 @@ type createPartnerAppInput struct { Tier string `json:"Tier,omitempty"` KmsKeyID string `json:"KmsKeyId,omitempty"` Tags []tagObject `json:"Tags"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig,omitempty"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig,omitempty"` EnableAutoMinorVersionUpgrade bool `json:"EnableAutoMinorVersionUpgrade,omitempty"` EnableIamSessionBasedIdentity bool `json:"EnableIamSessionBasedIdentity,omitempty"` } @@ -31,7 +34,7 @@ type createPartnerAppInput struct { func (h *Handler) handleCreatePartnerApp(ctx context.Context, body []byte) ([]byte, error) { var req createPartnerAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -72,7 +75,7 @@ func (h *Handler) handleCreatePartnerApp(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]any{keyGenericArn: result.Arn}) + return json.Marshal(map[string]any{keyGenericArn: result.Arn}, jsonx.V1()) } // describePartnerAppResponse is the response body for DescribePartnerApp @@ -94,7 +97,7 @@ type describePartnerAppResponse struct { KmsKeyID string `json:"KmsKeyId,omitempty"` Status string `json:"Status"` Name string `json:"Name"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig,omitempty"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig,omitempty"` CreationTime float64 `json:"CreationTime"` LastModifiedTime float64 `json:"LastModifiedTime"` EnableAutoMinorVersionUpgrade bool `json:"EnableAutoMinorVersionUpgrade,omitempty"` @@ -114,7 +117,7 @@ type describePartnerAppInput struct { func (h *Handler) handleDescribePartnerApp(ctx context.Context, body []byte) ([]byte, error) { var req describePartnerAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -143,7 +146,7 @@ func (h *Handler) handleDescribePartnerApp(ctx context.Context, body []byte) ([] EnableIamSessionBasedIdentity: result.EnableIamSessionBasedIdentity, CreationTime: epochSeconds(result.CreationTime), LastModifiedTime: epochSeconds(result.LastModifiedTime), - }) + }, jsonx.V1()) } // deletePartnerAppInput mirrors DeletePartnerAppInput @@ -156,7 +159,7 @@ type deletePartnerAppInput struct { func (h *Handler) handleDeletePartnerApp(ctx context.Context, body []byte) ([]byte, error) { var req deletePartnerAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -168,7 +171,7 @@ func (h *Handler) handleDeletePartnerApp(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]string{keyGenericArn: req.Arn}) + return json.Marshal(map[string]string{keyGenericArn: req.Arn}, jsonx.V1()) } // updatePartnerAppInput mirrors UpdatePartnerAppInput @@ -184,13 +187,13 @@ type updatePartnerAppInput struct { AppVersion string `json:"AppVersion,omitempty"` Tier string `json:"Tier,omitempty"` Tags []tagObject `json:"Tags"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig,omitempty"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig,omitempty"` } func (h *Handler) handleUpdatePartnerApp(ctx context.Context, body []byte) ([]byte, error) { var req updatePartnerAppInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -211,7 +214,7 @@ func (h *Handler) handleUpdatePartnerApp(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]any{keyGenericArn: result.Arn}) + return json.Marshal(map[string]any{keyGenericArn: result.Arn}, jsonx.V1()) } // listPartnerAppsInput mirrors ListPartnerAppsInput @@ -224,7 +227,7 @@ type listPartnerAppsInput struct { func (h *Handler) handleListPartnerApps(ctx context.Context, body []byte) ([]byte, error) { var req listPartnerAppsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -257,7 +260,7 @@ type createPartnerAppPresignedURLInput struct { func (h *Handler) handleCreatePartnerAppPresignedURL(ctx context.Context, body []byte) ([]byte, error) { var req createPartnerAppPresignedURLInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -274,5 +277,5 @@ func (h *Handler) handleCreatePartnerAppPresignedURL(ctx context.Context, body [ return nil, err } - return json.Marshal(map[string]string{keyURL: url}) + return json.Marshal(map[string]string{keyURL: url}, jsonx.V1()) } diff --git a/services/sagemaker/handler_partner_apps_test.go b/services/sagemaker/handler_partner_apps_test.go index d041e8d295..d8383af0f8 100644 --- a/services/sagemaker/handler_partner_apps_test.go +++ b/services/sagemaker/handler_partner_apps_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreatePartnerApp(t *testing.T) { @@ -27,7 +29,7 @@ func TestHandler_CreatePartnerApp(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["Arn"], "my-app") } @@ -44,13 +46,13 @@ func TestHandler_DescribePartnerApp(t *testing.T) { "Tier": "small", }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doSageMakerRequest(t, h, "DescribePartnerApp", map[string]any{"Arn": createResp["Arn"]}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "app-1", resp["Name"]) } @@ -67,7 +69,7 @@ func TestHandler_DeletePartnerApp(t *testing.T) { "Tier": "small", }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doSageMakerRequest(t, h, "DeletePartnerApp", map[string]any{"Arn": createResp["Arn"]}) assert.Equal(t, http.StatusOK, rec.Code) @@ -95,14 +97,14 @@ func TestHandler_PartnerApp_ExtendedLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) appARN := createResp["Arn"] describeRec := doSageMakerRequest(t, h, "DescribePartnerApp", map[string]any{"Arn": appARN}) assert.Equal(t, http.StatusOK, describeRec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "papp-1", describeResp["Name"]) assert.Equal(t, "comet", describeResp["Type"]) assert.Equal(t, "arn:aws:iam::000000000000:role/partner", describeResp["ExecutionRoleArn"]) @@ -115,14 +117,14 @@ func TestHandler_PartnerApp_ExtendedLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, updateRec.Code) describeRec = doSageMakerRequest(t, h, "DescribePartnerApp", map[string]any{"Arn": appARN}) - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "large", describeResp["Tier"]) listRec := doSageMakerRequest(t, h, "ListPartnerApps", map[string]any{}) assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["Summaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -131,7 +133,7 @@ func TestHandler_PartnerApp_ExtendedLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, presignRec.Code) var presignResp map[string]string - require.NoError(t, json.Unmarshal(presignRec.Body.Bytes(), &presignResp)) + require.NoError(t, json.Unmarshal(presignRec.Body.Bytes(), &presignResp, jsonx.V1())) assert.Contains(t, presignResp["Url"], "papp-1") } @@ -176,7 +178,7 @@ func TestHandler_ListPartnerApps_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Summaries"]) } @@ -193,13 +195,13 @@ func TestHandler_DeletePartnerApp_ReturnsArn(t *testing.T) { "Tier": "small", }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doSageMakerRequest(t, h, "DeletePartnerApp", map[string]any{"Arn": createResp["Arn"]}) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, createResp["Arn"], resp["Arn"]) } diff --git a/services/sagemaker/handler_pipeline_versions.go b/services/sagemaker/handler_pipeline_versions.go index a19504f69d..d80a3413bb 100644 --- a/services/sagemaker/handler_pipeline_versions.go +++ b/services/sagemaker/handler_pipeline_versions.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -49,7 +51,7 @@ type listPipelineVersionsRequest struct { func (h *Handler) handleListPipelineVersions(ctx context.Context, body []byte) ([]byte, error) { var req listPipelineVersionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -103,7 +105,7 @@ type updatePipelineVersionRequest struct { func (h *Handler) handleUpdatePipelineVersion(ctx context.Context, body []byte) ([]byte, error) { var req updatePipelineVersionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -125,7 +127,7 @@ func (h *Handler) handleUpdatePipelineVersion(ctx context.Context, body []byte) return json.Marshal(map[string]any{ keyPipelineArn: v.PipelineArn, "PipelineVersionId": v.PipelineVersionID, - }) + }, jsonx.V1()) } type describePipelineDefinitionForExecutionRequest struct { @@ -135,7 +137,7 @@ type describePipelineDefinitionForExecutionRequest struct { func (h *Handler) handleDescribePipelineDefinitionForExecution(ctx context.Context, body []byte) ([]byte, error) { var req describePipelineDefinitionForExecutionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -151,7 +153,7 @@ func (h *Handler) handleDescribePipelineDefinitionForExecution(ctx context.Conte return json.Marshal(map[string]any{ keyPipelineDefinition: definition, keyCreationTime: epochSeconds(creationTime), - }) + }, jsonx.V1()) } type updatePipelineExecutionRequest struct { @@ -164,7 +166,7 @@ type updatePipelineExecutionRequest struct { func (h *Handler) handleUpdatePipelineExecution(ctx context.Context, body []byte) ([]byte, error) { var req updatePipelineExecutionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -183,5 +185,5 @@ func (h *Handler) handleUpdatePipelineExecution(ctx context.Context, body []byte return nil, err } - return json.Marshal(map[string]string{keyPipelineExecutionArn: pe.PipelineExecutionArn}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: pe.PipelineExecutionArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_pipeline_versions_test.go b/services/sagemaker/handler_pipeline_versions_test.go index e556fa2792..182a012376 100644 --- a/services/sagemaker/handler_pipeline_versions_test.go +++ b/services/sagemaker/handler_pipeline_versions_test.go @@ -1,13 +1,15 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) versions, _ := listOut["PipelineVersionSummaries"].([]any) require.Len(t, versions, 1) assert.InDelta(t, float64(1), versions[0].(map[string]any)["PipelineVersionId"], 0) @@ -36,7 +38,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recList2.Code) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2, jsonx.V1())) versions2, _ := listOut2["PipelineVersionSummaries"].([]any) require.Len(t, versions2, 2) // Newest first. @@ -49,7 +51,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recAsc.Code) var ascOut map[string]any - require.NoError(t, json.Unmarshal(recAsc.Body.Bytes(), &ascOut)) + require.NoError(t, json.Unmarshal(recAsc.Body.Bytes(), &ascOut, jsonx.V1())) ascVersions, _ := ascOut["PipelineVersionSummaries"].([]any) require.Len(t, ascVersions, 2) assert.InDelta(t, float64(1), ascVersions[0].(map[string]any)["PipelineVersionId"], 0) @@ -63,7 +65,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recFuture.Code) var futureOut map[string]any - require.NoError(t, json.Unmarshal(recFuture.Body.Bytes(), &futureOut)) + require.NoError(t, json.Unmarshal(recFuture.Body.Bytes(), &futureOut, jsonx.V1())) futureVersions, _ := futureOut["PipelineVersionSummaries"].([]any) assert.Empty(t, futureVersions, "CreatedAfter in the future must exclude every version") @@ -74,13 +76,13 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recPast.Code) var pastOut map[string]any - require.NoError(t, json.Unmarshal(recPast.Body.Bytes(), &pastOut)) + require.NoError(t, json.Unmarshal(recPast.Body.Bytes(), &pastOut, jsonx.V1())) pastVersions, _ := pastOut["PipelineVersionSummaries"].([]any) assert.Empty(t, pastVersions, "CreatedBefore in the past must exclude every version") recDescribe := doSageMakerRequest(t, h, "DescribePipeline", map[string]any{"PipelineName": "ver-pipeline"}) var pipelineOut map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &pipelineOut)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &pipelineOut, jsonx.V1())) pipelineArn, _ := pipelineOut["PipelineArn"].(string) require.NotEmpty(t, pipelineArn) @@ -93,7 +95,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recStart.Code) var startOut map[string]any - require.NoError(t, json.Unmarshal(recStart.Body.Bytes(), &startOut)) + require.NoError(t, json.Unmarshal(recStart.Body.Bytes(), &startOut, jsonx.V1())) execArn, _ := startOut["PipelineExecutionArn"].(string) require.NotEmpty(t, execArn) @@ -103,7 +105,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDefinition.Code) var defOut map[string]any - require.NoError(t, json.Unmarshal(recDefinition.Body.Bytes(), &defOut)) + require.NoError(t, json.Unmarshal(recDefinition.Body.Bytes(), &defOut, jsonx.V1())) assert.JSONEq(t, `{"Version":"v2"}`, defOut["PipelineDefinition"].(string)) recUpdateExec := doSageMakerRequest(t, h, "UpdatePipelineExecution", map[string]any{ @@ -112,7 +114,7 @@ func TestHandler_PipelineVersions_Lifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recUpdateExec.Code) var updateExecOut map[string]any - require.NoError(t, json.Unmarshal(recUpdateExec.Body.Bytes(), &updateExecOut)) + require.NoError(t, json.Unmarshal(recUpdateExec.Body.Bytes(), &updateExecOut, jsonx.V1())) assert.Equal(t, execArn, updateExecOut["PipelineExecutionArn"]) } diff --git a/services/sagemaker/handler_pipelines.go b/services/sagemaker/handler_pipelines.go index 183984216d..74ab158598 100644 --- a/services/sagemaker/handler_pipelines.go +++ b/services/sagemaker/handler_pipelines.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -36,7 +37,7 @@ type retryPipelineExecutionInput struct { func (h *Handler) handleRetryPipelineExecution(ctx context.Context, body []byte) ([]byte, error) { var req retryPipelineExecutionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.PipelineExecutionArn == "" { @@ -51,7 +52,7 @@ func (h *Handler) handleRetryPipelineExecution(ctx context.Context, body []byte) log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: retried pipeline execution", "arn", exec.PipelineExecutionArn) - return json.Marshal(map[string]string{keyPipelineExecutionArn: exec.PipelineExecutionArn}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: exec.PipelineExecutionArn}, jsonx.V1()) } // stopPipelineExecutionInput mirrors StopPipelineExecutionInput @@ -63,7 +64,7 @@ type stopPipelineExecutionInput struct { func (h *Handler) handleStopPipelineExecution(ctx context.Context, body []byte) ([]byte, error) { var req stopPipelineExecutionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.PipelineExecutionArn == "" { @@ -78,7 +79,7 @@ func (h *Handler) handleStopPipelineExecution(ctx context.Context, body []byte) log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: stopped pipeline execution", "arn", exec.PipelineExecutionArn) - return json.Marshal(map[string]string{keyPipelineExecutionArn: exec.PipelineExecutionArn}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: exec.PipelineExecutionArn}, jsonx.V1()) } // sendPipelineExecutionStepSuccessInput mirrors SendPipelineExecutionStepSuccessInput @@ -98,7 +99,7 @@ func (h *Handler) handleSendPipelineExecutionStepSuccess( body []byte, ) ([]byte, error) { var req sendPipelineExecutionStepSuccessInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.CallbackToken == "" { @@ -116,7 +117,7 @@ func (h *Handler) handleSendPipelineExecutionStepSuccess( // backend's execution-ARN convention, so it doubles as the real // response's PipelineExecutionArn (SendPipelineExecutionStepSuccessOutput, // api_op_SendPipelineExecutionStepSuccess.go:48-50). - return json.Marshal(map[string]string{keyPipelineExecutionArn: req.CallbackToken}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: req.CallbackToken}, jsonx.V1()) } // sendPipelineExecutionStepFailureInput mirrors SendPipelineExecutionStepFailureInput @@ -134,7 +135,7 @@ func (h *Handler) handleSendPipelineExecutionStepFailure( body []byte, ) ([]byte, error) { var req sendPipelineExecutionStepFailureInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.CallbackToken == "" { @@ -148,7 +149,7 @@ func (h *Handler) handleSendPipelineExecutionStepFailure( log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: sent pipeline step failure", "token", req.CallbackToken) - return json.Marshal(map[string]string{keyPipelineExecutionArn: req.CallbackToken}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: req.CallbackToken}, jsonx.V1()) } // pipelineStepCallbackMetadata mirrors types.CallbackStepMetadata @@ -191,7 +192,7 @@ type listPipelineExecutionStepsInput struct { func (h *Handler) handleListPipelineExecutionSteps(ctx context.Context, body []byte) ([]byte, error) { var req listPipelineExecutionStepsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.PipelineExecutionArn == "" { @@ -234,7 +235,7 @@ func (h *Handler) handleListPipelineExecutionSteps(ctx context.Context, body []b resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -258,7 +259,7 @@ type createPipelineInput struct { func (h *Handler) handleCreatePipelineFull(ctx context.Context, body []byte) ([]byte, error) { var req createPipelineInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -293,7 +294,7 @@ func (h *Handler) handleCreatePipelineFull(ctx context.Context, body []byte) ([] logger.Load(ctx).InfoContext(ctx, "sagemaker: created pipeline (full)", "name", p.PipelineName) - return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}) + return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}, jsonx.V1()) } // updatePipelineInput mirrors UpdatePipelineInput (api_op_UpdatePipeline.go: @@ -311,7 +312,7 @@ type updatePipelineInput struct { func (h *Handler) handleUpdatePipelineFull(ctx context.Context, body []byte) ([]byte, error) { var req updatePipelineInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -345,7 +346,7 @@ func (h *Handler) handleUpdatePipelineFull(ctx context.Context, body []byte) ([] logger.Load(ctx).InfoContext(ctx, "sagemaker: updated pipeline (full)", "name", p.PipelineName) - return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}) + return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}, jsonx.V1()) } // startPipelineExecutionInput mirrors StartPipelineExecutionInput @@ -371,7 +372,7 @@ func (h *Handler) handleStartPipelineExecutionFull( ) ([]byte, error) { var req startPipelineExecutionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -391,7 +392,7 @@ func (h *Handler) handleStartPipelineExecutionFull( pe.PipelineExecutionArn, ) - return json.Marshal(map[string]string{keyPipelineExecutionArn: pe.PipelineExecutionArn}) + return json.Marshal(map[string]string{keyPipelineExecutionArn: pe.PipelineExecutionArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -413,7 +414,7 @@ func (h *Handler) handleListPipelineParametersForExecution( ) ([]byte, error) { var req listPipelineParametersForExecutionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -438,7 +439,7 @@ func (h *Handler) handleListPipelineParametersForExecution( resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -455,7 +456,7 @@ type describePipelineInput struct { func (h *Handler) handleDescribePipeline(ctx context.Context, body []byte) ([]byte, error) { var req describePipelineInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -506,7 +507,7 @@ func (h *Handler) handleDescribePipeline(ctx context.Context, body []byte) ([]by } } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // pipelineSummary mirrors PipelineSummary (types.go:17593-17620, @@ -539,7 +540,7 @@ type listPipelinesInput struct { func (h *Handler) handleListPipelines(ctx context.Context, body []byte) ([]byte, error) { var req listPipelinesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -577,7 +578,7 @@ func (h *Handler) handleListPipelines(ctx context.Context, body []byte) ([]byte, resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deletePipelineInput mirrors DeletePipelineInput (api_op_DeletePipeline.go: @@ -590,7 +591,7 @@ type deletePipelineInput struct { func (h *Handler) handleDeletePipeline(ctx context.Context, body []byte) ([]byte, error) { var req deletePipelineInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -605,7 +606,7 @@ func (h *Handler) handleDeletePipeline(ctx context.Context, body []byte) ([]byte logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted pipeline", "name", req.PipelineName) - return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}) + return json.Marshal(map[string]string{keyPipelineArn: p.PipelineArn}, jsonx.V1()) } // describePipelineExecutionInput mirrors DescribePipelineExecutionInput @@ -617,7 +618,7 @@ type describePipelineExecutionInput struct { func (h *Handler) handleDescribePipelineExecution(ctx context.Context, body []byte) ([]byte, error) { var req describePipelineExecutionInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -661,7 +662,7 @@ func (h *Handler) handleDescribePipelineExecution(ctx context.Context, body []by resp["MLflowConfig"] = map[string]string{"MlflowExperimentName": pe.MlflowExperimentName} } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type pipelineExecutionSummary struct { @@ -688,7 +689,7 @@ type listPipelineExecutionsInput struct { func (h *Handler) handleListPipelineExecutions(ctx context.Context, body []byte) ([]byte, error) { var req listPipelineExecutionsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -723,5 +724,5 @@ func (h *Handler) handleListPipelineExecutions(ctx context.Context, body []byte) resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_pipelines_test.go b/services/sagemaker/handler_pipelines_test.go index cd351a471e..c6b666db63 100644 --- a/services/sagemaker/handler_pipelines_test.go +++ b/services/sagemaker/handler_pipelines_test.go @@ -3,7 +3,7 @@ package sagemaker_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -68,7 +69,7 @@ func TestHandler_CreatePipeline_FullFields(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "My Pipeline", descResp["PipelineDisplayName"]) assert.Equal(t, "A test pipeline", descResp["PipelineDescription"]) assert.NotNil(t, descResp["ParallelismConfiguration"]) @@ -95,7 +96,7 @@ func TestHandler_UpdatePipeline_FullFields(t *testing.T) { "PipelineName": "my-pipeline", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Updated Display", descResp["PipelineDisplayName"]) assert.Equal(t, "Updated desc", descResp["PipelineDescription"]) } @@ -128,7 +129,7 @@ func TestHandler_CreatePipeline_S3Location_UnreadableRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) rec = doSageMakerRequest(t, h, "DescribePipeline", map[string]any{"PipelineName": "s3-def-pipeline"}) @@ -155,7 +156,7 @@ func TestHandler_UpdatePipeline_S3Location_UnreadableRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } @@ -270,7 +271,7 @@ func TestHandler_StartPipelineExecution_WithParams(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["PipelineExecutionArn"] assert.NotEmpty(t, execArn) @@ -281,7 +282,7 @@ func TestHandler_StartPipelineExecution_WithParams(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Run 1", descResp["PipelineExecutionDisplayName"]) assert.Equal(t, "First run", descResp["PipelineExecutionDescription"]) params := descResp["PipelineParameters"].([]any) @@ -314,7 +315,7 @@ func TestHandler_StartPipelineExecution_ParallelismAndSelectiveExecution(t *test require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["PipelineExecutionArn"] require.NotEmpty(t, execArn) @@ -324,7 +325,7 @@ func TestHandler_StartPipelineExecution_ParallelismAndSelectiveExecution(t *test require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) parallelism, ok := descResp["ParallelismConfiguration"].(map[string]any) require.True(t, ok, "ParallelismConfiguration must be present in DescribePipelineExecution") @@ -367,7 +368,7 @@ func TestHandler_ListPipelineParametersForExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execArn := startResp["PipelineExecutionArn"] require.NotEmpty(t, execArn) @@ -383,7 +384,7 @@ func TestHandler_ListPipelineParametersForExecution(t *testing.T) { Value string `json:"Value"` } `json:"PipelineParameters"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.PipelineParameters, 2) names := make(map[string]string) @@ -409,7 +410,7 @@ func TestHandler_ListPipelineParametersForExecution_Empty(t *testing.T) { "PipelineName": "empty-param-pipe", }) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) rec2 := doSageMakerRequest(t, h, "ListPipelineParametersForExecution", map[string]any{ "PipelineExecutionArn": startResp["PipelineExecutionArn"], @@ -419,7 +420,7 @@ func TestHandler_ListPipelineParametersForExecution_Empty(t *testing.T) { var listResp struct { PipelineParameters []any `json:"PipelineParameters"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listResp, jsonx.V1())) assert.NotNil(t, listResp.PipelineParameters) assert.Empty(t, listResp.PipelineParameters) } @@ -449,7 +450,7 @@ func TestHandler_PipelineLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["PipelineArn"]) // Describe pipeline. @@ -466,7 +467,7 @@ func TestHandler_PipelineLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["PipelineSummaries"].([]any), 1) // Update pipeline. @@ -483,7 +484,7 @@ func TestHandler_PipelineLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recExec.Code) var execOut map[string]any - require.NoError(t, json.Unmarshal(recExec.Body.Bytes(), &execOut)) + require.NoError(t, json.Unmarshal(recExec.Body.Bytes(), &execOut, jsonx.V1())) execArn := execOut["PipelineExecutionArn"].(string) assert.NotEmpty(t, execArn) @@ -553,7 +554,7 @@ func TestHandler_DeletePipeline_RunningExecution(t *testing.T) { require.Equal(t, http.StatusBadRequest, recDelete.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(recDelete.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) // Once the execution reaches Succeeded, the delete is allowed. @@ -640,7 +641,7 @@ func TestHandler_PipelineExecutionSteps(t *testing.T) { require.Equal(t, http.StatusOK, recExec.Code) var execOut map[string]any - require.NoError(t, json.Unmarshal(recExec.Body.Bytes(), &execOut)) + require.NoError(t, json.Unmarshal(recExec.Body.Bytes(), &execOut, jsonx.V1())) execArn := execOut["PipelineExecutionArn"].(string) // ListPipelineExecutionSteps. diff --git a/services/sagemaker/handler_pipelines_versionid_test.go b/services/sagemaker/handler_pipelines_versionid_test.go index 99c8837356..3816453bf2 100644 --- a/services/sagemaker/handler_pipelines_versionid_test.go +++ b/services/sagemaker/handler_pipelines_versionid_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DescribePipeline_PipelineVersionId(t *testing.T) { @@ -59,7 +61,7 @@ func TestHandler_DescribePipeline_PipelineVersionId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantDef, out["PipelineDefinition"]) }) } @@ -81,7 +83,7 @@ func TestHandler_DescribePipeline_LastRunTime(t *testing.T) { require.Equal(t, http.StatusOK, beforeRec.Code, beforeRec.Body.String()) var beforeOut map[string]any - require.NoError(t, json.Unmarshal(beforeRec.Body.Bytes(), &beforeOut)) + require.NoError(t, json.Unmarshal(beforeRec.Body.Bytes(), &beforeOut, jsonx.V1())) _, hasLastRunTime := beforeOut["LastRunTime"] assert.False(t, hasLastRunTime, "a pipeline that has never run must not emit LastRunTime") @@ -96,7 +98,7 @@ func TestHandler_DescribePipeline_LastRunTime(t *testing.T) { require.Equal(t, http.StatusOK, afterRec.Code, afterRec.Body.String()) var afterOut map[string]any - require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut)) + require.NoError(t, json.Unmarshal(afterRec.Body.Bytes(), &afterOut, jsonx.V1())) lastRunTime, ok := afterOut["LastRunTime"].(float64) require.True(t, ok, "a pipeline that has run must emit LastRunTime") diff --git a/services/sagemaker/handler_presigned_session.go b/services/sagemaker/handler_presigned_session.go index 4b06eb5631..f64a4781df 100644 --- a/services/sagemaker/handler_presigned_session.go +++ b/services/sagemaker/handler_presigned_session.go @@ -2,11 +2,13 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -108,7 +110,7 @@ type createPresignedDomainURLRequest struct { func (h *Handler) handleCreatePresignedDomainURL(ctx context.Context, body []byte) ([]byte, error) { var req createPresignedDomainURLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -133,7 +135,7 @@ func (h *Handler) handleCreatePresignedDomainURL(ctx context.Context, body []byt return nil, err } - return json.Marshal(map[string]string{keyAuthorizedURL: url}) + return json.Marshal(map[string]string{keyAuthorizedURL: url}, jsonx.V1()) } // templateVarPattern matches simple Liquid-style "{{ task.input. }}" @@ -162,7 +164,7 @@ type renderUITemplateRequest struct { func (h *Handler) handleRenderUITemplate(ctx context.Context, body []byte) ([]byte, error) { var req renderUITemplateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -195,7 +197,7 @@ func (h *Handler) handleRenderUITemplate(ctx context.Context, body []byte) ([]by return json.Marshal(map[string]any{ "RenderedContent": rendered, "Errors": []any{}, - }) + }, jsonx.V1()) } // renderUITemplateContent substitutes "{{ task.input. }}" variables in @@ -206,7 +208,7 @@ func renderUITemplateContent(content, taskInput string) string { } var fields map[string]any - if err := json.Unmarshal([]byte(taskInput), &fields); err != nil { + if err := json.Unmarshal([]byte(taskInput), &fields, jsonx.V1()); err != nil { return content } @@ -233,7 +235,7 @@ type startSessionRequest struct { func (h *Handler) handleStartSession(ctx context.Context, body []byte) ([]byte, error) { var req startSessionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -250,5 +252,5 @@ func (h *Handler) handleStartSession(ctx context.Context, body []byte) ([]byte, "SessionId": result.SessionID, "StreamUrl": result.StreamURL, "TokenValue": result.TokenValue, - }) + }, jsonx.V1()) } diff --git a/services/sagemaker/handler_presigned_session_test.go b/services/sagemaker/handler_presigned_session_test.go index aacc381360..8a58cdfa28 100644 --- a/services/sagemaker/handler_presigned_session_test.go +++ b/services/sagemaker/handler_presigned_session_test.go @@ -1,13 +1,15 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_RenderUiTemplate(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_RenderUiTemplate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "

hello world

", out["RenderedContent"]) assert.Empty(t, out["Errors"]) } @@ -70,7 +72,7 @@ func TestHandler_StartSession(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["SessionId"]) assert.NotEmpty(t, out["StreamUrl"]) assert.NotEmpty(t, out["TokenValue"]) @@ -95,7 +97,7 @@ func TestHandler_CreatePresignedDomainUrl(t *testing.T) { "DefaultUserSettings": map[string]any{}, }) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID, _ := domainOut["DomainId"].(string) require.NotEmpty(t, domainID) @@ -109,7 +111,7 @@ func TestHandler_CreatePresignedDomainUrl(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["AuthorizedUrl"]) } diff --git a/services/sagemaker/handler_processing_jobs.go b/services/sagemaker/handler_processing_jobs.go index 4cbc569ae0..63fd7023e1 100644 --- a/services/sagemaker/handler_processing_jobs.go +++ b/services/sagemaker/handler_processing_jobs.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -248,7 +249,7 @@ type createProcessingJobRequest struct { func (h *Handler) handleCreateProcessingJob(ctx context.Context, body []byte) ([]byte, error) { var req createProcessingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.ProcessingJobName == "" { @@ -312,7 +313,7 @@ func (h *Handler) handleCreateProcessingJob(ctx context.Context, body []byte) ([ log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created processing job", "name", pj.ProcessingJobName) - return json.Marshal(map[string]string{keyProcessingJobArn: pj.ProcessingJobArn}) + return json.Marshal(map[string]string{keyProcessingJobArn: pj.ProcessingJobArn}, jsonx.V1()) } type describeProcessingJobRequest struct { @@ -321,7 +322,7 @@ type describeProcessingJobRequest struct { func (h *Handler) handleDescribeProcessingJob(ctx context.Context, body []byte) ([]byte, error) { var req describeProcessingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.ProcessingJobName == "" { @@ -367,7 +368,7 @@ func (h *Handler) handleDescribeProcessingJob(ctx context.Context, body []byte) resp["Environment"] = pj.Environment } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type stopProcessingJobRequest struct { @@ -376,7 +377,7 @@ type stopProcessingJobRequest struct { func (h *Handler) handleStopProcessingJob(ctx context.Context, body []byte) error { var req stopProcessingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.ProcessingJobName == "" { @@ -399,7 +400,7 @@ type deleteProcessingJobRequest struct { func (h *Handler) handleDeleteProcessingJob(ctx context.Context, body []byte) error { var req deleteProcessingJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.ProcessingJobName == "" { @@ -440,7 +441,7 @@ type listProcessingJobsRequest struct { func (h *Handler) handleListProcessingJobs(ctx context.Context, body []byte) ([]byte, error) { var req listProcessingJobsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -477,5 +478,5 @@ func (h *Handler) handleListProcessingJobs(ctx context.Context, body []byte) ([] resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_processing_jobs_test.go b/services/sagemaker/handler_processing_jobs_test.go index 65da75075a..06a085de42 100644 --- a/services/sagemaker/handler_processing_jobs_test.go +++ b/services/sagemaker/handler_processing_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -12,6 +12,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DeleteProcessingJob(t *testing.T) { @@ -233,7 +235,7 @@ func TestHandler_ListProcessingJobs_FilterSort(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["ProcessingJobSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "pj-beta", summaries[0].(map[string]any)["ProcessingJobName"]) @@ -243,7 +245,7 @@ func TestHandler_ListProcessingJobs_FilterSort(t *testing.T) { "SortOrder": "Descending", }) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries = resp["ProcessingJobSummaries"].([]any) require.Len(t, summaries, 2) assert.Equal(t, "pj-beta", summaries[0].(map[string]any)["ProcessingJobName"]) diff --git a/services/sagemaker/handler_projects.go b/services/sagemaker/handler_projects.go index 7bf40101d9..0b6e777c06 100644 --- a/services/sagemaker/handler_projects.go +++ b/services/sagemaker/handler_projects.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -12,17 +15,17 @@ import ( // createProjectInput mirrors CreateProjectInput (api_op_CreateProject.go:30-59). type createProjectInput struct { - ServiceCatalogProvisioningDetails json.RawMessage `json:"ServiceCatalogProvisioningDetails"` - TemplateProviders json.RawMessage `json:"TemplateProviders"` - ProjectName string `json:"ProjectName"` - ProjectDescription string `json:"ProjectDescription"` - Tags []tagObject `json:"Tags"` + ServiceCatalogProvisioningDetails jsontext.Value `json:"ServiceCatalogProvisioningDetails"` + TemplateProviders jsontext.Value `json:"TemplateProviders"` + ProjectName string `json:"ProjectName"` + ProjectDescription string `json:"ProjectDescription"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateProject(ctx context.Context, body []byte) ([]byte, error) { var req createProjectInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -44,7 +47,7 @@ func (h *Handler) handleCreateProject(ctx context.Context, body []byte) ([]byte, return json.Marshal(map[string]any{ keyProjectArn: result.ProjectArn, keyProjectID: result.ProjectID, - }) + }, jsonx.V1()) } // describeProjectInput mirrors DescribeProjectInput (api_op_DescribeProject.go:29-34). @@ -100,7 +103,7 @@ func projectResponseMap(p *Project) map[string]any { func (h *Handler) handleDescribeProject(ctx context.Context, body []byte) ([]byte, error) { var req describeProjectInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -113,7 +116,7 @@ func (h *Handler) handleDescribeProject(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(projectResponseMap(result)) + return json.Marshal(projectResponseMap(result), jsonx.V1()) } // deleteProjectInput mirrors DeleteProjectInput (api_op_DeleteProject.go:27-32). @@ -124,7 +127,7 @@ type deleteProjectInput struct { func (h *Handler) handleDeleteProject(ctx context.Context, body []byte) error { var req deleteProjectInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -149,7 +152,7 @@ type listProjectsInput struct { func (h *Handler) handleListProjects(ctx context.Context, body []byte) ([]byte, error) { var req listProjectsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -183,7 +186,7 @@ func (h *Handler) handleListProjects(ctx context.Context, body []byte) ([]byte, resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -202,7 +205,7 @@ type updateProjectInput struct { func (h *Handler) handleUpdateProject(ctx context.Context, body []byte) ([]byte, error) { var req updateProjectInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -228,5 +231,5 @@ func (h *Handler) handleUpdateProject(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyProjectArn: p.ProjectArn}) + return json.Marshal(map[string]string{keyProjectArn: p.ProjectArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_projects_test.go b/services/sagemaker/handler_projects_test.go index 17fb110c3c..734d171a12 100644 --- a/services/sagemaker/handler_projects_test.go +++ b/services/sagemaker/handler_projects_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateProject(t *testing.T) { @@ -23,7 +25,7 @@ func TestHandler_CreateProject(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ProjectArn"], "my-project") assert.NotEmpty(t, resp["ProjectId"]) } @@ -39,7 +41,7 @@ func TestHandler_DescribeProject(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "proj-1", resp["ProjectName"]) assert.Equal(t, "CreateCompleted", resp["ProjectStatus"]) } @@ -69,7 +71,7 @@ func TestHandler_ListProjects(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ProjectSummaryList"].([]any) assert.Len(t, items, 2) } @@ -104,7 +106,7 @@ func TestHandler_ListProjects_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ProjectSummaryList"].([]any) require.Len(t, items, len(tc.wantNames)) @@ -135,7 +137,7 @@ func TestHandler_DescribeProject_NoTagsLeak(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "Tags") assert.NotZero(t, resp["LastModifiedTime"]) } @@ -185,7 +187,7 @@ func TestHandler_UpdateProject(t *testing.T) { require.Equal(t, http.StatusOK, recDescribe.Code) var out map[string]any - require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(recDescribe.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "updated description", out["ProjectDescription"]) } diff --git a/services/sagemaker/handler_servicecatalog.go b/services/sagemaker/handler_servicecatalog.go index c72befbb23..ecbae1207a 100644 --- a/services/sagemaker/handler_servicecatalog.go +++ b/services/sagemaker/handler_servicecatalog.go @@ -2,7 +2,9 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -35,11 +37,11 @@ func (h *Handler) dispatchServicecatalogOps( return []byte("{}"), true, nil case "GetSagemakerServicecatalogPortfolioStatus": status := h.Backend.GetSagemakerServicecatalogPortfolioStatus(ctx) - r, err := json.Marshal(map[string]string{keyStatus: status}) + r, err := json.Marshal(map[string]string{keyStatus: status}, jsonx.V1()) return r, true, err case "ListResourceCatalogs": - r, err := json.Marshal(map[string]any{"ResourceCatalogs": h.Backend.ListResourceCatalogs()}) + r, err := json.Marshal(map[string]any{"ResourceCatalogs": h.Backend.ListResourceCatalogs()}, jsonx.V1()) return r, true, err } diff --git a/services/sagemaker/handler_servicecatalog_test.go b/services/sagemaker/handler_servicecatalog_test.go index b8c341f10b..bacff5951f 100644 --- a/services/sagemaker/handler_servicecatalog_test.go +++ b/services/sagemaker/handler_servicecatalog_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ServicecatalogPortfolio_Toggle(t *testing.T) { @@ -19,7 +21,7 @@ func TestHandler_ServicecatalogPortfolio_Toggle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, want, out["Status"]) } @@ -47,7 +49,7 @@ func TestHandler_ListResourceCatalogs_Empty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["ResourceCatalogs"]) } diff --git a/services/sagemaker/handler_spaces.go b/services/sagemaker/handler_spaces.go index 2982a9e202..9bdc4f1d00 100644 --- a/services/sagemaker/handler_spaces.go +++ b/services/sagemaker/handler_spaces.go @@ -2,8 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -16,19 +19,19 @@ import ( // carried as opaque json.RawMessage passthrough per this file's established // convention. type createSpaceInput struct { - DomainID string `json:"DomainId"` - SpaceName string `json:"SpaceName"` - SpaceDisplayName string `json:"SpaceDisplayName"` - OwnershipSettings json.RawMessage `json:"OwnershipSettings"` - SpaceSettings json.RawMessage `json:"SpaceSettings"` - SpaceSharingSettings json.RawMessage `json:"SpaceSharingSettings"` - Tags []tagObject `json:"Tags"` + DomainID string `json:"DomainId"` + SpaceName string `json:"SpaceName"` + SpaceDisplayName string `json:"SpaceDisplayName"` + OwnershipSettings jsontext.Value `json:"OwnershipSettings"` + SpaceSettings jsontext.Value `json:"SpaceSettings"` + SpaceSharingSettings jsontext.Value `json:"SpaceSharingSettings"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateSpace(ctx context.Context, body []byte) ([]byte, error) { var req createSpaceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -54,7 +57,7 @@ func (h *Handler) handleCreateSpace(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]any{"SpaceArn": result.SpaceArn}) + return json.Marshal(map[string]any{"SpaceArn": result.SpaceArn}, jsonx.V1()) } type describeSpaceInput struct { @@ -65,7 +68,7 @@ type describeSpaceInput struct { func (h *Handler) handleDescribeSpace(ctx context.Context, body []byte) ([]byte, error) { var req describeSpaceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -82,7 +85,7 @@ func (h *Handler) handleDescribeSpace(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } type deleteSpaceInput struct { @@ -93,7 +96,7 @@ type deleteSpaceInput struct { func (h *Handler) handleDeleteSpace(ctx context.Context, body []byte) error { var req deleteSpaceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -120,7 +123,7 @@ type listSpacesInput struct { func (h *Handler) handleListSpaces(ctx context.Context, body []byte) ([]byte, error) { var req listSpacesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -147,21 +150,21 @@ func (h *Handler) handleListSpaces(ctx context.Context, body []byte) ([]byte, er return json.Marshal(map[string]any{ "Spaces": summaries, keyNextToken: next, - }) + }, jsonx.V1()) } // updateSpaceInput mirrors UpdateSpaceInput (api_op_UpdateSpace.go:27-42). type updateSpaceInput struct { - DomainID string `json:"DomainId"` - SpaceName string `json:"SpaceName"` - SpaceDisplayName string `json:"SpaceDisplayName"` - SpaceSettings json.RawMessage `json:"SpaceSettings"` + DomainID string `json:"DomainId"` + SpaceName string `json:"SpaceName"` + SpaceDisplayName string `json:"SpaceDisplayName"` + SpaceSettings jsontext.Value `json:"SpaceSettings"` } func (h *Handler) handleUpdateSpace(ctx context.Context, body []byte) ([]byte, error) { var req updateSpaceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -185,5 +188,5 @@ func (h *Handler) handleUpdateSpace(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]string{keySpaceArn: s.SpaceArn}) + return json.Marshal(map[string]string{keySpaceArn: s.SpaceArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_spaces_test.go b/services/sagemaker/handler_spaces_test.go index 579dcc1a2e..be593eb580 100644 --- a/services/sagemaker/handler_spaces_test.go +++ b/services/sagemaker/handler_spaces_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateSpace(t *testing.T) { @@ -21,7 +23,7 @@ func TestHandler_CreateSpace(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["SpaceArn"], "my-space") } @@ -36,7 +38,7 @@ func TestHandler_DescribeSpace(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "space-1", resp["SpaceName"]) assert.Equal(t, "InService", resp["Status"]) } @@ -67,7 +69,7 @@ func TestHandler_ListSpaces(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Spaces"].([]any) assert.Len(t, items, 2) } @@ -91,7 +93,7 @@ func TestHandler_ListSpaces_NameContainsAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Spaces"].([]any), 2) }) @@ -105,7 +107,7 @@ func TestHandler_ListSpaces_NameContainsAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Spaces"].([]any), 1, "MaxResults must cap the page, not just be parsed and ignored") assert.NotEmpty(t, resp["NextToken"]) }) @@ -127,7 +129,7 @@ func TestHandler_CreateSpace_DisplayNameRoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "My Display Name", resp["SpaceDisplayName"]) } @@ -148,7 +150,7 @@ func TestHandler_UpdateSpace(t *testing.T) { var domainResp map[string]any rec := doSageMakerRequest(t, h, "ListDomains", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &domainResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &domainResp, jsonx.V1())) domains := domainResp["Domains"].([]any) require.Len(t, domains, 1) domainID := domains[0].(map[string]any)["DomainId"].(string) @@ -165,7 +167,7 @@ func TestHandler_UpdateSpace(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["SpaceArn"]) } diff --git a/services/sagemaker/handler_studio_lifecycle_configs.go b/services/sagemaker/handler_studio_lifecycle_configs.go index dba90be8fe..67040238b1 100644 --- a/services/sagemaker/handler_studio_lifecycle_configs.go +++ b/services/sagemaker/handler_studio_lifecycle_configs.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -22,7 +24,7 @@ type createStudioLifecycleConfigInput struct { func (h *Handler) handleCreateStudioLifecycleConfig(ctx context.Context, body []byte) ([]byte, error) { var req createStudioLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -42,7 +44,7 @@ func (h *Handler) handleCreateStudioLifecycleConfig(ctx context.Context, body [] return nil, err } - return json.Marshal(map[string]any{keyStudioLifecycleConfigArn: result.StudioLifecycleConfigArn}) + return json.Marshal(map[string]any{keyStudioLifecycleConfigArn: result.StudioLifecycleConfigArn}, jsonx.V1()) } // describeStudioLifecycleConfigInput mirrors DescribeStudioLifecycleConfigInput @@ -54,7 +56,7 @@ type describeStudioLifecycleConfigInput struct { func (h *Handler) handleDescribeStudioLifecycleConfig(ctx context.Context, body []byte) ([]byte, error) { var req describeStudioLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -67,7 +69,7 @@ func (h *Handler) handleDescribeStudioLifecycleConfig(ctx context.Context, body return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // deleteStudioLifecycleConfigInput mirrors DeleteStudioLifecycleConfigInput @@ -79,7 +81,7 @@ type deleteStudioLifecycleConfigInput struct { func (h *Handler) handleDeleteStudioLifecycleConfig(ctx context.Context, body []byte) error { var req deleteStudioLifecycleConfigInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -110,7 +112,7 @@ type listStudioLifecycleConfigsInput struct { func (h *Handler) handleListStudioLifecycleConfigs(ctx context.Context, body []byte) ([]byte, error) { var req listStudioLifecycleConfigsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_studio_lifecycle_configs_test.go b/services/sagemaker/handler_studio_lifecycle_configs_test.go index e24af8cc23..2c512db2f2 100644 --- a/services/sagemaker/handler_studio_lifecycle_configs_test.go +++ b/services/sagemaker/handler_studio_lifecycle_configs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateStudioLifecycleConfig(t *testing.T) { @@ -25,7 +27,7 @@ func TestHandler_CreateStudioLifecycleConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["StudioLifecycleConfigArn"], "my-lc") } @@ -45,7 +47,7 @@ func TestHandler_DescribeStudioLifecycleConfig(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "lc-1", resp["StudioLifecycleConfigName"]) } @@ -67,7 +69,7 @@ func TestHandler_CreateStudioLifecycleConfig_Content(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "IyEvYmluL2Jhc2g=", resp["StudioLifecycleConfigContent"]) } @@ -134,7 +136,7 @@ func TestHandler_ListStudioLifecycleConfigs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["StudioLifecycleConfigs"]) doSageMakerRequest(t, h, "CreateStudioLifecycleConfig", map[string]any{ @@ -144,7 +146,7 @@ func TestHandler_ListStudioLifecycleConfigs(t *testing.T) { }) rec = doSageMakerRequest(t, h, "ListStudioLifecycleConfigs", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs := resp["StudioLifecycleConfigs"].([]any) assert.Len(t, configs, 1) c := configs[0].(map[string]any) diff --git a/services/sagemaker/handler_tags.go b/services/sagemaker/handler_tags.go index e925d9cce8..2d366166e5 100644 --- a/services/sagemaker/handler_tags.go +++ b/services/sagemaker/handler_tags.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -29,7 +30,7 @@ type deleteTagsRequest struct { func (h *Handler) handleAddTags(ctx context.Context, body []byte) ([]byte, error) { var req addTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -55,12 +56,12 @@ func (h *Handler) handleAddTags(ctx context.Context, body []byte) ([]byte, error return nil, err } - return json.Marshal(map[string]any{"Tags": toTagObjects(allTags)}) + return json.Marshal(map[string]any{"Tags": toTagObjects(allTags)}, jsonx.V1()) } func (h *Handler) handleListTags(ctx context.Context, body []byte) ([]byte, error) { var req listTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -80,12 +81,12 @@ func (h *Handler) handleListTags(ctx context.Context, body []byte) ([]byte, erro resp["NextToken"] = outToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleDeleteTags(ctx context.Context, body []byte) error { var req deleteTagsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/sagemaker/handler_tags_test.go b/services/sagemaker/handler_tags_test.go index 4bdbeb828c..8999f8f465 100644 --- a/services/sagemaker/handler_tags_test.go +++ b/services/sagemaker/handler_tags_test.go @@ -2,12 +2,14 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_Tags(t *testing.T) { @@ -34,7 +36,7 @@ func TestHandler_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var addResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &addResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &addResp, jsonx.V1())) addTags, ok := addResp["Tags"].([]any) require.True(t, ok, "AddTags response body: %s", rec.Body.String()) @@ -47,7 +49,7 @@ func TestHandler_Tags(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].([]any) require.True(t, ok) @@ -64,7 +66,7 @@ func TestHandler_Tags(t *testing.T) { rec = doSageMakerRequest(t, h, "ListTags", map[string]any{ "ResourceArn": m.ModelARN, }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, ok = resp["Tags"].([]any) require.True(t, ok) @@ -203,7 +205,7 @@ func TestHandler_ListTags_MaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) page, ok := resp["Tags"].([]any) require.True(t, ok) diff --git a/services/sagemaker/handler_test.go b/services/sagemaker/handler_test.go index f627737373..2089a2d263 100644 --- a/services/sagemaker/handler_test.go +++ b/services/sagemaker/handler_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -32,7 +33,7 @@ func doSageMakerRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -246,7 +247,7 @@ func TestHandler_SageMakerReset(t *testing.T) { require.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) require.NotEmpty(t, listOut["Models"]) // Reset. @@ -257,7 +258,7 @@ func TestHandler_SageMakerReset(t *testing.T) { require.Equal(t, http.StatusOK, recList2.Code) var listOut2 map[string]any - require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2)) + require.NoError(t, json.Unmarshal(recList2.Body.Bytes(), &listOut2, jsonx.V1())) assert.Empty(t, listOut2["Models"]) // Feature groups gone. @@ -265,7 +266,7 @@ func TestHandler_SageMakerReset(t *testing.T) { require.Equal(t, http.StatusOK, recFG.Code) var fgOut map[string]any - require.NoError(t, json.Unmarshal(recFG.Body.Bytes(), &fgOut)) + require.NoError(t, json.Unmarshal(recFG.Body.Bytes(), &fgOut, jsonx.V1())) assert.Empty(t, fgOut["FeatureGroupSummaries"]) } diff --git a/services/sagemaker/handler_training_jobs.go b/services/sagemaker/handler_training_jobs.go index 2c2f37a5ad..f5eda7cac6 100644 --- a/services/sagemaker/handler_training_jobs.go +++ b/services/sagemaker/handler_training_jobs.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -43,7 +45,7 @@ type createTrainingJobFullRequest struct { func (h *Handler) handleCreateTrainingJobFull(ctx context.Context, body []byte) ([]byte, error) { var req createTrainingJobFullRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.TrainingJobName == "" { @@ -110,7 +112,7 @@ func (h *Handler) handleCreateTrainingJobFull(ctx context.Context, body []byte) log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: created training job (full)", "name", tj.TrainingJobName) - return json.Marshal(map[string]string{keyTrainingJobArn: tj.TrainingJobArn}) + return json.Marshal(map[string]string{keyTrainingJobArn: tj.TrainingJobArn}, jsonx.V1()) } // describeTrainingJobInput mirrors DescribeTrainingJobInput (api_op_DescribeTrainingJob.go:29-36). @@ -127,18 +129,18 @@ func trainingJobPassthroughKeys() []string { } } -func trainingJobPassthroughConfigs(body []byte) (map[string]json.RawMessage, error) { - var all map[string]json.RawMessage - if err := json.Unmarshal(body, &all); err != nil { +func trainingJobPassthroughConfigs(body []byte) (map[string]jsontext.Value, error) { + var all map[string]jsontext.Value + if err := json.Unmarshal(body, &all, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } - var out map[string]json.RawMessage + var out map[string]jsontext.Value for _, k := range trainingJobPassthroughKeys() { if v, ok := all[k]; ok && string(v) != "null" { if out == nil { - out = make(map[string]json.RawMessage) + out = make(map[string]jsontext.Value) } out[k] = v @@ -150,7 +152,7 @@ func trainingJobPassthroughConfigs(body []byte) (map[string]json.RawMessage, err func (h *Handler) handleDescribeTrainingJobFull(ctx context.Context, body []byte) ([]byte, error) { var req describeTrainingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.TrainingJobName == "" { @@ -181,7 +183,7 @@ func (h *Handler) handleDescribeTrainingJobFull(ctx context.Context, body []byte } addTrainingJobOptionalFields(resp, tj) - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func addTrainingJobOptionalFields(resp map[string]any, tj *TrainingJob) { @@ -252,7 +254,7 @@ type stopTrainingJobInput struct { func (h *Handler) handleStopTrainingJobFSM(ctx context.Context, body []byte) error { var req stopTrainingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } if req.TrainingJobName == "" { @@ -287,7 +289,7 @@ type listTrainingJobsInput struct { func (h *Handler) handleListTrainingJobsFiltered(ctx context.Context, body []byte) ([]byte, error) { var req listTrainingJobsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -328,7 +330,7 @@ func (h *Handler) handleListTrainingJobsFiltered(ctx context.Context, body []byt resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -356,7 +358,7 @@ type deleteTrainingJobInput struct { func (h *Handler) handleDeleteTrainingJob(ctx context.Context, body []byte) error { var req deleteTrainingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -392,15 +394,15 @@ type resourceConfigForUpdate struct { type updateTrainingJobInput struct { ResourceConfig *resourceConfigForUpdate `json:"ResourceConfig"` TrainingJobName string `json:"TrainingJobName"` - ProfilerConfig json.RawMessage `json:"ProfilerConfig"` - ProfilerRuleConfigurations json.RawMessage `json:"ProfilerRuleConfigurations"` - RemoteDebugConfig json.RawMessage `json:"RemoteDebugConfig"` + ProfilerConfig jsontext.Value `json:"ProfilerConfig"` + ProfilerRuleConfigurations jsontext.Value `json:"ProfilerRuleConfigurations"` + RemoteDebugConfig jsontext.Value `json:"RemoteDebugConfig"` } func (h *Handler) handleUpdateTrainingJob(ctx context.Context, body []byte) ([]byte, error) { var req updateTrainingJobInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -421,5 +423,5 @@ func (h *Handler) handleUpdateTrainingJob(ctx context.Context, body []byte) ([]b log := logger.Load(ctx) log.InfoContext(ctx, "sagemaker: updated training job", "name", req.TrainingJobName) - return json.Marshal(map[string]string{keyTrainingJobArn: tj.TrainingJobArn}) + return json.Marshal(map[string]string{keyTrainingJobArn: tj.TrainingJobArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_training_jobs_test.go b/services/sagemaker/handler_training_jobs_test.go index 7efd37761f..f326328281 100644 --- a/services/sagemaker/handler_training_jobs_test.go +++ b/services/sagemaker/handler_training_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -12,6 +12,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TrainingJobLifecycle(t *testing.T) { @@ -43,7 +45,7 @@ func testHandlerTrainingJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recCreate.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(recCreate.Body.Bytes(), &createOut, jsonx.V1())) assert.NotEmpty(t, createOut["TrainingJobArn"]) // Describe training job. @@ -58,7 +60,7 @@ func testHandlerTrainingJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["TrainingJobSummaries"].([]any), 1) // StopTrainingJob. diff --git a/services/sagemaker/handler_training_plan.go b/services/sagemaker/handler_training_plan.go index fc79577bf0..72986bda32 100644 --- a/services/sagemaker/handler_training_plan.go +++ b/services/sagemaker/handler_training_plan.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // trainingPlanExtOpsSupported returns the real stateful operations implemented @@ -150,7 +152,7 @@ type listTrainingPlansInput struct { func (h *Handler) handleListTrainingPlans(ctx context.Context, body []byte) ([]byte, error) { var req listTrainingPlansInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -207,7 +209,7 @@ type searchTrainingPlanOfferingsInput struct { func (h *Handler) handleSearchTrainingPlanOfferings(ctx context.Context, body []byte) ([]byte, error) { var req searchTrainingPlanOfferingsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -241,7 +243,7 @@ func (h *Handler) handleSearchTrainingPlanOfferings(ctx context.Context, body [] return json.Marshal(map[string]any{ "TrainingPlanOfferings": items, "TrainingPlanExtensionOfferings": extOfferings, - }) + }, jsonx.V1()) } // extendTrainingPlanInput mirrors ExtendTrainingPlanInput @@ -254,7 +256,7 @@ type extendTrainingPlanInput struct { func (h *Handler) handleExtendTrainingPlan(ctx context.Context, body []byte) ([]byte, error) { var req extendTrainingPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -267,7 +269,7 @@ func (h *Handler) handleExtendTrainingPlan(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{"TrainingPlanExtensions": extensions}) + return json.Marshal(map[string]any{"TrainingPlanExtensions": extensions}, jsonx.V1()) } // describeTrainingPlanExtensionHistoryInput mirrors @@ -283,7 +285,7 @@ type describeTrainingPlanExtensionHistoryInput struct { func (h *Handler) handleDescribeTrainingPlanExtensionHistory(ctx context.Context, body []byte) ([]byte, error) { var req describeTrainingPlanExtensionHistoryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -298,7 +300,7 @@ func (h *Handler) handleDescribeTrainingPlanExtensionHistory(ctx context.Context return nil, err } - return json.Marshal(map[string]any{"TrainingPlanExtensions": extensions, keyNextToken: next}) + return json.Marshal(map[string]any{"TrainingPlanExtensions": extensions, keyNextToken: next}, jsonx.V1()) } // describeReservedCapacityInput mirrors DescribeReservedCapacityInput @@ -311,7 +313,7 @@ type describeReservedCapacityInput struct { func (h *Handler) handleDescribeReservedCapacity(ctx context.Context, body []byte) ([]byte, error) { var req describeReservedCapacityInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -324,7 +326,7 @@ func (h *Handler) handleDescribeReservedCapacity(ctx context.Context, body []byt return nil, err } - return json.Marshal(rc) + return json.Marshal(rc, jsonx.V1()) } // listUltraServersByReservedCapacityInput mirrors @@ -340,7 +342,7 @@ type listUltraServersByReservedCapacityInput struct { func (h *Handler) handleListUltraServersByReservedCapacity(ctx context.Context, body []byte) ([]byte, error) { var req listUltraServersByReservedCapacityInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -355,5 +357,5 @@ func (h *Handler) handleListUltraServersByReservedCapacity(ctx context.Context, return nil, err } - return json.Marshal(map[string]any{"UltraServers": servers, keyNextToken: next}) + return json.Marshal(map[string]any{"UltraServers": servers, keyNextToken: next}, jsonx.V1()) } diff --git a/services/sagemaker/handler_training_plan_test.go b/services/sagemaker/handler_training_plan_test.go index 8454478e5e..2660952f2d 100644 --- a/services/sagemaker/handler_training_plan_test.go +++ b/services/sagemaker/handler_training_plan_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, searchRec.Code) var searchResp map[string]any - require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &searchResp)) + require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &searchResp, jsonx.V1())) offerings, ok := searchResp["TrainingPlanOfferings"].([]any) require.True(t, ok) require.NotEmpty(t, offerings) @@ -46,7 +48,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, describeRec.Code) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) assert.Equal(t, "Scheduled", describeResp["Status"]) assert.InEpsilon(t, float64(720), describeResp["DurationHours"], 0.001) @@ -68,7 +70,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rcRec.Code) var rcResp map[string]any - require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcResp)) + require.NoError(t, json.Unmarshal(rcRec.Body.Bytes(), &rcResp, jsonx.V1())) assert.Equal(t, "ml.p5.48xlarge", rcResp["InstanceType"]) assert.InEpsilon(t, float64(8), rcResp["TotalInstanceCount"], 0.001) @@ -77,7 +79,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries, ok := listResp["TrainingPlanSummaries"].([]any) require.True(t, ok) assert.Len(t, summaries, 1) @@ -89,7 +91,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, extSearchRec.Code) var extSearchResp map[string]any - require.NoError(t, json.Unmarshal(extSearchRec.Body.Bytes(), &extSearchResp)) + require.NoError(t, json.Unmarshal(extSearchRec.Body.Bytes(), &extSearchResp, jsonx.V1())) extOfferings, ok := extSearchResp["TrainingPlanExtensionOfferings"].([]any) require.True(t, ok) require.NotEmpty(t, extOfferings) @@ -105,7 +107,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, extendRec.Code) var extendResp map[string]any - require.NoError(t, json.Unmarshal(extendRec.Body.Bytes(), &extendResp)) + require.NoError(t, json.Unmarshal(extendRec.Body.Bytes(), &extendResp, jsonx.V1())) extensions, ok := extendResp["TrainingPlanExtensions"].([]any) require.True(t, ok) assert.Len(t, extensions, 1) @@ -116,7 +118,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, historyRec.Code) var historyResp map[string]any - require.NoError(t, json.Unmarshal(historyRec.Body.Bytes(), &historyResp)) + require.NoError(t, json.Unmarshal(historyRec.Body.Bytes(), &historyResp, jsonx.V1())) historyExtensions, ok := historyResp["TrainingPlanExtensions"].([]any) require.True(t, ok) assert.Len(t, historyExtensions, 1) @@ -127,7 +129,7 @@ func TestHandler_TrainingPlan_OfferingLifecycle(t *testing.T) { }) var describeAfterResp map[string]any - require.NoError(t, json.Unmarshal(describeAfterRec.Body.Bytes(), &describeAfterResp)) + require.NoError(t, json.Unmarshal(describeAfterRec.Body.Bytes(), &describeAfterResp, jsonx.V1())) assert.Greater(t, describeAfterResp["DurationHours"], describeResp["DurationHours"]) } @@ -141,7 +143,7 @@ func TestHandler_TrainingPlan_UltraServerOffering(t *testing.T) { }) var searchResp map[string]any - require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &searchResp)) + require.NoError(t, json.Unmarshal(searchRec.Body.Bytes(), &searchResp, jsonx.V1())) offerings, ok := searchResp["TrainingPlanOfferings"].([]any) require.True(t, ok) require.NotEmpty(t, offerings) @@ -160,7 +162,7 @@ func TestHandler_TrainingPlan_UltraServerOffering(t *testing.T) { }) var describeResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &describeResp, jsonx.V1())) rcSummaries, ok := describeResp["ReservedCapacitySummaries"].([]any) require.True(t, ok) require.Len(t, rcSummaries, 1) @@ -176,7 +178,7 @@ func TestHandler_TrainingPlan_UltraServerOffering(t *testing.T) { assert.Equal(t, http.StatusOK, ultraRec.Code) var ultraResp map[string]any - require.NoError(t, json.Unmarshal(ultraRec.Body.Bytes(), &ultraResp)) + require.NoError(t, json.Unmarshal(ultraRec.Body.Bytes(), &ultraResp, jsonx.V1())) servers, ok := ultraResp["UltraServers"].([]any) require.True(t, ok) assert.Len(t, servers, 1) diff --git a/services/sagemaker/handler_training_plans.go b/services/sagemaker/handler_training_plans.go index 27a815b67d..735943b5ac 100644 --- a/services/sagemaker/handler_training_plans.go +++ b/services/sagemaker/handler_training_plans.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -25,7 +27,7 @@ type createTrainingPlanInput struct { func (h *Handler) handleCreateTrainingPlan(ctx context.Context, body []byte) ([]byte, error) { var req createTrainingPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -45,7 +47,7 @@ func (h *Handler) handleCreateTrainingPlan(ctx context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{keyTrainingPlanArn: result.TrainingPlanArn}) + return json.Marshal(map[string]any{keyTrainingPlanArn: result.TrainingPlanArn}, jsonx.V1()) } // describeTrainingPlanInput mirrors DescribeTrainingPlanInput @@ -58,7 +60,7 @@ type describeTrainingPlanInput struct { func (h *Handler) handleDescribeTrainingPlan(ctx context.Context, body []byte) ([]byte, error) { var req describeTrainingPlanInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -71,5 +73,5 @@ func (h *Handler) handleDescribeTrainingPlan(ctx context.Context, body []byte) ( return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } diff --git a/services/sagemaker/handler_training_plans_test.go b/services/sagemaker/handler_training_plans_test.go index 1ea544a645..0968dd25c5 100644 --- a/services/sagemaker/handler_training_plans_test.go +++ b/services/sagemaker/handler_training_plans_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateTrainingPlan(t *testing.T) { @@ -21,7 +23,7 @@ func TestHandler_CreateTrainingPlan(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["TrainingPlanArn"], "my-plan") } @@ -38,7 +40,7 @@ func TestHandler_DescribeTrainingPlan(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "plan-1", resp["TrainingPlanName"]) // TargetResources/TotalInstanceCount/UpfrontFee were previously tagged diff --git a/services/sagemaker/handler_transform_jobs.go b/services/sagemaker/handler_transform_jobs.go index 62026d07e6..3f986c4f67 100644 --- a/services/sagemaker/handler_transform_jobs.go +++ b/services/sagemaker/handler_transform_jobs.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -32,7 +33,7 @@ type createTransformJobRequest struct { func (h *Handler) handleCreateTransformJob(ctx context.Context, body []byte) ([]byte, error) { var req createTransformJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -94,7 +95,7 @@ func (h *Handler) handleCreateTransformJob(ctx context.Context, body []byte) ([] logger.Load(ctx).InfoContext(ctx, "sagemaker: created transform job", "name", tj.TransformJobName) - return json.Marshal(map[string]string{"TransformJobArn": tj.TransformJobArn}) + return json.Marshal(map[string]string{"TransformJobArn": tj.TransformJobArn}, jsonx.V1()) } type describeTransformJobRequest struct { @@ -104,7 +105,7 @@ type describeTransformJobRequest struct { func (h *Handler) handleDescribeTransformJob(ctx context.Context, body []byte) ([]byte, error) { var req describeTransformJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -156,7 +157,7 @@ func (h *Handler) handleDescribeTransformJob(ctx context.Context, body []byte) ( resp["ModelClientConfig"] = tj.ModelClientConfig } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type stopTransformJobRequest struct { @@ -166,7 +167,7 @@ type stopTransformJobRequest struct { func (h *Handler) handleStopTransformJob(ctx context.Context, body []byte) error { var req stopTransformJobRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -212,7 +213,7 @@ type listTransformJobsRequest struct { func (h *Handler) handleListTransformJobs(ctx context.Context, body []byte) ([]byte, error) { var req listTransformJobsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -252,5 +253,5 @@ func (h *Handler) handleListTransformJobs(ctx context.Context, body []byte) ([]b resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_transform_jobs_test.go b/services/sagemaker/handler_transform_jobs_test.go index edf1955d24..3e93533214 100644 --- a/services/sagemaker/handler_transform_jobs_test.go +++ b/services/sagemaker/handler_transform_jobs_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -50,7 +51,7 @@ func TestHandler_TransformJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotEmpty(t, createResp["TransformJobArn"]) // Describe — InProgress initially @@ -60,7 +61,7 @@ func TestHandler_TransformJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "my-transform", descResp["TransformJobName"]) assert.Equal(t, "my-model", descResp["ModelName"]) assert.Equal(t, "InProgress", descResp["TransformJobStatus"]) @@ -71,7 +72,7 @@ func TestHandler_TransformJobLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["TransformJobSummaries"].([]any) assert.Len(t, summaries, 1) @@ -81,7 +82,7 @@ func TestHandler_TransformJobLifecycle(t *testing.T) { rec = doSageMakerRequest(t, h, "DescribeTransformJob", map[string]any{ "TransformJobName": "my-transform", }) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Completed", descResp["TransformJobStatus"]) }) } @@ -147,7 +148,7 @@ func TestHandler_CreateTransformJob_ModelNotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFound", errResp["__type"]) rec = doSageMakerRequest(t, h, "DescribeTransformJob", map[string]any{ @@ -185,7 +186,7 @@ func TestHandler_StopTransformJob(t *testing.T) { "TransformJobName": "stop-me", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Stopping", descResp["TransformJobStatus"]) } @@ -219,7 +220,7 @@ func TestHandler_ListTransformJobs_StatusFilter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["TransformJobSummaries"].([]any) assert.Len(t, summaries, 1) assert.Equal(t, "tj-2", summaries[0].(map[string]any)["TransformJobName"]) @@ -253,7 +254,7 @@ func TestHandler_ListTransformJobs_SortByName(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["TransformJobSummaries"].([]any) require.Len(t, summaries, 2) assert.Equal(t, "tj-alpha", summaries[0].(map[string]any)["TransformJobName"]) @@ -341,7 +342,7 @@ func TestHandler_CreateTransformJob_RoleArnNotPartOfWireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) _, present := descResp["RoleArn"] assert.False(t, present, "RoleArn is not a real CreateTransformJobInput/DescribeTransformJobOutput field") } @@ -482,7 +483,7 @@ func TestHandler_Tags_TransformJob(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobARN := createResp["TransformJobArn"] require.NotEmpty(t, jobARN) @@ -492,7 +493,7 @@ func TestHandler_Tags_TransformJob(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) require.Len(t, tags, 1) assert.Equal(t, "owner", tags[0].(map[string]any)["Key"]) diff --git a/services/sagemaker/handler_trial_components.go b/services/sagemaker/handler_trial_components.go index 6f5f5b0bd6..723ba00523 100644 --- a/services/sagemaker/handler_trial_components.go +++ b/services/sagemaker/handler_trial_components.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -31,7 +32,7 @@ type createTrialComponentInput struct { func (h *Handler) handleCreateTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req createTrialComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -58,7 +59,7 @@ func (h *Handler) handleCreateTrialComponent(ctx context.Context, body []byte) ( logger.Load(ctx). InfoContext(ctx, "sagemaker: created trial component", "name", tc.TrialComponentName) - return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}) + return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}, jsonx.V1()) } // describeTrialComponentInput mirrors DescribeTrialComponentInput @@ -78,7 +79,7 @@ type describeTrialComponentInput struct { func (h *Handler) handleDescribeTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req describeTrialComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -129,7 +130,7 @@ func (h *Handler) handleDescribeTrialComponent(ctx context.Context, body []byte) resp["LastModifiedBy"] = tc.LastModifiedBy } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteTrialComponentInput mirrors DeleteTrialComponentInput @@ -142,7 +143,7 @@ type deleteTrialComponentInput struct { func (h *Handler) handleDeleteTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req deleteTrialComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -158,7 +159,7 @@ func (h *Handler) handleDeleteTrialComponent(ctx context.Context, body []byte) ( logger.Load(ctx). InfoContext(ctx, "sagemaker: deleted trial component", "name", req.TrialComponentName) - return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}) + return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}, jsonx.V1()) } // --------------------------------------------------------------------------- @@ -176,7 +177,7 @@ type disassociateTrialComponentInput struct { func (h *Handler) handleDisassociateTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req disassociateTrialComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -198,7 +199,7 @@ func (h *Handler) handleDisassociateTrialComponent(ctx context.Context, body []b return json.Marshal(map[string]string{ keyTrialArn: trialArn, keyTrialComponentArn: trialComponentArn, - }) + }, jsonx.V1()) } // listTrialComponentsInput mirrors ListTrialComponentsInput @@ -220,7 +221,7 @@ type listTrialComponentsInput struct { func (h *Handler) handleListTrialComponents(ctx context.Context, body []byte) ([]byte, error) { var req listTrialComponentsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -283,7 +284,7 @@ type updateTrialComponentInput struct { func (h *Handler) handleUpdateTrialComponent(ctx context.Context, body []byte) ([]byte, error) { var req updateTrialComponentInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -316,5 +317,5 @@ func (h *Handler) handleUpdateTrialComponent(ctx context.Context, body []byte) ( tc.TrialComponentName, ) - return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}) + return json.Marshal(map[string]string{keyTrialComponentArn: tc.TrialComponentArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_trial_components_test.go b/services/sagemaker/handler_trial_components_test.go index 8f753730e7..cc7c647a12 100644 --- a/services/sagemaker/handler_trial_components_test.go +++ b/services/sagemaker/handler_trial_components_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { @@ -31,7 +33,7 @@ func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { require.Equal(t, http.StatusOK, recByTrial.Code) var byTrial map[string]any - require.NoError(t, json.Unmarshal(recByTrial.Body.Bytes(), &byTrial)) + require.NoError(t, json.Unmarshal(recByTrial.Body.Bytes(), &byTrial, jsonx.V1())) summaries, _ := byTrial["TrialComponentSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "tc-a", summaries[0].(map[string]any)["TrialComponentName"]) @@ -41,7 +43,7 @@ func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { require.Equal(t, http.StatusOK, recByExp.Code) var byExp map[string]any - require.NoError(t, json.Unmarshal(recByExp.Body.Bytes(), &byExp)) + require.NoError(t, json.Unmarshal(recByExp.Body.Bytes(), &byExp, jsonx.V1())) expSummaries, _ := byExp["TrialComponentSummaries"].([]any) require.Len(t, expSummaries, 1) @@ -50,7 +52,7 @@ func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { require.Equal(t, http.StatusOK, recAll.Code) var all map[string]any - require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &all)) + require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &all, jsonx.V1())) allSummaries, _ := all["TrialComponentSummaries"].([]any) assert.Len(t, allSummaries, 2) @@ -61,7 +63,7 @@ func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { require.Equal(t, http.StatusOK, recDisassoc.Code) var disassocOut map[string]any - require.NoError(t, json.Unmarshal(recDisassoc.Body.Bytes(), &disassocOut)) + require.NoError(t, json.Unmarshal(recDisassoc.Body.Bytes(), &disassocOut, jsonx.V1())) assert.NotEmpty(t, disassocOut["TrialArn"]) assert.NotEmpty(t, disassocOut["TrialComponentArn"]) @@ -69,7 +71,7 @@ func TestHandler_ListAndDisassociateTrialComponents(t *testing.T) { require.Equal(t, http.StatusOK, recByTrial2.Code) var byTrial2 map[string]any - require.NoError(t, json.Unmarshal(recByTrial2.Body.Bytes(), &byTrial2)) + require.NoError(t, json.Unmarshal(recByTrial2.Body.Bytes(), &byTrial2, jsonx.V1())) assert.Empty(t, byTrial2["TrialComponentSummaries"]) } @@ -87,7 +89,7 @@ func TestHandler_DisassociateTrialComponent_Idempotent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out["TrialArn"], "never-existed") } @@ -173,7 +175,7 @@ func TestHandler_UpdateTrialComponent(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["TrialComponentArn"]) // Describe returns updated fields @@ -181,7 +183,7 @@ func TestHandler_UpdateTrialComponent(t *testing.T) { "TrialComponentName": "my-tc", }) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "TC Display", descResp["DisplayName"]) assert.Equal(t, "InProgress", descResp["Status"].(map[string]any)["PrimaryStatus"]) assert.NotNil(t, descResp["Parameters"]) @@ -218,7 +220,7 @@ func TestHandler_CreateTrialComponent_FullFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Training Run 1", descResp["DisplayName"]) assert.InEpsilon(t, float64(1700000000), descResp["StartTime"], 0) assert.InEpsilon(t, float64(1700003600), descResp["EndTime"], 0) diff --git a/services/sagemaker/handler_trials.go b/services/sagemaker/handler_trials.go index 8f605e9eff..4ff655bacc 100644 --- a/services/sagemaker/handler_trials.go +++ b/services/sagemaker/handler_trials.go @@ -2,9 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -24,7 +25,7 @@ type createTrialInput struct { func (h *Handler) handleCreateTrial(ctx context.Context, body []byte) ([]byte, error) { var req createTrialInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -41,7 +42,7 @@ func (h *Handler) handleCreateTrial(ctx context.Context, body []byte) ([]byte, e logger.Load(ctx).InfoContext(ctx, "sagemaker: created trial", "name", t.TrialName) - return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}) + return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}, jsonx.V1()) } // describeTrialInput mirrors DescribeTrialInput (api_op_DescribeTrial.go:29-36). @@ -52,7 +53,7 @@ type describeTrialInput struct { func (h *Handler) handleDescribeTrial(ctx context.Context, body []byte) ([]byte, error) { var req describeTrialInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -86,7 +87,7 @@ func (h *Handler) handleDescribeTrial(ctx context.Context, body []byte) ([]byte, resp["LastModifiedBy"] = t.LastModifiedBy } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type trialSummary struct { @@ -112,7 +113,7 @@ type listTrialsInput struct { func (h *Handler) handleListTrials(ctx context.Context, body []byte) ([]byte, error) { var req listTrialsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -142,7 +143,7 @@ func (h *Handler) handleListTrials(ctx context.Context, body []byte) ([]byte, er resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // deleteTrialInput mirrors DeleteTrialInput (api_op_DeleteTrial.go:30-37). @@ -153,7 +154,7 @@ type deleteTrialInput struct { func (h *Handler) handleDeleteTrial(ctx context.Context, body []byte) ([]byte, error) { var req deleteTrialInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -168,7 +169,7 @@ func (h *Handler) handleDeleteTrial(ctx context.Context, body []byte) ([]byte, e logger.Load(ctx).InfoContext(ctx, "sagemaker: deleted trial", "name", req.TrialName) - return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}) + return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}, jsonx.V1()) } // updateTrialInput mirrors UpdateTrialInput (api_op_UpdateTrial.go:27-39). @@ -180,7 +181,7 @@ type updateTrialInput struct { func (h *Handler) handleUpdateTrial(ctx context.Context, body []byte) ([]byte, error) { var req updateTrialInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -195,5 +196,5 @@ func (h *Handler) handleUpdateTrial(ctx context.Context, body []byte) ([]byte, e logger.Load(ctx).InfoContext(ctx, "sagemaker: updated trial", "name", t.TrialName) - return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}) + return json.Marshal(map[string]string{keyTrialArn: t.TrialArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_trials_test.go b/services/sagemaker/handler_trials_test.go index acbede9677..267027f82f 100644 --- a/services/sagemaker/handler_trials_test.go +++ b/services/sagemaker/handler_trials_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TrialLifecycle(t *testing.T) { @@ -44,7 +46,7 @@ func TestHandler_TrialLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["TrialSummaries"].([]any), 1) // Delete trial. @@ -70,13 +72,13 @@ func TestHandler_UpdateTrial(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["TrialArn"]) // Describe returns updated DisplayName rec = doSageMakerRequest(t, h, "DescribeTrial", map[string]any{"TrialName": "my-trial"}) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "Trial Display", descResp["DisplayName"]) } @@ -98,14 +100,14 @@ func TestHandler_CreateTrial_DisplayName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, "My Trial", descResp["DisplayName"]) rec = doSageMakerRequest(t, h, "ListTrials", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["TrialSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "My Trial", summaries[0].(map[string]any)["DisplayName"]) @@ -264,7 +266,7 @@ func TestHandler_ListTrials_TrialComponentName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["TrialSummaries"].([]any) require.Len(t, summaries, 1) assert.Equal(t, "trial-with-tc", summaries[0].(map[string]any)["TrialName"]) diff --git a/services/sagemaker/handler_user_profiles.go b/services/sagemaker/handler_user_profiles.go index d59665daca..43d576b06a 100644 --- a/services/sagemaker/handler_user_profiles.go +++ b/services/sagemaker/handler_user_profiles.go @@ -2,9 +2,11 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -17,18 +19,18 @@ import ( // it — see gopherstack-oc9v). UserSettings is carried as opaque // json.RawMessage passthrough per this file's established convention. type createUserProfileInput struct { - DomainID string `json:"DomainId"` - UserProfileName string `json:"UserProfileName"` - SingleSignOnUserIdentifier string `json:"SingleSignOnUserIdentifier"` - SingleSignOnUserValue string `json:"SingleSignOnUserValue"` - UserSettings json.RawMessage `json:"UserSettings"` - Tags []tagObject `json:"Tags"` + DomainID string `json:"DomainId"` + UserProfileName string `json:"UserProfileName"` + SingleSignOnUserIdentifier string `json:"SingleSignOnUserIdentifier"` + SingleSignOnUserValue string `json:"SingleSignOnUserValue"` + UserSettings jsontext.Value `json:"UserSettings"` + Tags []tagObject `json:"Tags"` } func (h *Handler) handleCreateUserProfile(ctx context.Context, body []byte) ([]byte, error) { var req createUserProfileInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -61,7 +63,7 @@ func (h *Handler) handleCreateUserProfile(ctx context.Context, body []byte) ([]b logger.Load(ctx).InfoContext(ctx, "sagemaker: created user profile", "name", up.UserProfileName) - return json.Marshal(map[string]string{keyUserProfileArn: up.UserProfileArn}) + return json.Marshal(map[string]string{keyUserProfileArn: up.UserProfileArn}, jsonx.V1()) } type describeUserProfileInput struct { @@ -72,7 +74,7 @@ type describeUserProfileInput struct { func (h *Handler) handleDescribeUserProfile(ctx context.Context, body []byte) ([]byte, error) { var req describeUserProfileInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -110,7 +112,7 @@ func (h *Handler) handleDescribeUserProfile(ctx context.Context, body []byte) ([ resp["UserSettings"] = up.UserSettings } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type userProfileSummary struct { @@ -133,7 +135,7 @@ type listUserProfilesInput struct { func (h *Handler) handleListUserProfiles(ctx context.Context, body []byte) ([]byte, error) { var req listUserProfilesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -155,7 +157,7 @@ func (h *Handler) handleListUserProfiles(ctx context.Context, body []byte) ([]by resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type deleteUserProfileInput struct { @@ -166,7 +168,7 @@ type deleteUserProfileInput struct { func (h *Handler) handleDeleteUserProfile(ctx context.Context, body []byte) error { var req deleteUserProfileInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -191,15 +193,15 @@ func (h *Handler) handleDeleteUserProfile(ctx context.Context, body []byte) erro // updateUserProfileInput mirrors UpdateUserProfileInput // (api_op_UpdateUserProfile.go:26-38). type updateUserProfileInput struct { - DomainID string `json:"DomainId"` - UserProfileName string `json:"UserProfileName"` - UserSettings json.RawMessage `json:"UserSettings"` + DomainID string `json:"DomainId"` + UserProfileName string `json:"UserProfileName"` + UserSettings jsontext.Value `json:"UserSettings"` } func (h *Handler) handleUpdateUserProfile(ctx context.Context, body []byte) ([]byte, error) { var req updateUserProfileInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -222,5 +224,5 @@ func (h *Handler) handleUpdateUserProfile(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]string{keyUserProfileArn: up.UserProfileArn}) + return json.Marshal(map[string]string{keyUserProfileArn: up.UserProfileArn}, jsonx.V1()) } diff --git a/services/sagemaker/handler_user_profiles_test.go b/services/sagemaker/handler_user_profiles_test.go index bae48b909e..5006a94570 100644 --- a/services/sagemaker/handler_user_profiles_test.go +++ b/services/sagemaker/handler_user_profiles_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_UserProfileLifecycle(t *testing.T) { @@ -24,7 +26,7 @@ func TestHandler_UserProfileLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) // Create user profile. @@ -51,7 +53,7 @@ func TestHandler_UserProfileLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, recList.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(recList.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut["UserProfiles"].([]any), 1) // Delete user profile. @@ -75,7 +77,7 @@ func TestHandler_UserProfile_NotFound(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) for _, op := range []string{"DescribeUserProfile", "DeleteUserProfile"} { @@ -104,7 +106,7 @@ func TestHandler_UpdateUserProfile(t *testing.T) { var domainResp map[string]any rec := doSageMakerRequest(t, h, "ListDomains", map[string]any{}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &domainResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &domainResp, jsonx.V1())) domains := domainResp["Domains"].([]any) require.Len(t, domains, 1) domainID := domains[0].(map[string]any)["DomainId"].(string) @@ -121,7 +123,7 @@ func TestHandler_UpdateUserProfile(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["UserProfileArn"]) } @@ -137,7 +139,7 @@ func TestHandler_ListUserProfiles_NameContainsAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) for _, name := range []string{"alpha-user", "beta-user", "alpha-other"} { @@ -158,7 +160,7 @@ func TestHandler_ListUserProfiles_NameContainsAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["UserProfiles"].([]any), 2) }) @@ -172,7 +174,7 @@ func TestHandler_ListUserProfiles_NameContainsAndMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["UserProfiles"].([]any), 1, "MaxResults must cap the page, not just be parsed and ignored") assert.NotEmpty(t, out["NextToken"]) }) @@ -191,7 +193,7 @@ func TestHandler_CreateUserProfile_SSOFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recDomain.Code) var domainOut map[string]any - require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut)) + require.NoError(t, json.Unmarshal(recDomain.Body.Bytes(), &domainOut, jsonx.V1())) domainID := domainOut["DomainId"].(string) rec := doSageMakerRequest(t, h, "CreateUserProfile", map[string]any{ @@ -209,7 +211,7 @@ func TestHandler_CreateUserProfile_SSOFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "UserName", out["SingleSignOnUserIdentifier"]) assert.Equal(t, "jdoe", out["SingleSignOnUserValue"]) } diff --git a/services/sagemaker/handler_workforces.go b/services/sagemaker/handler_workforces.go index a807595820..92632138c0 100644 --- a/services/sagemaker/handler_workforces.go +++ b/services/sagemaker/handler_workforces.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -96,7 +98,7 @@ type createWorkforceInput struct { func (h *Handler) handleCreateWorkforce(ctx context.Context, body []byte) ([]byte, error) { var req createWorkforceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +119,7 @@ func (h *Handler) handleCreateWorkforce(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{keyWorkforceArn: result.WorkforceArn}) + return json.Marshal(map[string]any{keyWorkforceArn: result.WorkforceArn}, jsonx.V1()) } // describeWorkforceInput mirrors DescribeWorkforceInput (api_op_DescribeWorkforce.go:34-42). @@ -128,7 +130,7 @@ type describeWorkforceInput struct { func (h *Handler) handleDescribeWorkforce(ctx context.Context, body []byte) ([]byte, error) { var req describeWorkforceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -141,7 +143,7 @@ func (h *Handler) handleDescribeWorkforce(ctx context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]any{"Workforce": workforceResponseMap(result)}) + return json.Marshal(map[string]any{"Workforce": workforceResponseMap(result)}, jsonx.V1()) } // updateWorkforceInput mirrors UpdateWorkforceInput (api_op_UpdateWorkforce.go:65-93). @@ -156,7 +158,7 @@ type updateWorkforceInput struct { func (h *Handler) handleUpdateWorkforce(ctx context.Context, body []byte) ([]byte, error) { var req updateWorkforceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -175,7 +177,7 @@ func (h *Handler) handleUpdateWorkforce(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{"Workforce": workforceResponseMap(result)}) + return json.Marshal(map[string]any{"Workforce": workforceResponseMap(result)}, jsonx.V1()) } // deleteWorkforceInput mirrors DeleteWorkforceInput (api_op_DeleteWorkforce.go:39-44). @@ -186,7 +188,7 @@ type deleteWorkforceInput struct { func (h *Handler) handleDeleteWorkforce(ctx context.Context, body []byte) ([]byte, error) { var req deleteWorkforceInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -198,7 +200,7 @@ func (h *Handler) handleDeleteWorkforce(ctx context.Context, body []byte) ([]byt return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } // listWorkforcesInput mirrors ListWorkforcesInput (api_op_ListWorkforces.go:31-48). @@ -213,7 +215,7 @@ type listWorkforcesInput struct { func (h *Handler) handleListWorkforces(ctx context.Context, body []byte) ([]byte, error) { var req listWorkforcesInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -234,5 +236,5 @@ func (h *Handler) handleListWorkforces(ctx context.Context, body []byte) ([]byte resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_workforces_test.go b/services/sagemaker/handler_workforces_test.go index b5c903db27..dccc49422d 100644 --- a/services/sagemaker/handler_workforces_test.go +++ b/services/sagemaker/handler_workforces_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateWorkforce(t *testing.T) { @@ -20,7 +22,7 @@ func TestHandler_CreateWorkforce(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["WorkforceArn"], "my-workforce") } @@ -34,7 +36,7 @@ func TestHandler_DescribeWorkforce(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Workforce"]) } @@ -48,7 +50,7 @@ func TestHandler_UpdateWorkforce(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Workforce"]) } @@ -66,7 +68,7 @@ func TestHandler_CreateWorkforce_IPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wf := resp["Workforce"].(map[string]any) assert.Equal(t, "dualstack", wf["IpAddressType"]) } @@ -88,7 +90,7 @@ func TestHandler_UpdateWorkforce_IPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wf := resp["Workforce"].(map[string]any) assert.Equal(t, "dualstack", wf["IpAddressType"]) } @@ -118,7 +120,7 @@ func TestHandler_ListWorkforces_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["Workforces"].([]any) assert.Len(t, items, tc.wantCount) }) diff --git a/services/sagemaker/handler_workteams.go b/services/sagemaker/handler_workteams.go index bee8920421..f8bb19d0a7 100644 --- a/services/sagemaker/handler_workteams.go +++ b/services/sagemaker/handler_workteams.go @@ -2,8 +2,10 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -59,7 +61,7 @@ type createWorkteamInput struct { func (h *Handler) handleCreateWorkteam(ctx context.Context, body []byte) ([]byte, error) { var req createWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -91,7 +93,7 @@ func (h *Handler) handleCreateWorkteam(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(map[string]any{"WorkteamArn": result.WorkteamArn}) + return json.Marshal(map[string]any{"WorkteamArn": result.WorkteamArn}, jsonx.V1()) } // describeWorkteamInput mirrors DescribeWorkteamInput (api_op_DescribeWorkteam.go:31-36). @@ -102,7 +104,7 @@ type describeWorkteamInput struct { func (h *Handler) handleDescribeWorkteam(ctx context.Context, body []byte) ([]byte, error) { var req describeWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -115,7 +117,7 @@ func (h *Handler) handleDescribeWorkteam(ctx context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]any{"Workteam": workteamResponseMap(result)}) + return json.Marshal(map[string]any{"Workteam": workteamResponseMap(result)}, jsonx.V1()) } // updateWorkteamInput mirrors UpdateWorkteamInput (api_op_UpdateWorkteam.go:28-70). @@ -130,7 +132,7 @@ type updateWorkteamInput struct { func (h *Handler) handleUpdateWorkteam(ctx context.Context, body []byte) ([]byte, error) { var req updateWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -149,7 +151,7 @@ func (h *Handler) handleUpdateWorkteam(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(map[string]any{"Workteam": workteamResponseMap(result)}) + return json.Marshal(map[string]any{"Workteam": workteamResponseMap(result)}, jsonx.V1()) } // deleteWorkteamInput mirrors DeleteWorkteamInput (api_op_DeleteWorkteam.go:27-32). @@ -160,7 +162,7 @@ type deleteWorkteamInput struct { func (h *Handler) handleDeleteWorkteam(ctx context.Context, body []byte) ([]byte, error) { var req deleteWorkteamInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -172,7 +174,7 @@ func (h *Handler) handleDeleteWorkteam(ctx context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(map[string]any{"Success": true}) + return json.Marshal(map[string]any{"Success": true}, jsonx.V1()) } // listWorkteamsInput mirrors ListWorkteamsInput (api_op_ListWorkteams.go:31-51). @@ -187,7 +189,7 @@ type listWorkteamsInput struct { func (h *Handler) handleListWorkteams(ctx context.Context, body []byte) ([]byte, error) { var req listWorkteamsInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -208,5 +210,5 @@ func (h *Handler) handleListWorkteams(ctx context.Context, body []byte) ([]byte, resp[keyNextToken] = next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/sagemaker/handler_workteams_test.go b/services/sagemaker/handler_workteams_test.go index 233de8cd5a..545f838d48 100644 --- a/services/sagemaker/handler_workteams_test.go +++ b/services/sagemaker/handler_workteams_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // defaultMemberDefinitions returns a minimal, real-shaped MemberDefinitions @@ -33,7 +35,7 @@ func TestHandler_CreateWorkteam(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["WorkteamArn"], "my-team") } @@ -50,7 +52,7 @@ func TestHandler_DescribeWorkteam(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wt := resp["Workteam"].(map[string]any) assert.Equal(t, "team-1", wt["WorkteamName"]) } @@ -86,7 +88,7 @@ func TestHandler_ListWorkteams(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Workteams"].([]any) assert.Len(t, items, 2) } @@ -130,7 +132,7 @@ func TestHandler_ListWorkteams_Filters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["Workteams"].([]any) require.Len(t, items, len(tc.wantNames)) @@ -166,7 +168,7 @@ func TestHandler_CreateWorkteam_NotificationAndWorkerAccessConfig(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wt := resp["Workteam"].(map[string]any) nc := wt["NotificationConfiguration"].(map[string]any) @@ -196,7 +198,7 @@ func TestHandler_UpdateWorkteam_NotificationConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wt := resp["Workteam"].(map[string]any) nc := wt["NotificationConfiguration"].(map[string]any) assert.Equal(t, "arn:aws:sns:us-east-1:123456789012:updated", nc["NotificationTopicArn"]) diff --git a/services/sagemaker/hp_tuning_jobs.go b/services/sagemaker/hp_tuning_jobs.go index 79432ecd40..6e3c972480 100644 --- a/services/sagemaker/hp_tuning_jobs.go +++ b/services/sagemaker/hp_tuning_jobs.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -68,11 +68,11 @@ type HyperParameterTuningJob struct { Strategy string `json:"Strategy,omitempty"` HyperParameterTuningJobStatus string `json:"HyperParameterTuningJobStatus"` HyperParameterTuningJobArn string `json:"HyperParameterTuningJobArn"` - HyperParameterTuningJobConfig json.RawMessage `json:"HyperParameterTuningJobConfig,omitempty"` - TrainingJobDefinitions json.RawMessage `json:"TrainingJobDefinitions,omitempty"` - TrainingJobDefinition json.RawMessage `json:"TrainingJobDefinition,omitempty"` - WarmStartConfig json.RawMessage `json:"WarmStartConfig,omitempty"` - Autotune json.RawMessage `json:"Autotune,omitempty"` + HyperParameterTuningJobConfig jsontext.Value `json:"HyperParameterTuningJobConfig,omitempty"` + TrainingJobDefinitions jsontext.Value `json:"TrainingJobDefinitions,omitempty"` + TrainingJobDefinition jsontext.Value `json:"TrainingJobDefinition,omitempty"` + WarmStartConfig jsontext.Value `json:"WarmStartConfig,omitempty"` + Autotune jsontext.Value `json:"Autotune,omitempty"` TrainingJobStatusCounters HPTrainingJobStatusCounters `json:"TrainingJobStatusCounters"` ResourceLimits HPResourceLimits `json:"ResourceLimits"` ObjectiveStatusCounters HPObjectiveStatusCounters `json:"ObjectiveStatusCounters"` @@ -82,11 +82,11 @@ type HyperParameterTuningJob struct { func cloneHPTuningJob(j *HyperParameterTuningJob) *HyperParameterTuningJob { cp := *j cp.Tags = maps.Clone(j.Tags) - cp.HyperParameterTuningJobConfig = append(json.RawMessage(nil), j.HyperParameterTuningJobConfig...) - cp.Autotune = append(json.RawMessage(nil), j.Autotune...) - cp.WarmStartConfig = append(json.RawMessage(nil), j.WarmStartConfig...) - cp.TrainingJobDefinition = append(json.RawMessage(nil), j.TrainingJobDefinition...) - cp.TrainingJobDefinitions = append(json.RawMessage(nil), j.TrainingJobDefinitions...) + cp.HyperParameterTuningJobConfig = append(jsontext.Value(nil), j.HyperParameterTuningJobConfig...) + cp.Autotune = append(jsontext.Value(nil), j.Autotune...) + cp.WarmStartConfig = append(jsontext.Value(nil), j.WarmStartConfig...) + cp.TrainingJobDefinition = append(jsontext.Value(nil), j.TrainingJobDefinition...) + cp.TrainingJobDefinitions = append(jsontext.Value(nil), j.TrainingJobDefinitions...) return &cp } @@ -101,11 +101,11 @@ type CreateHyperParameterTuningJobOptions struct { Tags map[string]string Name string Strategy string - HyperParameterTuningJobConfig json.RawMessage - Autotune json.RawMessage - WarmStartConfig json.RawMessage - TrainingJobDefinition json.RawMessage - TrainingJobDefinitions json.RawMessage + HyperParameterTuningJobConfig jsontext.Value + Autotune jsontext.Value + WarmStartConfig jsontext.Value + TrainingJobDefinition jsontext.Value + TrainingJobDefinitions jsontext.Value Limits HPResourceLimits } diff --git a/services/sagemaker/human_task_ui.go b/services/sagemaker/human_task_ui.go index 04e396bdc9..001bddf387 100644 --- a/services/sagemaker/human_task_ui.go +++ b/services/sagemaker/human_task_ui.go @@ -4,13 +4,14 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrHumanTaskUINotFound is returned when a human task UI does not exist. @@ -63,7 +64,7 @@ func (h *HumanTaskUI) MarshalJSON() ([]byte, error) { alias: (*alias)(h), CreationTime: epochSeconds(h.CreationTime), UITemplate: uiTemplateInfo{ContentSha256: h.UITemplateContentSha256}, - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [HumanTaskUI.MarshalJSON], read by @@ -77,7 +78,7 @@ func (h *HumanTaskUI) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(h)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/hyperpod_scheduling.go b/services/sagemaker/hyperpod_scheduling.go index cf9eec7b5f..ee76f13e4f 100644 --- a/services/sagemaker/hyperpod_scheduling.go +++ b/services/sagemaker/hyperpod_scheduling.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -10,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -105,7 +106,7 @@ func (c *ClusterSchedulerConfig) MarshalJSON() ([]byte, error) { alias: (*alias)(c), CreationTime: epochSeconds(c.CreationTime), LastModifiedTime: epochSeconds(c.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ClusterSchedulerConfig.MarshalJSON], read @@ -119,7 +120,7 @@ func (c *ClusterSchedulerConfig) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(c)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -595,7 +596,7 @@ func (q *ComputeQuota) MarshalJSON() ([]byte, error) { alias: (*alias)(q), CreationTime: epochSeconds(q.CreationTime), LastModifiedTime: epochSeconds(q.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ComputeQuota.MarshalJSON], read by @@ -609,7 +610,7 @@ func (q *ComputeQuota) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(q)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/images.go b/services/sagemaker/images.go index c344e8000e..45108cc057 100644 --- a/services/sagemaker/images.go +++ b/services/sagemaker/images.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -66,7 +67,7 @@ func (img *SMImage) MarshalJSON() ([]byte, error) { alias: (*alias)(img), CreationTime: epochSeconds(img.CreationTime), LastModifiedTime: epochSeconds(img.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [SMImage.MarshalJSON], read by @@ -80,7 +81,7 @@ func (img *SMImage) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(img)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -358,7 +359,7 @@ func (v *ImageVersion) MarshalJSON() ([]byte, error) { alias: (*alias)(v), CreationTime: epochSeconds(v.CreationTime), LastModifiedTime: epochSeconds(v.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ImageVersion.MarshalJSON], read by @@ -372,7 +373,7 @@ func (v *ImageVersion) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(v)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/inference_components.go b/services/sagemaker/inference_components.go index acc20b2c20..8edb7fffed 100644 --- a/services/sagemaker/inference_components.go +++ b/services/sagemaker/inference_components.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -35,7 +35,7 @@ type InferenceComponentContainerSpec struct { Environment map[string]string `json:"Environment,omitempty"` ArtifactURL string `json:"ArtifactUrl,omitempty"` Image string `json:"Image,omitempty"` - ContainerMetricsConfig json.RawMessage `json:"ContainerMetricsConfig,omitempty"` + ContainerMetricsConfig jsontext.Value `json:"ContainerMetricsConfig,omitempty"` } // InferenceComponentSpecification mirrors types.InferenceComponentSpecification @@ -52,10 +52,10 @@ type InferenceComponentSpecification struct { BaseInferenceComponentName string `json:"BaseInferenceComponentName,omitempty"` InstanceType string `json:"InstanceType,omitempty"` ModelName string `json:"ModelName,omitempty"` - ComputeResourceRequirements json.RawMessage `json:"ComputeResourceRequirements,omitempty"` - DataCacheConfig json.RawMessage `json:"DataCacheConfig,omitempty"` - SchedulingConfig json.RawMessage `json:"SchedulingConfig,omitempty"` - StartupParameters json.RawMessage `json:"StartupParameters,omitempty"` + ComputeResourceRequirements jsontext.Value `json:"ComputeResourceRequirements,omitempty"` + DataCacheConfig jsontext.Value `json:"DataCacheConfig,omitempty"` + SchedulingConfig jsontext.Value `json:"SchedulingConfig,omitempty"` + StartupParameters jsontext.Value `json:"StartupParameters,omitempty"` } // inferenceComponentSpecificationSummary builds the InferenceComponentSpecificationSummary @@ -129,7 +129,7 @@ type InferenceComponent struct { VariantName string InferenceComponentStatus string FailureReason string - DeploymentConfig json.RawMessage + DeploymentConfig jsontext.Value Specifications []InferenceComponentSpecification CopyCount int32 CurrentCopyCount int32 @@ -369,7 +369,7 @@ func (b *InMemoryBackend) ListInferenceComponents( // VariantName member on the real input — a production variant is fixed at // Create time and cannot be moved via Update. type UpdateInferenceComponentOptions struct { - DeploymentConfig json.RawMessage + DeploymentConfig jsontext.Value RuntimeConfig *InferenceComponentRuntimeConfigInput Specification *InferenceComponentSpecification Specifications []InferenceComponentSpecification diff --git a/services/sagemaker/inference_experiments.go b/services/sagemaker/inference_experiments.go index 60eb9095a8..03a3bdba31 100644 --- a/services/sagemaker/inference_experiments.go +++ b/services/sagemaker/inference_experiments.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -10,6 +10,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrInferenceExperimentNotFound is returned when an inference experiment does not exist. @@ -206,7 +207,7 @@ func (e *InferenceExperiment) MarshalJSON() ([]byte, error) { CreationTime: epochSeconds(e.CreationTime), LastModifiedTime: epochSeconds(e.LastModifiedTime), ModelVariants: modelVariantConfigSummaries(e.ModelVariants), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [InferenceExperiment.MarshalJSON], read by @@ -221,7 +222,7 @@ func (e *InferenceExperiment) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(e)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/inference_recommendations_jobs.go b/services/sagemaker/inference_recommendations_jobs.go index 6bfe9a2d16..7c28918440 100644 --- a/services/sagemaker/inference_recommendations_jobs.go +++ b/services/sagemaker/inference_recommendations_jobs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -50,17 +52,17 @@ type InferenceRecommendationsJob struct { JobDescription string `json:"JobDescription,omitempty"` Status string `json:"Status"` RoleArn string `json:"RoleArn,omitempty"` - InputConfig json.RawMessage `json:"InputConfig,omitempty"` - OutputConfig json.RawMessage `json:"OutputConfig,omitempty"` - StoppingConditions json.RawMessage `json:"StoppingConditions,omitempty"` + InputConfig jsontext.Value `json:"InputConfig,omitempty"` + OutputConfig jsontext.Value `json:"OutputConfig,omitempty"` + StoppingConditions jsontext.Value `json:"StoppingConditions,omitempty"` } func cloneInferenceRecommendationsJob(j *InferenceRecommendationsJob) *InferenceRecommendationsJob { cp := *j cp.Tags = maps.Clone(j.Tags) - cp.InputConfig = append(json.RawMessage(nil), j.InputConfig...) - cp.OutputConfig = append(json.RawMessage(nil), j.OutputConfig...) - cp.StoppingConditions = append(json.RawMessage(nil), j.StoppingConditions...) + cp.InputConfig = append(jsontext.Value(nil), j.InputConfig...) + cp.OutputConfig = append(jsontext.Value(nil), j.OutputConfig...) + cp.StoppingConditions = append(jsontext.Value(nil), j.StoppingConditions...) return &cp } @@ -74,10 +76,10 @@ type inputConfigModelIdentity struct { ModelPackageVersionArn string `json:"ModelPackageVersionArn"` } -func decodeInputConfigModelIdentity(raw json.RawMessage) inputConfigModelIdentity { +func decodeInputConfigModelIdentity(raw jsontext.Value) inputConfigModelIdentity { var id inputConfigModelIdentity - _ = json.Unmarshal(raw, &id) + _ = json.Unmarshal(raw, &id, jsonx.V1()) return id } @@ -89,9 +91,9 @@ type CreateInferenceRecommendationsJobOptions struct { JobType string JobDescription string RoleArn string - InputConfig json.RawMessage - OutputConfig json.RawMessage - StoppingConditions json.RawMessage + InputConfig jsontext.Value + OutputConfig jsontext.Value + StoppingConditions jsontext.Value } // CreateInferenceRecommendationsJob creates an inference recommendations job. diff --git a/services/sagemaker/interfaces.go b/services/sagemaker/interfaces.go index 6b51700497..6863c90e7a 100644 --- a/services/sagemaker/interfaces.go +++ b/services/sagemaker/interfaces.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" ) // StorageBackend defines the interface for SageMaker backend implementations. @@ -47,7 +47,7 @@ type StorageBackend interface { kmsKeyID string, shadowProductionVariants []ProductionVariant, enableNetworkIsolation bool, - explainerConfig, metricsConfig json.RawMessage, + explainerConfig, metricsConfig jsontext.Value, ) error AddTags(ctx context.Context, resourceARN string, tags map[string]string) error diff --git a/services/sagemaker/jobs.go b/services/sagemaker/jobs.go index 8f5a37de82..69bb4270be 100644 --- a/services/sagemaker/jobs.go +++ b/services/sagemaker/jobs.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -74,7 +75,7 @@ func (t JobSecondaryStatusTransition) MarshalJSON() ([]byte, error) { alias: (*alias)(&t), StartTime: epochSeconds(t.StartTime), EndTime: epochSecondsPtr(t.EndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [JobSecondaryStatusTransition.MarshalJSON], @@ -88,7 +89,7 @@ func (t *JobSecondaryStatusTransition) UnmarshalJSON(data []byte) error { StartTime float64 `json:"StartTime"` }{alias: (*alias)(t)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -135,7 +136,7 @@ func (j *Job) MarshalJSON() ([]byte, error) { CreationTime: epochSeconds(j.CreationTime), LastModifiedTime: epochSeconds(j.LastModifiedTime), EndTime: epochSecondsPtr(j.EndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [Job.MarshalJSON], used by persistence.go's @@ -150,7 +151,7 @@ func (j *Job) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/list_summary_shapes_test.go b/services/sagemaker/list_summary_shapes_test.go index d0111aa6bf..2a6d061ccc 100644 --- a/services/sagemaker/list_summary_shapes_test.go +++ b/services/sagemaker/list_summary_shapes_test.go @@ -1,13 +1,15 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" sagemakersdk "github.com/aws/aws-sdk-go-v2/service/sagemaker" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListSummaries_MemberRoundTrips drives each op's Create then List @@ -199,7 +201,7 @@ func TestListInferenceExperiments_SummaryOmitsArn(t *testing.T) { var resp struct { InferenceExperiments []map[string]any `json:"InferenceExperiments"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.InferenceExperiments, 1) _, hasArn := resp.InferenceExperiments[0]["Arn"] @@ -244,7 +246,7 @@ func TestListTransformJobs_SummaryOmitsModelName(t *testing.T) { var resp struct { TransformJobSummaries []map[string]any `json:"TransformJobSummaries"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.TransformJobSummaries, 1) _, hasModelName := resp.TransformJobSummaries[0]["ModelName"] diff --git a/services/sagemaker/mlflow.go b/services/sagemaker/mlflow.go index 3789cf7aea..79a17e9ae2 100644 --- a/services/sagemaker/mlflow.go +++ b/services/sagemaker/mlflow.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -64,7 +65,7 @@ func (s *MlflowTrackingServer) MarshalJSON() ([]byte, error) { alias: (*alias)(s), CreationTime: epochSeconds(s.CreationTime), LastModifiedTime: epochSeconds(s.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [MlflowTrackingServer.MarshalJSON], read by @@ -78,7 +79,7 @@ func (s *MlflowTrackingServer) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(s)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/model_cards.go b/services/sagemaker/model_cards.go index ac0203308d..9075ffa174 100644 --- a/services/sagemaker/model_cards.go +++ b/services/sagemaker/model_cards.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "slices" @@ -12,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrModelCardNotFound is returned when a model card does not exist. @@ -83,7 +85,7 @@ func (c *ModelCard) MarshalJSON() ([]byte, error) { alias: (*alias)(c), CreationTime: epochSeconds(c.CreationTime), LastModifiedTime: epochSeconds(c.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ModelCard.MarshalJSON], read by @@ -97,7 +99,7 @@ func (c *ModelCard) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(c)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -222,22 +224,22 @@ var modelCardMetadataOnlyPaths = [][2]string{ // object sanitizes to "{}", since a non-object document trivially has none // of the preserved paths. func sanitizeModelCardContent(content string) string { - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - out := map[string]map[string]json.RawMessage{} + out := map[string]map[string]jsontext.Value{} - if unmarshalErr := json.Unmarshal([]byte(content), &doc); unmarshalErr == nil { + if unmarshalErr := json.Unmarshal([]byte(content), &doc, jsonx.V1()); unmarshalErr == nil { for _, path := range modelCardMetadataOnlyPaths { section, value := path[0], path[1] - var fields map[string]json.RawMessage - if json.Unmarshal(doc[section], &fields) != nil { + var fields map[string]jsontext.Value + if json.Unmarshal(doc[section], &fields, jsonx.V1()) != nil { continue } if v, ok := fields[value]; ok { if out[section] == nil { - out[section] = map[string]json.RawMessage{} + out[section] = map[string]jsontext.Value{} } out[section][value] = v @@ -245,7 +247,7 @@ func sanitizeModelCardContent(content string) string { } } - sanitized, err := json.Marshal(out) + sanitized, err := json.Marshal(out, jsonx.V1()) if err != nil { return "{}" } diff --git a/services/sagemaker/model_packages.go b/services/sagemaker/model_packages.go index 964609449e..a2be19ab5c 100644 --- a/services/sagemaker/model_packages.go +++ b/services/sagemaker/model_packages.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -12,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -128,7 +130,7 @@ func (g *ModelPackageGroup) MarshalJSON() ([]byte, error) { }{ alias: (*alias)(g), CreationTime: epochSeconds(g.CreationTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ModelPackageGroup.MarshalJSON], read by @@ -141,7 +143,7 @@ func (g *ModelPackageGroup) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(g)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -405,16 +407,16 @@ type CreateModelPackageOptions struct { ApprovalStatus string Tags map[string]string CustomerMetadataProperties map[string]string - InferenceSpecification json.RawMessage - SourceAlgorithmSpecification json.RawMessage - ValidationSpecification json.RawMessage - DriftCheckBaselines json.RawMessage - ModelMetrics json.RawMessage - ModelCard json.RawMessage - ModelLifeCycle json.RawMessage - MetadataProperties json.RawMessage - SecurityConfig json.RawMessage - AdditionalInferenceSpecifications json.RawMessage + InferenceSpecification jsontext.Value + SourceAlgorithmSpecification jsontext.Value + ValidationSpecification jsontext.Value + DriftCheckBaselines jsontext.Value + ModelMetrics jsontext.Value + ModelCard jsontext.Value + ModelLifeCycle jsontext.Value + MetadataProperties jsontext.Value + SecurityConfig jsontext.Value + AdditionalInferenceSpecifications jsontext.Value CertifyForMarketplace bool } @@ -698,10 +700,10 @@ type UpdateModelPackageOptions struct { SourceURI string CustomerMetadataProperties map[string]string CustomerMetadataPropertiesToRemove []string - InferenceSpecification json.RawMessage - ModelCard json.RawMessage - ModelLifeCycle json.RawMessage - AdditionalInferenceSpecificationsToAdd json.RawMessage + InferenceSpecification jsontext.Value + ModelCard jsontext.Value + ModelLifeCycle jsontext.Value + AdditionalInferenceSpecificationsToAdd jsontext.Value } // UpdateModelPackage updates a model package identified by its ARN (the sole @@ -776,22 +778,22 @@ func (b *InMemoryBackend) UpdateModelPackage( // the existing array". Both are opaque json.RawMessage passthrough (see // ModelPackage's own doc comment), so this manipulates them as raw JSON // arrays rather than unmarshaling into a typed element. -func appendInferenceSpecifications(existing, toAdd json.RawMessage) json.RawMessage { - var newItems []json.RawMessage - if err := json.Unmarshal(toAdd, &newItems); err != nil { +func appendInferenceSpecifications(existing, toAdd jsontext.Value) jsontext.Value { + var newItems []jsontext.Value + if err := json.Unmarshal(toAdd, &newItems, jsonx.V1()); err != nil { return existing } - existingItems := make([]json.RawMessage, 0, len(newItems)) + existingItems := make([]jsontext.Value, 0, len(newItems)) if len(existing) > 0 { - if err := json.Unmarshal(existing, &existingItems); err != nil { + if err := json.Unmarshal(existing, &existingItems, jsonx.V1()); err != nil { return existing } } existingItems = append(existingItems, newItems...) - out, err := json.Marshal(existingItems) + out, err := json.Marshal(existingItems, jsonx.V1()) if err != nil { return existing } diff --git a/services/sagemaker/models.go b/services/sagemaker/models.go index 041486e2e7..80be4bee14 100644 --- a/services/sagemaker/models.go +++ b/services/sagemaker/models.go @@ -1,10 +1,13 @@ package sagemaker import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ImageConfig specifies where SageMaker should pull the container image from. @@ -139,7 +142,7 @@ func (pv *ProductionVariant) UnmarshalJSON(data []byte) error { type plain ProductionVariant aux := plain{InitialVariantWeight: defaultVariantWeight} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -157,7 +160,7 @@ func (pv ProductionVariant) MarshalJSON() ([]byte, error) { plain InitialVariantWeight float64 `json:"InitialVariantWeight"` - }{plain: plain(pv), InitialVariantWeight: pv.InitialVariantWeight}) + }{plain: plain(pv), InitialVariantWeight: pv.InitialVariantWeight}, jsonx.V1()) } // DataCaptureConfig specifies real-time data capture for an endpoint config. @@ -219,8 +222,8 @@ type EndpointConfig struct { VpcConfig *VpcConfig `json:"VpcConfig,omitempty"` DataCaptureConfig *DataCaptureConfig `json:"DataCaptureConfig,omitempty"` AsyncInferenceConfig *AsyncInferenceConfig `json:"AsyncInferenceConfig,omitempty"` - ExplainerConfig json.RawMessage `json:"ExplainerConfig,omitempty"` - MetricsConfig json.RawMessage `json:"MetricsConfig,omitempty"` + ExplainerConfig jsontext.Value `json:"ExplainerConfig,omitempty"` + MetricsConfig jsontext.Value `json:"MetricsConfig,omitempty"` EndpointConfigName string `json:"EndpointConfigName"` EndpointConfigARN string `json:"EndpointConfigARN"` ExecutionRoleArn string `json:"ExecutionRoleArn,omitempty"` @@ -275,8 +278,8 @@ func cloneEndpointConfig(ec *EndpointConfig) *EndpointConfig { aic := *ec.AsyncInferenceConfig cp.AsyncInferenceConfig = &aic } - cp.ExplainerConfig = append(json.RawMessage(nil), ec.ExplainerConfig...) - cp.MetricsConfig = append(json.RawMessage(nil), ec.MetricsConfig...) + cp.ExplainerConfig = append(jsontext.Value(nil), ec.ExplainerConfig...) + cp.MetricsConfig = append(jsontext.Value(nil), ec.MetricsConfig...) return &cp } @@ -360,9 +363,9 @@ type Algorithm struct { AlgorithmStatus string `json:"AlgorithmStatus"` ProductID string `json:"ProductId,omitempty"` AlgorithmStatusDetails AlgorithmStatusDetails `json:"AlgorithmStatusDetails"` - TrainingSpecification json.RawMessage `json:"TrainingSpecification,omitempty"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification,omitempty"` - ValidationSpecification json.RawMessage `json:"ValidationSpecification,omitempty"` + TrainingSpecification jsontext.Value `json:"TrainingSpecification,omitempty"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification,omitempty"` + ValidationSpecification jsontext.Value `json:"ValidationSpecification,omitempty"` CertifyForMarketplace bool `json:"CertifyForMarketplace,omitempty"` } @@ -370,9 +373,9 @@ type Algorithm struct { func cloneAlgorithm(al *Algorithm) *Algorithm { cp := *al cp.Tags = maps.Clone(al.Tags) - cp.TrainingSpecification = append(json.RawMessage(nil), al.TrainingSpecification...) - cp.InferenceSpecification = append(json.RawMessage(nil), al.InferenceSpecification...) - cp.ValidationSpecification = append(json.RawMessage(nil), al.ValidationSpecification...) + cp.TrainingSpecification = append(jsontext.Value(nil), al.TrainingSpecification...) + cp.InferenceSpecification = append(jsontext.Value(nil), al.InferenceSpecification...) + cp.ValidationSpecification = append(jsontext.Value(nil), al.ValidationSpecification...) cp.AlgorithmStatusDetails.ImageScanStatuses = append( []AlgorithmStatusItem(nil), al.AlgorithmStatusDetails.ImageScanStatuses..., ) @@ -847,18 +850,18 @@ type ModelPackage struct { LastModifiedTime time.Time `json:"LastModifiedTime,omitzero"` Tags map[string]string `json:"Tags,omitempty"` CustomerMetadataProperties map[string]string `json:"CustomerMetadataProperties,omitempty"` - InferenceSpecification json.RawMessage `json:"InferenceSpecification,omitempty"` - SourceAlgorithmSpecification json.RawMessage `json:"SourceAlgorithmSpecification,omitempty"` - ValidationSpecification json.RawMessage `json:"ValidationSpecification,omitempty"` - DriftCheckBaselines json.RawMessage `json:"DriftCheckBaselines,omitempty"` - ModelMetrics json.RawMessage `json:"ModelMetrics,omitempty"` - ModelCard json.RawMessage `json:"ModelCard,omitempty"` - ModelLifeCycle json.RawMessage `json:"ModelLifeCycle,omitempty"` + InferenceSpecification jsontext.Value `json:"InferenceSpecification,omitempty"` + SourceAlgorithmSpecification jsontext.Value `json:"SourceAlgorithmSpecification,omitempty"` + ValidationSpecification jsontext.Value `json:"ValidationSpecification,omitempty"` + DriftCheckBaselines jsontext.Value `json:"DriftCheckBaselines,omitempty"` + ModelMetrics jsontext.Value `json:"ModelMetrics,omitempty"` + ModelCard jsontext.Value `json:"ModelCard,omitempty"` + ModelLifeCycle jsontext.Value `json:"ModelLifeCycle,omitempty"` CreatedBy *UserContext `json:"CreatedBy,omitempty"` LastModifiedBy *UserContext `json:"LastModifiedBy,omitempty"` - MetadataProperties json.RawMessage `json:"MetadataProperties,omitempty"` - SecurityConfig json.RawMessage `json:"SecurityConfig,omitempty"` - AdditionalInferenceSpecifications json.RawMessage `json:"AdditionalInferenceSpecifications,omitempty"` + MetadataProperties jsontext.Value `json:"MetadataProperties,omitempty"` + SecurityConfig jsontext.Value `json:"SecurityConfig,omitempty"` + AdditionalInferenceSpecifications jsontext.Value `json:"AdditionalInferenceSpecifications,omitempty"` ModelPackageName string `json:"ModelPackageName"` ModelPackageArn string `json:"ModelPackageArn"` ModelPackageGroupName string `json:"ModelPackageGroupName,omitempty"` @@ -891,16 +894,16 @@ func cloneModelPackage(mp *ModelPackage) *ModelPackage { cp.ModelPackageStatusDetails.ImageScanStatuses = append( []ModelPackageStatusItem{}, mp.ModelPackageStatusDetails.ImageScanStatuses..., ) - cp.InferenceSpecification = append(json.RawMessage(nil), mp.InferenceSpecification...) - cp.SourceAlgorithmSpecification = append(json.RawMessage(nil), mp.SourceAlgorithmSpecification...) - cp.ValidationSpecification = append(json.RawMessage(nil), mp.ValidationSpecification...) - cp.DriftCheckBaselines = append(json.RawMessage(nil), mp.DriftCheckBaselines...) - cp.ModelMetrics = append(json.RawMessage(nil), mp.ModelMetrics...) - cp.ModelCard = append(json.RawMessage(nil), mp.ModelCard...) - cp.ModelLifeCycle = append(json.RawMessage(nil), mp.ModelLifeCycle...) - cp.MetadataProperties = append(json.RawMessage(nil), mp.MetadataProperties...) - cp.SecurityConfig = append(json.RawMessage(nil), mp.SecurityConfig...) - cp.AdditionalInferenceSpecifications = append(json.RawMessage(nil), mp.AdditionalInferenceSpecifications...) + cp.InferenceSpecification = append(jsontext.Value(nil), mp.InferenceSpecification...) + cp.SourceAlgorithmSpecification = append(jsontext.Value(nil), mp.SourceAlgorithmSpecification...) + cp.ValidationSpecification = append(jsontext.Value(nil), mp.ValidationSpecification...) + cp.DriftCheckBaselines = append(jsontext.Value(nil), mp.DriftCheckBaselines...) + cp.ModelMetrics = append(jsontext.Value(nil), mp.ModelMetrics...) + cp.ModelCard = append(jsontext.Value(nil), mp.ModelCard...) + cp.ModelLifeCycle = append(jsontext.Value(nil), mp.ModelLifeCycle...) + cp.MetadataProperties = append(jsontext.Value(nil), mp.MetadataProperties...) + cp.SecurityConfig = append(jsontext.Value(nil), mp.SecurityConfig...) + cp.AdditionalInferenceSpecifications = append(jsontext.Value(nil), mp.AdditionalInferenceSpecifications...) return &cp } @@ -923,7 +926,7 @@ func (mp *ModelPackage) MarshalJSON() ([]byte, error) { aux.LastModifiedTime = epochSeconds(mp.LastModifiedTime) } - return json.Marshal(aux) + return json.Marshal(aux, jsonx.V1()) } // UnmarshalJSON is the inverse of [ModelPackage.MarshalJSON], read by @@ -937,7 +940,7 @@ func (mp *ModelPackage) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(mp)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/monitoring_job_definitions.go b/services/sagemaker/monitoring_job_definitions.go index 5374f4ccc3..327b496bf0 100644 --- a/services/sagemaker/monitoring_job_definitions.go +++ b/services/sagemaker/monitoring_job_definitions.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -46,14 +46,14 @@ var ( // StoppingCondition/BaselineConfig blocks, so Describe echoes back exactly // what Create received. type JobDefinition struct { - CreationTime time.Time `json:"CreationTime"` - Tags map[string]string `json:"Tags,omitempty"` - Config map[string]json.RawMessage `json:"Config,omitempty"` - JobDefinitionName string `json:"JobDefinitionName"` - JobDefinitionArn string `json:"JobDefinitionArn"` - JobDefinitionType string `json:"JobDefinitionType"` - RoleArn string `json:"RoleArn,omitempty"` - EndpointName string `json:"EndpointName,omitempty"` + CreationTime time.Time `json:"CreationTime"` + Tags map[string]string `json:"Tags,omitempty"` + Config map[string]jsontext.Value `json:"Config,omitempty"` + JobDefinitionName string `json:"JobDefinitionName"` + JobDefinitionArn string `json:"JobDefinitionArn"` + JobDefinitionType string `json:"JobDefinitionType"` + RoleArn string `json:"RoleArn,omitempty"` + EndpointName string `json:"EndpointName,omitempty"` } func cloneJobDefinition(j *JobDefinition) *JobDefinition { @@ -71,7 +71,7 @@ func (b *InMemoryBackend) createJobDefinition( ctx context.Context, storeFn func(string) *store.Table[JobDefinition], defType, name, roleArn, endpointName string, - config map[string]json.RawMessage, + config map[string]jsontext.Value, tags map[string]string, resourceType string, alreadyExists error, @@ -227,7 +227,7 @@ func (b *InMemoryBackend) listJobDefinitions( func (b *InMemoryBackend) CreateDataQualityJobDefinition( ctx context.Context, name, roleArn, endpointName string, - config map[string]json.RawMessage, + config map[string]jsontext.Value, tags map[string]string, ) (*JobDefinition, error) { return b.createJobDefinition( @@ -266,7 +266,7 @@ func (b *InMemoryBackend) ListDataQualityJobDefinitions( func (b *InMemoryBackend) CreateModelBiasJobDefinition( ctx context.Context, name, roleArn, endpointName string, - config map[string]json.RawMessage, + config map[string]jsontext.Value, tags map[string]string, ) (*JobDefinition, error) { return b.createJobDefinition( @@ -305,7 +305,7 @@ func (b *InMemoryBackend) ListModelBiasJobDefinitions( func (b *InMemoryBackend) CreateModelQualityJobDefinition( ctx context.Context, name, roleArn, endpointName string, - config map[string]json.RawMessage, + config map[string]jsontext.Value, tags map[string]string, ) (*JobDefinition, error) { return b.createJobDefinition( @@ -344,7 +344,7 @@ func (b *InMemoryBackend) ListModelQualityJobDefinitions( func (b *InMemoryBackend) CreateModelExplainabilityJobDefinition( ctx context.Context, name, roleArn, endpointName string, - config map[string]json.RawMessage, + config map[string]jsontext.Value, tags map[string]string, ) (*JobDefinition, error) { return b.createJobDefinition( diff --git a/services/sagemaker/monitoring_schedules.go b/services/sagemaker/monitoring_schedules.go index 10e0acb1e6..0ef320fc15 100644 --- a/services/sagemaker/monitoring_schedules.go +++ b/services/sagemaker/monitoring_schedules.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -45,7 +47,7 @@ type MonitoringScheduleConfig struct { ScheduleConfig *MonitoringScheduleSchedule `json:"ScheduleConfig,omitempty"` MonitoringJobDefinitionName string `json:"MonitoringJobDefinitionName,omitempty"` MonitoringType string `json:"MonitoringType,omitempty"` - MonitoringJobDefinition json.RawMessage `json:"MonitoringJobDefinition,omitempty"` + MonitoringJobDefinition jsontext.Value `json:"MonitoringJobDefinition,omitempty"` } // MonitoringSchedule represents a SageMaker monitoring schedule. @@ -67,7 +69,7 @@ func cloneMonitoringSchedule(ms *MonitoringSchedule) *MonitoringSchedule { if ms.MonitoringScheduleConfig != nil { cfg := *ms.MonitoringScheduleConfig cfg.MonitoringJobDefinition = append( - json.RawMessage(nil), + jsontext.Value(nil), ms.MonitoringScheduleConfig.MonitoringJobDefinition...) if ms.MonitoringScheduleConfig.ScheduleConfig != nil { @@ -106,7 +108,7 @@ func (ms *MonitoringSchedule) MarshalJSON() ([]byte, error) { CreationTime: epochSeconds(ms.CreationTime), LastModifiedTime: epochSeconds(ms.LastModifiedTime), MonitoringType: monitoringType, - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [MonitoringSchedule.MarshalJSON], read by @@ -120,7 +122,7 @@ func (ms *MonitoringSchedule) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(ms)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -141,7 +143,7 @@ func (ms *MonitoringSchedule) UnmarshalJSON(data []byte) error { // raw is empty, unparseable, or has no endpoint input (e.g. a batch // transform monitoring job, or a schedule referencing a job definition by // name only). -func monitoringEndpointNameFromJobDefinition(raw json.RawMessage) string { +func monitoringEndpointNameFromJobDefinition(raw jsontext.Value) string { var doc struct { MonitoringInputs []struct { EndpointInput *struct { @@ -150,7 +152,7 @@ func monitoringEndpointNameFromJobDefinition(raw json.RawMessage) string { } `json:"MonitoringInputs"` } - if len(raw) == 0 || json.Unmarshal(raw, &doc) != nil { + if len(raw) == 0 || json.Unmarshal(raw, &doc, jsonx.V1()) != nil { return "" } diff --git a/services/sagemaker/notebook_url_test.go b/services/sagemaker/notebook_url_test.go index 9bc0279bf0..c0f774b0cf 100644 --- a/services/sagemaker/notebook_url_test.go +++ b/services/sagemaker/notebook_url_test.go @@ -1,13 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -42,7 +43,7 @@ func TestNotebookInstance_UrlHost(t *testing.T) { var out struct { URL string `json:"Url"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.want, out.URL) }) } diff --git a/services/sagemaker/optimization_jobs.go b/services/sagemaker/optimization_jobs.go index 11f89df379..aa681ae3b6 100644 --- a/services/sagemaker/optimization_jobs.go +++ b/services/sagemaker/optimization_jobs.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrOptimizationJobNotFound is returned when an optimization job does not exist. @@ -45,7 +47,7 @@ func optimizationConfigTypeName(key string) (string, bool) { // optimizationTypesOf derives OptimizationJobSummary's required // OptimizationTypes from the raw OptimizationConfigs the client sent, by // checking which union member keys appear in the JSON. -func optimizationTypesOf(rawConfigs json.RawMessage) []string { +func optimizationTypesOf(rawConfigs jsontext.Value) []string { // Every OptimizationConfig union member's wire key, in the same order // optimizationConfigTypeName recognizes them. wireKeys := [...]string{ @@ -84,11 +86,11 @@ type OptimizationJob struct { OptimizationJobArn string `json:"OptimizationJobArn"` RoleArn string `json:"RoleArn,omitempty"` DeploymentInstanceType string `json:"DeploymentInstanceType,omitempty"` - OutputConfig json.RawMessage `json:"OutputConfig,omitempty"` - VpcConfig json.RawMessage `json:"VpcConfig,omitempty"` - OptimizationConfigs json.RawMessage `json:"OptimizationConfigs,omitempty"` + OutputConfig jsontext.Value `json:"OutputConfig,omitempty"` + VpcConfig jsontext.Value `json:"VpcConfig,omitempty"` + OptimizationConfigs jsontext.Value `json:"OptimizationConfigs,omitempty"` TrainingPlanArns []string `json:"TrainingPlanArns,omitempty"` - ModelSource json.RawMessage `json:"ModelSource,omitempty"` + ModelSource jsontext.Value `json:"ModelSource,omitempty"` MaxInstanceCount int32 `json:"MaxInstanceCount,omitempty"` } @@ -96,10 +98,10 @@ func cloneOptimizationJob(j *OptimizationJob) *OptimizationJob { cp := *j cp.Tags = maps.Clone(j.Tags) cp.OptimizationEnvironment = maps.Clone(j.OptimizationEnvironment) - cp.ModelSource = append(json.RawMessage(nil), j.ModelSource...) - cp.OptimizationConfigs = append(json.RawMessage(nil), j.OptimizationConfigs...) - cp.OutputConfig = append(json.RawMessage(nil), j.OutputConfig...) - cp.VpcConfig = append(json.RawMessage(nil), j.VpcConfig...) + cp.ModelSource = append(jsontext.Value(nil), j.ModelSource...) + cp.OptimizationConfigs = append(jsontext.Value(nil), j.OptimizationConfigs...) + cp.OutputConfig = append(jsontext.Value(nil), j.OutputConfig...) + cp.VpcConfig = append(jsontext.Value(nil), j.VpcConfig...) cp.TrainingPlanArns = append([]string(nil), j.TrainingPlanArns...) if j.StoppingCondition != nil { @@ -128,7 +130,7 @@ func (j *OptimizationJob) MarshalJSON() ([]byte, error) { LastModifiedTime: epochSeconds(j.LastModifiedTime), OptimizationStartTime: epochSecondsPtr(j.OptimizationStartTime), OptimizationEndTime: epochSecondsPtr(j.OptimizationEndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [OptimizationJob.MarshalJSON], read by @@ -144,7 +146,7 @@ func (j *OptimizationJob) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(j)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -165,10 +167,10 @@ type CreateOptimizationJobOptions struct { Name string RoleArn string DeploymentInstanceType string - ModelSource json.RawMessage - OptimizationConfigs json.RawMessage - OutputConfig json.RawMessage - VpcConfig json.RawMessage + ModelSource jsontext.Value + OptimizationConfigs jsontext.Value + OutputConfig jsontext.Value + VpcConfig jsontext.Value TrainingPlanArns []string MaxInstanceCount int32 } diff --git a/services/sagemaker/partner_apps.go b/services/sagemaker/partner_apps.go index b7e5eb976a..797570fafa 100644 --- a/services/sagemaker/partner_apps.go +++ b/services/sagemaker/partner_apps.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -40,7 +40,7 @@ type PartnerApp struct { Tier string `json:"Tier,omitempty"` KmsKeyID string `json:"KmsKeyId,omitempty"` BaseURL string `json:"-"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig,omitempty"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig,omitempty"` EnableAutoMinorVersionUpgrade bool `json:"EnableAutoMinorVersionUpgrade,omitempty"` EnableIamSessionBasedIdentity bool `json:"EnableIamSessionBasedIdentity,omitempty"` } @@ -48,7 +48,7 @@ type PartnerApp struct { func clonePartnerApp(p *PartnerApp) *PartnerApp { cp := *p cp.Tags = maps.Clone(p.Tags) - cp.ApplicationConfig = append(json.RawMessage(nil), p.ApplicationConfig...) + cp.ApplicationConfig = append(jsontext.Value(nil), p.ApplicationConfig...) if p.MaintenanceConfig != nil { mc := *p.MaintenanceConfig @@ -81,7 +81,7 @@ type CreatePartnerAppOptions struct { AuthType string Tier string KmsKeyID string - ApplicationConfig json.RawMessage + ApplicationConfig jsontext.Value EnableAutoMinorVersionUpgrade bool EnableIamSessionBasedIdentity bool } @@ -178,7 +178,7 @@ type UpdatePartnerAppOptions struct { EnableIamSessionBasedIdentity *bool Arn string Tier string - ApplicationConfig json.RawMessage + ApplicationConfig jsontext.Value } // UpdatePartnerApp updates a partner app's mutable fields. diff --git a/services/sagemaker/persistence.go b/services/sagemaker/persistence.go index d583803c30..c43d0ce8db 100644 --- a/services/sagemaker/persistence.go +++ b/services/sagemaker/persistence.go @@ -2,11 +2,13 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -158,7 +160,7 @@ func hubContentKeyString(k hubContentKey) string { // each one doesn't fit the Table[V] keyed-by-string-derived-from-V model — // so they are persisted directly here instead of via Tables. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ImageVersions map[string]map[string]map[int]*ImageVersion `json:"imageVersions,omitempty"` ImageVersionCounts map[string]map[string]int `json:"imageVersionCounts,omitempty"` MonitoringAlertHistory map[string][]*MonitoringAlertHistoryEntry `json:"monitoringAlertHistory,omitempty"` @@ -193,7 +195,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { dtos[i] = toPersistedCluster(c) } - data, marshalErr := json.Marshal(dtos) + data, marshalErr := json.Marshal(dtos, jsonx.V1()) if marshalErr != nil { logger.Load(ctx).WarnContext(ctx, "sagemaker: snapshot cluster table marshal failed", "region", region, "error", marshalErr) @@ -256,7 +258,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // b.clusters here; RestoreAll's "reset tables absent from data" behavior // (since we've stripped every "clusters:*" key from snap.Tables) clears // any such region's table if it was already registered. - clusterTables := make(map[string]json.RawMessage) + clusterTables := make(map[string]jsontext.Value) for name, raw := range snap.Tables { if region, ok := strings.CutPrefix(name, clusterTablePrefix); ok { @@ -281,7 +283,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { for region, raw := range clusterTables { var dtos []*persistedCluster - if err := json.Unmarshal(raw, &dtos); err != nil { + if err := json.Unmarshal(raw, &dtos, jsonx.V1()); err != nil { return fmt.Errorf("sagemaker: restore cluster table %q: %w", region, err) } @@ -543,7 +545,7 @@ var tableRegistrars = map[string]func(*InMemoryBackend, string){ // key present in raw — see tableRegistrars — so the subsequent // registry.RestoreAll call actually receives every table the snapshot // contains, not just whichever ones happen to already be registered. -func (b *InMemoryBackend) ensureTablesRegistered(raw map[string]json.RawMessage) { +func (b *InMemoryBackend) ensureTablesRegistered(raw map[string]jsontext.Value) { for name := range raw { field, region, cut := strings.Cut(name, ":") if !cut { diff --git a/services/sagemaker/persistence_test.go b/services/sagemaker/persistence_test.go index be88ff4fd6..62705bb02c 100644 --- a/services/sagemaker/persistence_test.go +++ b/services/sagemaker/persistence_test.go @@ -2,7 +2,7 @@ package sagemaker_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -104,19 +105,19 @@ func TestPersistenceRoundtrip_PolicyCatalogAndPipelineVersions(t *testing.T) { require.Equal(t, http.StatusOK, recPolicy.Code) var policyOut map[string]any - require.NoError(t, json.Unmarshal(recPolicy.Body.Bytes(), &policyOut)) + require.NoError(t, json.Unmarshal(recPolicy.Body.Bytes(), &policyOut, jsonx.V1())) assert.JSONEq(t, `{"Version":"2012-10-17"}`, policyOut["ResourcePolicy"].(string)) recStatus := doSageMakerRequest(t, h2, "GetSagemakerServicecatalogPortfolioStatus", map[string]any{}) var statusOut map[string]any - require.NoError(t, json.Unmarshal(recStatus.Body.Bytes(), &statusOut)) + require.NoError(t, json.Unmarshal(recStatus.Body.Bytes(), &statusOut, jsonx.V1())) assert.Equal(t, "Enabled", statusOut["Status"]) recVersions := doSageMakerRequest(t, h2, "ListPipelineVersions", map[string]any{"PipelineName": "persist-pipeline"}) require.Equal(t, http.StatusOK, recVersions.Code) var versionsOut map[string]any - require.NoError(t, json.Unmarshal(recVersions.Body.Bytes(), &versionsOut)) + require.NoError(t, json.Unmarshal(recVersions.Body.Bytes(), &versionsOut, jsonx.V1())) versions, _ := versionsOut["PipelineVersionSummaries"].([]any) assert.Len(t, versions, 2) } @@ -201,7 +202,7 @@ func TestPersistenceRoundtrip_ClusterFullFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "arn:aws:iam::000000000000:role/HyperPodClusterRole", resp["ClusterRole"]) @@ -297,7 +298,7 @@ func TestPersistenceRoundtrip_PipelineDefinitionFromS3(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.JSONEq(t, `{"Version":"2020-12-01","Steps":[{"Name":"Step1"}]}`, resp["PipelineDefinition"].(string)) } @@ -434,7 +435,7 @@ func TestPersistenceRoundtrip_AIAndGenericJobFamilies(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantValue, out[tt.wantField]) }) } @@ -458,7 +459,7 @@ func TestPersistenceRoundtrip_Domain(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) domainID := createOut["DomainId"].(string) snap := h1.Snapshot(t.Context()) @@ -471,7 +472,7 @@ func TestPersistenceRoundtrip_Domain(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, descRec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "VpcOnly", out["AppNetworkAccessType"]) assert.ElementsMatch(t, []any{"subnet-1"}, out["SubnetIds"]) assert.NotEmpty(t, out["DefaultUserSettings"]) diff --git a/services/sagemaker/pipeline_executions.go b/services/sagemaker/pipeline_executions.go index 8dd1fa1ef0..b673c27ca4 100644 --- a/services/sagemaker/pipeline_executions.go +++ b/services/sagemaker/pipeline_executions.go @@ -2,11 +2,13 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -285,7 +287,7 @@ func pipelineDefinitionSteps(definition string) []definedPipelineStep { Steps []definedPipelineStep `json:"Steps"` } - if json.Unmarshal([]byte(definition), &doc) != nil { + if json.Unmarshal([]byte(definition), &doc, jsonx.V1()) != nil { return nil } diff --git a/services/sagemaker/projects.go b/services/sagemaker/projects.go index 8bc3b60086..29547d98c5 100644 --- a/services/sagemaker/projects.go +++ b/services/sagemaker/projects.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrProjectNotFound is returned when a project does not exist. @@ -37,8 +39,8 @@ type Project struct { ProjectStatus string `json:"ProjectStatus"` ProjectDescription string `json:"ProjectDescription,omitempty"` ProvisionedProductID string `json:"ProvisionedProductId,omitempty"` - ServiceCatalogProvisioningDetails json.RawMessage `json:"ServiceCatalogProvisioningDetails,omitempty"` - TemplateProviders json.RawMessage `json:"TemplateProviders,omitempty"` + ServiceCatalogProvisioningDetails jsontext.Value `json:"ServiceCatalogProvisioningDetails,omitempty"` + TemplateProviders jsontext.Value `json:"TemplateProviders,omitempty"` } func cloneProject(p *Project) *Project { @@ -46,8 +48,8 @@ func cloneProject(p *Project) *Project { cp.Tags = maps.Clone(p.Tags) cp.CreatedBy = cloneUserContext(p.CreatedBy) cp.LastModifiedBy = cloneUserContext(p.LastModifiedBy) - cp.ServiceCatalogProvisioningDetails = append(json.RawMessage(nil), p.ServiceCatalogProvisioningDetails...) - cp.TemplateProviders = append(json.RawMessage(nil), p.TemplateProviders...) + cp.ServiceCatalogProvisioningDetails = append(jsontext.Value(nil), p.ServiceCatalogProvisioningDetails...) + cp.TemplateProviders = append(jsontext.Value(nil), p.TemplateProviders...) return &cp } @@ -68,7 +70,7 @@ func (p *Project) MarshalJSON() ([]byte, error) { alias: (*alias)(p), CreationTime: epochSeconds(p.CreationTime), LastModifiedTime: epochSeconds(p.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [Project.MarshalJSON], read by @@ -82,7 +84,7 @@ func (p *Project) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(p)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -97,8 +99,8 @@ type CreateProjectOptions struct { Tags map[string]string Name string Description string - ServiceCatalogProvisioningDetails json.RawMessage - TemplateProviders json.RawMessage + ServiceCatalogProvisioningDetails jsontext.Value + TemplateProviders jsontext.Value } // CreateProject creates a SageMaker project. diff --git a/services/sagemaker/projects_update.go b/services/sagemaker/projects_update.go index 085ce2e530..0811944eed 100644 --- a/services/sagemaker/projects_update.go +++ b/services/sagemaker/projects_update.go @@ -1,8 +1,11 @@ package sagemaker import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // UpdateProjectOptions holds the parameters for UpdateProject. @@ -35,7 +38,7 @@ type updateTemplateProviderInput struct { // applyProvisioningUpdate merges ServiceCatalogProvisioningUpdateDetails // (ProvisioningArtifactId, ProvisioningParameters) into the stored details. -func applyProvisioningUpdate(stored json.RawMessage, upd *serviceCatalogUpdateDetails) (json.RawMessage, error) { +func applyProvisioningUpdate(stored jsontext.Value, upd *serviceCatalogUpdateDetails) (jsontext.Value, error) { if upd == nil { return stored, nil } @@ -47,7 +50,7 @@ func applyProvisioningUpdate(stored json.RawMessage, upd *serviceCatalogUpdateDe } var details map[string]any - if err := json.Unmarshal(stored, &details); err != nil { + if err := json.Unmarshal(stored, &details, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: stored provisioning details: %w", ErrValidation, err) } @@ -59,22 +62,22 @@ func applyProvisioningUpdate(stored json.RawMessage, upd *serviceCatalogUpdateDe details["ProvisioningParameters"] = upd.ProvisioningParameters } - return json.Marshal(details) + return json.Marshal(details, jsonx.V1()) } // applyTemplateProviderUpdates replaces TemplateURL/Parameters of each stored // CfnTemplateProvider matched by TemplateName. func applyTemplateProviderUpdates( - stored json.RawMessage, + stored jsontext.Value, updates []cfnUpdateTemplateProvider, -) (json.RawMessage, error) { +) (jsontext.Value, error) { if len(updates) == 0 { return stored, nil } var providers []map[string]map[string]any if len(stored) > 0 { - if err := json.Unmarshal(stored, &providers); err != nil { + if err := json.Unmarshal(stored, &providers, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: stored template providers: %w", ErrValidation, err) } } @@ -85,7 +88,7 @@ func applyTemplateProviderUpdates( } } - return json.Marshal(providers) + return json.Marshal(providers, jsonx.V1()) } func updateOneTemplateProvider(providers []map[string]map[string]any, u cfnUpdateTemplateProvider) bool { @@ -108,9 +111,9 @@ func updateOneTemplateProvider(providers []map[string]map[string]any, u cfnUpdat // templateProviderDetails converts stored CreateTemplateProvider entries to the // TemplateProviderDetail wire shape (CfnTemplateProviderDetail key). -func templateProviderDetails(stored json.RawMessage) []map[string]any { +func templateProviderDetails(stored jsontext.Value) []map[string]any { var providers []map[string]map[string]any - if err := json.Unmarshal(stored, &providers); err != nil { + if err := json.Unmarshal(stored, &providers, jsonx.V1()); err != nil { return nil } diff --git a/services/sagemaker/request_realism_test.go b/services/sagemaker/request_realism_test.go index ac36d79e39..47017fde73 100644 --- a/services/sagemaker/request_realism_test.go +++ b/services/sagemaker/request_realism_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const realismRole = "arn:aws:iam::000000000000:role/r" @@ -37,7 +39,7 @@ func errorOf(t *testing.T, body []byte) (string, string) { t.Helper() var out map[string]string - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out["__type"], out["message"] } @@ -188,7 +190,7 @@ func TestRequestRealism_TrainingSecondaryStatusProgression(t *testing.T) { r := doSageMakerRequest(t, h, "DescribeTrainingJob", map[string]any{"TrainingJobName": "progress"}) got.SecondaryStatusTransitions = nil - return json.Unmarshal(r.Body.Bytes(), &got) == nil && got.TrainingJobStatus == "Completed" + return json.Unmarshal(r.Body.Bytes(), &got, jsonx.V1()) == nil && got.TrainingJobStatus == "Completed" }, 5*time.Second, 5*time.Millisecond) statuses := make([]string, 0, len(got.SecondaryStatusTransitions)) @@ -217,7 +219,7 @@ func TestRequestRealism_StopCompletedTrainingJobIsNoop(t *testing.T) { } r := doSageMakerRequest(t, h, "DescribeTrainingJob", map[string]any{"TrainingJobName": "done"}) - return json.Unmarshal(r.Body.Bytes(), &out) == nil && out.TrainingJobStatus == "Completed" + return json.Unmarshal(r.Body.Bytes(), &out, jsonx.V1()) == nil && out.TrainingJobStatus == "Completed" }, 5*time.Second, 5*time.Millisecond) require.Equal(t, http.StatusOK, @@ -227,7 +229,7 @@ func TestRequestRealism_StopCompletedTrainingJobIsNoop(t *testing.T) { TrainingJobStatus string `json:"TrainingJobStatus"` } r := doSageMakerRequest(t, h, "DescribeTrainingJob", map[string]any{"TrainingJobName": "done"}) - require.NoError(t, json.Unmarshal(r.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(r.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "Completed", out.TrainingJobStatus) }) } @@ -254,7 +256,7 @@ func TestRequestRealism_StopFinishedProcessingJobKeepsStatus(t *testing.T) { ProcessingJobStatus string `json:"ProcessingJobStatus"` } r := doSageMakerRequest(t, h, "DescribeProcessingJob", map[string]any{"ProcessingJobName": "pj"}) - _ = json.Unmarshal(r.Body.Bytes(), &out) + _ = json.Unmarshal(r.Body.Bytes(), &out, jsonx.V1()) return out.ProcessingJobStatus } diff --git a/services/sagemaker/request_validation.go b/services/sagemaker/request_validation.go index 60d2d10076..f6c428b0f5 100644 --- a/services/sagemaker/request_validation.go +++ b/services/sagemaker/request_validation.go @@ -1,13 +1,14 @@ package sagemaker import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strconv" "strings" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -152,7 +153,7 @@ func validateListPaging(op string, body []byte) error { NextToken string `json:"NextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if m := req.MaxResults; m != nil && (*m < minListMaxResults || *m > maxListMaxResults) { return fmt.Errorf( diff --git a/services/sagemaker/shape_validation.go b/services/sagemaker/shape_validation.go index 0d0483532f..51efcee9af 100644 --- a/services/sagemaker/shape_validation.go +++ b/services/sagemaker/shape_validation.go @@ -3,12 +3,15 @@ package sagemaker import ( "bytes" _ "embed" // shape_table.json - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "reflect" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errShapeInvalid = errors.New("invalid document") @@ -34,7 +37,7 @@ type shapeTableDoc struct { //nolint:gochecknoglobals // lazily parsed read-only schema var loadShapeTable = sync.OnceValue(func() shapeTableDoc { var doc shapeTableDoc - if err := json.Unmarshal(shapeTableJSON, &doc); err != nil { + if err := json.Unmarshal(shapeTableJSON, &doc, jsonx.V1()); err != nil { panic(fmt.Sprintf("sagemaker: invalid shape_table.json: %v", err)) } @@ -49,7 +52,7 @@ func validateRequestShapes(op string, req any) error { for i := range v.NumField() { f := v.Type().Field(i) - if f.Type != reflect.TypeFor[json.RawMessage]() { + if f.Type != reflect.TypeFor[jsontext.Value]() { continue } @@ -60,7 +63,7 @@ func validateRequestShapes(op string, req any) error { continue } - raw, _ := reflect.TypeAssert[json.RawMessage](v.Field(i)) + raw, _ := reflect.TypeAssert[jsontext.Value](v.Field(i)) if isAbsentJSON(raw) { continue } @@ -73,13 +76,13 @@ func validateRequestShapes(op string, req any) error { return nil } -func isAbsentJSON(raw json.RawMessage) bool { +func isAbsentJSON(raw jsontext.Value) bool { t := bytes.TrimSpace(raw) return len(t) == 0 || bytes.Equal(t, []byte("null")) } -func validateShape(table shapeTableDoc, raw json.RawMessage, shape, path string) error { +func validateShape(table shapeTableDoc, raw jsontext.Value, shape, path string) error { rule, ok := table.Shapes[shape] if !ok { return nil @@ -89,8 +92,8 @@ func validateShape(table shapeTableDoc, raw json.RawMessage, shape, path string) return validateShapeList(table, raw, rule.List, path) } - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil { return fmt.Errorf("%w: %s must be an object", errShapeInvalid, path) } @@ -115,7 +118,7 @@ func validateShape(table shapeTableDoc, raw json.RawMessage, shape, path string) return nil } -func validateShapeMember(table shapeTableDoc, raw json.RawMessage, shape, path string) error { +func validateShapeMember(table shapeTableDoc, raw jsontext.Value, shape, path string) error { if isAbsentJSON(raw) { return nil } @@ -123,9 +126,9 @@ func validateShapeMember(table shapeTableDoc, raw json.RawMessage, shape, path s return validateShape(table, raw, shape, path) } -func validateShapeList(table shapeTableDoc, raw json.RawMessage, elem, path string) error { - var items []json.RawMessage - if err := json.Unmarshal(raw, &items); err != nil { +func validateShapeList(table shapeTableDoc, raw jsontext.Value, elem, path string) error { + var items []jsontext.Value + if err := json.Unmarshal(raw, &items, jsonx.V1()); err != nil { return fmt.Errorf("%w: %s must be a list", errShapeInvalid, path) } diff --git a/services/sagemaker/spaces.go b/services/sagemaker/spaces.go index 1053928181..08aebe306e 100644 --- a/services/sagemaker/spaces.go +++ b/services/sagemaker/spaces.go @@ -2,7 +2,8 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrSpaceNotFound is returned when a space does not exist. @@ -34,17 +36,17 @@ type Space struct { DomainID string `json:"DomainId"` SpaceStatus string `json:"Status"` SpaceDisplayName string `json:"SpaceDisplayName,omitempty"` - OwnershipSettings json.RawMessage `json:"OwnershipSettings,omitempty"` - SpaceSettings json.RawMessage `json:"SpaceSettings,omitempty"` - SpaceSharingSettings json.RawMessage `json:"SpaceSharingSettings,omitempty"` + OwnershipSettings jsontext.Value `json:"OwnershipSettings,omitempty"` + SpaceSettings jsontext.Value `json:"SpaceSettings,omitempty"` + SpaceSharingSettings jsontext.Value `json:"SpaceSharingSettings,omitempty"` } func cloneSpace(s *Space) *Space { cp := *s cp.Tags = maps.Clone(s.Tags) - cp.OwnershipSettings = append(json.RawMessage(nil), s.OwnershipSettings...) - cp.SpaceSettings = append(json.RawMessage(nil), s.SpaceSettings...) - cp.SpaceSharingSettings = append(json.RawMessage(nil), s.SpaceSharingSettings...) + cp.OwnershipSettings = append(jsontext.Value(nil), s.OwnershipSettings...) + cp.SpaceSettings = append(jsontext.Value(nil), s.SpaceSettings...) + cp.SpaceSharingSettings = append(jsontext.Value(nil), s.SpaceSharingSettings...) return &cp } @@ -63,7 +65,7 @@ func (s *Space) MarshalJSON() ([]byte, error) { alias: (*alias)(s), CreationTime: epochSeconds(s.CreationTime), LastModifiedTime: epochSeconds(s.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [Space.MarshalJSON], read by @@ -77,7 +79,7 @@ func (s *Space) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(s)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -94,9 +96,9 @@ func spaceKey(domainID, spaceName string) string { // CreateSpaceOptions bundles CreateSpace's optional fields. type CreateSpaceOptions struct { SpaceDisplayName string - OwnershipSettings json.RawMessage - SpaceSettings json.RawMessage - SpaceSharingSettings json.RawMessage + OwnershipSettings jsontext.Value + SpaceSettings jsontext.Value + SpaceSharingSettings jsontext.Value } // CreateSpace creates a SageMaker Studio space. @@ -249,7 +251,7 @@ func (b *InMemoryBackend) ListSpaces(ctx context.Context, params ListSpacesParam // unset, so only a non-empty value is applied. type UpdateSpaceOptions struct { SpaceDisplayName string - SpaceSettings json.RawMessage + SpaceSettings jsontext.Value } // UpdateSpace updates a space in a domain. Returns the updated space. diff --git a/services/sagemaker/studio_lifecycle_configs.go b/services/sagemaker/studio_lifecycle_configs.go index 8897a80c12..b5da90e117 100644 --- a/services/sagemaker/studio_lifecycle_configs.go +++ b/services/sagemaker/studio_lifecycle_configs.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +11,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrStudioLifecycleConfigNotFound is returned when a studio lifecycle config does not exist. @@ -52,7 +53,7 @@ func (s *StudioLifecycleConfig) MarshalJSON() ([]byte, error) { alias: (*alias)(s), CreationTime: epochSeconds(s.CreationTime), LastModifiedTime: epochSeconds(s.LastModifiedTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [StudioLifecycleConfig.MarshalJSON], read @@ -66,7 +67,7 @@ func (s *StudioLifecycleConfig) UnmarshalJSON(data []byte) error { LastModifiedTime float64 `json:"LastModifiedTime"` }{alias: (*alias)(s)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/training_jobs.go b/services/sagemaker/training_jobs.go index 088367887e..5313610b3a 100644 --- a/services/sagemaker/training_jobs.go +++ b/services/sagemaker/training_jobs.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -21,7 +21,7 @@ var ( ) // rawConfigMap holds CreateTrainingJob members stored verbatim. -type rawConfigMap = map[string]json.RawMessage +type rawConfigMap = map[string]jsontext.Value type TrainingJob struct { PassthroughConfigs rawConfigMap `json:"PassthroughConfigs,omitempty"` diff --git a/services/sagemaker/training_plan.go b/services/sagemaker/training_plan.go index ef9480ac76..758063d4a9 100644 --- a/services/sagemaker/training_plan.go +++ b/services/sagemaker/training_plan.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // trainingPlanStatusScheduled mirrors AWS's TrainingPlanStatus "Scheduled" @@ -126,7 +127,7 @@ func (rc *ReservedCapacity) MarshalJSON() ([]byte, error) { StartTime: epochSeconds(rc.StartTime), EndTime: epochSeconds(rc.EndTime), UltraServerSummary: rc.ultraServerSummary(), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ReservedCapacity.MarshalJSON], read by @@ -140,7 +141,7 @@ func (rc *ReservedCapacity) UnmarshalJSON(data []byte) error { EndTime float64 `json:"EndTime"` }{alias: (*alias)(rc)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -478,7 +479,7 @@ func (o TrainingPlanExtensionOffering) MarshalJSON() ([]byte, error) { alias: alias(o), StartDate: epochSeconds(o.StartDate), EndDate: epochSeconds(o.EndDate), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [TrainingPlanExtensionOffering.MarshalJSON]. @@ -491,7 +492,7 @@ func (o *TrainingPlanExtensionOffering) UnmarshalJSON(data []byte) error { EndDate float64 `json:"EndDate"` }{alias: (*alias)(o)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/training_plans.go b/services/sagemaker/training_plans.go index 54c17dcc4f..c043dc6333 100644 --- a/services/sagemaker/training_plans.go +++ b/services/sagemaker/training_plans.go @@ -2,13 +2,14 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrTrainingPlanNotFound is returned when a training plan does not exist. @@ -48,7 +49,7 @@ func (r *ReservedCapacitySummary) MarshalJSON() ([]byte, error) { alias: (*alias)(r), StartTime: epochSecondsPtr(r.StartTime), EndTime: epochSecondsPtr(r.EndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [ReservedCapacitySummary.MarshalJSON], @@ -62,7 +63,7 @@ func (r *ReservedCapacitySummary) UnmarshalJSON(data []byte) error { EndTime *float64 `json:"EndTime,omitempty"` }{alias: (*alias)(r)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -142,7 +143,7 @@ func (e TrainingPlanExtension) MarshalJSON() ([]byte, error) { ExtendedAt: epochSeconds(e.ExtendedAt), StartDate: epochSeconds(e.StartDate), EndDate: epochSeconds(e.EndDate), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [TrainingPlanExtension.MarshalJSON], read @@ -157,7 +158,7 @@ func (e *TrainingPlanExtension) UnmarshalJSON(data []byte) error { EndDate float64 `json:"EndDate"` }{alias: (*alias)(e)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } @@ -184,7 +185,7 @@ func (t *TrainingPlan) MarshalJSON() ([]byte, error) { CreationTime: epochSeconds(t.CreationTime), StartTime: epochSecondsPtr(t.StartTime), EndTime: epochSecondsPtr(t.EndTime), - }) + }, jsonx.V1()) } // UnmarshalJSON is the inverse of [TrainingPlan.MarshalJSON], read by @@ -199,7 +200,7 @@ func (t *TrainingPlan) UnmarshalJSON(data []byte) error { CreationTime float64 `json:"CreationTime"` }{alias: (*alias)(t)} - if err := json.Unmarshal(data, &aux); err != nil { + if err := json.Unmarshal(data, &aux, jsonx.V1()); err != nil { return err } diff --git a/services/sagemaker/user_profiles.go b/services/sagemaker/user_profiles.go index c9b472ff08..14755b725d 100644 --- a/services/sagemaker/user_profiles.go +++ b/services/sagemaker/user_profiles.go @@ -2,7 +2,7 @@ package sagemaker import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -45,13 +45,13 @@ type UserProfile struct { Status string `json:"Status"` SingleSignOnUserIdentifier string `json:"SingleSignOnUserIdentifier,omitempty"` SingleSignOnUserValue string `json:"SingleSignOnUserValue,omitempty"` - UserSettings json.RawMessage `json:"UserSettings,omitempty"` + UserSettings jsontext.Value `json:"UserSettings,omitempty"` } func cloneUserProfile(up *UserProfile) *UserProfile { cp := *up cp.Tags = maps.Clone(up.Tags) - cp.UserSettings = append(json.RawMessage(nil), up.UserSettings...) + cp.UserSettings = append(jsontext.Value(nil), up.UserSettings...) return &cp } @@ -60,7 +60,7 @@ func cloneUserProfile(up *UserProfile) *UserProfile { type CreateUserProfileOptions struct { SingleSignOnUserIdentifier string SingleSignOnUserValue string - UserSettings json.RawMessage + UserSettings jsontext.Value } // CreateUserProfile creates a new user profile in a domain. @@ -226,7 +226,7 @@ func (b *InMemoryBackend) DeleteUserProfile(ctx context.Context, domainID, name // (api_op_UpdateUserProfile.go:26-38). UserSettings is omitted from the // request when unset, so only a non-empty value is applied. type UpdateUserProfileOptions struct { - UserSettings json.RawMessage + UserSettings jsontext.Value } // UpdateUserProfile updates a user profile in a domain. Returns the updated profile. diff --git a/services/sagemaker/variant_weight_default_test.go b/services/sagemaker/variant_weight_default_test.go index 89758544d6..5e94114547 100644 --- a/services/sagemaker/variant_weight_default_test.go +++ b/services/sagemaker/variant_weight_default_test.go @@ -1,13 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemaker" ) @@ -73,7 +74,7 @@ func TestEndpointConfig_VariantWeightDefaultsToOne(t *testing.T) { DesiredWeight float64 `json:"DesiredWeight"` } `json:"ProductionVariants"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ep)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &ep, jsonx.V1())) require.Len(t, ep.ProductionVariants, 1) assert.InDelta(t, tt.want, ep.ProductionVariants[0].DesiredWeight, 0.0001) @@ -85,7 +86,7 @@ func TestEndpointConfig_VariantWeightDefaultsToOne(t *testing.T) { InitialVariantWeight float64 `json:"InitialVariantWeight"` } `json:"ProductionVariants"` } - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &cfg)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &cfg, jsonx.V1())) require.Len(t, cfg.ProductionVariants, 1) assert.InDelta(t, tt.want, cfg.ProductionVariants[0].InitialVariantWeight, 0.0001) }) diff --git a/services/sagemaker/wire_field_fixes_respsweep2_test.go b/services/sagemaker/wire_field_fixes_respsweep2_test.go index 019f58c0b6..b7d4a1cf11 100644 --- a/services/sagemaker/wire_field_fixes_respsweep2_test.go +++ b/services/sagemaker/wire_field_fixes_respsweep2_test.go @@ -1,7 +1,7 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -11,6 +11,8 @@ import ( smtypes "github.com/aws/aws-sdk-go-v2/service/sagemaker/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateEndpointConfig_DataCaptureConfig proves @@ -83,7 +85,7 @@ func TestCreateEndpointConfig_VpcConfigSurvivesEmptyArrays(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) vpc, ok := resp["VpcConfig"].(map[string]any) require.True(t, ok, "VpcConfig key must be present") @@ -128,7 +130,7 @@ func TestCreateTrainingJob_ModelArtifactsRequiredWhileInProgress(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.Equal(t, "InProgress", resp["TrainingJobStatus"]) artifacts, ok := resp["ModelArtifacts"].(map[string]any) @@ -267,7 +269,7 @@ func TestCreatePipeline_ParallelismConfigurationSurvivesZero(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) pc, ok := resp["ParallelismConfiguration"].(map[string]any) require.True(t, ok, "ParallelismConfiguration key must be present") @@ -301,7 +303,7 @@ func TestCreateProcessingJob_OutputsSurvivesEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) poc, ok := resp["ProcessingOutputConfig"].(map[string]any) require.True(t, ok) @@ -337,7 +339,7 @@ func TestDescribeAutoMLJob_RequiredFieldsSurviveEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) outCfg, ok := resp["OutputDataConfig"].(map[string]any) require.True(t, ok) @@ -421,7 +423,7 @@ func TestCreateCompilationJob_InputConfigOrModelPackageVersionArnRequired(t *tes require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) inCfg, ok := resp["InputConfig"].(map[string]any) require.True(t, ok) s3uri, ok := inCfg["S3Uri"] @@ -472,7 +474,7 @@ func TestListTrainingJobsForHPTuningJob_TunedHyperParametersSurvivesEmpty(t *tes require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TrainingJobSummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 1) diff --git a/services/sagemaker/wire_field_fixes_respsweep_test.go b/services/sagemaker/wire_field_fixes_respsweep_test.go index 22773e74bb..49a99186a8 100644 --- a/services/sagemaker/wire_field_fixes_respsweep_test.go +++ b/services/sagemaker/wire_field_fixes_respsweep_test.go @@ -1,12 +1,14 @@ package sagemaker_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCompilationJob_RequiredOutputMembersSurviveInProgress drives @@ -47,7 +49,7 @@ func TestCompilationJob_RequiredOutputMembersSurviveInProgress(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) require.Equal( t, "INPROGRESS", resp["CompilationJobStatus"], @@ -86,7 +88,7 @@ func TestModelCard_ContentSurvivesEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp, jsonx.V1())) content, ok := resp["Content"] assert.True(t, ok, "Content key must be present even when empty") diff --git a/services/sagemakerruntime/endpoint_validation_test.go b/services/sagemakerruntime/endpoint_validation_test.go index 002af55518..d6eb33d23a 100644 --- a/services/sagemakerruntime/endpoint_validation_test.go +++ b/services/sagemakerruntime/endpoint_validation_test.go @@ -2,7 +2,7 @@ package sagemakerruntime_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sagemaker" "github.com/blackbirdworks/gopherstack/services/sagemakerruntime" @@ -88,7 +89,7 @@ func TestHandler_EndpointValidation(t *testing.T) { if want == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationError", body["__type"]) assert.Contains(t, body["message"], tt.endpointName) } diff --git a/services/sagemakerruntime/handler.go b/services/sagemakerruntime/handler.go index 70301fd784..98e75a3e34 100644 --- a/services/sagemakerruntime/handler.go +++ b/services/sagemakerruntime/handler.go @@ -3,7 +3,7 @@ package sagemakerruntime import ( "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "hash/crc32" "math" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -266,7 +267,7 @@ func (h *Handler) handleInvokeEndpointAsync( acc.FailureLocation, ) - out, err := json.Marshal(map[string]string{"InferenceId": acc.InferenceID}) + out, err := json.Marshal(map[string]string{"InferenceId": acc.InferenceID}, jsonx.V1()) if err != nil { return err } diff --git a/services/sagemakerruntime/handler_test.go b/services/sagemakerruntime/handler_test.go index e160516882..2cbd724645 100644 --- a/services/sagemakerruntime/handler_test.go +++ b/services/sagemakerruntime/handler_test.go @@ -2,7 +2,7 @@ package sagemakerruntime_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sagemakerruntime" @@ -54,7 +55,7 @@ func doRequestWithHeaders( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -294,7 +295,7 @@ func TestHandler_ErrorCodes(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantType, body["__type"]) }) } diff --git a/services/sagemakerruntime/invoke_endpoint_async_test.go b/services/sagemakerruntime/invoke_endpoint_async_test.go index cbb85c7940..c4a95f990c 100644 --- a/services/sagemakerruntime/invoke_endpoint_async_test.go +++ b/services/sagemakerruntime/invoke_endpoint_async_test.go @@ -1,7 +1,7 @@ package sagemakerruntime_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,8 @@ import ( sagemakerruntimesdk "github.com/aws/aws-sdk-go-v2/service/sagemakerruntime" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --- InvokeEndpointAsync tests --- @@ -41,7 +43,7 @@ func TestHandler_InvokeEndpointAsync(t *testing.T) { assert.Equal(t, http.StatusAccepted, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["InferenceId"]) assert.NotContains(t, out, "OutputLocation") assert.NotContains(t, out, "FailureLocation") @@ -351,7 +353,7 @@ func TestAsyncInvocation_BodyInputLocationMutualExclusion(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "ValidationError", out["__type"]) assert.Contains(t, out["message"], "mutually exclusive") } diff --git a/services/sagemakerruntime/invoke_errors_test.go b/services/sagemakerruntime/invoke_errors_test.go index 0925f80743..bbabac3562 100644 --- a/services/sagemakerruntime/invoke_errors_test.go +++ b/services/sagemakerruntime/invoke_errors_test.go @@ -1,7 +1,7 @@ package sagemakerruntime_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sagemakerruntime" ) @@ -36,7 +37,7 @@ func TestInvoke_NoContainerBackend_NeverFabricatesOutput(t *testing.T) { require.Equal(t, http.StatusServiceUnavailable, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ServiceUnavailable", body["__type"]) assert.NotContains(t, rec.Body.String(), "mock") assert.Empty(t, h.Backend.ListInvocations(), "a failed invoke must not be recorded") @@ -95,7 +96,7 @@ func TestInvoke_ErrorMapping(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, tt.wantType, body["__type"]) }) } diff --git a/services/sagemakerruntime/multi_region_test.go b/services/sagemakerruntime/multi_region_test.go index ae59a72123..08f71637ea 100644 --- a/services/sagemakerruntime/multi_region_test.go +++ b/services/sagemakerruntime/multi_region_test.go @@ -2,7 +2,8 @@ package sagemakerruntime_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sagemaker" "github.com/blackbirdworks/gopherstack/services/sagemakerruntime" @@ -152,8 +154,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/sagemakerruntime/persistence.go b/services/sagemakerruntime/persistence.go index 1a80643607..e7dc4c3f60 100644 --- a/services/sagemakerruntime/persistence.go +++ b/services/sagemakerruntime/persistence.go @@ -2,9 +2,11 @@ package sagemakerruntime import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -32,10 +34,10 @@ const sagemakerruntimeSnapshotVersion = 1 // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Invocations []*Invocation `json:"invocations"` - NextID uint64 `json:"nextId"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Invocations []*Invocation `json:"invocations"` + NextID uint64 `json:"nextId"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. @@ -64,7 +66,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NextID: b.nextID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "sagemakerruntime: failed to marshal snapshot", "error", err) diff --git a/services/scheduler/handler.go b/services/scheduler/handler.go index 61753a3150..6f7fcddab8 100644 --- a/services/scheduler/handler.go +++ b/services/scheduler/handler.go @@ -3,7 +3,8 @@ package scheduler import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -16,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -266,7 +268,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } var req scheduleNameInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.Name } @@ -443,13 +445,13 @@ func (h *Handler) enrichRESTBody(action, name string, body []byte, q url.Values) enrichTagsPath(m, name, action, q) } - result, _ := json.Marshal(m) + result, _ := json.Marshal(m, jsonx.V1()) return result } // enrichScheduleByPath injects Name from the URL path and GroupName from the query string. -func enrichScheduleByPath(m map[string]json.RawMessage, name string, q url.Values) { +func enrichScheduleByPath(m map[string]jsontext.Value, name string, q url.Values) { if name != "" { setJSONString(m, "Name", name) } @@ -460,7 +462,7 @@ func enrichScheduleByPath(m map[string]json.RawMessage, name string, q url.Value } // enrichListSchedulesQuery injects ListSchedules query parameters into the JSON map. -func enrichListSchedulesQuery(m map[string]json.RawMessage, q url.Values) { +func enrichListSchedulesQuery(m map[string]jsontext.Value, q url.Values) { if sg := q.Get("ScheduleGroup"); sg != "" { setJSONString(m, "GroupName", sg) } @@ -478,7 +480,7 @@ func enrichListSchedulesQuery(m map[string]json.RawMessage, q url.Values) { } // enrichListScheduleGroupsQuery injects ListScheduleGroups query parameters into the JSON map. -func enrichListScheduleGroupsQuery(m map[string]json.RawMessage, q url.Values) { +func enrichListScheduleGroupsQuery(m map[string]jsontext.Value, q url.Values) { for _, qk := range []struct{ query, json string }{ {keyNamePrefix, keyNamePrefix}, {keyNextToken, keyNextToken}, @@ -494,7 +496,7 @@ func enrichListScheduleGroupsQuery(m map[string]json.RawMessage, q url.Values) { // string. UntagResource sends TagKeys as a repeated query parameter // (?TagKeys=a&TagKeys=b, see awsRestjson1_serializeOpHttpBindingsUntagResourceInput's // encoder.AddQuery("TagKeys", ...)), not a single comma-separated value. -func enrichTagsPath(m map[string]json.RawMessage, name, action string, q url.Values) { +func enrichTagsPath(m map[string]jsontext.Value, name, action string, q url.Values) { if name != "" { setJSONString(m, "ResourceArn", name) } @@ -504,34 +506,34 @@ func enrichTagsPath(m map[string]json.RawMessage, name, action string, q url.Val } if keys := q["TagKeys"]; len(keys) > 0 { - keysJSON, _ := json.Marshal(keys) - m["TagKeys"] = json.RawMessage(keysJSON) + keysJSON, _ := json.Marshal(keys, jsonx.V1()) + m["TagKeys"] = jsontext.Value(keysJSON) } } // unmarshalOrEmpty parses body as a JSON object, returning an empty map on failure. -func unmarshalOrEmpty(body []byte) map[string]json.RawMessage { - var m map[string]json.RawMessage +func unmarshalOrEmpty(body []byte) map[string]jsontext.Value { + var m map[string]jsontext.Value if len(body) > 0 { - if err := json.Unmarshal(body, &m); err != nil { - return make(map[string]json.RawMessage) + if err := json.Unmarshal(body, &m, jsonx.V1()); err != nil { + return make(map[string]jsontext.Value) } return m } - return make(map[string]json.RawMessage) + return make(map[string]jsontext.Value) } // setJSONString encodes value as a JSON string and sets it on key in the map, // unless key is already present. -func setJSONString(m map[string]json.RawMessage, key, value string) { +func setJSONString(m map[string]jsontext.Value, key, value string) { if _, exists := m[key]; exists { return } - quoted, _ := json.Marshal(value) - m[key] = json.RawMessage(quoted) + quoted, _ := json.Marshal(value, jsonx.V1()) + m[key] = jsontext.Value(quoted) } // buildOps constructs the dispatch map once at handler creation time. @@ -563,33 +565,30 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrNotFound): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ResourceNotFoundException", Message: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusNotFound, payload) case errors.Is(err, ErrAlreadyExists): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "ConflictException", Message: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusConflict, payload) case errors.Is(err, ErrValidation): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: errCodeValidation, Message: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) // scheduler@v1.20.4 deserializers.go models ValidationException and @@ -597,18 +596,18 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err // scanning every awsRestjson1_deserializeOpError* switch), so both are safe // blanket fallbacks regardless of which operation reached this path. case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): payload, _ := json.Marshal(service.JSONErrorResponse{ Type: errCodeValidation, Message: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) default: payload, _ := json.Marshal(service.JSONErrorResponse{ Type: "InternalServerException", Message: errMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusInternalServerError, payload) } diff --git a/services/scheduler/handler_error_type_internal_test.go b/services/scheduler/handler_error_type_internal_test.go index ae609beac6..b2049e1c32 100644 --- a/services/scheduler/handler_error_type_internal_test.go +++ b/services/scheduler/handler_error_type_internal_test.go @@ -2,7 +2,7 @@ package scheduler import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -42,6 +43,6 @@ func TestHandleError_GenericErrorSurfacesInternalServerException(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, rec.Code) var payload service.JSONErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload, jsonx.V1())) assert.Equal(t, "InternalServerException", payload.Type) } diff --git a/services/scheduler/handler_test.go b/services/scheduler/handler_test.go index 4742ce7448..8740822c23 100644 --- a/services/scheduler/handler_test.go +++ b/services/scheduler/handler_test.go @@ -2,7 +2,7 @@ package scheduler_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/scheduler" ) @@ -27,7 +28,7 @@ func doSchedulerRequest(t *testing.T, h *scheduler.Handler, action string, body var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -78,7 +79,7 @@ func doRESTRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/scheduler/idempotency_test.go b/services/scheduler/idempotency_test.go index 1cdf337e9a..7e78e6cf66 100644 --- a/services/scheduler/idempotency_test.go +++ b/services/scheduler/idempotency_test.go @@ -1,13 +1,15 @@ package scheduler_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateSchedule_ClientToken_ReplaysOnRetry verifies that retrying a @@ -32,13 +34,13 @@ func TestCreateSchedule_ClientToken_ReplaysOnRetry(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rec2 := doSchedulerRequest(t, h, "CreateSchedule", req) require.Equal(t, http.StatusOK, rec2.Code, "retry with the same ClientToken must replay, not 409") var out2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1["ScheduleArn"], out2["ScheduleArn"]) @@ -49,7 +51,7 @@ func TestCreateSchedule_ClientToken_ReplaysOnRetry(t *testing.T) { var list struct { Schedules []map[string]any `json:"Schedules"` } - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &list)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &list, jsonx.V1())) assert.Len(t, list.Schedules, 1) } @@ -120,13 +122,13 @@ func TestCreateScheduleGroup_ClientToken_ReplaysOnRetry(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rec2 := doSchedulerRequest(t, h, "CreateScheduleGroup", req) require.Equal(t, http.StatusOK, rec2.Code, "retry with the same ClientToken must replay, not 409") var out2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1["ScheduleGroupArn"], out2["ScheduleGroupArn"]) } diff --git a/services/scheduler/persistence.go b/services/scheduler/persistence.go index 72bf0df938..6bb035af08 100644 --- a/services/scheduler/persistence.go +++ b/services/scheduler/persistence.go @@ -2,7 +2,7 @@ package scheduler import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "time" @@ -82,10 +82,10 @@ func persistedScheduleGroupKeyFn(v *persistedScheduleGroup) string { return v.AR // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // buildPersistenceDTORegistry constructs the ephemeral DTO registry used by both @@ -260,7 +260,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds the schedules and scheduleGroups store.Table // values from snap's per-table JSON, factored out of Restore to keep Restore's // own cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { dtoReg, scheduleDTOs, groupDTOs := buildPersistenceDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/scheduler/runner.go b/services/scheduler/runner.go index df181480ea..74bf9b7636 100644 --- a/services/scheduler/runner.go +++ b/services/scheduler/runner.go @@ -2,12 +2,13 @@ package scheduler import ( "context" - "encoding/json" + "encoding/json/v2" "strings" "sync" "time" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" @@ -598,7 +599,7 @@ func buildSchedulerPayload(s *Schedule) []byte { ScheduleName: s.Name, } - b, _ := json.Marshal(p) + b, _ := json.Marshal(p, jsonx.V1()) return b } diff --git a/services/scheduler/schedule_groups_test.go b/services/scheduler/schedule_groups_test.go index 83b15936c4..194b48261c 100644 --- a/services/scheduler/schedule_groups_test.go +++ b/services/scheduler/schedule_groups_test.go @@ -2,7 +2,8 @@ package scheduler_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/scheduler" ) @@ -60,7 +62,7 @@ func TestSchedulerHandler_CreateScheduleGroup(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) if tt.wantARN { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ScheduleGroupArn"], "arn:aws:scheduler:") assert.Contains(t, resp["ScheduleGroupArn"], "schedule-group/my-group") } @@ -125,7 +127,7 @@ func TestCreateScheduleGroup_DescriptionNotAccepted(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) _, hasDescription := resp["Description"] assert.False(t, hasDescription, "GetScheduleGroupOutput has no Description member on real AWS") } @@ -172,7 +174,7 @@ func TestSchedulerHandler_GetScheduleGroup(t *testing.T) { if tt.wantName != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantName, resp["Name"]) assert.Contains(t, resp["Arn"], "schedule-group/"+tt.wantName) assert.Equal(t, "ACTIVE", resp["State"]) @@ -192,7 +194,7 @@ func TestGetScheduleGroup_ReturnsCreationDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["CreationDate"]) assert.NotEmpty(t, resp["LastModificationDate"]) } @@ -206,11 +208,11 @@ func TestGetScheduleGroup_TimestampsAreEpochSeconds(t *testing.T) { rec := doSchedulerRequest(t, h, "GetScheduleGroup", map[string]any{"Name": "default"}) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) var cd float64 - require.NoError(t, json.Unmarshal(out["CreationDate"], &cd), "CreationDate must be a JSON number") + require.NoError(t, json.Unmarshal(out["CreationDate"], &cd, jsonx.V1()), "CreationDate must be a JSON number") assert.Greater(t, cd, float64(0)) } @@ -232,19 +234,19 @@ func TestGetScheduleGroup_OmitsTags(t *testing.T) { rec2 := doSchedulerRequest(t, h, "GetScheduleGroup", map[string]any{"Name": "grp-with-tags"}) require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) _, hasTags := out["Tags"] assert.False(t, hasTags, "GetScheduleGroup must not include a Tags field (not in real GetScheduleGroupOutput)") var groupARN string - require.NoError(t, json.Unmarshal(out["Arn"], &groupARN)) + require.NoError(t, json.Unmarshal(out["Arn"], &groupARN, jsonx.V1())) listRec := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": groupARN}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagsMap := wireTagsToMap(t, listResp["Tags"]) assert.Equal(t, "test", tagsMap["env"]) } @@ -371,7 +373,7 @@ func TestSchedulerHandler_ListScheduleGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ScheduleGroups") groups, ok := resp["ScheduleGroups"].([]any) require.True(t, ok) @@ -395,7 +397,7 @@ func TestListScheduleGroups_FilterByNamePrefix(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ScheduleGroups"].([]any) require.Len(t, groups, 1) @@ -417,7 +419,7 @@ func TestListScheduleGroups_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ScheduleGroups"].([]any) names := make([]string, len(groups)) @@ -441,13 +443,13 @@ func TestListScheduleGroups_TimestampsAreEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out struct { - ScheduleGroups []map[string]json.RawMessage `json:"ScheduleGroups"` + ScheduleGroups []map[string]jsontext.Value `json:"ScheduleGroups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.ScheduleGroups) var cd float64 - require.NoError(t, json.Unmarshal(out.ScheduleGroups[0]["CreationDate"], &cd)) + require.NoError(t, json.Unmarshal(out.ScheduleGroups[0]["CreationDate"], &cd, jsonx.V1())) assert.Greater(t, cd, float64(0)) } @@ -469,13 +471,13 @@ func TestListScheduleGroups_OmitsTags(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out struct { - ScheduleGroups []map[string]json.RawMessage `json:"ScheduleGroups"` + ScheduleGroups []map[string]jsontext.Value `json:"ScheduleGroups"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) for _, g := range out.ScheduleGroups { var name string - require.NoError(t, json.Unmarshal(g["Name"], &name)) + require.NoError(t, json.Unmarshal(g["Name"], &name, jsonx.V1())) if name == "list-grp-tags" { _, hasTags := g["Tags"] @@ -503,15 +505,15 @@ func TestListScheduleGroups_MaxResultsPagination(t *testing.T) { rec := doSchedulerRequest(t, h, "ListScheduleGroups", map[string]any{"MaxResults": "2"}) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) - var groups []json.RawMessage - require.NoError(t, json.Unmarshal(out["ScheduleGroups"], &groups)) + var groups []jsontext.Value + require.NoError(t, json.Unmarshal(out["ScheduleGroups"], &groups, jsonx.V1())) assert.Len(t, groups, 2) var token string - require.NoError(t, json.Unmarshal(out["NextToken"], &token)) + require.NoError(t, json.Unmarshal(out["NextToken"], &token, jsonx.V1())) assert.NotEmpty(t, token) } @@ -527,7 +529,7 @@ func TestRESTListScheduleGroups(t *testing.T) { var out struct { ScheduleGroups []map[string]any `json:"ScheduleGroups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.ScheduleGroups, "default group should always be present") } @@ -551,7 +553,7 @@ func TestSchedulerHandler_ScheduleGroup_RESTRouting(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-group", getResp["Name"]) req3 := httptest.NewRequest(http.MethodGet, "/schedule-groups", nil) diff --git a/services/scheduler/schedules_list_test.go b/services/scheduler/schedules_list_test.go index 3d48eac0d7..c13ad8093f 100644 --- a/services/scheduler/schedules_list_test.go +++ b/services/scheduler/schedules_list_test.go @@ -2,13 +2,15 @@ package scheduler_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/scheduler" ) @@ -39,7 +41,7 @@ func TestSchedulerHandler_ListSchedules(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Schedules") schedules, ok := resp["Schedules"].([]any) require.True(t, ok) @@ -64,7 +66,7 @@ func TestListSchedules_FilterByGroupName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedules := resp["Schedules"].([]any) require.Len(t, schedules, 1) @@ -84,7 +86,7 @@ func TestListSchedules_FilterByNamePrefix(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedules := resp["Schedules"].([]any) assert.Len(t, schedules, 2) @@ -108,7 +110,7 @@ func TestListSchedules_FilterByState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedules := resp["Schedules"].([]any) require.Len(t, schedules, 1) @@ -128,7 +130,7 @@ func TestListSchedules_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedules := resp["Schedules"].([]any) require.Len(t, schedules, 3) @@ -152,7 +154,7 @@ func TestListSchedules_IncludesGroupNameAndDates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schedules := resp["Schedules"].([]any) require.Len(t, schedules, 1) @@ -174,13 +176,13 @@ func TestListSchedules_TimestampsAreEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out struct { - Schedules []map[string]json.RawMessage `json:"Schedules"` + Schedules []map[string]jsontext.Value `json:"Schedules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.Schedules) var cd float64 - require.NoError(t, json.Unmarshal(out.Schedules[0]["CreationDate"], &cd)) + require.NoError(t, json.Unmarshal(out.Schedules[0]["CreationDate"], &cd, jsonx.V1())) assert.Greater(t, cd, float64(0)) } @@ -198,7 +200,7 @@ func TestListSchedules_NextTokenOmittedWhenComplete(t *testing.T) { NextToken string `json:"NextToken"` Schedules []any `json:"Schedules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // NextToken should be empty when all results fit. assert.Empty(t, out.NextToken) } @@ -217,26 +219,26 @@ func TestListSchedules_MaxResultsPagination(t *testing.T) { rec1 := doSchedulerRequest(t, h, "ListSchedules", map[string]any{"MaxResults": "2"}) require.Equal(t, http.StatusOK, rec1.Code) - var page1 map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1)) + var page1 map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1, jsonx.V1())) - var schedules1 []json.RawMessage - require.NoError(t, json.Unmarshal(page1["Schedules"], &schedules1)) + var schedules1 []jsontext.Value + require.NoError(t, json.Unmarshal(page1["Schedules"], &schedules1, jsonx.V1())) require.Len(t, schedules1, 2) var nextToken string - require.NoError(t, json.Unmarshal(page1["NextToken"], &nextToken)) + require.NoError(t, json.Unmarshal(page1["NextToken"], &nextToken, jsonx.V1())) assert.NotEmpty(t, nextToken) // Get second page using the token. rec2 := doSchedulerRequest(t, h, "ListSchedules", map[string]any{"MaxResults": "2", "NextToken": nextToken}) require.Equal(t, http.StatusOK, rec2.Code) - var page2 map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2)) + var page2 map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2, jsonx.V1())) - var schedules2 []json.RawMessage - require.NoError(t, json.Unmarshal(page2["Schedules"], &schedules2)) + var schedules2 []jsontext.Value + require.NoError(t, json.Unmarshal(page2["Schedules"], &schedules2, jsonx.V1())) assert.Len(t, schedules2, 2) } @@ -254,11 +256,11 @@ func TestListSchedules_NoTokenReturnsAll(t *testing.T) { rec := doSchedulerRequest(t, h, "ListSchedules", map[string]any{}) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) - var schedules []json.RawMessage - require.NoError(t, json.Unmarshal(out["Schedules"], &schedules)) + var schedules []jsontext.Value + require.NoError(t, json.Unmarshal(out["Schedules"], &schedules, jsonx.V1())) assert.Len(t, schedules, 3) assert.Nil(t, out["NextToken"]) } @@ -286,9 +288,9 @@ func TestListSchedules_IncludesTargetSummary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out struct { - Schedules []map[string]json.RawMessage `json:"Schedules"` + Schedules []map[string]jsontext.Value `json:"Schedules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Schedules, 1) item := out.Schedules[0] @@ -298,13 +300,13 @@ func TestListSchedules_IncludesTargetSummary(t *testing.T) { var target struct { Arn string `json:"Arn"` } - require.NoError(t, json.Unmarshal(targetRaw, &target)) + require.NoError(t, json.Unmarshal(targetRaw, &target, jsonx.V1())) assert.Equal(t, "arn:aws:lambda:us-east-1:123:function:fn", target.Arn) // Real AWS's TargetSummary has no RoleArn field; assert gopherstack doesn't // invent one on the wire. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(targetRaw, &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(targetRaw, &raw, jsonx.V1())) _, hasRoleArn := raw["RoleArn"] assert.False(t, hasRoleArn, "ListSchedules Target summary must not include RoleArn (not in real TargetSummary)") } @@ -338,7 +340,7 @@ func TestListSchedules_TargetMatchesGetSchedule(t *testing.T) { } `json:"Target"` } `json:"Schedules"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Schedules, 1) assert.Equal(t, targetArn, listOut.Schedules[0].Target.Arn) } diff --git a/services/scheduler/schedules_rest_test.go b/services/scheduler/schedules_rest_test.go index 728eb6d98f..321d0528e0 100644 --- a/services/scheduler/schedules_rest_test.go +++ b/services/scheduler/schedules_rest_test.go @@ -1,12 +1,15 @@ package scheduler_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestRESTGetScheduleByPath verifies GET /schedules/{name} works with REST routing. @@ -20,7 +23,7 @@ func TestRESTGetScheduleByPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "rest-sched", out["Name"]) } @@ -37,7 +40,7 @@ func TestRESTGetScheduleWithGroupName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "grp-sched", out["Name"]) assert.Equal(t, "default", out["GroupName"]) } @@ -99,7 +102,7 @@ func TestRESTUpdateSchedule(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "rate(10 minutes)", out["ScheduleExpression"]) } @@ -124,7 +127,7 @@ func TestRESTListSchedulesWithGroupNameFilter(t *testing.T) { var out struct { Schedules []map[string]any `json:"Schedules"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Schedules, 1) assert.Equal(t, "s-grp", out.Schedules[0]["Name"]) } @@ -143,7 +146,7 @@ func TestRESTCreateScheduleByPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["ScheduleArn"]) // Verify it can be retrieved. @@ -166,11 +169,11 @@ func TestRESTListSchedules_MaxResultsQueryParam(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) - var schedules []json.RawMessage - require.NoError(t, json.Unmarshal(out["Schedules"], &schedules)) + var schedules []jsontext.Value + require.NoError(t, json.Unmarshal(out["Schedules"], &schedules, jsonx.V1())) assert.Len(t, schedules, 2) } diff --git a/services/scheduler/schedules_target_test.go b/services/scheduler/schedules_target_test.go index c185a8c45c..137037e999 100644 --- a/services/scheduler/schedules_target_test.go +++ b/services/scheduler/schedules_target_test.go @@ -1,12 +1,15 @@ package scheduler_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateSchedule_RetryPolicyRoundTrip verifies RetryPolicy fields round-trip. @@ -33,14 +36,14 @@ func TestCreateSchedule_RetryPolicyRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-retry"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var rp map[string]int - require.NoError(t, json.Unmarshal(target["RetryPolicy"], &rp)) + require.NoError(t, json.Unmarshal(target["RetryPolicy"], &rp, jsonx.V1())) assert.Equal(t, 3600, rp["MaximumEventAgeInSeconds"]) assert.Equal(t, 10, rp["MaximumRetryAttempts"]) } @@ -131,14 +134,14 @@ func TestUpdateSchedule_RetryPolicyPersisted(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "retry-update"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var rp map[string]int - require.NoError(t, json.Unmarshal(target["RetryPolicy"], &rp)) + require.NoError(t, json.Unmarshal(target["RetryPolicy"], &rp, jsonx.V1())) assert.Equal(t, 5, rp["MaximumRetryAttempts"]) } @@ -167,14 +170,14 @@ func TestCreateSchedule_DeadLetterConfigRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-dlq"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var dlc map[string]string - require.NoError(t, json.Unmarshal(target["DeadLetterConfig"], &dlc)) + require.NoError(t, json.Unmarshal(target["DeadLetterConfig"], &dlc, jsonx.V1())) assert.Equal(t, dlqARN, dlc["Arn"]) } @@ -224,14 +227,14 @@ func TestCreateSchedule_EventBridgeParametersRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-eb"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var ebParams map[string]string - require.NoError(t, json.Unmarshal(target["EventBridgeParameters"], &ebParams)) + require.NoError(t, json.Unmarshal(target["EventBridgeParameters"], &ebParams, jsonx.V1())) assert.Equal(t, "MyDetailType", ebParams["DetailType"]) assert.Equal(t, "com.example.app", ebParams["Source"]) } @@ -299,14 +302,14 @@ func TestCreateSchedule_KinesisParametersRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-kinesis"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var kp map[string]string - require.NoError(t, json.Unmarshal(target["KinesisParameters"], &kp)) + require.NoError(t, json.Unmarshal(target["KinesisParameters"], &kp, jsonx.V1())) assert.Equal(t, "my-partition-key", kp["PartitionKey"]) } @@ -357,14 +360,14 @@ func TestCreateSchedule_SqsParametersMessageGroupIdRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-fifo"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var sqsP map[string]string - require.NoError(t, json.Unmarshal(target["SqsParameters"], &sqsP)) + require.NoError(t, json.Unmarshal(target["SqsParameters"], &sqsP, jsonx.V1())) assert.Equal(t, "grp-1", sqsP["MessageGroupId"]) } @@ -395,17 +398,17 @@ func TestCreateSchedule_SageMakerPipelineParametersRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-sm"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) - var smParams map[string]json.RawMessage - require.NoError(t, json.Unmarshal(target["SageMakerPipelineParameters"], &smParams)) + var smParams map[string]jsontext.Value + require.NoError(t, json.Unmarshal(target["SageMakerPipelineParameters"], &smParams, jsonx.V1())) var paramList []map[string]string - require.NoError(t, json.Unmarshal(smParams["PipelineParameterList"], ¶mList)) + require.NoError(t, json.Unmarshal(smParams["PipelineParameterList"], ¶mList, jsonx.V1())) require.Len(t, paramList, 2) assert.Equal(t, "param1", paramList[0]["Name"]) assert.Equal(t, "val1", paramList[0]["Value"]) @@ -436,21 +439,21 @@ func TestCreateSchedule_EcsParametersRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-ecs"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) - var ecsP map[string]json.RawMessage - require.NoError(t, json.Unmarshal(target["EcsParameters"], &ecsP)) + var ecsP map[string]jsontext.Value + require.NoError(t, json.Unmarshal(target["EcsParameters"], &ecsP, jsonx.V1())) var tdArn string - require.NoError(t, json.Unmarshal(ecsP["TaskDefinitionArn"], &tdArn)) + require.NoError(t, json.Unmarshal(ecsP["TaskDefinitionArn"], &tdArn, jsonx.V1())) assert.Equal(t, "arn:aws:ecs:us-east-1:000000000000:task-definition/my-td:1", tdArn) var launchType string - require.NoError(t, json.Unmarshal(ecsP["LaunchType"], &launchType)) + require.NoError(t, json.Unmarshal(ecsP["LaunchType"], &launchType, jsonx.V1())) assert.Equal(t, "FARGATE", launchType) } @@ -519,7 +522,7 @@ func TestCreateSchedule_EcsParametersNetworkConfigurationRoundtrip(t *testing.T) } `json:"EcsParameters"` } `json:"Target"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) cfg := out.Target.EcsParameters.NetworkConfiguration.AwsvpcConfiguration assert.Equal(t, []string{"subnet-aaa", "subnet-bbb"}, cfg.Subnets) @@ -564,7 +567,7 @@ func TestCreateSchedule_EcsParametersCapacityProviderStrategyRoundtrip(t *testin } `json:"EcsParameters"` } `json:"Target"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) strategy := out.Target.EcsParameters.CapacityProviderStrategy require.Len(t, strategy, 2) @@ -616,7 +619,7 @@ func TestCreateSchedule_EcsParametersPlacementConstraintsRoundtrip(t *testing.T) } `json:"EcsParameters"` } `json:"Target"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) constraints := out.Target.EcsParameters.PlacementConstraints require.Len(t, constraints, 1) @@ -666,7 +669,7 @@ func TestCreateSchedule_EcsParametersTaskTagsRoundtrip(t *testing.T) { } `json:"EcsParameters"` } `json:"Target"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) ecsTags := out.Target.EcsParameters.Tags require.Len(t, ecsTags, 2) @@ -704,11 +707,11 @@ func TestCreateSchedule_InputTransformerNotEchoed(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "sched-transformer"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) _, present := target["InputTransformer"] assert.False(t, present, "InputTransformer is not a real Scheduler Target member and must not be echoed back") @@ -753,13 +756,13 @@ func TestFullTarget_UpdateRoundTrip(t *testing.T) { get := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "full-target"}) require.Equal(t, http.StatusOK, get.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(get.Body.Bytes(), &out, jsonx.V1())) - var target map[string]json.RawMessage - require.NoError(t, json.Unmarshal(out["Target"], &target)) + var target map[string]jsontext.Value + require.NoError(t, json.Unmarshal(out["Target"], &target, jsonx.V1())) var kp map[string]string - require.NoError(t, json.Unmarshal(target["KinesisParameters"], &kp)) + require.NoError(t, json.Unmarshal(target["KinesisParameters"], &kp, jsonx.V1())) assert.Equal(t, "updated-key", kp["PartitionKey"]) } diff --git a/services/scheduler/schedules_test.go b/services/scheduler/schedules_test.go index 6bc19fb18d..da5443a977 100644 --- a/services/scheduler/schedules_test.go +++ b/services/scheduler/schedules_test.go @@ -2,7 +2,8 @@ package scheduler_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "net/http" "strings" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/scheduler" ) @@ -73,7 +75,7 @@ func TestSchedulerHandler_CreateSchedule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ScheduleArn"], "arn:aws:scheduler:") } @@ -114,7 +116,7 @@ func TestSchedulerHandler_CreateScheduleDefaultState(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ENABLED", resp["State"]) } @@ -139,7 +141,7 @@ func TestCreateSchedule_DefaultStateEnabled(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ENABLED", resp["State"]) } @@ -404,7 +406,7 @@ func TestCreateSchedule_ScheduleExpression_Validation(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -805,7 +807,7 @@ func TestCreateSchedule_FlexibleModeRequiresMaximumWindowInMinutes(t *testing.T) if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) } }) @@ -835,7 +837,7 @@ func TestCreateSchedule_FlexibleWindowRoundtrip(t *testing.T) { MaximumWindowInMinutes int `json:"MaximumWindowInMinutes"` } `json:"FlexibleTimeWindow"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "FLEXIBLE", out.FlexibleTimeWindow.Mode) assert.Equal(t, 20, out.FlexibleTimeWindow.MaximumWindowInMinutes) } @@ -864,7 +866,7 @@ func TestCreateSchedule_FlexibleTimeWindowMaximumWindowInMinutes(t *testing.T) { MaximumWindowInMinutes int `json:"MaximumWindowInMinutes"` } `json:"FlexibleTimeWindow"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "FLEXIBLE", out.FlexibleTimeWindow.Mode) assert.Equal(t, 15, out.FlexibleTimeWindow.MaximumWindowInMinutes) } @@ -884,7 +886,7 @@ func TestSchedulerHandler_GetSchedule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "my-schedule", resp["Name"]) assert.Equal(t, "rate(5 minutes)", resp["ScheduleExpression"]) assert.Contains(t, resp, "Target") @@ -913,7 +915,7 @@ func TestGetSchedule_ReturnsGroupNameAndDates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "mygroup", resp["GroupName"]) assert.Equal(t, "test description", resp["Description"]) @@ -938,7 +940,7 @@ func TestGetSchedule_ReturnsARNWithGroupName(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) assert.Contains(t, createResp["ScheduleArn"], "schedule/my-group/grouped-sched") } @@ -968,18 +970,18 @@ func TestGetSchedule_TimestampsAreEpochSeconds(t *testing.T) { rec2 := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "ts-schedule"}) require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) // CreationDate must be a JSON number (float64), not a string. var creationDate float64 - require.NoError(t, json.Unmarshal(out["CreationDate"], &creationDate), "CreationDate must be a number") + require.NoError(t, json.Unmarshal(out["CreationDate"], &creationDate, jsonx.V1()), "CreationDate must be a number") assert.Greater(t, creationDate, float64(0), "CreationDate must be a positive epoch value") var lastModDate float64 require.NoError( t, - json.Unmarshal(out["LastModificationDate"], &lastModDate), + json.Unmarshal(out["LastModificationDate"], &lastModDate, jsonx.V1()), "LastModificationDate must be a number", ) assert.GreaterOrEqual(t, lastModDate, creationDate) @@ -1006,11 +1008,11 @@ func TestCreateSchedule_EpochSecondsConvertBack(t *testing.T) { rec2 := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "epoch-test"}) require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) var startDate float64 - require.NoError(t, json.Unmarshal(out["StartDate"], &startDate)) + require.NoError(t, json.Unmarshal(out["StartDate"], &startDate, jsonx.V1())) // The stored epoch should round-trip back to the same second. assert.InDelta(t, epoch, startDate, 1.0) } @@ -1037,7 +1039,7 @@ func TestCreateSchedule_ActionAfterCompletion(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "DELETE", out["ActionAfterCompletion"]) } @@ -1061,7 +1063,7 @@ func TestCreateSchedule_KmsKeyArn(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, kmsARN, out["KmsKeyArn"]) } @@ -1086,8 +1088,8 @@ func TestCreateSchedule_StartDateEndDate(t *testing.T) { rec2 := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "dated-sched"}) require.Equal(t, http.StatusOK, rec2.Code) - var out map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + var out map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) _, hasStart := out["StartDate"] _, hasEnd := out["EndDate"] @@ -1113,7 +1115,7 @@ func TestCreateSchedule_ScheduleExpressionTimezone(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "America/New_York", resp["ScheduleExpressionTimezone"]) } @@ -1152,7 +1154,7 @@ func TestCreateSchedule_ScheduleExpressionTimezone_Validation(t *testing.T) { if tt.wantCode != http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } }) @@ -1183,7 +1185,7 @@ func TestUpdateSchedule_ScheduleExpressionTimezone_Validation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -1261,13 +1263,13 @@ func TestSchedulerHandler_UpdateSchedule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["ScheduleArn"], "arn:aws:scheduler:") // Verify the update getRec := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "my-schedule"}) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "rate(10 minutes)", getResp["ScheduleExpression"]) assert.Equal(t, "DISABLED", getResp["State"]) } @@ -1355,7 +1357,7 @@ func TestUpdateSchedule_WithActionAfterCompletion(t *testing.T) { rec2 := doSchedulerRequest(t, h, "GetSchedule", map[string]any{"Name": "upd-aac"}) var out map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "NONE", out["ActionAfterCompletion"]) } @@ -1440,7 +1442,7 @@ func TestUpdateSchedule_RequiredFieldValidation(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) } }) @@ -1490,7 +1492,7 @@ func TestUpdateSchedule_ScheduleExpression_SemanticValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) }) } @@ -1523,7 +1525,7 @@ func TestUpdateSchedule_DoesNotBlankFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) assert.Equal(t, "rate(10 minutes)", out["ScheduleExpression"]) @@ -1582,7 +1584,7 @@ func TestUpdateSchedule_ClearsOmittedOptionalFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.NewDecoder(getRec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(getRec.Body), &out, jsonx.V1())) assert.NotContains(t, out, tc.field, "%s should be cleared after an update that omits it", tc.field) }) diff --git a/services/scheduler/tags_test.go b/services/scheduler/tags_test.go index 21f0569cef..0acd8ce1e7 100644 --- a/services/scheduler/tags_test.go +++ b/services/scheduler/tags_test.go @@ -2,7 +2,7 @@ package scheduler_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/scheduler" ) @@ -30,7 +31,7 @@ func TestSchedulerHandler_TagResource(t *testing.T) { "FlexibleTimeWindow": map[string]any{"Mode": "OFF"}, }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["ScheduleArn"] rec := doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -56,7 +57,7 @@ func TestSchedulerHandler_ListTagsForResource(t *testing.T) { "FlexibleTimeWindow": map[string]any{"Mode": "OFF"}, }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn := createResp["ScheduleArn"] doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -68,7 +69,7 @@ func TestSchedulerHandler_ListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Tags") tags := wireTagsToMap(t, resp["Tags"]) assert.Equal(t, "prod", tags["env"]) @@ -88,7 +89,7 @@ func TestTagResource_AndListTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) schedARN := createResp["ScheduleArn"] tagRec := doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -101,7 +102,7 @@ func TestTagResource_AndListTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := wireTagsToMap(t, tagsResp["Tags"]) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -178,7 +179,7 @@ func TestSchedulerHandler_UntagResource(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) resourceARN = createResp["ScheduleArn"] if len(tt.setupTags) > 0 { @@ -201,7 +202,7 @@ func TestSchedulerHandler_UntagResource(t *testing.T) { listRec := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": resourceARN}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) remaining := wireTagsToMap(t, listResp["Tags"]) for _, key := range tt.wantRemoved { assert.NotContains(t, remaining, key) @@ -233,7 +234,7 @@ func TestUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := wireTagsToMap(t, tagsResp["Tags"]) assert.NotContains(t, tags, "k1") assert.Contains(t, tags, "k2") @@ -252,7 +253,7 @@ func TestSchedulerHandler_UntagResource_ScheduleGroup(t *testing.T) { getRec := doSchedulerRequest(t, h, "GetScheduleGroup", map[string]any{"Name": "tagged-group"}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) groupARN, _ := getResp["Arn"].(string) require.NotEmpty(t, groupARN) @@ -265,7 +266,7 @@ func TestSchedulerHandler_UntagResource_ScheduleGroup(t *testing.T) { listRec := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": groupARN}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) remaining := wireTagsToMap(t, listResp["Tags"]) assert.NotContains(t, remaining, "env") assert.Contains(t, remaining, "team") @@ -279,7 +280,7 @@ func TestSchedulerHandler_TagResource_ScheduleGroup(t *testing.T) { doSchedulerRequest(t, h, "CreateScheduleGroup", map[string]any{"Name": "tag-group"}) getRec := doSchedulerRequest(t, h, "GetScheduleGroup", map[string]any{"Name": "tag-group"}) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) groupARN, _ := getResp["Arn"].(string) rec := doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -291,7 +292,7 @@ func TestSchedulerHandler_TagResource_ScheduleGroup(t *testing.T) { listRec := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": groupARN}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tagsMap := wireTagsToMap(t, listResp["Tags"]) assert.Equal(t, "team-a", tagsMap["owner"]) } @@ -312,7 +313,7 @@ func TestRESTTagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) schedARN := createOut["ScheduleArn"] require.NotEmpty(t, schedARN) @@ -327,7 +328,7 @@ func TestRESTTagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut, jsonx.V1())) tags := wireTagsToMap(t, tagsOut["Tags"]) assert.Equal(t, "prod", tags["env"]) assert.Equal(t, "platform", tags["team"]) @@ -349,7 +350,7 @@ func TestRESTListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) schedARN := createOut["ScheduleArn"] // Tag the resource first via the standard operation. @@ -362,7 +363,7 @@ func TestRESTListTagsForResource(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &tagsOut, jsonx.V1())) tags := wireTagsToMap(t, tagsOut["Tags"]) assert.Equal(t, "val1", tags["key1"]) } @@ -382,7 +383,7 @@ func TestRESTUntagResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) schedARN := createOut["ScheduleArn"] _ = doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -397,7 +398,7 @@ func TestRESTUntagResource(t *testing.T) { rec3 := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": schedARN}) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut, jsonx.V1())) tags := wireTagsToMap(t, tagsOut["Tags"]) assert.NotContains(t, tags, "key1") assert.Equal(t, "val2", tags["key2"]) @@ -422,7 +423,7 @@ func TestRESTUntagResourceMultipleTagKeys(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) schedARN := createOut["ScheduleArn"] _ = doSchedulerRequest(t, h, "TagResource", map[string]any{ @@ -440,7 +441,7 @@ func TestRESTUntagResourceMultipleTagKeys(t *testing.T) { rec3 := doSchedulerRequest(t, h, "ListTagsForResource", map[string]any{"ResourceArn": schedARN}) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &tagsOut, jsonx.V1())) tags := wireTagsToMap(t, tagsOut["Tags"]) assert.NotContains(t, tags, "key1") assert.NotContains(t, tags, "key2") diff --git a/services/scheduler/whitebox_test.go b/services/scheduler/whitebox_test.go index 07263154cb..e85f7045fe 100644 --- a/services/scheduler/whitebox_test.go +++ b/services/scheduler/whitebox_test.go @@ -3,7 +3,7 @@ package scheduler import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,12 +12,14 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doWhiteboxRequest(t *testing.T, h *Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() diff --git a/services/secretsmanager/batchgetsecretvalue_test.go b/services/secretsmanager/batchgetsecretvalue_test.go index 65768f0536..369f892d18 100644 --- a/services/secretsmanager/batchgetsecretvalue_test.go +++ b/services/secretsmanager/batchgetsecretvalue_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -334,7 +335,7 @@ func TestBatchGetSecretValue_SecretIDListTooLong_HTTP(t *testing.T) { if tt.wantType != "" { var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp.Type) } }) @@ -358,7 +359,7 @@ func TestBatchGetSecretValue_SecretIdListAndFiltersRejected(t *testing.T) { "BatchGetSecretValue with both SecretIdList and Filters must return 400; body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", resp["__type"]) } @@ -386,7 +387,7 @@ func TestBatchGetSecretValue_FilterUsesPrefix(t *testing.T) { var out struct { SecretValues []map[string]any `json:"SecretValues"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecretValues, 2, "real AWS: BatchGetSecretValue filter uses prefix matching; body: %s", rec.Body.String()) } @@ -426,7 +427,7 @@ func TestBatchGetSecretValue_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.BatchGetSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecretValues, 2) assert.Empty(t, out.Errors) }, @@ -446,7 +447,7 @@ func TestBatchGetSecretValue_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.BatchGetSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecretValues, 1) assert.Len(t, out.Errors, 1) assert.Equal(t, "ResourceNotFoundException", out.Errors[0].ErrorCode) @@ -473,7 +474,7 @@ func TestBatchGetSecretValue_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.BatchGetSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecretValues, 2) }, }, diff --git a/services/secretsmanager/cancelrotatesecret_test.go b/services/secretsmanager/cancelrotatesecret_test.go index dcd207842b..4c37034866 100644 --- a/services/secretsmanager/cancelrotatesecret_test.go +++ b/services/secretsmanager/cancelrotatesecret_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -128,7 +129,7 @@ func TestCancelRotateSecret_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.CancelRotateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "cancel-rot", out.Name) assert.NotEmpty(t, out.ARN) }, diff --git a/services/secretsmanager/createsecret_test.go b/services/secretsmanager/createsecret_test.go index f038db032a..acc1ad7c4c 100644 --- a/services/secretsmanager/createsecret_test.go +++ b/services/secretsmanager/createsecret_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -100,7 +101,7 @@ func TestCreateSecret_NameAWSPrefixHTTP(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) } @@ -227,7 +228,7 @@ func TestCreateSecret_DuplicateHTTPStatus(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceExistsException", errResp.Type) } @@ -459,7 +460,7 @@ func TestCreateSecret_ReplicaRegionsAddedIsReadableInReplicaRegion(t *testing.T) require.Equal(t, http.StatusOK, getRec.Code) var out secretsmanager.GetSecretValueOutput - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "secret-value", out.SecretString) } @@ -513,12 +514,12 @@ func TestCreateSecret_ReplicaRegionsAddedHTTP(t *testing.T) { "AddReplicaRegions": []map[string]string{ {"Region": "eu-central-1"}, }, - }) + }, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.CreateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ReplicationStatus, 1) assert.Equal(t, "eu-central-1", out.ReplicationStatus[0].Region) } @@ -540,7 +541,7 @@ func TestCreateSecret_ReplicaRegionsAddedMultipleHTTP(t *testing.T) { {"Region": "eu-north-1"}, {"Region": "ap-east-1"}, }, - }) + }, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -548,7 +549,7 @@ func TestCreateSecret_ReplicaRegionsAddedMultipleHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Len(t, desc.ReplicationStatus, 3) } @@ -801,7 +802,7 @@ func TestCreateSecret_DeletedNameCollision_HTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) }) } @@ -1007,7 +1008,7 @@ func TestCreateSecret_NameValidation(t *testing.T) { t.Cleanup(b.StopRotationScheduler) h := secretsmanager.NewHandler(b) - body, _ := json.Marshal(map[string]string{"Name": tt.secretName, "SecretString": "v"}) + body, _ := json.Marshal(map[string]string{"Name": tt.secretName, "SecretString": "v"}, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) }) @@ -1033,7 +1034,7 @@ func TestCreateSecret_CreateDescribeRoundtrip(t *testing.T) { }, } - body, _ := json.Marshal(createBody) + body, _ := json.Marshal(createBody, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -1041,7 +1042,7 @@ func TestCreateSecret_CreateDescribeRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "roundtrip/secret", desc.Name) assert.Equal(t, "roundtrip test", desc.Description) assert.Equal(t, "alias/roundtrip", desc.KmsKeyID) diff --git a/services/secretsmanager/deletesecret_test.go b/services/secretsmanager/deletesecret_test.go index 8e2144fb1b..f3f9436d5f 100644 --- a/services/secretsmanager/deletesecret_test.go +++ b/services/secretsmanager/deletesecret_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -283,7 +284,7 @@ func TestDeleteSecret_ForceAndRecoveryWindowHTTPErrorType(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) assert.Equal(t, "InvalidParameterException", rec.Header().Get("X-Amzn-Errortype"), "error response must echo the error code in X-Amzn-Errortype") @@ -501,7 +502,7 @@ func TestDeleteSecret_RecoveryWindowInDaysHTTP(t *testing.T) { // Create the secret(s) needed for the test. for _, name := range []string{"recover-test", "recover-test2"} { - body, _ := json.Marshal(map[string]string{"Name": name, "SecretString": "v"}) + body, _ := json.Marshal(map[string]string{"Name": name, "SecretString": "v"}, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) } @@ -511,7 +512,7 @@ func TestDeleteSecret_RecoveryWindowInDaysHTTP(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.checkForward != nil { var out secretsmanager.DeleteSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tt.checkForward(t, out.DeletionDate) } }) diff --git a/services/secretsmanager/describesecret_test.go b/services/secretsmanager/describesecret_test.go index c64a0540cf..fc1369a959 100644 --- a/services/secretsmanager/describesecret_test.go +++ b/services/secretsmanager/describesecret_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -251,7 +252,7 @@ func TestDescribeSecret_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "alias/http-key", out.KmsKeyID) } @@ -282,7 +283,7 @@ func TestDescribeSecret_HasAllFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "full-meta", desc.Name) assert.Equal(t, "full metadata", desc.Description) assert.Equal(t, "alias/mykey", desc.KmsKeyID) @@ -306,7 +307,7 @@ func TestDescribeSecret_HasCreatedDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.NotNil(t, desc.CreatedDate) assert.Greater(t, *desc.CreatedDate, float64(0)) } diff --git a/services/secretsmanager/explicit_empty.go b/services/secretsmanager/explicit_empty.go index 706cfb0b1f..40843d9c2e 100644 --- a/services/secretsmanager/explicit_empty.go +++ b/services/secretsmanager/explicit_empty.go @@ -1,15 +1,18 @@ package secretsmanager import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // rejectExplicitEmpty fails when any named member is present as "": the SDK // models them as *string with a min length, so omitted keeps and "" is invalid. func rejectExplicitEmpty(data []byte, op string, members ...string) error { - var probe map[string]json.RawMessage - if err := json.Unmarshal(data, &probe); err != nil { + var probe map[string]jsontext.Value + if err := json.Unmarshal(data, &probe, jsonx.V1()); err != nil { return err } @@ -32,7 +35,7 @@ func (in *PutSecretValueInput) UnmarshalJSON(data []byte) error { return err } - return json.Unmarshal(data, (*putSecretValueInputAlias)(in)) + return json.Unmarshal(data, (*putSecretValueInputAlias)(in), jsonx.V1()) } type updateSecretInputAlias UpdateSecretInput @@ -43,7 +46,7 @@ func (in *UpdateSecretInput) UnmarshalJSON(data []byte) error { return err } - return json.Unmarshal(data, (*updateSecretInputAlias)(in)) + return json.Unmarshal(data, (*updateSecretInputAlias)(in), jsonx.V1()) } type updateSecretVersionStageInputAlias UpdateSecretVersionStageInput @@ -56,5 +59,5 @@ func (in *UpdateSecretVersionStageInput) UnmarshalJSON(data []byte) error { return err } - return json.Unmarshal(data, (*updateSecretVersionStageInputAlias)(in)) + return json.Unmarshal(data, (*updateSecretVersionStageInputAlias)(in), jsonx.V1()) } diff --git a/services/secretsmanager/getrandompassword_test.go b/services/secretsmanager/getrandompassword_test.go index 079e84e981..cb26545e4c 100644 --- a/services/secretsmanager/getrandompassword_test.go +++ b/services/secretsmanager/getrandompassword_test.go @@ -1,7 +1,7 @@ package secretsmanager_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -381,7 +382,7 @@ func TestGetRandomPassword_Handler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.GetRandomPasswordOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, []rune(out.RandomPassword), 32) }, }, @@ -392,7 +393,7 @@ func TestGetRandomPassword_Handler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.GetRandomPasswordOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, []rune(out.RandomPassword), 20) }, }, @@ -403,7 +404,7 @@ func TestGetRandomPassword_Handler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) }, }, @@ -414,7 +415,7 @@ func TestGetRandomPassword_Handler(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.GetRandomPasswordOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, []rune(out.RandomPassword), 50) for _, ch := range out.RandomPassword { assert.NotContains(t, "0123456789", string(ch)) @@ -497,7 +498,7 @@ func TestGetRandomPassword_RandomRunesOptimized(t *testing.T) { RandomPassword string `json:"RandomPassword"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.RandomPassword), tt.minLen) }) } diff --git a/services/secretsmanager/getsecretvalue_test.go b/services/secretsmanager/getsecretvalue_test.go index 9437c72ec4..bc191863a1 100644 --- a/services/secretsmanager/getsecretvalue_test.go +++ b/services/secretsmanager/getsecretvalue_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -38,7 +39,7 @@ func TestGetSecretValue_NotFoundHTTP(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type) } @@ -74,7 +75,7 @@ func TestGetSecretValue_DeletedHTTPStatus(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errResp.Type) } @@ -230,12 +231,12 @@ func TestGetSecretValue_VersionMismatchHTTPErrorType(t *testing.T) { "SecretId": "mismatch-http", "VersionId": "v1", "VersionStage": secretsmanager.StagingLabelPrevious, - }) + }, jsonx.V1()) rec = doR1Request(t, h, "secretsmanager.GetSecretValue", string(body)) require.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type, "HTTP error body must carry ResourceNotFoundException") } @@ -371,7 +372,7 @@ func TestGetSecretValue_LastAccessedDateTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc1 secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc1, jsonx.V1())) assert.Nil(t, desc1.LastAccessedDate) // GetSecretValue should update LastAccessedDate. @@ -383,7 +384,7 @@ func TestGetSecretValue_LastAccessedDateTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc2 secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc2, jsonx.V1())) require.NotNil(t, desc2.LastAccessedDate) // The LastAccessedDate should be today (day-granularity as AWS does). accessTime := time.Unix(int64(*desc2.LastAccessedDate), 0).UTC() diff --git a/services/secretsmanager/handler.go b/services/secretsmanager/handler.go index babd9c8b0e..c14933502b 100644 --- a/services/secretsmanager/handler.go +++ b/services/secretsmanager/handler.go @@ -2,7 +2,7 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/worker" @@ -188,7 +189,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -226,7 +227,7 @@ type smActionFn func(ctx context.Context, region string, body []byte) (any, erro func decodeAction[I any](fn func(ctx context.Context, input *I) (any, error)) smActionFn { return func(ctx context.Context, _ string, body []byte) (any, error) { var input I - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } @@ -250,7 +251,7 @@ func (h *Handler) dispatch(ctx context.Context, r *http.Request, action string, return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a structured error response for a Secrets Manager operation failure. @@ -307,7 +308,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin payload, _ := json.Marshal(ErrorResponse{ Type: errorType, Message: awserr.StripCode(errorType, reqErr.Error()), - }) + }, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/secretsmanager/handler_dispatch_test.go b/services/secretsmanager/handler_dispatch_test.go index 4f4a0ce3e9..bf93794c67 100644 --- a/services/secretsmanager/handler_dispatch_test.go +++ b/services/secretsmanager/handler_dispatch_test.go @@ -7,7 +7,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -47,7 +48,7 @@ func TestHandler_Dispatch(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.CreateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "test-secret", out.Name) }, }, @@ -66,7 +67,7 @@ func TestHandler_Dispatch(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.GetSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "the-value", out.SecretString) }, }, @@ -78,7 +79,7 @@ func TestHandler_Dispatch(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "UnknownOperationException", errResp.Type) }, }, @@ -96,7 +97,7 @@ func TestHandler_Dispatch(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.Contains(t, ops, "CreateSecret") }, }, @@ -188,7 +189,7 @@ func TestHandler_FullCycle(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var listOut secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) assert.Len(t, listOut.SecretList, 1) // UpdateSecret @@ -402,7 +403,7 @@ func TestHandler_ErrorCases(t *testing.T) { assert.Equal(t, tt.expectedStatus, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.expectedErrTyp, errResp.Type) }) } @@ -762,7 +763,7 @@ func TestHandler_ErrorResponseAmznErrortypeHeader(t *testing.T) { require.GreaterOrEqual(t, rec.Code, http.StatusBadRequest) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp.Type, "body __type") assert.Equal(t, tt.wantType, rec.Header().Get("X-Amzn-Errortype"), "X-Amzn-Errortype header must equal body __type") diff --git a/services/secretsmanager/handler_random_password.go b/services/secretsmanager/handler_random_password.go index 95a71454e0..dd0c4d1559 100644 --- a/services/secretsmanager/handler_random_password.go +++ b/services/secretsmanager/handler_random_password.go @@ -2,14 +2,16 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) smRandomPasswordActions() map[string]smActionFn { return map[string]smActionFn{ "GetRandomPassword": func(_ context.Context, _ string, b []byte) (any, error) { var input GetRandomPasswordInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/secretsmanager/handler_rotation.go b/services/secretsmanager/handler_rotation.go index f4f2522862..46c6b0fe3a 100644 --- a/services/secretsmanager/handler_rotation.go +++ b/services/secretsmanager/handler_rotation.go @@ -2,10 +2,11 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -13,7 +14,7 @@ func (h *Handler) smRotationActions() map[string]smActionFn { return map[string]smActionFn{ "RotateSecret": func(ctx context.Context, region string, b []byte) (any, error) { var input RotateSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -21,7 +22,7 @@ func (h *Handler) smRotationActions() map[string]smActionFn { }, "CancelRotateSecret": func(ctx context.Context, _ string, b []byte) (any, error) { var input CancelRotateSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -57,7 +58,7 @@ func buildRotationStepEvent(secretID, token, step string) ([]byte, error) { rotationEventSecretIDKey: secretID, rotationEventTokenKey: token, rotationEventStepKey: step, - }) + }, jsonx.V1()) } // extractFunctionNameFromARN extracts the bare function name from a Lambda ARN. diff --git a/services/secretsmanager/handler_secrets.go b/services/secretsmanager/handler_secrets.go index 668708a041..158521b6c2 100644 --- a/services/secretsmanager/handler_secrets.go +++ b/services/secretsmanager/handler_secrets.go @@ -2,8 +2,9 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/v2" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -11,7 +12,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { return map[string]smActionFn{ "CreateSecret": func(ctx context.Context, region string, b []byte) (any, error) { var input CreateSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } input.Region = region @@ -23,7 +24,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, "GetSecretValue": func(ctx context.Context, _ string, b []byte) (any, error) { var input GetSecretValueInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -31,7 +32,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, "PutSecretValue": func(ctx context.Context, _ string, b []byte) (any, error) { var input PutSecretValueInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -39,7 +40,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, "DeleteSecret": func(ctx context.Context, _ string, b []byte) (any, error) { var input DeleteSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -47,7 +48,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, opListSecrets: func(ctx context.Context, _ string, b []byte) (any, error) { var input ListSecretsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -55,7 +56,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, opDescribeSecret: func(ctx context.Context, _ string, b []byte) (any, error) { var input DescribeSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -63,7 +64,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, "UpdateSecret": func(ctx context.Context, _ string, b []byte) (any, error) { var input UpdateSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -71,7 +72,7 @@ func (h *Handler) smSecretsActions() map[string]smActionFn { }, "RestoreSecret": func(ctx context.Context, _ string, b []byte) (any, error) { var input RestoreSecretInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/secretsmanager/handler_tags.go b/services/secretsmanager/handler_tags.go index 7de93cc5d5..bb264275ad 100644 --- a/services/secretsmanager/handler_tags.go +++ b/services/secretsmanager/handler_tags.go @@ -2,9 +2,10 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -12,7 +13,7 @@ func (h *Handler) smTagActions() map[string]smActionFn { return map[string]smActionFn{ "TagResource": func(ctx context.Context, _ string, b []byte) (any, error) { var input TagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -26,7 +27,7 @@ func (h *Handler) smTagActions() map[string]smActionFn { }, "UntagResource": func(ctx context.Context, _ string, b []byte) (any, error) { var input UntagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/secretsmanager/listsecrets_test.go b/services/secretsmanager/listsecrets_test.go index 46554b236f..638ec99d30 100644 --- a/services/secretsmanager/listsecrets_test.go +++ b/services/secretsmanager/listsecrets_test.go @@ -5,7 +5,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -658,7 +659,7 @@ func TestListSecrets_OwningServiceHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.SecretList, "owning-service=rds must match neither om-1 nor om-2") } @@ -924,7 +925,7 @@ func TestListSecrets_FilterByNameHTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.SecretList, 2) } @@ -969,7 +970,7 @@ func TestListSecrets_SortOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.SecretList, 3) assert.Equal(t, "gamma", out.SecretList[0].Name) assert.Equal(t, "alpha", out.SecretList[2].Name) @@ -987,7 +988,7 @@ func TestListSecrets_FilterAll(t *testing.T) { "Name": "findme/secret", "Description": "unique-token", "SecretString": "v", - }) + }, jsonx.V1()) rec := doR1Request(t, h, "secretsmanager.CreateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -1001,7 +1002,7 @@ func TestListSecrets_FilterAll(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.SecretList, 1) assert.Equal(t, "findme/secret", out.SecretList[0].Name) } @@ -1025,7 +1026,7 @@ func TestListSecrets_EntryHasRotationFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.SecretList, 1) entry := out.SecretList[0] @@ -1056,7 +1057,7 @@ func TestListSecrets_EntryHasCreatedAndRotatedDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.SecretList, 1) assert.NotNil(t, out.SecretList[0].CreatedDate) assert.NotNil(t, out.SecretList[0].LastRotatedDate) diff --git a/services/secretsmanager/listsecretversionids_test.go b/services/secretsmanager/listsecretversionids_test.go index c959855911..b6c3357415 100644 --- a/services/secretsmanager/listsecretversionids_test.go +++ b/services/secretsmanager/listsecretversionids_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -280,7 +281,7 @@ func TestListSecretVersionIds_HTTPDispatchTable(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.ListSecretVersionIDsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "lsvi-secret", out.Name) assert.NotEmpty(t, out.Versions) }, @@ -481,7 +482,7 @@ func TestListSecretVersionIds_ViaHandler(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ListSecretVersionIDsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "handler-lsv", out.Name) assert.NotEmpty(t, out.Versions) } diff --git a/services/secretsmanager/persistence.go b/services/secretsmanager/persistence.go index a4b8acbd20..dfcaf33998 100644 --- a/services/secretsmanager/persistence.go +++ b/services/secretsmanager/persistence.go @@ -2,7 +2,7 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -67,7 +67,7 @@ func secretSnapshotKeyFn(ss *secretSnapshot) string { // from an incompatible (older or newer) build of this backend as though it // were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourcePolicies map[string]map[string]string `json:"resourcePolicies,omitempty"` ReplicationConfigs map[string]map[string][]ReplicationStatusType `json:"replicationConfigs,omitempty"` AccountID string `json:"accountID"` diff --git a/services/secretsmanager/putsecretvalue_test.go b/services/secretsmanager/putsecretvalue_test.go index 7df8364aab..6718342bb5 100644 --- a/services/secretsmanager/putsecretvalue_test.go +++ b/services/secretsmanager/putsecretvalue_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -486,7 +487,7 @@ func TestPutSecretValue_Idempotency(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out1 secretsmanager.PutSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1, jsonx.V1())) assert.Equal(t, token, out1.VersionID) // Second put with same token + content: should return the existing version. @@ -494,7 +495,7 @@ func TestPutSecretValue_Idempotency(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out2 secretsmanager.PutSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, token, out2.VersionID) assert.Equal(t, out1.VersionID, out2.VersionID) } diff --git a/services/secretsmanager/replica_write_guard_test.go b/services/secretsmanager/replica_write_guard_test.go index 71b97b2dbf..9690c4026a 100644 --- a/services/secretsmanager/replica_write_guard_test.go +++ b/services/secretsmanager/replica_write_guard_test.go @@ -13,13 +13,14 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -97,7 +98,7 @@ func TestReplicaWriteGuard_BlockedOnReplica(t *testing.T) { var out struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "InvalidRequestException", out.Type) }) } diff --git a/services/secretsmanager/replication_test.go b/services/secretsmanager/replication_test.go index 1f9fc790f4..657b649076 100644 --- a/services/secretsmanager/replication_test.go +++ b/services/secretsmanager/replication_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -180,7 +181,7 @@ func TestReplication_ReplicaSecretReadableInReplicaRegion(t *testing.T) { "replica region must serve the replicated secret, got: %s", getRec.Body.String()) var getOut secretsmanager.GetSecretValueOutput - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) assert.Equal(t, "replicated-value", getOut.SecretString) descRec := doSMRequestInRegion(t, h, "us-west-2", "secretsmanager.DescribeSecret", @@ -188,7 +189,7 @@ func TestReplication_ReplicaSecretReadableInReplicaRegion(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, secretsmanager.MockRegion, descOut.PrimaryRegion) assert.Contains(t, descOut.ARN, "us-west-2") } @@ -251,7 +252,7 @@ func TestReplication_Operations(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.ReplicateSecretToRegionsOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.ARN) require.Len(t, out.ReplicationStatus, 1) assert.Equal(t, "us-west-2", out.ReplicationStatus[0].Region) @@ -279,7 +280,7 @@ func TestReplication_Operations(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.RemoveRegionsFromReplicationOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.ReplicationStatus, 1) assert.Equal(t, "ap-east-1", out.ReplicationStatus[0].Region) }, @@ -305,7 +306,7 @@ func TestReplication_Operations(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.StopReplicationToReplicaOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.ARN) }, }, @@ -474,7 +475,7 @@ func TestReplicateSecretToRegions_ExistingRegionErrorType_WireCode(t *testing.T) var body struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidRequestException", body.Type) assert.NotEqual(t, "ResourceExistsException", body.Type) } diff --git a/services/secretsmanager/resource_policy.go b/services/secretsmanager/resource_policy.go index 728d6a941a..3d3d75e57e 100644 --- a/services/secretsmanager/resource_policy.go +++ b/services/secretsmanager/resource_policy.go @@ -2,9 +2,12 @@ package secretsmanager import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // GetResourcePolicy retrieves the resource-based policy for a secret. @@ -142,8 +145,8 @@ func (b *InMemoryBackend) ValidateResourcePolicy( } func validateResourcePolicyDocument(policyStr string) []PolicyValidationException { - var policy map[string]json.RawMessage - if err := json.Unmarshal([]byte(policyStr), &policy); err != nil { + var policy map[string]jsontext.Value + if err := json.Unmarshal([]byte(policyStr), &policy, jsonx.V1()); err != nil { return []PolicyValidationException{ { CheckName: "SyntaxCheck", @@ -172,25 +175,25 @@ func validateResourcePolicyDocument(policyStr string) []PolicyValidationExceptio } type policyStatement struct { - Effect string `json:"Effect"` - Principal json.RawMessage `json:"Principal"` - Condition json.RawMessage `json:"Condition"` + Effect string `json:"Effect"` + Principal jsontext.Value `json:"Principal"` + Condition jsontext.Value `json:"Condition"` } // policyAllowsPublicAccess reports whether an unconditioned Allow statement // grants a wildcard principal. func policyAllowsPublicAccess(policyStr string) bool { var doc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if json.Unmarshal([]byte(policyStr), &doc) != nil { + if json.Unmarshal([]byte(policyStr), &doc, jsonx.V1()) != nil { return false } var stmts []policyStatement - if json.Unmarshal(doc.Statement, &stmts) != nil { + if json.Unmarshal(doc.Statement, &stmts, jsonx.V1()) != nil { var one policyStatement - if json.Unmarshal(doc.Statement, &one) != nil { + if json.Unmarshal(doc.Statement, &one, jsonx.V1()) != nil { return false } @@ -206,14 +209,14 @@ func policyAllowsPublicAccess(policyStr string) bool { return false } -func principalIsWildcard(raw json.RawMessage) bool { +func principalIsWildcard(raw jsontext.Value) bool { var s string - if json.Unmarshal(raw, &s) == nil { + if json.Unmarshal(raw, &s, jsonx.V1()) == nil { return s == "*" } - var m map[string]json.RawMessage - if json.Unmarshal(raw, &m) != nil { + var m map[string]jsontext.Value + if json.Unmarshal(raw, &m, jsonx.V1()) != nil { return false } @@ -222,11 +225,11 @@ func principalIsWildcard(raw json.RawMessage) bool { return false } - if json.Unmarshal(aws, &s) == nil { + if json.Unmarshal(aws, &s, jsonx.V1()) == nil { return s == "*" } var list []string - return json.Unmarshal(aws, &list) == nil && slices.Contains(list, "*") + return json.Unmarshal(aws, &list, jsonx.V1()) == nil && slices.Contains(list, "*") } diff --git a/services/secretsmanager/resource_policy_test.go b/services/secretsmanager/resource_policy_test.go index 0c47c2156b..1e64e3a7ca 100644 --- a/services/secretsmanager/resource_policy_test.go +++ b/services/secretsmanager/resource_policy_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -360,7 +361,7 @@ func TestGetResourcePolicy_DeletedSecret_HTTP(t *testing.T) { assert.Equal(t, tt.expectedStatus, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.expectedType, errResp.Type) }) } @@ -398,7 +399,7 @@ func TestResourcePolicy_Cycle(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.PutResourcePolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "policy-secret", out.Name) assert.NotEmpty(t, out.ARN) }, @@ -424,7 +425,7 @@ func TestResourcePolicy_Cycle(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.GetResourcePolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "get-policy", out.Name) assert.Contains(t, out.ResourcePolicy, "2012-10-17") }, @@ -450,7 +451,7 @@ func TestResourcePolicy_Cycle(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.DeleteResourcePolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "del-policy", out.Name) }, }, @@ -690,7 +691,7 @@ func TestValidateResourcePolicy_HTTP(t *testing.T) { if tt.wantStatus == http.StatusOK { var out secretsmanager.ValidateResourcePolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantPassed, out.PolicyValidationPassed) if tt.wantErrCount > 0 { @@ -720,12 +721,12 @@ func TestValidateResourcePolicy_HTTP_SecretExists(t *testing.T) { body, _ := json.Marshal(map[string]string{ "SecretId": "validate-test", "ResourcePolicy": validPolicy, - }) + }, jsonx.V1()) rec = doR1Request(t, h, "secretsmanager.ValidateResourcePolicy", string(body)) require.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.ValidateResourcePolicyOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.True(t, out.PolicyValidationPassed) } diff --git a/services/secretsmanager/rotatesecret_test.go b/services/secretsmanager/rotatesecret_test.go index c12929cdf6..83d59c2a9c 100644 --- a/services/secretsmanager/rotatesecret_test.go +++ b/services/secretsmanager/rotatesecret_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -280,7 +281,7 @@ func TestRotateSecret_Backend(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.RotateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "rotate-secret", out.Name) assert.NotEmpty(t, out.VersionID) @@ -335,7 +336,7 @@ func TestRotateSecret_WithLambda(t *testing.T) { assert.Equal(t, "my-rotator", call.name) assert.Equal(t, "RequestResponse", call.invocationType) var event map[string]string - require.NoError(t, json.Unmarshal(call.payload, &event)) + require.NoError(t, json.Unmarshal(call.payload, &event, jsonx.V1())) assert.Equal(t, "lambda-rotate-secret", event["SecretId"]) assert.Equal(t, steps[i], event["Step"]) assert.NotEmpty(t, event["ClientRequestToken"]) @@ -394,7 +395,7 @@ func TestRotateSecret_OmittedARNUsesStoredLambda(t *testing.T) { assert.Equal(t, "my-rotator", call.name) assert.Equal(t, steps[i], func() string { var event map[string]string - require.NoError(t, json.Unmarshal(call.payload, &event)) + require.NoError(t, json.Unmarshal(call.payload, &event, jsonx.V1())) return event["Step"] }()) @@ -448,7 +449,7 @@ func TestRotateSecret_RotateImmediatelyFalseWithLambdaRunsTestSecretProbe(t *tes assert.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.RotateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out.VersionID, "no permanent version is created by a test probe") // Exactly one Lambda invocation: the testSecret step. @@ -456,7 +457,7 @@ func TestRotateSecret_RotateImmediatelyFalseWithLambdaRunsTestSecretProbe(t *tes assert.Equal(t, "probe-rotator", mock.calls[0].name) var event map[string]string - require.NoError(t, json.Unmarshal(mock.calls[0].payload, &event)) + require.NoError(t, json.Unmarshal(mock.calls[0].payload, &event, jsonx.V1())) assert.Equal(t, "probe-secret", event["SecretId"]) assert.Equal(t, "testSecret", event["Step"]) @@ -546,7 +547,7 @@ func TestRotateSecret_NoLambdaInvoker(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out secretsmanager.RotateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.VersionID) } @@ -668,7 +669,7 @@ func TestRotateSecret_InvalidDays(t *testing.T) { rotBody, _ := json.Marshal(map[string]any{ "SecretId": "days-validation", "RotationRules": map[string]any{"AutomaticallyAfterDays": tt.days}, - }) + }, jsonx.V1()) rotRec := doR1Request(t, h, "secretsmanager.RotateSecret", string(rotBody)) assert.Equal(t, http.StatusBadRequest, rotRec.Code) }) diff --git a/services/secretsmanager/secret_versions_test.go b/services/secretsmanager/secret_versions_test.go index b6172cadee..0dd30d7d57 100644 --- a/services/secretsmanager/secret_versions_test.go +++ b/services/secretsmanager/secret_versions_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -302,7 +303,7 @@ func TestSecretSizeValidation_Handler(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp.Type) } @@ -388,7 +389,7 @@ func TestPutSecretValueLabelRotation(t *testing.T) { putBody, _ := json.Marshal(map[string]string{ "SecretId": "rotate-test", "SecretString": "v2", - }) + }, jsonx.V1()) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(putBody))) req.Header.Set("X-Amz-Target", "secretsmanager.PutSecretValue") rec := httptest.NewRecorder() @@ -396,7 +397,7 @@ func TestPutSecretValueLabelRotation(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var putOut secretsmanager.PutSecretValueOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putOut, jsonx.V1())) assert.Contains(t, putOut.VersionStages, secretsmanager.StagingLabelCurrent) // Current should be v2 diff --git a/services/secretsmanager/updatesecret_test.go b/services/secretsmanager/updatesecret_test.go index 37653fd01e..b9d3a08ee5 100644 --- a/services/secretsmanager/updatesecret_test.go +++ b/services/secretsmanager/updatesecret_test.go @@ -6,7 +6,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -235,7 +236,7 @@ func TestUpdateSecret_KmsKeyID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var desc secretsmanager.DescribeSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "arn:aws:kms:us-east-1:123456789012:key/test-key-id", desc.KmsKeyID) } @@ -257,17 +258,17 @@ func TestUpdateSecret_ClientRequestTokenIdempotency(t *testing.T) { "SecretId": "update-idempotent", "SecretString": "updated-value", "ClientRequestToken": token, - }) + }, jsonx.V1()) rec = doR1Request(t, h, "secretsmanager.UpdateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) var out1 secretsmanager.UpdateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out1, jsonx.V1())) rec = doR1Request(t, h, "secretsmanager.UpdateSecret", string(body)) require.Equal(t, http.StatusOK, rec.Code) var out2 secretsmanager.UpdateSecretOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out2, jsonx.V1())) assert.Equal(t, out1.VersionID, out2.VersionID) } diff --git a/services/secretsmanager/updatesecretversionstage_test.go b/services/secretsmanager/updatesecretversionstage_test.go index 5614330ad7..0f9422bd9f 100644 --- a/services/secretsmanager/updatesecretversionstage_test.go +++ b/services/secretsmanager/updatesecretversionstage_test.go @@ -2,7 +2,7 @@ package secretsmanager_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/secretsmanager" ) @@ -515,7 +516,7 @@ func TestUpdateSecretVersionStage_CannotRemoveAWSCURRENT_HTTP(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var errResp secretsmanager.ErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) }) } @@ -560,7 +561,7 @@ func TestUpdateSecretVersionStage_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.UpdateSecretVersionStageOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "stage-secret", out.Name) assert.NotEmpty(t, out.ARN) }, @@ -592,7 +593,7 @@ func TestUpdateSecretVersionStage_HTTP(t *testing.T) { checkFn: func(t *testing.T, rec *httptest.ResponseRecorder) { t.Helper() var out secretsmanager.UpdateSecretVersionStageOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "stage-remove", out.Name) }, }, diff --git a/services/securityhub/action_targets_test.go b/services/securityhub/action_targets_test.go index 007002f241..8df6e34370 100644 --- a/services/securityhub/action_targets_test.go +++ b/services/securityhub/action_targets_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -27,7 +28,7 @@ func TestCreateActionTargetPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["ActionTargetArn"].(string) assert.NotEmpty(t, arn) @@ -51,7 +52,7 @@ func TestDescribeActionTargetsIsPOSTActionTargetsGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, _ := resp["ActionTargets"].([]any) assert.Len(t, targets, 1) @@ -74,7 +75,7 @@ func TestDeleteActionTargetIsDELETEActionTargetsArn(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arn, _ := createResp["ActionTargetArn"].(string) @@ -82,7 +83,7 @@ func TestDeleteActionTargetIsDELETEActionTargetsArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deletedArn, _ := resp["ActionTargetArn"].(string) assert.Equal(t, arn, deletedArn, "DeleteActionTarget must return the deleted ActionTargetArn") diff --git a/services/securityhub/aggregators_v2_test.go b/services/securityhub/aggregators_v2_test.go index 0a150de588..3439bc1b9a 100644 --- a/services/securityhub/aggregators_v2_test.go +++ b/services/securityhub/aggregators_v2_test.go @@ -1,12 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestAggregatorV2(t *testing.T) { @@ -90,7 +92,7 @@ func TestAggregatorV2(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -111,7 +113,7 @@ func TestHandler_UpdateAggregatorV2(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["AggregatorV2Arn"].(string) rec2 := doRequest(t, h, http.MethodPatch, "/aggregatorv2/update/"+arn, map[string]any{ @@ -120,7 +122,7 @@ func TestHandler_UpdateAggregatorV2(t *testing.T) { }) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) require.Equal(t, "SPECIFIED_REGIONS", resp2["RegionLinkingMode"]) }) @@ -135,7 +137,7 @@ func TestHandler_UpdateAggregatorV2(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["AggregatorV2Arn"].(string) rec2 := doRequest(t, h, http.MethodDelete, "/aggregatorv2/delete/"+arn, nil) @@ -164,7 +166,7 @@ func TestCreateAggregatorV2_LinkedRegionsAndTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["AggregatorV2Arn"].(string) require.NotEmpty(t, arn) @@ -175,7 +177,7 @@ func TestCreateAggregatorV2_LinkedRegionsAndTags(t *testing.T) { require.Equal(t, http.StatusOK, tagRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp, jsonx.V1())) tags, _ := tagResp["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) } diff --git a/services/securityhub/analytics_filters_test.go b/services/securityhub/analytics_filters_test.go index 27d323170b..b7a9319912 100644 --- a/services/securityhub/analytics_filters_test.go +++ b/services/securityhub/analytics_filters_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -48,7 +49,7 @@ func decodeMap(t *testing.T, body []byte) map[string]any { t.Helper() var out map[string]any - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out } diff --git a/services/securityhub/automation_rules_test.go b/services/securityhub/automation_rules_test.go index 94afdf48cf..22ed7faee3 100644 --- a/services/securityhub/automation_rules_test.go +++ b/services/securityhub/automation_rules_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -29,7 +30,7 @@ func TestCreateAutomationRulePath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ruleArn, _ := resp["RuleArn"].(string) assert.NotEmpty(t, ruleArn) @@ -55,7 +56,7 @@ func TestListAutomationRulesIsGETAutomationrulesList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, _ := resp["AutomationRulesMetadata"].([]any) assert.Len(t, rules, 1) @@ -81,7 +82,7 @@ func TestBatchGetAutomationRulesPath(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ruleArn, _ := createResp["RuleArn"].(string) rec := doRequest(t, h, http.MethodPost, "/automationrules/get", map[string]any{ @@ -91,7 +92,7 @@ func TestBatchGetAutomationRulesPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, _ := resp["Rules"].([]any) assert.Len(t, rules, 1) @@ -117,7 +118,7 @@ func TestBatchDeleteAutomationRulesPath(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ruleArn, _ := createResp["RuleArn"].(string) rec := doRequest(t, h, http.MethodPost, "/automationrules/delete", map[string]any{ @@ -127,7 +128,7 @@ func TestBatchDeleteAutomationRulesPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ProcessedAutomationRules") assert.Contains(t, resp, "UnprocessedAutomationRules") @@ -156,7 +157,7 @@ func TestCreateAutomationRule_ResponseFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["RuleArn"], "RuleArn must be present") assert.NotEmpty(t, resp["CreatedAt"], "CreatedAt must be present") assert.Equal(t, "my-rule", resp["RuleName"]) @@ -184,7 +185,7 @@ func TestCreateAutomationRule_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ruleArn, _ := resp["RuleArn"].(string) require.NotEmpty(t, ruleArn) @@ -192,7 +193,7 @@ func TestCreateAutomationRule_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, tagRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp, jsonx.V1())) tags, _ := tagResp["Tags"].(map[string]any) assert.Equal(t, "sec", tags["team"]) } @@ -215,7 +216,7 @@ func TestBatchUpdateAutomationRules_CriteriaAndActions(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ruleArn := createResp["RuleArn"].(string) // Update its Criteria and Actions @@ -240,7 +241,7 @@ func TestBatchUpdateAutomationRules_CriteriaAndActions(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules, _ := getResp["Rules"].([]any) require.Len(t, rules, 1) @@ -272,7 +273,7 @@ func TestUpdateAutomationRuleV2_ActionsApplied(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) identifier, _ := createResp["RuleId"].(string) require.NotEmpty(t, identifier) @@ -289,7 +290,7 @@ func TestUpdateAutomationRuleV2_ActionsApplied(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) actions, _ := updateResp["Actions"].([]any) require.Len(t, actions, 1, "Actions update must be applied, not silently dropped") @@ -378,7 +379,7 @@ func TestHandler_BatchUpdateAutomationRules(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) ruleArn, _ := createResp["RuleArn"].(string) require.NotEmpty(t, ruleArn) @@ -581,7 +582,7 @@ func TestAutomationRulesV2(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -654,7 +655,7 @@ func TestBatchImportFindings_AutomationRuleFires(t *testing.T) { var resp struct { Findings []map[string]any `json:"Findings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Findings, 1) severity, _ := resp.Findings[0]["Severity"].(map[string]any) diff --git a/services/securityhub/configuration_policies_test.go b/services/securityhub/configuration_policies_test.go index ea58727798..a7b36ca85c 100644 --- a/services/securityhub/configuration_policies_test.go +++ b/services/securityhub/configuration_policies_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -422,7 +423,7 @@ func TestConfigurationPolicy(t *testing.T) { rec := doRequest(t, h, s.method, s.path, substitutePolicyID(s.body, policyID)) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if id := s.check(t, rec.Code, resp); id != "" { policyID = id } @@ -478,7 +479,7 @@ func TestConfigurationPolicyAssociation_TargetTypeDerived(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID, _ := createResp["Id"].(string) require.NotEmpty(t, policyID) @@ -495,7 +496,7 @@ func TestConfigurationPolicyAssociation_TargetTypeDerived(t *testing.T) { require.Equal(t, http.StatusOK, assocRec.Code) var assocResp map[string]any - require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp)) + require.NoError(t, json.Unmarshal(assocRec.Body.Bytes(), &assocResp, jsonx.V1())) assert.Equal( t, tc.wantTargetType, @@ -517,7 +518,7 @@ func TestConfigurationPolicyAssociation_TargetTypeDerived(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tc.wantTargetType, getResp["TargetType"]) }) } @@ -541,7 +542,7 @@ func TestCreateConfigurationPolicy_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policyArn, _ := resp["Arn"].(string) require.NotEmpty(t, policyArn) @@ -549,7 +550,7 @@ func TestCreateConfigurationPolicy_TagsReachTagStore(t *testing.T) { require.Equal(t, http.StatusOK, tagRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(tagRec.Body.Bytes(), &tagResp, jsonx.V1())) tags, _ := tagResp["Tags"].(map[string]any) assert.Equal(t, "sec", tags["team"]) } diff --git a/services/securityhub/connectors_v2_test.go b/services/securityhub/connectors_v2_test.go index 109b44ebed..d591b2b36a 100644 --- a/services/securityhub/connectors_v2_test.go +++ b/services/securityhub/connectors_v2_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -148,7 +149,7 @@ func TestConnectorsV2(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -217,7 +218,7 @@ func TestTicketV2(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) connectorID, _ := created["ConnectorId"].(string) require.NotEmpty(t, connectorID) @@ -584,7 +585,7 @@ func TestConnectors(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/controls_test.go b/services/securityhub/controls_test.go index 0233babd92..c6cc0ba356 100644 --- a/services/securityhub/controls_test.go +++ b/services/securityhub/controls_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -23,7 +24,7 @@ func TestListSecurityControlDefinitionsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) defs, _ := resp["SecurityControlDefinitions"].([]any) assert.NotEmpty(t, defs) @@ -51,7 +52,7 @@ func TestGetSecurityControlDefinitionPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) def, _ := resp["SecurityControlDefinition"].(map[string]any) assert.Equal(t, "CloudTrail.1", def["SecurityControlId"]) @@ -70,7 +71,7 @@ func TestBatchGetSecurityControlsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) controls, _ := resp["SecurityControls"].([]any) assert.Len(t, controls, 2) diff --git a/services/securityhub/exportjobs_v2.go b/services/securityhub/exportjobs_v2.go index 13549c7258..184b5383c7 100644 --- a/services/securityhub/exportjobs_v2.go +++ b/services/securityhub/exportjobs_v2.go @@ -4,7 +4,8 @@ import ( "bytes" "context" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "path" @@ -20,6 +21,7 @@ import ( shtypes "github.com/aws/aws-sdk-go-v2/service/securityhub/types" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -195,9 +197,9 @@ func (b *InMemoryBackend) renderExportLocked(job *ExportJobV2) ([]byte, string, if job.Format == exportFormatOCSFJSON { var buf bytes.Buffer - enc := json.NewEncoder(&buf) + enc := jsontext.NewEncoder(&buf, jsonx.V1()) for _, f := range findings { - if err := enc.Encode(f); err != nil { + if err := json.MarshalEncode(enc, f); err != nil { return nil, "", fmt.Errorf("encode finding: %w", err) } } diff --git a/services/securityhub/finding_aggregators_test.go b/services/securityhub/finding_aggregators_test.go index a2c3c6bd54..657e5f0753 100644 --- a/services/securityhub/finding_aggregators_test.go +++ b/services/securityhub/finding_aggregators_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -157,7 +158,7 @@ func TestFindingAggregator(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/findings.go b/services/securityhub/findings.go index beee5a999b..46307e7f9d 100644 --- a/services/securityhub/findings.go +++ b/services/securityhub/findings.go @@ -1,7 +1,7 @@ package securityhub import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -9,6 +9,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // findingCustomerManagedFields lists ASFF fields that AWS documents as @@ -304,7 +305,7 @@ func findingSortValue(f map[string]any, field string) string { return s } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return fmt.Sprintf("%v", v) } @@ -649,7 +650,7 @@ func historyValueString(v any) *string { return &s } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { s := fmt.Sprintf("%v", v) diff --git a/services/securityhub/findings_test.go b/services/securityhub/findings_test.go index 37438817d5..4710a20ea7 100644 --- a/services/securityhub/findings_test.go +++ b/services/securityhub/findings_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -25,7 +26,7 @@ func TestGetFindingsIsPOSTFindings(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasFindings := resp["Findings"] assert.True(t, hasFindings, "GetFindings must return 'Findings' key") @@ -59,7 +60,7 @@ func TestBatchImportFindingsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "SuccessCount") assert.Contains(t, resp, "FailedCount") @@ -108,7 +109,7 @@ func TestBatchUpdateFindingsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ProcessedFindings") assert.Contains(t, resp, "UnprocessedFindings") @@ -145,7 +146,7 @@ func TestGetFindingHistoryPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Records") } @@ -274,7 +275,7 @@ func TestBatchImportFindings_RequiredFields(t *testing.T) { SuccessCount int `json:"SuccessCount"` FailedCount int `json:"FailedCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantSuccess, resp.SuccessCount, "SuccessCount") assert.Equal(t, tc.wantFailed, resp.FailedCount, "FailedCount") @@ -308,7 +309,7 @@ func TestBatchImportFindings_MixedValidity(t *testing.T) { SuccessCount int `json:"SuccessCount"` FailedCount int `json:"FailedCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 2, resp.SuccessCount) assert.Equal(t, 1, resp.FailedCount) assert.Len(t, resp.FailedFindings, 1) @@ -329,7 +330,7 @@ func TestBatchImportFindings_EmptyFindingsList(t *testing.T) { SuccessCount int `json:"SuccessCount"` FailedCount int `json:"FailedCount"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, 0, resp.SuccessCount) assert.Equal(t, 0, resp.FailedCount) } @@ -452,7 +453,7 @@ func TestGetFindings_FiltersApplied(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) assert.Len(t, findings, tt.wantCount) }) @@ -497,7 +498,7 @@ func TestGetFindings_MultipleFilterCombinations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) assert.Len(t, findings, 1) @@ -546,7 +547,7 @@ func TestGetFindings_MultiValueSameFieldCombination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) gotIDs := make([]string, 0, len(findings)) @@ -589,7 +590,7 @@ func TestGetFindings_MultiValueSameFieldCombination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) gotIDs := make([]string, 0, len(findings)) @@ -875,7 +876,7 @@ func TestGetFindingHistory_RecordsChanges(t *testing.T) { var resp struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Records, 3, "creation + 2 field-change events") @@ -914,6 +915,6 @@ func TestGetFindingHistory_UnknownFinding(t *testing.T) { var resp struct { Records []map[string]any `json:"Records"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.Records) } diff --git a/services/securityhub/findings_v2_test.go b/services/securityhub/findings_v2_test.go index 3db52a1e3d..d714f3a33b 100644 --- a/services/securityhub/findings_v2_test.go +++ b/services/securityhub/findings_v2_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -90,7 +91,7 @@ func TestGetFindingsV2_ReturnsSameStoreAsV1(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) assert.NotEmpty(t, findings) } @@ -213,7 +214,7 @@ func TestGetFindingsV2_CompositeFilters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) assert.Len(t, findings, tc.wantCount) }) @@ -573,7 +574,7 @@ func TestGetFindingsV2_CompositeFilters_DateMapIPBooleanNested(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) findings, _ := resp["Findings"].([]any) assert.Len(t, findings, tc.wantCount) @@ -652,7 +653,7 @@ func TestBatchUpdateFindingsV2_WireShape(t *testing.T) { ProcessedFindings []map[string]any `json:"ProcessedFindings"` UnprocessedFindings []map[string]any `json:"UnprocessedFindings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ProcessedFindings, 1, "matching identifier must process") assert.Empty(t, resp.UnprocessedFindings) @@ -674,7 +675,7 @@ func TestBatchUpdateFindingsV2_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) findings, _ := getResp["Findings"].([]any) require.Len(t, findings, 1) @@ -718,7 +719,7 @@ func TestBatchUpdateFindingsV2_UnmatchedIdentifiers(t *testing.T) { ProcessedFindings []map[string]any `json:"ProcessedFindings"` UnprocessedFindings []map[string]any `json:"UnprocessedFindings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.ProcessedFindings) require.Len(t, resp.UnprocessedFindings, 2) @@ -749,7 +750,7 @@ func TestBatchUpdateFindingsV2_MetadataUids(t *testing.T) { } `json:"metadata"` } `json:"Findings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got, jsonx.V1())) require.Len(t, got.Findings, 1) uid := got.Findings[0].Metadata.UID require.NotEmpty(t, uid) @@ -764,7 +765,7 @@ func TestBatchUpdateFindingsV2_MetadataUids(t *testing.T) { ProcessedFindings []map[string]any `json:"ProcessedFindings"` UnprocessedFindings []map[string]any `json:"UnprocessedFindings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.ProcessedFindings, 1) assert.Equal(t, uid, resp.ProcessedFindings[0]["MetadataUid"]) require.Len(t, resp.UnprocessedFindings, 1) diff --git a/services/securityhub/handler.go b/services/securityhub/handler.go index 6af5cce26e..cb17f85531 100644 --- a/services/securityhub/handler.go +++ b/services/securityhub/handler.go @@ -1,7 +1,8 @@ package securityhub import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strconv" @@ -12,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -569,7 +571,12 @@ func decodeJSONBody(c *echo.Context) (map[string]any, error) { var body map[string]any if c.Request().ContentLength != 0 { - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && err.Error() != "EOF" { + if err := json.UnmarshalDecode( + jsontext.NewDecoder(c.Request().Body), + &body, + jsonx.V1(), + ); err != nil && + err.Error() != "EOF" { return nil, errInvalidJSONBody } } diff --git a/services/securityhub/handler_malformed_body_test.go b/services/securityhub/handler_malformed_body_test.go index 0c9726e12b..6bfe66ac54 100644 --- a/services/securityhub/handler_malformed_body_test.go +++ b/services/securityhub/handler_malformed_body_test.go @@ -2,7 +2,7 @@ package securityhub_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -77,6 +78,6 @@ func TestMalformedJSONBody_DoesNotDoubleWrite(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]any - err := json.Unmarshal(rec.Body.Bytes(), &body) + err := json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1()) require.NoError(t, err, "a single write must produce one well-formed JSON body, got: %s", rec.Body.String()) } diff --git a/services/securityhub/handler_test.go b/services/securityhub/handler_test.go index 16e8d9fc57..ca62e3017d 100644 --- a/services/securityhub/handler_test.go +++ b/services/securityhub/handler_test.go @@ -2,7 +2,7 @@ package securityhub_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -29,7 +30,7 @@ func doRequest(t *testing.T, h *securityhub.Handler, method, path string, body a if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/securityhub/hub_test.go b/services/securityhub/hub_test.go index edce2bedfd..a3548a8b9e 100644 --- a/services/securityhub/hub_test.go +++ b/services/securityhub/hub_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "sync" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -24,7 +25,7 @@ func TestEnableSecurityHubPath(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // EnableSecurityHub returns an empty object, not a hub object assert.NotContains(t, resp, "HubArn", "EnableSecurityHub must return empty body") } @@ -41,7 +42,7 @@ func TestDescribeHubIsGETAccounts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "HubArn") assert.Contains(t, resp, "SubscribedAt") @@ -97,7 +98,7 @@ func TestUpdateSecurityHubConfigurationIsPATCHAccounts(t *testing.T) { // Verify the change rec2 := doRequest(t, h, http.MethodGet, "/accounts", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, false, resp["AutoEnableControls"]) assert.Equal(t, "NONE", resp["AutoEnableStandards"]) @@ -115,7 +116,7 @@ func TestHubArnFormat(t *testing.T) { rec := doRequest(t, h, http.MethodGet, "/accounts", nil) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hubArn, _ := resp["HubArn"].(string) assert.Equal(t, "arn:aws:securityhub:eu-west-1:123456789012:hub/default", hubArn) @@ -214,7 +215,7 @@ func TestHubV2(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -404,7 +405,7 @@ func TestHubV2Features(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/insights_test.go b/services/securityhub/insights_test.go index 89e6962324..a1097c96d1 100644 --- a/services/securityhub/insights_test.go +++ b/services/securityhub/insights_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -27,7 +28,7 @@ func TestCreateInsightPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["InsightArn"].(string) assert.NotEmpty(t, arn) @@ -47,7 +48,7 @@ func TestGetInsightsIsPOSTInsightsGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Insights") } @@ -67,7 +68,7 @@ func TestGetInsightResultsIsGETInsightsResults(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) insightArn, _ := createResp["InsightArn"].(string) require.NotEmpty(t, insightArn) @@ -76,7 +77,7 @@ func TestGetInsightResultsIsGETInsightsResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Response is wrapped in InsightResults key insightResults, hasWrapper := resp["InsightResults"] @@ -103,7 +104,7 @@ func TestUpdateInsightIsPATCHInsightsArn(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) insightArn, _ := createResp["InsightArn"].(string) require.NotEmpty(t, insightArn) @@ -129,7 +130,7 @@ func TestDeleteInsightIsDELETEInsightsArn(t *testing.T) { }) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) insightArn, _ := createResp["InsightArn"].(string) require.NotEmpty(t, insightArn) @@ -138,7 +139,7 @@ func TestDeleteInsightIsDELETEInsightsArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deletedArn, _ := resp["InsightArn"].(string) assert.Equal(t, insightArn, deletedArn, "DeleteInsight must return the deleted InsightArn") diff --git a/services/securityhub/invitations_test.go b/services/securityhub/invitations_test.go index 40c945ec34..1f6fa9dcb3 100644 --- a/services/securityhub/invitations_test.go +++ b/services/securityhub/invitations_test.go @@ -1,13 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -176,7 +177,7 @@ func TestInvitations(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -375,7 +376,7 @@ func TestHandler_DeclineDeleteInvitations(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/members_only_associated_test.go b/services/securityhub/members_only_associated_test.go index 91858b60d5..f770b4c89a 100644 --- a/services/securityhub/members_only_associated_test.go +++ b/services/securityhub/members_only_associated_test.go @@ -1,12 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListMembers_OnlyAssociated(t *testing.T) { @@ -43,7 +45,7 @@ func TestListMembers_OnlyAssociated(t *testing.T) { InvitationID string `json:"InvitationId"` } `json:"Invitations"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inv)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &inv, jsonx.V1())) require.Len(t, inv.Invitations, 1) require.Equal(t, http.StatusOK, doRequest(t, h, http.MethodPost, "/administrator", map[string]any{ @@ -59,7 +61,7 @@ func TestListMembers_OnlyAssociated(t *testing.T) { MemberStatus string `json:"MemberStatus"` } `json:"Members"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Members, tt.wantCount) if tt.accept { diff --git a/services/securityhub/members_test.go b/services/securityhub/members_test.go index 6302e69ced..dcc3716295 100644 --- a/services/securityhub/members_test.go +++ b/services/securityhub/members_test.go @@ -1,12 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestMembers(t *testing.T) { @@ -196,7 +198,7 @@ func TestMembers(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) @@ -242,7 +244,7 @@ func TestInviteMembers_UnknownAccountUnprocessed(t *testing.T) { require.Equal(t, http.StatusOK, inviteRec.Code) var inviteResp map[string]any - require.NoError(t, json.Unmarshal(inviteRec.Body.Bytes(), &inviteResp)) + require.NoError(t, json.Unmarshal(inviteRec.Body.Bytes(), &inviteResp, jsonx.V1())) unprocessed, _ := inviteResp["UnprocessedAccounts"].([]any) assert.Len(t, unprocessed, tc.wantUnproc) @@ -252,7 +254,7 @@ func TestInviteMembers_UnknownAccountUnprocessed(t *testing.T) { require.Equal(t, http.StatusOK, membersRec.Code) var membersResp map[string]any - require.NoError(t, json.Unmarshal(membersRec.Body.Bytes(), &membersResp)) + require.NoError(t, json.Unmarshal(membersRec.Body.Bytes(), &membersResp, jsonx.V1())) members, _ := membersResp["Members"].([]any) require.Len(t, members, 1) diff --git a/services/securityhub/multi_region_test.go b/services/securityhub/multi_region_test.go index 07a6fd9aa7..d1c4f3b66c 100644 --- a/services/securityhub/multi_region_test.go +++ b/services/securityhub/multi_region_test.go @@ -2,7 +2,7 @@ package securityhub_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/securityhub/organizations_test.go b/services/securityhub/organizations_test.go index c9c92ddbdf..71cb1c983b 100644 --- a/services/securityhub/organizations_test.go +++ b/services/securityhub/organizations_test.go @@ -1,12 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestOrganization(t *testing.T) { @@ -133,7 +135,7 @@ func TestOrganization(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/persistence_test.go b/services/securityhub/persistence_test.go index 933d687449..1b35e47c82 100644 --- a/services/securityhub/persistence_test.go +++ b/services/securityhub/persistence_test.go @@ -1,12 +1,14 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -285,12 +287,12 @@ func TestInMemoryBackend_SnapshotRestore_FullState(t *testing.T) { assert.Equal(t, "finding-metadata-uid-1", ticketV2.FindingMetadataUid) var snapOut struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` } - require.NoError(t, json.Unmarshal(snap, &snapOut)) + require.NoError(t, json.Unmarshal(snap, &snapOut, jsonx.V1())) var restoredTickets []securityhub.TicketV2 - require.NoError(t, json.Unmarshal(snapOut.Tables["ticketsV2"], &restoredTickets)) + require.NoError(t, json.Unmarshal(snapOut.Tables["ticketsV2"], &restoredTickets, jsonx.V1())) require.Len(t, restoredTickets, 1) assert.Equal(t, ticketV2.TicketId, restoredTickets[0].TicketId) assert.Equal(t, connV2.ConnectorId, restoredTickets[0].ConnectorId) diff --git a/services/securityhub/products_test.go b/services/securityhub/products_test.go index c16355686f..fe99496c0d 100644 --- a/services/securityhub/products_test.go +++ b/services/securityhub/products_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -24,7 +25,7 @@ func TestDescribeProductsIsGETProducts(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) products, _ := resp["Products"].([]any) assert.NotEmpty(t, products) @@ -49,7 +50,7 @@ func TestEnableImportFindingsForProductPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) subArn, _ := resp["ProductSubscriptionArn"].(string) assert.NotEmpty(t, subArn) @@ -69,7 +70,7 @@ func TestListEnabledProductsForImportIsGETProductSubscriptions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) subs, _ := resp["ProductSubscriptions"].([]any) assert.Len(t, subs, 1) @@ -170,7 +171,7 @@ func TestHandler_DisableImportFindingsForProduct(t *testing.T) { require.Equal(t, http.StatusOK, enableRec.Code) var enableResp map[string]any - require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp)) + require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp, jsonx.V1())) subArn, _ := enableResp["ProductSubscriptionArn"].(string) require.NotEmpty(t, subArn) @@ -308,7 +309,7 @@ func TestRecommendedPolicyV2(t *testing.T) { rec := doRequest(t, h, s.method, s.path, s.body) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s.check(t, rec.Code, resp) } }) diff --git a/services/securityhub/resources_v2_test.go b/services/securityhub/resources_v2_test.go index 365c7caeeb..a9376cd268 100644 --- a/services/securityhub/resources_v2_test.go +++ b/services/securityhub/resources_v2_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -23,7 +24,7 @@ func TestGetResourcesV2_Empty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, _ := resp["Resources"].([]any) assert.Empty(t, resources) } diff --git a/services/securityhub/standards_test.go b/services/securityhub/standards_test.go index b5a83bbb6e..1c9a8f12e1 100644 --- a/services/securityhub/standards_test.go +++ b/services/securityhub/standards_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/securityhub" ) @@ -30,7 +31,7 @@ func TestBatchEnableStandardsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) subs, _ := resp["StandardsSubscriptions"].([]any) assert.Len(t, subs, 1) @@ -55,7 +56,7 @@ func TestBatchEnableStandardsPath(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) gotSubs, _ := getResp["StandardsSubscriptions"].([]any) require.Len(t, gotSubs, 1) @@ -76,7 +77,7 @@ func TestBatchDisableStandardsPath(t *testing.T) { }) var enableResp map[string]any - require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp)) + require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp, jsonx.V1())) subs, _ := enableResp["StandardsSubscriptions"].([]any) require.Len(t, subs, 1) @@ -89,7 +90,7 @@ func TestBatchDisableStandardsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) disabledSubs, _ := resp["StandardsSubscriptions"].([]any) assert.Len(t, disabledSubs, 1) @@ -113,7 +114,7 @@ func TestGetEnabledStandardsIsPOSTStandardsGet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "StandardsSubscriptions") } @@ -128,7 +129,7 @@ func TestDescribeStandardsIsGETStandards(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) standards, _ := resp["Standards"].([]any) assert.NotEmpty(t, standards, "DescribeStandards must return built-in standards") @@ -157,7 +158,7 @@ func TestDescribeStandardsControlsPath(t *testing.T) { }) var enableResp map[string]any - require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp)) + require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp, jsonx.V1())) subs, _ := enableResp["StandardsSubscriptions"].([]any) subArn, _ := subs[0].(map[string]any)["StandardsSubscriptionArn"].(string) @@ -166,7 +167,7 @@ func TestDescribeStandardsControlsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) controls, _ := resp["Controls"].([]any) assert.NotEmpty(t, controls) @@ -192,7 +193,7 @@ func TestUpdateStandardsControlPath(t *testing.T) { }) var enableResp map[string]any - require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp)) + require.NoError(t, json.Unmarshal(enableRec.Body.Bytes(), &enableResp, jsonx.V1())) subs, _ := enableResp["StandardsSubscriptions"].([]any) subArn, _ := subs[0].(map[string]any)["StandardsSubscriptionArn"].(string) @@ -253,7 +254,7 @@ func TestListStandardsControlAssociationsIsGETAssociations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assocs, _ := resp["StandardsControlAssociationSummaries"].([]any) assert.NotEmpty(t, assocs) @@ -282,7 +283,7 @@ func TestBatchGetStdCtlAssociationsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "StandardsControlAssociationDetails") assert.Contains(t, resp, "UnprocessedAssociations") @@ -308,7 +309,7 @@ func TestBatchUpdateStdCtlAssociationsPath(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "UnprocessedAssociationUpdates") } @@ -332,7 +333,7 @@ func TestBatchDisableStandards_StatusIsDeleting(t *testing.T) { require.Equal(t, http.StatusOK, enRec.Code) var enResp map[string]any - require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp)) + require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp, jsonx.V1())) subs, _ := enResp["StandardsSubscriptions"].([]any) require.NotEmpty(t, subs) @@ -346,7 +347,7 @@ func TestBatchDisableStandards_StatusIsDeleting(t *testing.T) { require.Equal(t, http.StatusOK, disRec.Code) var disResp map[string]any - require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp)) + require.NoError(t, json.Unmarshal(disRec.Body.Bytes(), &disResp, jsonx.V1())) disabledSubs, _ := disResp["StandardsSubscriptions"].([]any) require.NotEmpty(t, disabledSubs) @@ -375,7 +376,7 @@ func TestUpdateStandardsControl_StatusValidation(t *testing.T) { require.Equal(t, http.StatusOK, enRec.Code) var enResp map[string]any - require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp)) + require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp, jsonx.V1())) subs, _ := enResp["StandardsSubscriptions"].([]any) require.NotEmpty(t, subs) @@ -454,7 +455,7 @@ func TestBatchUpdateStdCtlAssociations_Persisted(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) unprocessed, _ := updateResp["UnprocessedAssociationUpdates"].([]any) assert.Empty(t, unprocessed, "update must succeed without unprocessed items") @@ -470,7 +471,7 @@ func TestBatchUpdateStdCtlAssociations_Persisted(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) details, _ := getResp["StandardsControlAssociationDetails"].([]any) require.Len(t, details, 1) @@ -503,7 +504,7 @@ func TestStandardsSubscriptions_IncludesStandardsStatusReason(t *testing.T) { require.Equal(t, http.StatusOK, enRec.Code) var enResp map[string]any - require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp)) + require.NoError(t, json.Unmarshal(enRec.Body.Bytes(), &enResp, jsonx.V1())) subs, _ := enResp["StandardsSubscriptions"].([]any) require.NotEmpty(t, subs) @@ -516,7 +517,7 @@ func TestStandardsSubscriptions_IncludesStandardsStatusReason(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) getSubs, _ := getResp["StandardsSubscriptions"].([]any) require.NotEmpty(t, getSubs) _, hasStatusReasonGet := getSubs[0].(map[string]any)["StandardsStatusReason"] @@ -544,7 +545,7 @@ func TestBatchGetStdCtlAssociations_MissingFields(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) details, _ := resp["StandardsControlAssociationDetails"].([]any) require.Len(t, details, 1) diff --git a/services/securityhub/store.go b/services/securityhub/store.go index 91f8855b2e..6c78a2da4a 100644 --- a/services/securityhub/store.go +++ b/services/securityhub/store.go @@ -2,7 +2,7 @@ package securityhub import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "strconv" @@ -229,9 +229,9 @@ type snapshot struct { OrgAdminAccounts map[string]string `json:"orgAdminAccounts"` OrgAdminAccountFeatures map[string]string `json:"orgAdminAccountFeatures,omitempty"` // V2 - HubV2 *HubV2 `json:"hubV2"` - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + HubV2 *HubV2 `json:"hubV2"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` StandardsSeq int `json:"standardsSeq"` ActionTargetSeq int `json:"actionTargetSeq"` diff --git a/services/securityhub/tags_test.go b/services/securityhub/tags_test.go index 2a03bc6b18..e1775d28e1 100644 --- a/services/securityhub/tags_test.go +++ b/services/securityhub/tags_test.go @@ -1,7 +1,7 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -9,6 +9,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Batch-1 accuracy gap: TagResource is POST /tags/{ResourceArn}. @@ -40,7 +42,7 @@ func TestListTagsForResourceIsGETTagsArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["Tags"].(map[string]any) assert.Equal(t, "prod", tags["env"]) @@ -71,7 +73,7 @@ func TestUntagResourceIsDELETETagsArn(t *testing.T) { // Verify tag removed getRec := doRequest(t, h, http.MethodGet, "/tags/"+testArn, nil) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["Tags"].(map[string]any) _, hasEnv := tags["env"] diff --git a/services/securityhub/ticket_v2_client_token_test.go b/services/securityhub/ticket_v2_client_token_test.go index e025a37104..16f83719c2 100644 --- a/services/securityhub/ticket_v2_client_token_test.go +++ b/services/securityhub/ticket_v2_client_token_test.go @@ -1,13 +1,15 @@ package securityhub_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateTicketV2_ClientToken(t *testing.T) { @@ -43,7 +45,7 @@ func TestCreateTicketV2_ClientToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var conn map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &conn)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &conn, jsonx.V1())) create := func(token, uid string) *httptest.ResponseRecorder { body := map[string]any{"ConnectorId": conn["ConnectorId"], "FindingMetadataUid": uid} @@ -64,8 +66,8 @@ func TestCreateTicketV2_ClientToken(t *testing.T) { } var a, b map[string]any - require.NoError(t, json.Unmarshal(first.Body.Bytes(), &a)) - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b)) + require.NoError(t, json.Unmarshal(first.Body.Bytes(), &a, jsonx.V1())) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &b, jsonx.V1())) assert.Equal(t, tt.wantSame, a["TicketId"] == b["TicketId"]) }) } diff --git a/services/serverlessrepo/handler.go b/services/serverlessrepo/handler.go index db8f90cc83..1ddefb91c4 100644 --- a/services/serverlessrepo/handler.go +++ b/services/serverlessrepo/handler.go @@ -3,7 +3,7 @@ package serverlessrepo import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -18,6 +18,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -412,34 +413,31 @@ func (h *Handler) dispatchPolicyAndMiscOps( } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): // json.Marshal on map[string]string never returns an error; _ is intentional. payload, _ := json.Marshal(map[string]string{ keyTypeField: "NotFoundException", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusNotFound, payload) case errors.Is(err, awserr.ErrConflict): payload, _ := json.Marshal(map[string]string{ keyTypeField: "ConflictException", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusConflict, payload) case errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): payload, _ := json.Marshal(map[string]string{ keyTypeField: "BadRequestException", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) default: @@ -452,7 +450,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: "InternalServerErrorException", keyMessageField: wireMessage(err), - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusInternalServerError, payload) } diff --git a/services/serverlessrepo/handler_application_dependencies.go b/services/serverlessrepo/handler_application_dependencies.go index 6b5ee859fe..ff10b8b17a 100644 --- a/services/serverlessrepo/handler_application_dependencies.go +++ b/services/serverlessrepo/handler_application_dependencies.go @@ -1,8 +1,10 @@ package serverlessrepo import ( - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleListApplicationDependencies(req *http.Request) ([]byte, error) { @@ -60,5 +62,5 @@ func (h *Handler) handleListApplicationDependencies(req *http.Request) ([]byte, resp["nextToken"] = encodePageToken(deps[end-1].ApplicationID) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/serverlessrepo/handler_application_dependencies_test.go b/services/serverlessrepo/handler_application_dependencies_test.go index ef4312c455..e1de9bc828 100644 --- a/services/serverlessrepo/handler_application_dependencies_test.go +++ b/services/serverlessrepo/handler_application_dependencies_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -44,7 +45,7 @@ func TestHandler_ListApplicationDependencies(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, ok := resp["dependencies"].([]any) require.True(t, ok) assert.Empty(t, deps) @@ -64,7 +65,7 @@ func TestListApplicationDependencies_EmptyList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps, ok := resp["dependencies"].([]any) require.True(t, ok, "dependencies must be an array (not nil)") assert.Empty(t, deps) @@ -91,7 +92,7 @@ func TestListApplicationDependencies_NoSemanticVersion_ReturnsEmpty(t *testing.T require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) deps := resp["dependencies"].([]any) assert.Empty(t, deps) } @@ -139,7 +140,7 @@ func TestListApplicationDependencies_PersistedState(t *testing.T) { var response struct { Dependencies []serverlessrepo.ApplicationDependency `json:"dependencies"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response, jsonx.V1())) assert.ElementsMatch(t, tests, response.Dependencies) } @@ -171,7 +172,7 @@ func TestListApplicationDependencies_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp1, jsonx.V1())) page1, ok := resp1["dependencies"].([]any) require.True(t, ok) assert.Len(t, page1, 2) @@ -186,7 +187,7 @@ func TestListApplicationDependencies_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) page2, ok := resp2["dependencies"].([]any) require.True(t, ok) assert.Len(t, page2, 1) @@ -214,7 +215,7 @@ func TestListApplicationDependencies_MaxItemsDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) page, ok := resp["dependencies"].([]any) require.True(t, ok) assert.Len(t, page, 3) @@ -243,7 +244,7 @@ func TestListApplicationDependencies_DeterministicOrder(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) page, ok := resp["dependencies"].([]any) require.True(t, ok) require.Len(t, page, 3) diff --git a/services/serverlessrepo/handler_application_policy.go b/services/serverlessrepo/handler_application_policy.go index 92b55284d7..14867354f6 100644 --- a/services/serverlessrepo/handler_application_policy.go +++ b/services/serverlessrepo/handler_application_policy.go @@ -2,10 +2,11 @@ package serverlessrepo import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -33,7 +34,7 @@ func (h *Handler) handleGetApplicationPolicy(req *http.Request) ([]byte, error) return nil, backendErr } - return json.Marshal(map[string]any{"statements": toPolicyStatementsResponse(stmts)}) + return json.Marshal(map[string]any{"statements": toPolicyStatementsResponse(stmts)}, jsonx.V1()) } func (h *Handler) handlePutApplicationPolicy(ctx context.Context, req *http.Request, body []byte) ([]byte, error) { @@ -43,7 +44,7 @@ func (h *Handler) handlePutApplicationPolicy(ctx context.Context, req *http.Requ } var putReq putApplicationPolicyRequest - if jsonErr := json.Unmarshal(body, &putReq); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &putReq, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, jsonErr) } @@ -65,7 +66,7 @@ func (h *Handler) handlePutApplicationPolicy(ctx context.Context, req *http.Requ log := logger.Load(ctx) log.InfoContext(ctx, "serverlessrepo: updated application policy", "app", appName) - return json.Marshal(map[string]any{"statements": toPolicyStatementsResponse(result)}) + return json.Marshal(map[string]any{"statements": toPolicyStatementsResponse(result)}, jsonx.V1()) } func toPolicyStatementsResponse(stmts []*ApplicationPolicyStatement) []map[string]any { diff --git a/services/serverlessrepo/handler_application_policy_test.go b/services/serverlessrepo/handler_application_policy_test.go index 56d4e38805..9eff5ad563 100644 --- a/services/serverlessrepo/handler_application_policy_test.go +++ b/services/serverlessrepo/handler_application_policy_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -62,7 +63,7 @@ func TestHandler_GetApplicationPolicy(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok) assert.Len(t, stmts, tt.wantLen) @@ -82,7 +83,7 @@ func TestGetApplicationPolicy_EmptyForNewApp(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok, "statements must be an array") assert.Empty(t, stmts, "new app should have empty policy statements") @@ -99,7 +100,7 @@ func TestGetApplicationPolicy_EmptyPolicy_ReturnsEmptyStatements(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["statements"].([]any) assert.Empty(t, stmts) } @@ -125,7 +126,7 @@ func TestGetApplicationPolicy_ARNForm_Routing(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok) assert.Len(t, stmts, 1) @@ -198,7 +199,7 @@ func TestHandler_PutApplicationPolicy(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok) assert.Len(t, stmts, tt.wantLen) @@ -334,7 +335,7 @@ func TestErrValidation_MapsTo400(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) } @@ -357,7 +358,7 @@ func TestPutApplicationPolicy_AutoGeneratesStatementID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok) require.Len(t, stmts, 1) @@ -382,7 +383,7 @@ func TestPolicyResponse_NonNilSlices(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts, ok := resp["statements"].([]any) require.True(t, ok) @@ -419,7 +420,7 @@ func TestPutApplicationPolicy_PrincipalOrgIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) stmts := putResp["statements"].([]any) stmt := stmts[0].(map[string]any) orgIDs := stmt["principalOrgIDs"].([]any) @@ -431,7 +432,7 @@ func TestPutApplicationPolicy_PrincipalOrgIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) stmts2 := getResp["statements"].([]any) stmt2 := stmts2[0].(map[string]any) orgIDs2 := stmt2["principalOrgIDs"].([]any) @@ -466,7 +467,7 @@ func TestPutApplicationPolicy_ReplacesExistingStatements(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stmts := resp["statements"].([]any) require.Len(t, stmts, 1, "policy should have exactly 1 statement after replacement") assert.Equal(t, "s3", stmts[0].(map[string]any)["statementId"]) diff --git a/services/serverlessrepo/handler_application_versions.go b/services/serverlessrepo/handler_application_versions.go index 9ac76253e0..cb0019dc31 100644 --- a/services/serverlessrepo/handler_application_versions.go +++ b/services/serverlessrepo/handler_application_versions.go @@ -2,10 +2,11 @@ package serverlessrepo import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -32,7 +33,7 @@ func (h *Handler) handleCreateApplicationVersion(ctx context.Context, req *http. } var createReq createApplicationVersionRequest - if jsonErr := json.Unmarshal(body, &createReq); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &createReq, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, jsonErr) } @@ -66,7 +67,7 @@ func (h *Handler) handleCreateApplicationVersion(ctx context.Context, req *http. log.InfoContext(ctx, "serverlessrepo: created application version", "app", appName, "version", v.SemanticVersion) - b, marshalErr := json.Marshal(toVersionResponse(v)) + b, marshalErr := json.Marshal(toVersionResponse(v), jsonx.V1()) if marshalErr != nil { return nil, marshalErr } @@ -154,7 +155,7 @@ func (h *Handler) handleListApplicationVersions(req *http.Request) ([]byte, erro resp["nextToken"] = encodePageToken(versions[end-1].SemanticVersion) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // toVersionResponse converts an ApplicationVersion to a map matching the AWS SAR Version shape. diff --git a/services/serverlessrepo/handler_application_versions_test.go b/services/serverlessrepo/handler_application_versions_test.go index be50a10f44..8fb701489d 100644 --- a/services/serverlessrepo/handler_application_versions_test.go +++ b/services/serverlessrepo/handler_application_versions_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -69,7 +70,7 @@ func TestHandler_CreateApplicationVersion(t *testing.T) { if tt.wantCode == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "1.0.0", resp["semanticVersion"]) assert.NotNil(t, resp["parameterDefinitions"]) assert.NotNil(t, resp["requiredCapabilities"]) @@ -104,7 +105,7 @@ func TestCreateApplicationVersion_HasRequiredFields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["parameterDefinitions"], "parameterDefinitions must be present") assert.NotNil(t, resp["requiredCapabilities"], "requiredCapabilities must be present") assert.Equal(t, true, resp["resourcesSupported"]) @@ -157,7 +158,7 @@ func TestCreateApplicationVersion_ArchiveURL(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var response map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response, jsonx.V1())) assert.Equal(t, "s3://bucket/archive.zip", response["sourceCodeArchiveUrl"]) assert.NotEmpty(t, response["templateUrl"]) }) @@ -177,7 +178,7 @@ func TestCreateApplicationVersion_TemplateURLOnly(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "s3://bucket/tmpl.yaml", resp["templateUrl"]) } @@ -227,7 +228,7 @@ func TestCreateApplicationVersion_TemplateBody(t *testing.T) { if tt.wantCode == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["templateUrl"]) } }) @@ -251,7 +252,7 @@ func TestCreateApplicationVersion_DuplicateReturns409(t *testing.T) { assert.Equal(t, http.StatusConflict, rec2.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) } @@ -297,7 +298,7 @@ func TestCreateApplicationVersion_GeneratesTemplateURL(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["templateUrl"], "templateUrl should be auto-generated from sourceCodeUrl") } @@ -321,7 +322,7 @@ func TestCreateApplicationVersion_UpdatesLatestVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) version, ok := resp["version"].(map[string]any) require.True(t, ok, "version field must be present") assert.Equal(t, "2.0.0", version["semanticVersion"], "should return latest created version") @@ -341,7 +342,7 @@ func TestCreateApplicationVersion_FullVersionDataInGetApplication(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) version, ok := resp["version"].(map[string]any) require.True(t, ok, "version must be embedded in GetApplication response") assert.Equal(t, "3.1.4", version["semanticVersion"]) @@ -400,7 +401,7 @@ func TestHandler_ListApplicationVersions(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) assert.Len(t, versions, tt.wantLen) @@ -425,7 +426,7 @@ func TestListApplicationVersions_SemanticVersionFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) assert.Len(t, versions, 1, "filter should return only 1 matching version") @@ -448,7 +449,7 @@ func TestListApplicationVersions_MaxItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) assert.Len(t, versions, 2) @@ -472,7 +473,7 @@ func TestListApplicationVersions_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) v1 := r1["versions"].([]any) assert.Len(t, v1, 2) nt, ok := r1["nextToken"].(string) @@ -487,7 +488,7 @@ func TestListApplicationVersions_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) v2 := r2["versions"].([]any) assert.Len(t, v2, 2) assert.Nil(t, r2["nextToken"]) @@ -513,7 +514,7 @@ func TestListApplicationVersions_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) require.Len(t, versions, 1) @@ -543,7 +544,7 @@ func TestListApplicationVersions_ARNForm(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["versions"].([]any) require.True(t, ok) assert.Len(t, versions, 1) diff --git a/services/serverlessrepo/handler_applications.go b/services/serverlessrepo/handler_applications.go index f57d1d61d2..8c9d10a22b 100644 --- a/services/serverlessrepo/handler_applications.go +++ b/services/serverlessrepo/handler_applications.go @@ -2,10 +2,11 @@ package serverlessrepo import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -153,7 +154,7 @@ func toEmbeddedVersionResponse(v *ApplicationVersion) *versionResponse { func (h *Handler) handleCreateApplication(ctx context.Context, body []byte) ([]byte, error) { var req createApplicationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -205,7 +206,7 @@ func (h *Handler) handleCreateApplication(ctx context.Context, body []byte) ([]b resp.Version = toEmbeddedVersionResponse(v) } - b, marshalErr := json.Marshal(resp) + b, marshalErr := json.Marshal(resp, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } @@ -244,7 +245,7 @@ func (h *Handler) handleGetApplication(req *http.Request) ([]byte, error) { } } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleListApplications(req *http.Request) ([]byte, error) { @@ -300,7 +301,7 @@ func (h *Handler) handleListApplications(req *http.Request) ([]byte, error) { resp["nextToken"] = encodePageToken(apps[end-1].Name) } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateApplicationRequest is the request body for UpdateApplication. @@ -320,7 +321,7 @@ func (h *Handler) handleUpdateApplication(ctx context.Context, req *http.Request } var updateReq updateApplicationRequest - if err := json.Unmarshal(body, &updateReq); err != nil { + if err := json.Unmarshal(body, &updateReq, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -365,7 +366,7 @@ func (h *Handler) handleUpdateApplication(ctx context.Context, req *http.Request } } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleDeleteApplication(ctx context.Context, req *http.Request) error { @@ -396,7 +397,7 @@ func (h *Handler) handleUnshareApplication(ctx context.Context, req *http.Reques } var unshareReq unshareApplicationRequest - if jsonErr := json.Unmarshal(body, &unshareReq); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &unshareReq, jsonx.V1()); jsonErr != nil { return fmt.Errorf("%w: %w", errInvalidRequest, jsonErr) } diff --git a/services/serverlessrepo/handler_applications_list_test.go b/services/serverlessrepo/handler_applications_list_test.go index 5a7004cad4..44427f46b2 100644 --- a/services/serverlessrepo/handler_applications_list_test.go +++ b/services/serverlessrepo/handler_applications_list_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -50,7 +51,7 @@ func TestHandler_ListApplications(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps, ok := resp["applications"].([]any) require.True(t, ok) @@ -70,7 +71,7 @@ func TestListApplications_IncludesLabels(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps, ok := resp["applications"].([]any) require.True(t, ok) require.Len(t, apps, 1) @@ -96,7 +97,7 @@ func TestListApplications_PageBoundaryExact(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) apps1 := r1["applications"].([]any) assert.Len(t, apps1, 2) nt := r1["nextToken"].(string) @@ -106,7 +107,7 @@ func TestListApplications_PageBoundaryExact(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2, jsonx.V1())) apps2 := r2["applications"].([]any) assert.Len(t, apps2, 2) assert.Nil(t, r2["nextToken"], "no more pages when exactly on boundary") @@ -126,7 +127,7 @@ func TestListApplications_Pagination_MaxItems(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apps, ok := resp["applications"].([]any) require.True(t, ok) assert.Len(t, apps, 2, "maxItems=2 should return 2 items") @@ -148,7 +149,7 @@ func TestListApplications_Pagination_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) nextToken, ok := resp1["nextToken"].(string) require.True(t, ok, "nextToken should be a string") @@ -157,7 +158,7 @@ func TestListApplications_Pagination_NextToken(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) apps, ok := resp2["applications"].([]any) require.True(t, ok) assert.Len(t, apps, 1, "second page should have 1 remaining item") diff --git a/services/serverlessrepo/handler_applications_test.go b/services/serverlessrepo/handler_applications_test.go index cb1bd90980..59a4f36e2d 100644 --- a/services/serverlessrepo/handler_applications_test.go +++ b/services/serverlessrepo/handler_applications_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -73,7 +74,7 @@ func TestHandler_CreateApplication(t *testing.T) { if tt.wantARN { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["applicationId"]) assert.Equal(t, tt.body["name"], resp["name"]) } @@ -104,7 +105,7 @@ func TestCreateApplication_MissingAuthor(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "Author is required") } @@ -119,7 +120,7 @@ func TestCreateApplication_MissingDescription(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "Description is required") } @@ -136,7 +137,7 @@ func TestCreateApplication_WithLabels(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) labels, ok := resp["labels"].([]any) require.True(t, ok) @@ -156,7 +157,7 @@ func TestCreateApplication_WithHomePageURL(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "https://github.com/example/my-app", resp["homePageUrl"]) } @@ -193,7 +194,7 @@ func TestApplicationResponse_LabelsField(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // SAR uses "labels" ([]string), not "tags" (map[string]string) labels, ok := resp["labels"].([]any) @@ -214,7 +215,7 @@ func TestApplication_ARNFormat(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) appID := resp["applicationId"].(string) assert.Contains(t, appID, "arn:aws:serverlessrepo:") assert.Contains(t, appID, ":applications/arn-check-app") @@ -234,7 +235,7 @@ func TestCreateApplication_TemplateURLOnly_CreatesVersion(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) version := resp["version"].(map[string]any) assert.Equal(t, "s3://bucket/template.yaml", version["templateUrl"]) assert.Equal(t, "1.0.0", version["semanticVersion"]) @@ -256,7 +257,7 @@ func TestCreateApplication_InlineVersion_FullResponseFields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "MIT", resp["spdxLicenseId"]) version, ok := resp["version"].(map[string]any) @@ -339,7 +340,7 @@ func TestApplicationMetadata(t *testing.T) { assert.Contains(t, []int{http.StatusCreated, http.StatusOK}, rec.Code) var response map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &response, jsonx.V1())) tt.check(t, response) }) } @@ -393,7 +394,7 @@ func TestHandler_GetApplication(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "existing-app", resp["name"]) } }) @@ -429,7 +430,7 @@ func TestGetApplication_WithSemanticVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versionObj, ok := resp["version"].(map[string]any) require.True(t, ok, "version field must be present when semanticVersion is requested") assert.Equal(t, "1.2.3", versionObj["semanticVersion"]) @@ -464,7 +465,7 @@ func TestGetApplication_VersionEmbedFromStore_IncludesTemplateURL(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) version := resp["version"].(map[string]any) assert.Equal(t, "1.5.0", version["semanticVersion"]) assert.Equal(t, "s3://my-bucket/tmpl.yaml", version["templateUrl"]) @@ -491,7 +492,7 @@ func TestGetApplication_ResponseFieldCompleteness(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "full-resp-app", resp["name"]) assert.Equal(t, "A full response app", resp["description"]) assert.Equal(t, "test-author", resp["author"]) @@ -576,7 +577,7 @@ func TestUpdateApplication_EmbedCurrentVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) version, ok := resp["version"].(map[string]any) require.True(t, ok, "version must be embedded in UpdateApplication response when current version exists") @@ -597,7 +598,7 @@ func TestUpdateApplication_NoVersionWhenNoneCreated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Nil(t, resp["version"], "version should be absent when no version has been created") } @@ -768,7 +769,7 @@ func TestApplication_ARNFormRouting(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -867,7 +868,7 @@ func TestCreateApplication_BodyVariants(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -910,7 +911,7 @@ func TestUpdateApplication_ReadmeBody(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["readmeUrl"]) } }) diff --git a/services/serverlessrepo/handler_applications_validation_test.go b/services/serverlessrepo/handler_applications_validation_test.go index cdc958b9dd..122c4fc8bc 100644 --- a/services/serverlessrepo/handler_applications_validation_test.go +++ b/services/serverlessrepo/handler_applications_validation_test.go @@ -1,13 +1,15 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestCreateApplication_NameLength(t *testing.T) { @@ -36,7 +38,7 @@ func TestCreateApplication_NameLength(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "Name must be at most") } }) @@ -108,7 +110,7 @@ func TestCreateApplication_AuthorLength(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "Author must be at most") } }) @@ -141,7 +143,7 @@ func TestCreateApplication_DescriptionLength(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "Description must be at most") } }) @@ -266,7 +268,7 @@ func TestCreateApplication_LabelsValidation(t *testing.T) { if tt.wantMessage != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], tt.wantMessage) } }) diff --git a/services/serverlessrepo/handler_cloud_formation.go b/services/serverlessrepo/handler_cloud_formation.go index a39f505d81..c32d30fd17 100644 --- a/services/serverlessrepo/handler_cloud_formation.go +++ b/services/serverlessrepo/handler_cloud_formation.go @@ -2,11 +2,12 @@ package serverlessrepo import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -26,7 +27,7 @@ func (h *Handler) handleCreateCloudFormationTemplate( } var createReq createCFTemplateRequest - if jsonErr := json.Unmarshal(body, &createReq); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &createReq, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, jsonErr) } @@ -51,7 +52,7 @@ func (h *Handler) handleCreateCloudFormationTemplate( resp[keySemanticVersion] = t.SemanticVersion } - b, marshalErr := json.Marshal(resp) + b, marshalErr := json.Marshal(resp, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } @@ -96,7 +97,7 @@ func (h *Handler) handleGetCloudFormationTemplate(req *http.Request) ([]byte, er resp[keySemanticVersion] = t.SemanticVersion } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // createCFChangeSetRequest is the request body for CreateCloudFormationChangeSet. @@ -120,7 +121,7 @@ func (h *Handler) handleCreateCloudFormationChangeSet( } var createReq createCFChangeSetRequest - if jsonErr := json.Unmarshal(body, &createReq); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &createReq, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, jsonErr) } @@ -162,7 +163,7 @@ func (h *Handler) handleCreateCloudFormationChangeSet( csResp[keySemanticVersion] = cs.SemanticVersion } - b, marshalErr := json.Marshal(csResp) + b, marshalErr := json.Marshal(csResp, jsonx.V1()) if marshalErr != nil { return nil, marshalErr } diff --git a/services/serverlessrepo/handler_cloud_formation_test.go b/services/serverlessrepo/handler_cloud_formation_test.go index 4fe44e7257..c2645d97a8 100644 --- a/services/serverlessrepo/handler_cloud_formation_test.go +++ b/services/serverlessrepo/handler_cloud_formation_test.go @@ -1,13 +1,14 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -52,7 +53,7 @@ func TestHandler_CreateCloudFormationTemplate(t *testing.T) { if tt.wantCode == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["templateId"]) assert.Equal(t, "ACTIVE", resp["status"]) } @@ -86,7 +87,7 @@ func TestCreateCloudFormationTemplate_HasTemplateURL(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["templateUrl"]) assert.NotEmpty(t, resp["expirationTime"]) } @@ -103,7 +104,7 @@ func TestCreateCloudFormationTemplate_OmitsSemanticVersionWhenEmpty(t *testing.T require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasSV := resp["semanticVersion"] assert.False(t, hasSV, "semanticVersion must be absent from response when not provided") assert.NotEmpty(t, resp["templateId"]) @@ -124,7 +125,7 @@ func TestCreateCloudFormationTemplate_IncludesSemanticVersionWhenProvided(t *tes require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "2.0.0", resp["semanticVersion"]) } @@ -186,7 +187,7 @@ func TestHandler_GetCloudFormationTemplate(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["templateId"]) assert.Equal(t, "ACTIVE", resp["status"]) } @@ -205,7 +206,7 @@ func TestGetCloudFormationTemplate_ActiveStatus(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) templateID := createResp["templateId"].(string) rec2 := doServerlessRepoRequest(t, h, http.MethodGet, @@ -213,7 +214,7 @@ func TestGetCloudFormationTemplate_ActiveStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "ACTIVE", getResp["status"]) } @@ -229,7 +230,7 @@ func TestGetCloudFormationTemplate_OmitsSemanticVersionWhenEmpty(t *testing.T) { require.Equal(t, http.StatusCreated, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) templateID := createResp["templateId"].(string) getRec := doServerlessRepoRequest(t, h, http.MethodGet, @@ -237,7 +238,7 @@ func TestGetCloudFormationTemplate_OmitsSemanticVersionWhenEmpty(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) _, hasSV := getResp["semanticVersion"] assert.False(t, hasSV, "semanticVersion must be absent from GetCloudFormationTemplate when not set") } @@ -258,7 +259,7 @@ func TestGetCloudFormationTemplate_ExpiredStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "EXPIRED", resp["status"]) assert.Equal(t, "1.0.0", resp["semanticVersion"]) } @@ -278,7 +279,7 @@ func TestGetCloudFormationTemplate_Fields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) templateID := createResp["templateId"].(string) rec2 := doServerlessRepoRequest(t, h, http.MethodGet, @@ -286,7 +287,7 @@ func TestGetCloudFormationTemplate_Fields(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "ACTIVE", getResp["status"]) assert.Equal(t, "1.0.0", getResp["semanticVersion"]) assert.NotEmpty(t, getResp["templateUrl"]) @@ -357,7 +358,7 @@ func TestHandler_CreateCloudFormationChangeSet(t *testing.T) { if tt.wantCode == http.StatusCreated { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["changeSetId"]) assert.NotEmpty(t, resp["stackId"]) } @@ -392,7 +393,7 @@ func TestCreateCloudFormationChangeSet_CustomName(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["changeSetId"].(string), "my-custom-changeset") } @@ -477,7 +478,7 @@ func TestCreateCloudFormationChangeSet_TemplateID(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) }) @@ -514,7 +515,7 @@ func TestCreateCloudFormationChangeSet_OmitsSemanticVersionWhenEmpty(t *testing. require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasSV := resp["semanticVersion"] assert.False(t, hasSV, "semanticVersion must be absent from response when not provided") assert.NotEmpty(t, resp["changeSetId"]) @@ -535,7 +536,7 @@ func TestCreateCloudFormationChangeSet_IncludesSemanticVersionWhenProvided(t *te require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "3.1.4", resp["semanticVersion"]) } @@ -557,7 +558,7 @@ func TestCreateCloudFormationChangeSet_ResponseFields(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["changeSetId"]) assert.NotEmpty(t, resp["stackId"]) assert.Equal(t, "1.0.0", resp["semanticVersion"]) @@ -579,7 +580,7 @@ func TestCreateCloudFormationChangeSet_ARNForm(t *testing.T) { require.Equal(t, http.StatusCreated, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["changeSetId"].(string), "cloudformation", "changeSetId should be a CloudFormation ARN") assert.Contains(t, resp["stackId"].(string), "cloudformation", "stackId should be a CloudFormation ARN") } diff --git a/services/serverlessrepo/handler_pagination_restart_test.go b/services/serverlessrepo/handler_pagination_restart_test.go index e2a6942467..2e6fb60569 100644 --- a/services/serverlessrepo/handler_pagination_restart_test.go +++ b/services/serverlessrepo/handler_pagination_restart_test.go @@ -2,13 +2,14 @@ package serverlessrepo_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -30,7 +31,7 @@ func TestListApplications_Pagination_DeletedMidPage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) nextToken, ok := r1["nextToken"].(string) require.True(t, ok) require.NotEmpty(t, nextToken) @@ -43,7 +44,7 @@ func TestListApplications_Pagination_DeletedMidPage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r2, jsonx.V1())) apps2, _ := r2["applications"].([]any) restarted := false @@ -78,7 +79,7 @@ func TestListApplicationVersions_Pagination_StaleTokenDoesNotRestart(t *testing. require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) v1, _ := r1["versions"].([]any) require.Len(t, v1, 2) @@ -96,7 +97,7 @@ func TestListApplicationVersions_Pagination_StaleTokenDoesNotRestart(t *testing. require.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) v2, _ := r2["versions"].([]any) for _, item := range v2 { @@ -137,7 +138,7 @@ func TestListApplicationDependencies_Pagination_StaleTokenDoesNotRestart(t *test require.Equal(t, http.StatusOK, rec.Code) var r1 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &r1, jsonx.V1())) page1, _ := r1["dependencies"].([]any) require.Len(t, page1, 2) @@ -154,7 +155,7 @@ func TestListApplicationDependencies_Pagination_StaleTokenDoesNotRestart(t *test require.Equal(t, http.StatusOK, rec2.Code) var r2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) page2, _ := r2["dependencies"].([]any) for _, item := range page2 { diff --git a/services/serverlessrepo/handler_test.go b/services/serverlessrepo/handler_test.go index 5cf76f6bd8..b087f2c8e9 100644 --- a/services/serverlessrepo/handler_test.go +++ b/services/serverlessrepo/handler_test.go @@ -2,7 +2,7 @@ package serverlessrepo_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -38,7 +39,7 @@ func doServerlessRepoRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -77,7 +78,7 @@ func doServerlessRepoRequestEncoded( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -275,7 +276,7 @@ func TestHandler_UnexpectedError_ReturnsInternalServerErrorException(t *testing. assert.Equal(t, http.StatusInternalServerError, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // The aws-sdk-go-v2 restjson1 error deserializer only recognizes the exact string // "InternalServerErrorException" (case-insensitively) in the __type field to build a // typed types.InternalServerErrorException; any other spelling (e.g. @@ -298,7 +299,7 @@ func TestErrorShape_NotFound(t *testing.T) { require.Equal(t, http.StatusNotFound, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "NotFoundException", resp["__type"]) assert.NotEmpty(t, resp["message"]) } @@ -316,7 +317,7 @@ func TestErrorShape_Conflict(t *testing.T) { require.Equal(t, http.StatusConflict, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ConflictException", resp["__type"]) } @@ -332,6 +333,6 @@ func TestErrorShape_BadRequest(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "BadRequestException", resp["__type"]) } diff --git a/services/serverlessrepo/multi_region_test.go b/services/serverlessrepo/multi_region_test.go index c6101e08f1..e540de25cd 100644 --- a/services/serverlessrepo/multi_region_test.go +++ b/services/serverlessrepo/multi_region_test.go @@ -2,7 +2,8 @@ package serverlessrepo_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/serverlessrepo" ) @@ -176,8 +178,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/serverlessrepo/persistence.go b/services/serverlessrepo/persistence.go index 7687858168..5a86458e0b 100644 --- a/services/serverlessrepo/persistence.go +++ b/services/serverlessrepo/persistence.go @@ -2,10 +2,12 @@ package serverlessrepo import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -80,7 +82,7 @@ func newDirtyDTORegistry() ( // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` AppPolicies map[string][]*ApplicationPolicyStatement `json:"appPolicies"` AppDependencies map[string]map[string][]*ApplicationDependency `json:"appDependencies"` AccountID string `json:"accountID"` @@ -157,7 +159,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "serverlessrepo: failed to marshal snapshot", "error", err) @@ -225,7 +227,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // cfTemplates, cfChangeSets) from tables via the ephemeral DTO registry, // converting each DTO back to its live type. The caller MUST hold b.mu for // writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, vDTOs, tDTOs, csDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/serverlessrepo/wire_sdk_roundtrip_test.go b/services/serverlessrepo/wire_sdk_roundtrip_test.go index fc2ac4f7af..afad95c3c3 100644 --- a/services/serverlessrepo/wire_sdk_roundtrip_test.go +++ b/services/serverlessrepo/wire_sdk_roundtrip_test.go @@ -1,7 +1,7 @@ package serverlessrepo_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/serverlessrepo" @@ -96,7 +97,7 @@ func TestCreateApplication_NoTopLevelSourceCodeURL_SDKRoundTrip(t *testing.T) { require.NoError(t, err) var raw map[string]any - require.NoError(t, json.Unmarshal(rawBody, &raw)) + require.NoError(t, json.Unmarshal(rawBody, &raw, jsonx.V1())) _, hasTopLevelSourceCodeURL := raw["sourceCodeUrl"] require.False(t, hasTopLevelSourceCodeURL, diff --git a/services/servicecatalog/constraints.go b/services/servicecatalog/constraints.go index e1bce0dbdc..7ba042002b 100644 --- a/services/servicecatalog/constraints.go +++ b/services/servicecatalog/constraints.go @@ -3,9 +3,11 @@ package servicecatalog import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const maxNotificationARNs = 5 @@ -28,7 +30,7 @@ func constraintDetail(c *Constraint) *ConstraintDetail { // validateConstraintParameters checks the JSON a constraint type requires. func validateConstraintParameters(typ, params string) error { var doc map[string]any - if err := json.Unmarshal([]byte(params), &doc); err != nil { + if err := json.Unmarshal([]byte(params), &doc, jsonx.V1()); err != nil { return invalid("Parameters is not a valid JSON object") } diff --git a/services/servicecatalog/handler.go b/services/servicecatalog/handler.go index 3dfee978b3..0ffa3f8522 100644 --- a/services/servicecatalog/handler.go +++ b/services/servicecatalog/handler.go @@ -3,7 +3,7 @@ package servicecatalog import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -35,7 +36,7 @@ func op[I, O any](fn func(context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, invalid("malformed request body: %v", err) } } diff --git a/services/servicecatalog/helpers_test.go b/services/servicecatalog/helpers_test.go index 3d7ac34ef0..0ebae9de99 100644 --- a/services/servicecatalog/helpers_test.go +++ b/services/servicecatalog/helpers_test.go @@ -2,7 +2,7 @@ package servicecatalog_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/sdktest" "github.com/blackbirdworks/gopherstack/services/servicecatalog" ) @@ -103,7 +104,7 @@ type fakeTemplate struct { func (f *fakeEngine) InspectTemplate(_ context.Context, body string) (servicecatalog.TemplateInfo, error) { var t fakeTemplate - if err := json.Unmarshal([]byte(body), &t); err != nil { + if err := json.Unmarshal([]byte(body), &t, jsonx.V1()); err != nil { return servicecatalog.TemplateInfo{}, err } @@ -145,7 +146,7 @@ func sortedMapKeys[V any](m map[string]V) []string { func (f *fakeEngine) outputsOf(template string) []servicecatalog.Output { var t fakeTemplate - _ = json.Unmarshal([]byte(template), &t) + _ = json.Unmarshal([]byte(template), &t, jsonx.V1()) out := make([]servicecatalog.Output, 0, len(t.Outputs)) for _, k := range sortedMapKeys(t.Outputs) { diff --git a/services/servicecatalog/persistence.go b/services/servicecatalog/persistence.go index d6d742ac43..f7d03d5b8b 100644 --- a/services/servicecatalog/persistence.go +++ b/services/servicecatalog/persistence.go @@ -2,7 +2,7 @@ package servicecatalog import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/servicecatalog/rules.go b/services/servicecatalog/rules.go index b82f6557f3..2b55490aa6 100644 --- a/services/servicecatalog/rules.go +++ b/services/servicecatalog/rules.go @@ -2,9 +2,11 @@ package servicecatalog import ( "context" - "encoding/json" + "encoding/json/v2" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const pairArgs = 2 @@ -58,13 +60,13 @@ func isRuleFunc(fn string) bool { // validateTemplateRules rejects a Rules document CloudFormation would not accept. func validateTemplateRules(rules map[string]any) error { - raw, err := json.Marshal(map[string]any{"Rules": rules}) + raw, err := json.Marshal(map[string]any{"Rules": rules}, jsonx.V1()) if err != nil { return invalid("Rules is not valid JSON") } var doc ruleDoc - if err = json.Unmarshal(raw, &doc); err != nil { + if err = json.Unmarshal(raw, &doc, jsonx.V1()); err != nil { return invalid("Rules is malformed: %v", err) } @@ -131,7 +133,7 @@ func validateRuleExpr(rule string, expr any) error { func checkTemplateRules(env *ruleEnv, constraintParams []string) error { for _, raw := range constraintParams { var doc ruleDoc - if err := json.Unmarshal([]byte(raw), &doc); err != nil { + if err := json.Unmarshal([]byte(raw), &doc, jsonx.V1()); err != nil { continue } @@ -435,7 +437,7 @@ func toStr(v any) string { return "false" case float64: - b, _ := json.Marshal(s) + b, _ := json.Marshal(s, jsonx.V1()) return string(b) } diff --git a/services/servicecatalog/serviceactions.go b/services/servicecatalog/serviceactions.go index a5da1f4eb3..d3b4fa6923 100644 --- a/services/servicecatalog/serviceactions.go +++ b/services/servicecatalog/serviceactions.go @@ -3,8 +3,10 @@ package servicecatalog import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -47,7 +49,7 @@ func parseActionParams(def map[string]string) ([]actionParam, error) { } var ps []actionParam - if err := json.Unmarshal([]byte(raw), &ps); err != nil { + if err := json.Unmarshal([]byte(raw), &ps, jsonx.V1()); err != nil { return nil, invalid("Definition Parameters is not a valid JSON list of {Name, Type}") } diff --git a/services/servicecatalog/util.go b/services/servicecatalog/util.go index 36a18ca856..d0df67925a 100644 --- a/services/servicecatalog/util.go +++ b/services/servicecatalog/util.go @@ -2,7 +2,7 @@ package servicecatalog import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "maps" "slices" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func sortedKeys[V any](m map[string]V) []string { @@ -91,6 +92,6 @@ func idemReplay[T any](b *InMemoryBackend, opName, token string, req any, do fun return res, err } -func jsonUnmarshal(s string, v any) error { return json.Unmarshal([]byte(s), v) } +func jsonUnmarshal(s string, v any) error { return json.Unmarshal([]byte(s), v, jsonx.V1()) } func awsmetaCaller(ctx context.Context) string { return awsmeta.CallerArn(ctx) } diff --git a/services/servicediscovery/discovery_test.go b/services/servicediscovery/discovery_test.go index 9a167497fb..1c1b617675 100644 --- a/services/servicediscovery/discovery_test.go +++ b/services/servicediscovery/discovery_test.go @@ -1,13 +1,14 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -20,14 +21,14 @@ func TestHandler_DiscoverInstances(t *testing.T) { require.Equal(t, http.StatusOK, nsRec.Code) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) opID := nsResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) operation := opResp["Operation"].(map[string]any) targets := operation["Targets"].(map[string]any) nsID := targets["NAMESPACE"].(string) @@ -39,7 +40,7 @@ func TestHandler_DiscoverInstances(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcData := svcResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -174,11 +175,11 @@ func TestHandler_DiscoverInstancesHealthStatusFilter(t *testing.T) { nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-discover-health"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{ @@ -187,7 +188,7 @@ func TestHandler_DiscoverInstancesHealthStatusFilter(t *testing.T) { "HealthCheckCustomConfig": map[string]any{"FailureThreshold": 1}, }) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -212,7 +213,7 @@ func TestHandler_DiscoverInstancesHealthStatusFilter(t *testing.T) { require.Equal(t, 200, recHealthy.Code) var respH map[string]any - require.NoError(t, json.Unmarshal(recHealthy.Body.Bytes(), &respH)) + require.NoError(t, json.Unmarshal(recHealthy.Body.Bytes(), &respH, jsonx.V1())) assert.Len(t, respH["Instances"].([]any), 1) assert.Equal(t, "healthy-i", respH["Instances"].([]any)[0].(map[string]any)["InstanceId"]) @@ -223,7 +224,7 @@ func TestHandler_DiscoverInstancesHealthStatusFilter(t *testing.T) { "HealthStatus": "ALL", }) var respAll map[string]any - require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &respAll)) + require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &respAll, jsonx.V1())) assert.Len(t, respAll["Instances"].([]any), 2) } @@ -235,16 +236,16 @@ func TestHandler_DiscoverInstancesQueryParameters(t *testing.T) { nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-qp"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-qp", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -264,7 +265,7 @@ func TestHandler_DiscoverInstancesQueryParameters(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insts := resp["Instances"].([]any) assert.Len(t, insts, 1) assert.Equal(t, "i-web", insts[0].(map[string]any)["InstanceId"]) @@ -287,16 +288,16 @@ func TestHandler_DiscoverInstancesOptionalParameters(t *testing.T) { nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-optparams"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-optparams", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -325,7 +326,7 @@ func TestHandler_DiscoverInstancesOptionalParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insts := resp["Instances"].([]any) require.Len(t, insts, 1, "only the instance matching both QueryParameters and OptionalParameters") assert.Equal(t, "i-blue-web", insts[0].(map[string]any)["InstanceId"]) @@ -345,7 +346,7 @@ func TestHandler_DiscoverInstancesOptionalParameters(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insts := resp["Instances"].([]any) assert.Len(t, insts, 2, "no instance matches OptionalParameters, so it's ignored") }) @@ -364,7 +365,7 @@ func TestHandler_DiscoverInstancesUnhealthyFilter(t *testing.T) { "HealthCheckCustomConfig": map[string]any{"FailureThreshold": 1}, }) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -383,7 +384,7 @@ func TestHandler_DiscoverInstancesUnhealthyFilter(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Instances"].([]any), 1) } @@ -405,7 +406,7 @@ func TestHandler_DiscoverInstancesHealthStatusIgnoredWithoutHealthCheck(t *testi "NamespaceId": nsID, }) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -420,7 +421,7 @@ func TestHandler_DiscoverInstancesHealthStatusIgnoredWithoutHealthCheck(t *testi require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["Instances"].([]any), 1, "HealthStatus must be ignored when the service has no health check") } @@ -437,7 +438,7 @@ func TestHandler_DiscoverInstancesEmptyWhenNamespaceNotFound(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["Instances"].([]any)) } @@ -471,11 +472,11 @@ func TestHandler_DiscoverInstancesRevision(t *testing.T) { createNsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "rev-ns"}) require.Equal(t, http.StatusOK, createNsRec.Code) var nsOut map[string]any - require.NoError(t, json.Unmarshal(createNsRec.Body.Bytes(), &nsOut)) + require.NoError(t, json.Unmarshal(createNsRec.Body.Bytes(), &nsOut, jsonx.V1())) opID := nsOut["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) nsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) createSvcRec := doSDRequest(t, h, "CreateService", map[string]any{ @@ -510,13 +511,13 @@ func TestHandler_DiscoverInstancesRevision(t *testing.T) { _ = nsOut var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) rev1 := out1["InstancesRevision"].(float64) // Register an instance to bump revision createSvcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "bump-svc"}) var svcOut map[string]any - require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut, jsonx.V1())) svcID := svcOut["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -532,7 +533,7 @@ func TestHandler_DiscoverInstancesRevision(t *testing.T) { }) assert.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) rev2 := out2["InstancesRevision"].(float64) assert.Greater(t, rev2, rev1) diff --git a/services/servicediscovery/dns_registrar_test.go b/services/servicediscovery/dns_registrar_test.go index 0b36407694..23ddf5549d 100644 --- a/services/servicediscovery/dns_registrar_test.go +++ b/services/servicediscovery/dns_registrar_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( gopherDNS "github.com/blackbirdworks/gopherstack/pkgs/dns" "github.com/blackbirdworks/gopherstack/pkgs/dns/dnstest" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -58,7 +59,7 @@ func createPublicDNSNamespace(t *testing.T, h *servicediscovery.Handler, name st require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) opID, ok := resp["OperationId"].(string) require.True(t, ok) @@ -66,7 +67,7 @@ func createPublicDNSNamespace(t *testing.T, h *servicediscovery.Handler, name st require.Equal(t, http.StatusOK, opRec.Code, opRec.Body.String()) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) targets := opResp["Operation"].(map[string]any)["Targets"].(map[string]any) @@ -100,7 +101,7 @@ func TestRegisterInstance_DNS_RealResolution(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code, svcRec.Body.String()) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) hostname := "svc-dns-resolve.ns-dns-resolve" @@ -146,19 +147,19 @@ func TestRegisterInstance_HTTPNamespace_NoDNSRegistration(t *testing.T) { require.Equal(t, http.StatusOK, nsRec.Code, nsRec.Body.String()) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) opID := nsResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) nsID := opResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-http", "NamespaceId": nsID}) require.Equal(t, http.StatusOK, svcRec.Code, svcRec.Body.String()) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) regRec := doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -198,7 +199,7 @@ func TestDeregisterInstance_DNS_ResyncKeepsSibling(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code, svcRec.Body.String()) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) hostname := "svc-multi.ns-multi." diff --git a/services/servicediscovery/handler.go b/services/servicediscovery/handler.go index 7ed2277ec7..3a31e66094 100644 --- a/services/servicediscovery/handler.go +++ b/services/servicediscovery/handler.go @@ -3,7 +3,7 @@ package servicediscovery import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -17,6 +17,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -90,7 +91,7 @@ func marshalPagedResponse(key string, items []map[string]any, nextToken string) resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // Handler is the HTTP handler for the AWS Cloud Map service discovery API. @@ -197,7 +198,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -434,10 +435,7 @@ var sentinelErrorCodes = sync.OnceValue(func() []struct { // just like errInvalidRequest but isn't itself a sentinel in // sentinelErrorCodes. func isMalformedRequest(err error) bool { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - - return errors.Is(err, errInvalidRequest) || errors.As(err, &syntaxErr) || errors.As(err, &typeErr) + return errors.Is(err, errInvalidRequest) || jsonx.IsSyntaxError(err) || jsonx.IsTypeError(err) } func (h *Handler) handleError(c *echo.Context, err error) error { @@ -468,7 +466,7 @@ func (h *Handler) errorResponse(c *echo.Context, errType string, err error) erro payload, _ := json.Marshal(map[string]string{ keyTypeField: errType, keyMessageField: msg, - }) + }, jsonx.V1()) return c.JSONBlob(http.StatusBadRequest, payload) } diff --git a/services/servicediscovery/handler_discovery.go b/services/servicediscovery/handler_discovery.go index baa7b789f3..5a0338de5d 100644 --- a/services/servicediscovery/handler_discovery.go +++ b/services/servicediscovery/handler_discovery.go @@ -2,8 +2,10 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type discoverInstancesRequest struct { @@ -17,7 +19,7 @@ type discoverInstancesRequest struct { func (h *Handler) handleDiscoverInstances(_ context.Context, body []byte) ([]byte, error) { var req discoverInstancesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -68,7 +70,7 @@ func (h *Handler) handleDiscoverInstances(_ context.Context, body []byte) ([]byt return json.Marshal(map[string]any{ "Instances": items, "InstancesRevision": revision, - }) + }, jsonx.V1()) } type discoverInstancesRevisionRequest struct { @@ -78,7 +80,7 @@ type discoverInstancesRevisionRequest struct { func (h *Handler) handleDiscoverInstancesRevision(_ context.Context, body []byte) ([]byte, error) { var req discoverInstancesRevisionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -97,5 +99,5 @@ func (h *Handler) handleDiscoverInstancesRevision(_ context.Context, body []byte return json.Marshal(map[string]any{ "InstancesRevision": revision, - }) + }, jsonx.V1()) } diff --git a/services/servicediscovery/handler_instances.go b/services/servicediscovery/handler_instances.go index 758891660e..5d57a7268f 100644 --- a/services/servicediscovery/handler_instances.go +++ b/services/servicediscovery/handler_instances.go @@ -2,10 +2,11 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type registerInstanceRequest struct { @@ -17,7 +18,7 @@ type registerInstanceRequest struct { func (h *Handler) handleRegisterInstance(_ context.Context, body []byte) ([]byte, error) { var req registerInstanceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -46,7 +47,7 @@ func (h *Handler) handleRegisterInstance(_ context.Context, body []byte) ([]byte return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type deregisterInstanceRequest struct { @@ -56,7 +57,7 @@ type deregisterInstanceRequest struct { func (h *Handler) handleDeregisterInstance(_ context.Context, body []byte) ([]byte, error) { var req deregisterInstanceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -73,7 +74,7 @@ func (h *Handler) handleDeregisterInstance(_ context.Context, body []byte) ([]by return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type getInstanceRequest struct { @@ -83,7 +84,7 @@ type getInstanceRequest struct { func (h *Handler) handleGetInstance(_ context.Context, body []byte) ([]byte, error) { var req getInstanceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -105,7 +106,7 @@ func (h *Handler) handleGetInstance(_ context.Context, body []byte) ([]byte, err "Id": inst.ID, keyAttributes: inst.Attributes, }, - }) + }, jsonx.V1()) } type listInstancesRequest struct { @@ -116,7 +117,7 @@ type listInstancesRequest struct { func (h *Handler) handleListInstances(_ context.Context, body []byte) ([]byte, error) { var req listInstancesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -156,7 +157,7 @@ func (h *Handler) handleListInstances(_ context.Context, body []byte) ([]byte, e resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type getInstancesHealthStatusRequest struct { @@ -170,7 +171,7 @@ type getInstancesHealthStatusRequest struct { // Instances without a recorded custom status default to HEALTHY. func (h *Handler) handleGetInstancesHealthStatus(_ context.Context, body []byte) ([]byte, error) { var req getInstancesHealthStatusRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -206,7 +207,7 @@ func (h *Handler) handleGetInstancesHealthStatus(_ context.Context, body []byte) resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type updateInstanceCustomHealthStatusRequest struct { @@ -217,7 +218,7 @@ type updateInstanceCustomHealthStatusRequest struct { func (h *Handler) handleUpdateInstanceCustomHealthStatus(_ context.Context, body []byte) error { var req updateInstanceCustomHealthStatusRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/servicediscovery/handler_list_summary_test.go b/services/servicediscovery/handler_list_summary_test.go index e0f5b05fff..ebe02a3689 100644 --- a/services/servicediscovery/handler_list_summary_test.go +++ b/services/servicediscovery/handler_list_summary_test.go @@ -1,11 +1,13 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListServices_SummaryShape locks that ListServices emits the @@ -27,7 +29,7 @@ func TestHandler_ListServices_SummaryShape(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["Services"].([]any) require.True(t, ok) require.Len(t, items, 1) diff --git a/services/servicediscovery/handler_namespaces.go b/services/servicediscovery/handler_namespaces.go index 717c37c05c..e4a54f1736 100644 --- a/services/servicediscovery/handler_namespaces.go +++ b/services/servicediscovery/handler_namespaces.go @@ -2,10 +2,11 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type dnsPropertiesRequest struct { @@ -23,7 +24,7 @@ type createHTTPNamespaceRequest struct { func (h *Handler) handleCreateHTTPNamespace(_ context.Context, body []byte) ([]byte, error) { var req createHTTPNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -44,7 +45,7 @@ func (h *Handler) handleCreateHTTPNamespace(_ context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type createPrivateDNSNamespaceRequest struct { @@ -60,7 +61,7 @@ type createPrivateDNSNamespaceRequest struct { func (h *Handler) handleCreatePrivateDNSNamespace(_ context.Context, body []byte) ([]byte, error) { var req createPrivateDNSNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -86,7 +87,7 @@ func (h *Handler) handleCreatePrivateDNSNamespace(_ context.Context, body []byte return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type createPublicDNSNamespaceRequest struct { @@ -101,7 +102,7 @@ type createPublicDNSNamespaceRequest struct { func (h *Handler) handleCreatePublicDNSNamespace(_ context.Context, body []byte) ([]byte, error) { var req createPublicDNSNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -127,7 +128,7 @@ func (h *Handler) handleCreatePublicDNSNamespace(_ context.Context, body []byte) return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type deleteNamespaceRequest struct { @@ -136,7 +137,7 @@ type deleteNamespaceRequest struct { func (h *Handler) handleDeleteNamespace(_ context.Context, body []byte) ([]byte, error) { var req deleteNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -149,7 +150,7 @@ func (h *Handler) handleDeleteNamespace(_ context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type getNamespaceRequest struct { @@ -158,7 +159,7 @@ type getNamespaceRequest struct { func (h *Handler) handleGetNamespace(_ context.Context, body []byte) ([]byte, error) { var req getNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -173,7 +174,7 @@ func (h *Handler) handleGetNamespace(_ context.Context, body []byte) ([]byte, er return json.Marshal(map[string]any{ "Namespace": namespaceToMap(ns, h.Backend.AccountID()), - }) + }, jsonx.V1()) } type namespaceFilter struct { @@ -218,7 +219,7 @@ func buildNamespacesFilter(filters []namespaceFilter) ListNamespacesFilter { func (h *Handler) handleListNamespaces(_ context.Context, body []byte) ([]byte, error) { var req listNamespacesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -306,7 +307,7 @@ type updateHTTPNamespaceRequest struct { func (h *Handler) handleUpdateHTTPNamespace(_ context.Context, body []byte) ([]byte, error) { var req updateHTTPNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -319,7 +320,7 @@ func (h *Handler) handleUpdateHTTPNamespace(_ context.Context, body []byte) ([]b return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } // dnsNamespacePropertiesChangeRequest mirrors types.PrivateDnsNamespacePropertiesChange @@ -355,7 +356,7 @@ type updatePrivateDNSNamespaceRequest struct { func (h *Handler) handleUpdatePrivateDNSNamespace(_ context.Context, body []byte) ([]byte, error) { var req updatePrivateDNSNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -370,7 +371,7 @@ func (h *Handler) handleUpdatePrivateDNSNamespace(_ context.Context, body []byte return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type updatePublicDNSNamespaceRequest struct { @@ -381,7 +382,7 @@ type updatePublicDNSNamespaceRequest struct { func (h *Handler) handleUpdatePublicDNSNamespace(_ context.Context, body []byte) ([]byte, error) { var req updatePublicDNSNamespaceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -396,7 +397,7 @@ func (h *Handler) handleUpdatePublicDNSNamespace(_ context.Context, body []byte) return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } func applyPaginationNamespaces(items []Namespace, nextToken string, maxResults int) ([]Namespace, string) { diff --git a/services/servicediscovery/handler_operations.go b/services/servicediscovery/handler_operations.go index 444bffb216..2aeff5699d 100644 --- a/services/servicediscovery/handler_operations.go +++ b/services/servicediscovery/handler_operations.go @@ -2,12 +2,13 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "time" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type getOperationRequest struct { @@ -16,7 +17,7 @@ type getOperationRequest struct { func (h *Handler) handleGetOperation(_ context.Context, body []byte) ([]byte, error) { var req getOperationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +32,7 @@ func (h *Handler) handleGetOperation(_ context.Context, body []byte) ([]byte, er return json.Marshal(map[string]any{ "Operation": operationToMap(op), - }) + }, jsonx.V1()) } type operationFilter struct { @@ -106,7 +107,7 @@ func buildOperationsFilter(filters []operationFilter) ListOperationsFilter { func (h *Handler) handleListOperations(_ context.Context, body []byte) ([]byte, error) { var req listOperationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/servicediscovery/handler_services.go b/services/servicediscovery/handler_services.go index 7c9ccaa5e1..a1489a772d 100644 --- a/services/servicediscovery/handler_services.go +++ b/services/servicediscovery/handler_services.go @@ -2,10 +2,11 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type dnsRecordRequest struct { @@ -124,7 +125,7 @@ func validateHealthCheckConfigEnum(hc *HealthCheckConfig) error { func (h *Handler) handleCreateService(_ context.Context, body []byte) ([]byte, error) { var req createServiceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -173,7 +174,7 @@ func (h *Handler) handleCreateService(_ context.Context, body []byte) ([]byte, e return json.Marshal(map[string]any{ keyService: serviceToMap(svc, h.Backend.AccountID()), - }) + }, jsonx.V1()) } type deleteServiceRequest struct { @@ -187,7 +188,7 @@ type deleteServiceRequest struct { // enforces this (returns ErrResourceInUse). func (h *Handler) handleDeleteService(_ context.Context, body []byte) error { var req deleteServiceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -204,7 +205,7 @@ type getServiceRequest struct { func (h *Handler) handleGetService(_ context.Context, body []byte) ([]byte, error) { var req getServiceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -219,7 +220,7 @@ func (h *Handler) handleGetService(_ context.Context, body []byte) ([]byte, erro return json.Marshal(map[string]any{ keyService: serviceToMap(svc, h.Backend.AccountID()), - }) + }, jsonx.V1()) } type serviceFilter struct { @@ -260,7 +261,7 @@ func buildServicesFilter(filters []serviceFilter) ListServicesFilter { func (h *Handler) handleListServices(_ context.Context, body []byte) ([]byte, error) { var req listServicesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -288,7 +289,7 @@ func (h *Handler) handleListServices(_ context.Context, body []byte) ([]byte, er resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // serviceToMap converts a Service to a JSON-serialisable map including DNS and @@ -373,7 +374,7 @@ type updateServiceRequest struct { func (h *Handler) handleUpdateService(_ context.Context, body []byte) ([]byte, error) { var req updateServiceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -401,7 +402,7 @@ func (h *Handler) handleUpdateService(_ context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]string{keyOperationID: opID}) + return json.Marshal(map[string]string{keyOperationID: opID}, jsonx.V1()) } type getServiceAttributesRequest struct { @@ -410,7 +411,7 @@ type getServiceAttributesRequest struct { func (h *Handler) handleGetServiceAttributes(_ context.Context, body []byte) ([]byte, error) { var req getServiceAttributesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -431,7 +432,7 @@ func (h *Handler) handleGetServiceAttributes(_ context.Context, body []byte) ([] "ServiceArn": arn, keyAttributes: attrs, }, - }) + }, jsonx.V1()) } type updateServiceAttributesRequest struct { @@ -441,7 +442,7 @@ type updateServiceAttributesRequest struct { func (h *Handler) handleUpdateServiceAttributes(_ context.Context, body []byte) error { var req updateServiceAttributesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -463,7 +464,7 @@ type deleteServiceAttributesRequest struct { func (h *Handler) handleDeleteServiceAttributes(_ context.Context, body []byte) error { var req deleteServiceAttributesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/servicediscovery/handler_tags.go b/services/servicediscovery/handler_tags.go index 9628901781..a0e45695ff 100644 --- a/services/servicediscovery/handler_tags.go +++ b/services/servicediscovery/handler_tags.go @@ -2,10 +2,11 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listTagsForResourceRequest struct { @@ -14,7 +15,7 @@ type listTagsForResourceRequest struct { func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]byte, error) { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -31,7 +32,7 @@ func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]b return json.Marshal(map[string]any{ keyTags: tagList, - }) + }, jsonx.V1()) } type tagResourceRequest struct { @@ -41,7 +42,7 @@ type tagResourceRequest struct { func (h *Handler) handleTagResource(_ context.Context, body []byte) ([]byte, error) { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -67,7 +68,7 @@ type untagResourceRequest struct { func (h *Handler) handleUntagResource(_ context.Context, body []byte) ([]byte, error) { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/servicediscovery/handler_test.go b/services/servicediscovery/handler_test.go index 19dc5708d9..ab894e6d16 100644 --- a/services/servicediscovery/handler_test.go +++ b/services/servicediscovery/handler_test.go @@ -2,7 +2,7 @@ package servicediscovery_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -29,7 +30,7 @@ func doSDRequest(t *testing.T, h *servicediscovery.Handler, op string, body any) if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -215,7 +216,7 @@ func TestHandler_ExtractResource(t *testing.T) { h := newTestHandler(t) e := echo.New() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -288,7 +289,7 @@ func TestServiceDiscovery_Handler_Reset(t *testing.T) { var out struct { Namespaces []any `json:"Namespaces"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Namespaces, tt.wantAfter) }) } @@ -358,13 +359,13 @@ func createNamespaceHelper(t *testing.T, h *servicediscovery.Handler, name strin require.Equal(t, 200, nsRec.Code) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) opID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) return nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) } diff --git a/services/servicediscovery/input_realism_test.go b/services/servicediscovery/input_realism_test.go index dfc81aafa9..841f1b1793 100644 --- a/services/servicediscovery/input_realism_test.go +++ b/services/servicediscovery/input_realism_test.go @@ -1,13 +1,15 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_NameAndAddressValidation(t *testing.T) { @@ -110,7 +112,7 @@ func extractFirstID(t *testing.T, body string) string { ID string `json:"Id"` } `json:"Namespaces"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) require.NotEmpty(t, out.Namespaces) return out.Namespaces[0].ID @@ -124,7 +126,7 @@ func extractServiceID(t *testing.T, body string) string { ID string `json:"Id"` } `json:"Service"` } - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) return out.Service.ID } diff --git a/services/servicediscovery/instances_test.go b/services/servicediscovery/instances_test.go index 6dbfb840a7..da227f6df5 100644 --- a/services/servicediscovery/instances_test.go +++ b/services/servicediscovery/instances_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -64,7 +65,7 @@ func TestHandler_RegisterInstance(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -160,7 +161,7 @@ func TestHandler_RegisterInstance_AttributeQuota(t *testing.T) { createRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "attr-quota-svc"}) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcID := createResp["Service"].(map[string]any)["Id"].(string) rec := doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -228,7 +229,7 @@ func TestHandler_RegisterInstance_InitHealthStatus(t *testing.T) { createRec := doSDRequest(t, h, "CreateService", body) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcID := createResp["Service"].(map[string]any)["Id"].(string) regRec := doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -239,7 +240,7 @@ func TestHandler_RegisterInstance_InitHealthStatus(t *testing.T) { statusRec := doSDRequest(t, h, "GetInstancesHealthStatus", map[string]any{"ServiceId": svcID}) require.Equal(t, http.StatusOK, statusRec.Code) var statusResp map[string]any - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp, jsonx.V1())) statuses := statusResp["Status"].(map[string]any) assert.Equal(t, tt.wantStatus, statuses["i-init-health"]) }) @@ -255,7 +256,7 @@ func TestHandler_GetInstance(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -317,7 +318,7 @@ func TestHandler_ListInstances(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -371,7 +372,7 @@ func TestHandler_DeregisterInstance(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -445,7 +446,7 @@ func TestHandler_GetInstancesHealthStatus(t *testing.T) { require.Equal(t, http.StatusOK, svcRec.Code) var svcOut map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcOut, jsonx.V1())) svc := svcOut["Service"].(map[string]any) return svc["Id"].(string) @@ -481,7 +482,7 @@ func TestHandler_GetInstancesHealthStatus(t *testing.T) { if len(tt.wantFields) > 0 { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) for _, field := range tt.wantFields { assert.Contains(t, out, field) } @@ -550,16 +551,16 @@ func TestHandler_RegisterInstanceRealOperationID(t *testing.T) { // Create namespace and service first. nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-reg"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-reg", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) // Register instance. @@ -571,7 +572,7 @@ func TestHandler_RegisterInstanceRealOperationID(t *testing.T) { require.Equal(t, 200, regRec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(regRec.Body.Bytes(), ®Resp, jsonx.V1())) opID := regResp["OperationId"].(string) assert.NotEqual(t, "op-register", opID, "must not be hardcoded op ID") @@ -582,7 +583,7 @@ func TestHandler_RegisterInstanceRealOperationID(t *testing.T) { require.Equal(t, 200, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) assert.Equal(t, "REGISTER_INSTANCE", opResp["Operation"].(map[string]any)["Type"]) } @@ -596,16 +597,16 @@ func TestHandler_DeregisterInstanceRealOperationID(t *testing.T) { // Setup. nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-dereg"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-dereg", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -622,7 +623,7 @@ func TestHandler_DeregisterInstanceRealOperationID(t *testing.T) { require.Equal(t, 200, deregRec.Code) var deregResp map[string]any - require.NoError(t, json.Unmarshal(deregRec.Body.Bytes(), &deregResp)) + require.NoError(t, json.Unmarshal(deregRec.Body.Bytes(), &deregResp, jsonx.V1())) opID := deregResp["OperationId"].(string) assert.NotEqual(t, "op-deregister", opID) @@ -632,7 +633,7 @@ func TestHandler_DeregisterInstanceRealOperationID(t *testing.T) { require.Equal(t, 200, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) assert.Equal(t, "DEREGISTER_INSTANCE", opResp["Operation"].(map[string]any)["Type"]) } @@ -646,11 +647,11 @@ func TestHandler_GetInstancesHealthStatusStoredValue(t *testing.T) { // Setup service and instances. nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-health"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{ @@ -659,7 +660,7 @@ func TestHandler_GetInstancesHealthStatusStoredValue(t *testing.T) { "HealthCheckCustomConfig": map[string]any{"FailureThreshold": 1}, }) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -683,7 +684,7 @@ func TestHandler_GetInstancesHealthStatusStoredValue(t *testing.T) { require.Equal(t, 200, statusRec.Code) var statusResp map[string]any - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp, jsonx.V1())) statuses := statusResp["Status"].(map[string]any) assert.Equal(t, "HEALTHY", statuses["inst-healthy"]) @@ -698,16 +699,16 @@ func TestHandler_GetInstancesHealthStatusFilterByIDs(t *testing.T) { nsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-filter-health"}) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) nsOpID := nsResp["OperationId"].(string) nsOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": nsOpID}) var nsOpResp map[string]any - require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp)) + require.NoError(t, json.Unmarshal(nsOpRec.Body.Bytes(), &nsOpResp, jsonx.V1())) nsID := nsOpResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-filter", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) for _, iid := range []string{"a", "b", "c"} { @@ -724,7 +725,7 @@ func TestHandler_GetInstancesHealthStatusFilterByIDs(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) statuses := resp["Status"].(map[string]any) assert.Len(t, statuses, 2) @@ -777,7 +778,7 @@ func TestHandler_GetInstancesHealthStatusUnknownInstanceID(t *testing.T) { svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-unknown-inst"}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) registerRec := doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -793,7 +794,7 @@ func TestHandler_GetInstancesHealthStatusUnknownInstanceID(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InstanceNotFound", errResp["__type"]) } }) @@ -809,7 +810,7 @@ func TestListInstances_Pagination(t *testing.T) { createSvcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-inst-page"}) require.Equal(t, http.StatusOK, createSvcRec.Code) var svcOut map[string]any - require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut, jsonx.V1())) svcID := svcOut["Service"].(map[string]any)["Id"].(string) for i := range 4 { @@ -849,7 +850,7 @@ func TestListInstances_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) insts := out["Instances"].([]any) assert.Len(t, insts, tt.wantLen) if tt.wantNextToken { @@ -896,7 +897,7 @@ func TestHandler_UpdateInstanceCustomHealthStatus(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) svcID := out["Service"].(map[string]any)["Id"].(string) regRec := doSDRequest(t, h, "RegisterInstance", map[string]any{ "ServiceId": svcID, diff --git a/services/servicediscovery/multi_region_test.go b/services/servicediscovery/multi_region_test.go index 276dc0bae1..59441204fc 100644 --- a/services/servicediscovery/multi_region_test.go +++ b/services/servicediscovery/multi_region_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -39,7 +40,7 @@ func regionCall(t *testing.T, h *servicediscovery.Handler, region, op, body stri require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/servicediscovery/namespaces_test.go b/services/servicediscovery/namespaces_test.go index 4aa28383d7..3b859cda19 100644 --- a/services/servicediscovery/namespaces_test.go +++ b/services/servicediscovery/namespaces_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -207,7 +208,7 @@ func TestHandler_GetNamespace(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) @@ -215,7 +216,7 @@ func TestHandler_GetNamespace(t *testing.T) { require.Equal(t, http.StatusOK, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) operation := opResp["Operation"].(map[string]any) targets := operation["Targets"].(map[string]any) @@ -277,14 +278,14 @@ func TestHandler_DeleteNamespace(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) operation := opResp["Operation"].(map[string]any) targets := operation["Targets"].(map[string]any) nsID := targets["NAMESPACE"].(string) @@ -336,13 +337,13 @@ func TestHandler_NamespaceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) nsID := opResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) // GetNamespace has CreateDate but never a Tags field. @@ -350,7 +351,7 @@ func TestHandler_NamespaceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ns := getResp["Namespace"].(map[string]any) assert.NotZero(t, ns["CreateDate"], "CreateDate must be present in GetNamespace response") @@ -361,7 +362,7 @@ func TestHandler_NamespaceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, tagsRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) assert.Len(t, tags, 2, "expected 2 tags") @@ -386,7 +387,7 @@ func TestHandler_ListNamespacesOmitsTags(t *testing.T) { require.Equal(t, 200, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nsList := resp["Namespaces"].([]any) require.Len(t, nsList, 1) @@ -431,13 +432,13 @@ func TestNamespace_ServiceCount(t *testing.T) { createNsRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "ns-parity"}) require.Equal(t, http.StatusOK, createNsRec.Code) var nsOp map[string]string - require.NoError(t, json.Unmarshal(createNsRec.Body.Bytes(), &nsOp)) + require.NoError(t, json.Unmarshal(createNsRec.Body.Bytes(), &nsOp, jsonx.V1())) opID := nsOp["OperationId"] opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, opRec.Code) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) nsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) for i := range tt.servicesBefore { @@ -452,7 +453,7 @@ func TestNamespace_ServiceCount(t *testing.T) { getRec := doSDRequest(t, h, "GetNamespace", map[string]any{"Id": nsID}) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) ns := getOut["Namespace"].(map[string]any) assert.Equal(t, tt.wantCount, int(ns["ServiceCount"].(float64)), "GetNamespace ServiceCount") @@ -460,7 +461,7 @@ func TestNamespace_ServiceCount(t *testing.T) { listRec := doSDRequest(t, h, "ListNamespaces", map[string]any{}) require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) nsList := listOut["Namespaces"].([]any) require.Len(t, nsList, 1) gotListCount := int(nsList[0].(map[string]any)["ServiceCount"].(float64)) @@ -512,7 +513,7 @@ func TestListNamespaces_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) nsList := out["Namespaces"].([]any) assert.Len(t, nsList, tt.wantLen) if tt.wantNextToken { @@ -559,7 +560,7 @@ func TestListNamespaces_TypeFilter(t *testing.T) { rec := doSDRequest(t, h, "ListNamespaces", req) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) nsList := out["Namespaces"].([]any) assert.Len(t, nsList, tt.wantLen, "filterType=%s", tt.filterType) }) @@ -600,7 +601,7 @@ func TestListNamespaces_HTTPNameFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Namespaces"].([]any), tt.wantLen) }) } @@ -638,7 +639,7 @@ func TestListNamespaces_ResourceOwnerFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Namespaces"].([]any), tt.wantLen) }) } @@ -663,7 +664,7 @@ func TestListNamespaces_NameBeginsWithFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Namespaces"].([]any), 2) } @@ -694,11 +695,11 @@ func TestHandler_UpdateHttpNamespace(t *testing.T) { createRec := doSDRequest(t, h, "CreateHttpNamespace", map[string]any{"Name": "http-ns"}) require.Equal(t, http.StatusOK, createRec.Code) var nsOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &nsOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &nsOut, jsonx.V1())) opID := nsOut["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) nsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) rec := doSDRequest(t, h, "UpdateHttpNamespace", map[string]any{ @@ -726,11 +727,11 @@ func TestHandler_UpdateHttpNamespace(t *testing.T) { createRec := doSDRequest(t, h, "CreatePrivateDnsNamespace", map[string]any{"Name": "private-ns"}) require.Equal(t, http.StatusOK, createRec.Code) var nsOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &nsOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &nsOut, jsonx.V1())) opID := nsOut["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) privateNsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) rec := doSDRequest(t, h, "UpdateHttpNamespace", map[string]any{ @@ -771,11 +772,11 @@ func TestHandler_UpdatePrivateDnsNamespace(t *testing.T) { case "success": createRec := doSDRequest(t, h, "CreatePrivateDnsNamespace", map[string]any{"Name": "private-ns"}) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) opID := out["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) nsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) rec := doSDRequest(t, h, "UpdatePrivateDnsNamespace", map[string]any{ "Id": nsID, @@ -825,11 +826,11 @@ func TestHandler_UpdatePublicDnsNamespace(t *testing.T) { case "success": createRec := doSDRequest(t, h, "CreatePublicDnsNamespace", map[string]any{"Name": "public-ns"}) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) opID := out["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opOut map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opOut, jsonx.V1())) nsID := opOut["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) rec := doSDRequest(t, h, "UpdatePublicDnsNamespace", map[string]any{ "Id": nsID, diff --git a/services/servicediscovery/operations_test.go b/services/servicediscovery/operations_test.go index a5c8082937..84a3755a92 100644 --- a/services/servicediscovery/operations_test.go +++ b/services/servicediscovery/operations_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -60,7 +61,7 @@ func TestHandler_GetOperation(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) rec = doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) @@ -100,7 +101,7 @@ func TestOperationCountIncrementsOnRegisterDeregister(t *testing.T) { nsID := createNamespaceHelper(t, h, "ns-opcount") svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-opcount", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) opsBefore := servicediscovery.OperationCount(b) @@ -158,7 +159,7 @@ func TestListOperations_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ops := out["Operations"].([]any) assert.Len(t, ops, tt.wantLen) if tt.wantNextToken { @@ -187,7 +188,7 @@ func TestListOperations_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ops := out["Operations"].([]any) require.Len(t, ops, 1) @@ -219,7 +220,7 @@ func TestListOperations_NamespaceAndServiceIDFilters(t *testing.T) { svcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-op-filter", "NamespaceId": nsID}) var svcResp map[string]any - require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp)) + require.NoError(t, json.Unmarshal(svcRec.Body.Bytes(), &svcResp, jsonx.V1())) svcID := svcResp["Service"].(map[string]any)["Id"].(string) doSDRequest(t, h, "RegisterInstance", map[string]any{ @@ -232,7 +233,7 @@ func TestListOperations_NamespaceAndServiceIDFilters(t *testing.T) { require.Equal(t, http.StatusOK, nsFilterRec.Code) var nsOut map[string]any - require.NoError(t, json.Unmarshal(nsFilterRec.Body.Bytes(), &nsOut)) + require.NoError(t, json.Unmarshal(nsFilterRec.Body.Bytes(), &nsOut, jsonx.V1())) assert.Len(t, nsOut["Operations"].([]any), 1, "only CREATE_NAMESPACE targets this namespace") svcFilterRec := doSDRequest(t, h, "ListOperations", map[string]any{ @@ -241,7 +242,7 @@ func TestListOperations_NamespaceAndServiceIDFilters(t *testing.T) { require.Equal(t, http.StatusOK, svcFilterRec.Code) var svcOut map[string]any - require.NoError(t, json.Unmarshal(svcFilterRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(svcFilterRec.Body.Bytes(), &svcOut, jsonx.V1())) assert.Len(t, svcOut["Operations"].([]any), 1, "only REGISTER_INSTANCE targets this service") } @@ -263,7 +264,7 @@ func TestListOperations_TypeFilterIN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Operations"].([]any), 2) } @@ -291,7 +292,7 @@ func TestListOperations_UpdateDateBetweenFilter(t *testing.T) { require.Equal(t, http.StatusOK, inRangeRec.Code) var inRangeOut map[string]any - require.NoError(t, json.Unmarshal(inRangeRec.Body.Bytes(), &inRangeOut)) + require.NoError(t, json.Unmarshal(inRangeRec.Body.Bytes(), &inRangeOut, jsonx.V1())) assert.Len(t, inRangeOut["Operations"].([]any), 1) outOfRangeRec := doSDRequest(t, h, "ListOperations", map[string]any{ @@ -309,7 +310,7 @@ func TestListOperations_UpdateDateBetweenFilter(t *testing.T) { require.Equal(t, http.StatusOK, outOfRangeRec.Code) var outOfRangeOut map[string]any - require.NoError(t, json.Unmarshal(outOfRangeRec.Body.Bytes(), &outOfRangeOut)) + require.NoError(t, json.Unmarshal(outOfRangeRec.Body.Bytes(), &outOfRangeOut, jsonx.V1())) assert.Empty(t, outOfRangeOut["Operations"].([]any)) } @@ -345,7 +346,7 @@ func TestListOperations_StatusFilter(t *testing.T) { rec := doSDRequest(t, h, "ListOperations", req) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ops := out["Operations"].([]any) assert.Len(t, ops, tt.wantLen, "filterStatus=%s", tt.filterStatus) }) diff --git a/services/servicediscovery/persistence.go b/services/servicediscovery/persistence.go index 9ca25c121d..6850445b95 100644 --- a/services/servicediscovery/persistence.go +++ b/services/servicediscovery/persistence.go @@ -2,9 +2,11 @@ package servicediscovery import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -32,7 +34,7 @@ const servicediscoverySnapshotVersion = 1 // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ServiceAttributes map[string]map[string]string `json:"serviceAttributes"` InstanceHealthStatuses map[string]string `json:"instanceHealthStatuses"` AccountID string `json:"accountID"` @@ -74,7 +76,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { OpCounter: b.opCounter, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "servicediscovery: snapshot marshal failed", "error", err) diff --git a/services/servicediscovery/persistence_test.go b/services/servicediscovery/persistence_test.go index 52ecf60c82..d02c4938e4 100644 --- a/services/servicediscovery/persistence_test.go +++ b/services/servicediscovery/persistence_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -322,7 +323,7 @@ func TestHandler_SnapshotRestore_ServiceAttributesAndHealth(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) svc := createOut["Service"].(map[string]any) svcID := svc["Id"].(string) svcARN := svc["Arn"].(string) @@ -359,7 +360,7 @@ func TestHandler_SnapshotRestore_ServiceAttributesAndHealth(t *testing.T) { getRec := doSDRequest(t, h2, "GetServiceAttributes", map[string]any{"ServiceId": svcID}) assert.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) attrs := getOut["ServiceAttributes"].(map[string]any)["Attributes"].(map[string]any) assert.Equal(t, "test", attrs["stage"]) assert.Equal(t, svcARN, getOut["ServiceAttributes"].(map[string]any)["ServiceArn"]) diff --git a/services/servicediscovery/services_test.go b/services/servicediscovery/services_test.go index 5f3d9fbf68..1d172c2d20 100644 --- a/services/servicediscovery/services_test.go +++ b/services/servicediscovery/services_test.go @@ -1,7 +1,7 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicediscovery" ) @@ -105,7 +106,7 @@ func TestHandler_GetService(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -161,7 +162,7 @@ func TestHandler_DeleteService(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcData := createResp["Service"].(map[string]any) svcID := svcData["Id"].(string) @@ -270,7 +271,7 @@ func TestHandler_ServiceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svc := createResp["Service"].(map[string]any) assert.NotContains(t, svc, "Tags", "real CreateService never returns a Tags field") @@ -282,7 +283,7 @@ func TestHandler_ServiceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) svcGet := getResp["Service"].(map[string]any) assert.NotContains(t, svcGet, "Tags", "real GetService never returns a Tags field") @@ -291,7 +292,7 @@ func TestHandler_ServiceTagsViaListTagsForResource(t *testing.T) { require.Equal(t, 200, tagsRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) assert.Len(t, tags, 1) @@ -316,7 +317,7 @@ func TestHandler_ListServicesNamespaceFilter(t *testing.T) { // List without filter - should return all 3. recAll := doSDRequest(t, h, "ListServices", map[string]any{}) var respAll map[string]any - require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &respAll)) + require.NoError(t, json.Unmarshal(recAll.Body.Bytes(), &respAll, jsonx.V1())) assert.Len(t, respAll["Services"].([]any), 3) // List with NAMESPACE_ID filter - should return only 2. @@ -328,7 +329,7 @@ func TestHandler_ListServicesNamespaceFilter(t *testing.T) { require.Equal(t, 200, recFiltered.Code) var respFiltered map[string]any - require.NoError(t, json.Unmarshal(recFiltered.Body.Bytes(), &respFiltered)) + require.NoError(t, json.Unmarshal(recFiltered.Body.Bytes(), &respFiltered, jsonx.V1())) assert.Len(t, respFiltered["Services"].([]any), 2) } @@ -450,7 +451,7 @@ func TestHandler_ListServicesResourceOwnerFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out["Services"].([]any), tt.wantLen) }) } @@ -464,13 +465,13 @@ func createPrivateDNSNamespaceHelper(t *testing.T, h *servicediscovery.Handler, require.Equal(t, http.StatusOK, nsRec.Code) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) opID := nsResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) return opResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) } @@ -555,7 +556,7 @@ func TestUpdateService_ReturnsOperationId(t *testing.T) { createRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-parity"}) require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) svcID := created["Service"].(map[string]any)["Id"].(string) rec := doSDRequest(t, h, "UpdateService", map[string]any{ @@ -565,7 +566,7 @@ func TestUpdateService_ReturnsOperationId(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tt.wantOpID { assert.NotEmpty(t, out["OperationId"], "should contain OperationId") } @@ -606,7 +607,7 @@ func TestService_InstanceCount(t *testing.T) { createSvcRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-count"}) require.Equal(t, http.StatusOK, createSvcRec.Code) var svcOut map[string]any - require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut)) + require.NoError(t, json.Unmarshal(createSvcRec.Body.Bytes(), &svcOut, jsonx.V1())) svcID := svcOut["Service"].(map[string]any)["Id"].(string) for i := range tt.instancesBefore { @@ -622,7 +623,7 @@ func TestService_InstanceCount(t *testing.T) { getRec := doSDRequest(t, h, "GetService", map[string]any{"Id": svcID}) require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) svc := getOut["Service"].(map[string]any) assert.Equal(t, tt.wantCount, int(svc["InstanceCount"].(float64)), "GetService InstanceCount") @@ -630,7 +631,7 @@ func TestService_InstanceCount(t *testing.T) { listRec := doSDRequest(t, h, "ListServices", map[string]any{}) require.Equal(t, http.StatusOK, listRec.Code) var listOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) svcs := listOut["Services"].([]any) require.Len(t, svcs, 1) gotListCount := int(svcs[0].(map[string]any)["InstanceCount"].(float64)) @@ -682,7 +683,7 @@ func TestListServices_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svcs := out["Services"].([]any) assert.Len(t, svcs, tt.wantLen) if tt.wantNextToken { @@ -703,7 +704,7 @@ func TestUpdateService_CreatesOperation(t *testing.T) { createRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "svc-op-check"}) require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) svcID := created["Service"].(map[string]any)["Id"].(string) updateRec := doSDRequest(t, h, "UpdateService", map[string]any{ @@ -713,14 +714,14 @@ func TestUpdateService_CreatesOperation(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateOut map[string]string - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateOut, jsonx.V1())) opID := updateOut["OperationId"] require.NotEmpty(t, opID) getOpRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, getOpRec.Code) var opOut map[string]any - require.NoError(t, json.Unmarshal(getOpRec.Body.Bytes(), &opOut)) + require.NoError(t, json.Unmarshal(getOpRec.Body.Bytes(), &opOut, jsonx.V1())) op := opOut["Operation"].(map[string]any) assert.Equal(t, "UPDATE_SERVICE", op["Type"]) assert.Equal(t, "SUCCESS", op["Status"]) @@ -774,7 +775,7 @@ func TestUpdateService_OmittedConfigDeletesIt(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &created, jsonx.V1())) svcID := created["Service"].(map[string]any)["Id"].(string) updateRec := doSDRequest(t, h, "UpdateService", map[string]any{ @@ -786,7 +787,7 @@ func TestUpdateService_OmittedConfigDeletesIt(t *testing.T) { getRec := doSDRequest(t, h, "GetService", map[string]any{"Id": svcID}) require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) svc := out["Service"].(map[string]any) if tt.wantDNS { @@ -835,7 +836,7 @@ func TestHandler_UpdateService(t *testing.T) { case "success": createRec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "my-svc"}) var out map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &out, jsonx.V1())) svcID := out["Service"].(map[string]any)["Id"].(string) rec := doSDRequest(t, h, "UpdateService", map[string]any{ "Id": svcID, @@ -898,7 +899,7 @@ func TestHandler_ServiceAttributes(t *testing.T) { rec := doSDRequest(t, h, "CreateService", map[string]any{"Name": "attrs-svc"}) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) svc := out["Service"].(map[string]any) return svc["Id"].(string), svc["Arn"].(string) @@ -917,7 +918,7 @@ func TestHandler_ServiceAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) sa := getOut["ServiceAttributes"].(map[string]any) attrs := sa["Attributes"].(map[string]any) assert.Equal(t, "prod", attrs["env"]) @@ -973,7 +974,7 @@ func TestHandler_ServiceAttributes(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) attrs := getOut["ServiceAttributes"].(map[string]any)["Attributes"].(map[string]any) assert.NotContains(t, attrs, "env") assert.Equal(t, "yes", attrs["keep"]) @@ -1003,7 +1004,7 @@ func TestHandler_ServiceAttributes(t *testing.T) { assert.Equal(t, tt.wantCode, getRec.Code, "body: %s", getRec.Body.String()) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) attrs := getOut["ServiceAttributes"].(map[string]any)["Attributes"] assert.Empty(t, attrs) @@ -1026,7 +1027,7 @@ func TestHandler_ServiceAttributes(t *testing.T) { assert.Equal(t, tt.wantCode, getRec.Code, "body: %s", getRec.Body.String()) var getOut map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getOut, jsonx.V1())) attrs := getOut["ServiceAttributes"].(map[string]any)["Attributes"] assert.Empty(t, attrs) } @@ -1096,7 +1097,7 @@ func TestHandler_UpdateServiceAttributesQuota(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcID := createResp["Service"].(map[string]any)["Id"].(string) if tt.presetTo > 0 { @@ -1120,7 +1121,7 @@ func TestHandler_UpdateServiceAttributesQuota(t *testing.T) { if tt.wantType != "" { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) } }) diff --git a/services/servicediscovery/tags_test.go b/services/servicediscovery/tags_test.go index 8efed4427f..8687fa2550 100644 --- a/services/servicediscovery/tags_test.go +++ b/services/servicediscovery/tags_test.go @@ -1,13 +1,15 @@ package servicediscovery_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagsLifecycle(t *testing.T) { @@ -22,14 +24,14 @@ func TestHandler_TagsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) operation := opResp["Operation"].(map[string]any) targets := operation["Targets"].(map[string]any) nsID := targets["NAMESPACE"].(string) @@ -38,7 +40,7 @@ func TestHandler_TagsLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, nsRec.Code) var nsResp map[string]any - require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp)) + require.NoError(t, json.Unmarshal(nsRec.Body.Bytes(), &nsResp, jsonx.V1())) arn := nsResp["Namespace"].(map[string]any)["Arn"].(string) rec := doSDRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) @@ -142,21 +144,21 @@ func TestHandler_TagResource_TooManyTags_Merged(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) opID := createResp["OperationId"].(string) opRec := doSDRequest(t, h, "GetOperation", map[string]any{"OperationId": opID}) require.Equal(t, http.StatusOK, opRec.Code) var opResp map[string]any - require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp)) + require.NoError(t, json.Unmarshal(opRec.Body.Bytes(), &opResp, jsonx.V1())) nsID := opResp["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) getRec := doSDRequest(t, h, "GetNamespace", map[string]any{"Id": nsID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) arn := getResp["Namespace"].(map[string]any)["Arn"].(string) firstBatch := make([]map[string]string, 0, 40) @@ -197,14 +199,14 @@ func TestMapToTagEntriesSorted(t *testing.T) { require.Equal(t, 200, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) svcARN := createResp["Service"].(map[string]any)["Arn"].(string) tagsRec := doSDRequest(t, h, "ListTagsForResource", map[string]any{"ResourceARN": svcARN}) require.Equal(t, 200, tagsRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tags := tagsResp["Tags"].([]any) require.Len(t, tags, 3) @@ -226,7 +228,7 @@ func TestHandler_ErrResourceNotFoundTagging(t *testing.T) { assert.Equal(t, 400, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } diff --git a/services/servicequotas/autonotify.go b/services/servicequotas/autonotify.go index edc4064cc4..5086969443 100644 --- a/services/servicequotas/autonotify.go +++ b/services/servicequotas/autonotify.go @@ -2,11 +2,12 @@ package servicequotas import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -151,7 +152,7 @@ func (b *InMemoryBackend) noticeFor( body, _ := json.Marshal(map[string]any{ "account": am.Account, "region": am.Region, "serviceCode": serviceCode, "quotaCode": def.Code, "quotaName": def.Name, "utilization": util, "threshold": level, "optInType": am.OptInType, - }) + }, jsonx.V1()) return pendingNotice{ topicArn: am.NotificationArn, diff --git a/services/servicequotas/catalog.go b/services/servicequotas/catalog.go index 88d1222800..ede079c8a6 100644 --- a/services/servicequotas/catalog.go +++ b/services/servicequotas/catalog.go @@ -4,11 +4,13 @@ import ( "bytes" "compress/gzip" _ "embed" // quota catalog - "encoding/json" + "encoding/json/v2" "io" "slices" "strings" "sync" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed catalog.json @@ -62,7 +64,7 @@ type Catalog struct { var defaultCatalog = sync.OnceValue(func() *Catalog { //nolint:gochecknoglobals // immutable embedded data var svcs []ServiceDef - if err := json.Unmarshal(catalogJSON, &svcs); err != nil { + if err := json.Unmarshal(catalogJSON, &svcs, jsonx.V1()); err != nil { panic("servicequotas: embedded catalog is invalid: " + err.Error()) } @@ -72,7 +74,7 @@ var defaultCatalog = sync.OnceValue(func() *Catalog { //nolint:gochecknoglobals } var docs []ServiceDef - if err = json.Unmarshal(raw, &docs); err != nil { + if err = json.Unmarshal(raw, &docs, jsonx.V1()); err != nil { panic("servicequotas: embedded docs catalog is invalid: " + err.Error()) } diff --git a/services/servicequotas/docs_catalog_test.go b/services/servicequotas/docs_catalog_test.go index d2cf99ee43..177bfb5495 100644 --- a/services/servicequotas/docs_catalog_test.go +++ b/services/servicequotas/docs_catalog_test.go @@ -4,7 +4,8 @@ import ( "bytes" "compress/gzip" _ "embed" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "regexp" "testing" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/servicequotas" ) @@ -30,7 +32,7 @@ func TestDocsCatalog_Provenance(t *testing.T) { require.NoError(t, err) var svcs []servicequotas.ServiceDef - require.NoError(t, json.Unmarshal(docsQuotas, &svcs)) + require.NoError(t, json.Unmarshal(docsQuotas, &svcs, jsonx.V1())) codeRe := regexp.MustCompile(`^L-[0-9A-F]{8}$`) seen := map[string]bool{} @@ -55,7 +57,7 @@ func TestDocsCatalog_Provenance(t *testing.T) { seen[s.Code] = true } - canon, err := json.MarshalIndent(svcs, "", " ") + canon, err := json.Marshal(svcs, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) require.NoError(t, err) assert.True(t, bytes.Equal(append(canon, '\n'), docsQuotas), "docs_quotas.json.gz is not in generator output form") } diff --git a/services/servicequotas/handler.go b/services/servicequotas/handler.go index 2a4e9bbd23..89a245723e 100644 --- a/services/servicequotas/handler.go +++ b/services/servicequotas/handler.go @@ -2,7 +2,7 @@ package servicequotas import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/worker" @@ -39,7 +40,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, fail(ErrIllegalArgument, "malformed request body: %v", err) } } diff --git a/services/servicequotas/persistence.go b/services/servicequotas/persistence.go index 4e17ce5c2a..3a18c22313 100644 --- a/services/servicequotas/persistence.go +++ b/services/servicequotas/persistence.go @@ -2,7 +2,7 @@ package servicequotas import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,11 +12,11 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Seq int64 `json:"seq"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Seq int64 `json:"seq"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/ses/deep_flows_sdk_test.go b/services/ses/deep_flows_sdk_test.go index ad57b0be1c..d6a77d33e7 100644 --- a/services/ses/deep_flows_sdk_test.go +++ b/services/ses/deep_flows_sdk_test.go @@ -1,7 +1,7 @@ package ses_test import ( - "encoding/json" + "encoding/json/v2" "sync" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -199,8 +200,8 @@ func TestEventDestination_SendAndDeliveryEvents_SDK(t *testing.T) { require.Len(t, pub.messages, 2) var send, delivery map[string]any - require.NoError(t, json.Unmarshal([]byte(pub.messages[0]), &send)) - require.NoError(t, json.Unmarshal([]byte(pub.messages[1]), &delivery)) + require.NoError(t, json.Unmarshal([]byte(pub.messages[0]), &send, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(pub.messages[1]), &delivery, jsonx.V1())) assert.Equal(t, "Send", send["eventType"]) assert.Contains(t, send, "send") assert.Equal(t, "Delivery", delivery["eventType"]) diff --git a/services/ses/email_sending.go b/services/ses/email_sending.go index 82b4653142..6f0f6f0fa7 100644 --- a/services/ses/email_sending.go +++ b/services/ses/email_sending.go @@ -1,13 +1,15 @@ package ses import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxRecipientsPerMessage is the AWS SES limit on the combined number of @@ -637,7 +639,7 @@ func (b *InMemoryBackend) SendBulkTemplatedEmail(in SendBulkTemplatedEmailInput) return nil, err } - mergedJSON, err := json.Marshal(merged) + mergedJSON, err := json.Marshal(merged, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: failed to encode template data", ErrInvalidParameter) } diff --git a/services/ses/identities.go b/services/ses/identities.go index 6bb2813d70..3b3fa11a10 100644 --- a/services/ses/identities.go +++ b/services/ses/identities.go @@ -1,12 +1,13 @@ package ses import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "net/mail" "strings" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -226,7 +227,7 @@ func (b *InMemoryBackend) PutIdentityPolicy(identity, policyName, policy string) return fmt.Errorf("%w: Policy is required", ErrInvalidParameter) } - if !json.Valid([]byte(policy)) { + if !jsontext.Value([]byte(policy)).IsValid(jsonx.V1()) { return fmt.Errorf("%w: Policy must be a valid JSON policy document", ErrInvalidPolicy) } diff --git a/services/ses/inbound_actions.go b/services/ses/inbound_actions.go index bc8f5d44b4..837d4dad23 100644 --- a/services/ses/inbound_actions.go +++ b/services/ses/inbound_actions.go @@ -3,7 +3,7 @@ package ses import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/mail" @@ -12,6 +12,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -337,7 +338,7 @@ func (r *inboundRun) lambdaAction(ctx context.Context, a ReceiptAction) (ruleFlo payload, err := json.Marshal(lambdaEvent{Records: []lambdaRecord{{ EventSource: "aws:ses", EventVersion: "1.0", SES: lambdaSES{Receipt: n.Receipt, Mail: n.Mail}, - }}}) + }}}, jsonx.V1()) if err != nil { return flowContinue, fmt.Errorf("marshal lambda event: %w", err) } @@ -356,7 +357,7 @@ func (r *inboundRun) lambdaAction(ctx context.Context, a ReceiptAction) (ruleFlo Disposition string `json:"disposition"` } - _ = json.Unmarshal(out, &resp) + _ = json.Unmarshal(out, &resp, jsonx.V1()) switch strings.ToUpper(resp.Disposition) { case "STOP_RULE": @@ -428,7 +429,7 @@ func (r *inboundRun) notify(topicARN string, n receiptNotification) error { return errInboundNoSNS } - body, err := json.Marshal(n) + body, err := json.Marshal(n, jsonx.V1()) if err != nil { return fmt.Errorf("marshal notification: %w", err) } diff --git a/services/ses/inbound_auth_test.go b/services/ses/inbound_auth_test.go index 6d1f12710c..82d6206d5a 100644 --- a/services/ses/inbound_auth_test.go +++ b/services/ses/inbound_auth_test.go @@ -9,7 +9,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/textproto" @@ -19,6 +19,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errNXDomain = errors.New("nxdomain") @@ -249,7 +251,7 @@ func TestInboundAuthVerdicts(t *testing.T) { } `json:"receipt"` } - require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n)) + require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n, jsonx.V1())) assert.Equal(t, tc.wantSPF, n.Receipt.SPF.Status) assert.Equal(t, tc.wantDKIM, n.Receipt.DKIM.Status) assert.Equal(t, tc.wantDMARC, n.Receipt.DMARC.Status) diff --git a/services/ses/inbound_dsn_kms_test.go b/services/ses/inbound_dsn_kms_test.go index 25720f4b99..b5a3a86c11 100644 --- a/services/ses/inbound_dsn_kms_test.go +++ b/services/ses/inbound_dsn_kms_test.go @@ -5,7 +5,7 @@ import ( "crypto/aes" "crypto/cipher" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "mime" "mime/multipart" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -173,7 +174,7 @@ func TestInboundS3KMS(t *testing.T) { assert.NotContains(t, string(ct), "body line") var mat map[string]string - require.NoError(t, json.Unmarshal([]byte(meta["x-amz-matdesc"]), &mat)) + require.NoError(t, json.Unmarshal([]byte(meta["x-amz-matdesc"]), &mat, jsonx.V1())) assert.Equal(t, "r", mat["aws:ses:rule-name"]) assert.Equal(t, log[0].MessageID, mat["aws:ses:message-id"]) diff --git a/services/ses/inbound_kms.go b/services/ses/inbound_kms.go index 389c5c2b82..ceaabe14d6 100644 --- a/services/ses/inbound_kms.go +++ b/services/ses/inbound_kms.go @@ -6,9 +6,11 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -61,7 +63,7 @@ func sealEnvelope( return nil, nil, fmt.Errorf("iv: %w", err) } - matdesc, err := json.Marshal(encCtx) + matdesc, err := json.Marshal(encCtx, jsonx.V1()) if err != nil { return nil, nil, fmt.Errorf("matdesc: %w", err) } diff --git a/services/ses/inbound_spf_test.go b/services/ses/inbound_spf_test.go index 93bdfc4bc3..6ea079e483 100644 --- a/services/ses/inbound_spf_test.go +++ b/services/ses/inbound_spf_test.go @@ -2,7 +2,7 @@ package ses_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/netip" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -94,7 +95,7 @@ func deliver(t *testing.T, dns ses.InboundTXTResolver, mailFrom, msg string) ver } `json:"receipt"` } - require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n)) + require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n, jsonx.V1())) return verdictSet{spf: n.Receipt.SPF.Status, dmarc: n.Receipt.DMARC.Status} } diff --git a/services/ses/inbound_test.go b/services/ses/inbound_test.go index 4a51fc3ec1..fd1f649857 100644 --- a/services/ses/inbound_test.go +++ b/services/ses/inbound_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto/tls" "encoding/base64" - "encoding/json" + "encoding/json/v2" "maps" "net/smtp" "net/textproto" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -364,7 +365,7 @@ func TestInboundNotificationShape(t *testing.T) { } `json:"receipt"` } - require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n)) + require.NoError(t, json.Unmarshal([]byte(snsF.msgs["arn:t"][0]), &n, jsonx.V1())) assert.Equal(t, "Received", n.NotificationType) assert.Equal(t, "sender@sender.test", n.Mail.Source) assert.Equal(t, []string{"user@example.com"}, n.Mail.Destination) @@ -400,7 +401,7 @@ func TestInboundNotificationShape(t *testing.T) { } `json:"Records"` } - require.NoError(t, json.Unmarshal(lam.payloads[0], &ev)) + require.NoError(t, json.Unmarshal(lam.payloads[0], &ev, jsonx.V1())) require.Len(t, ev.Records, 1) assert.Equal(t, "aws:ses", ev.Records[0].EventSource) assert.Equal(t, "Lambda", ev.Records[0].SES.Receipt.Action.Type) diff --git a/services/ses/multi_region_test.go b/services/ses/multi_region_test.go index a766d038cd..91479af8b0 100644 --- a/services/ses/multi_region_test.go +++ b/services/ses/multi_region_test.go @@ -1,7 +1,8 @@ package ses_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ses" ) @@ -161,9 +163,9 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(t.Context()) - var doc map[string]json.RawMessage + var doc map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &doc)) + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) _, hasRegions := doc["regions"] assert.Equal(t, tc.wantRegions, hasRegions) diff --git a/services/ses/notifications.go b/services/ses/notifications.go index 24d3b7c752..f73baacd34 100644 --- a/services/ses/notifications.go +++ b/services/ses/notifications.go @@ -1,10 +1,12 @@ package ses import ( - "encoding/json" + "encoding/json/v2" "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // sesNotificationMail is the "mail" object of an SES SNS notification. @@ -100,7 +102,7 @@ func buildSESNotificationPayload( } // map[string]any marshaling of these concrete field types cannot fail. - bs, _ := json.Marshal(data) + bs, _ := json.Marshal(data, jsonx.V1()) return bs } diff --git a/services/ses/persistence.go b/services/ses/persistence.go index 537f698a19..74e3c159da 100644 --- a/services/ses/persistence.go +++ b/services/ses/persistence.go @@ -2,11 +2,13 @@ package ses import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -88,7 +90,7 @@ func newDirtyDTORegistry() ( // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Policies map[string]map[string]string `json:"policies,omitempty"` ActiveRuleSet string `json:"activeRuleSet,omitempty"` Region string `json:"region,omitempty"` @@ -159,7 +161,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { AccountID: b.accountID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ses: failed to marshal snapshot", "error", err) @@ -234,7 +236,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // configSets, trackingOptions, eventDestinations) from tables via the // ephemeral DTO registry, converting each DTO back to its live type. The // caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, idDTOs, csDTOs, toDTOs, edDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/ses/templates.go b/services/ses/templates.go index f283d2072e..41d793596b 100644 --- a/services/ses/templates.go +++ b/services/ses/templates.go @@ -1,12 +1,13 @@ package ses import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "strings" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -117,7 +118,7 @@ func parseTemplateData(templateData string) (map[string]string, error) { } raw := map[string]any{} - if err := json.Unmarshal([]byte(templateData), &raw); err != nil { + if err := json.Unmarshal([]byte(templateData), &raw, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: TemplateData must be valid JSON", ErrInvalidParameter) } diff --git a/services/sesv2/account_test.go b/services/sesv2/account_test.go index e9a58c31bf..91d0da0095 100644 --- a/services/sesv2/account_test.go +++ b/services/sesv2/account_test.go @@ -1,7 +1,7 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,8 @@ import ( sesv2types "github.com/aws/aws-sdk-go-v2/service/sesv2/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestGetAccount tests the GetAccount operation. @@ -114,7 +116,7 @@ func TestGetBlacklistReports(t *testing.T) { var out struct { BlacklistReport map[string][]string `json:"BlacklistReport"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out.BlacklistReport, "1.2.3.4") assert.Contains(t, out.BlacklistReport, "5.6.7.8") assert.Empty(t, out.BlacklistReport["1.2.3.4"]) diff --git a/services/sesv2/configuration_sets_test.go b/services/sesv2/configuration_sets_test.go index 7b816d8ab9..91d9fa8fb1 100644 --- a/services/sesv2/configuration_sets_test.go +++ b/services/sesv2/configuration_sets_test.go @@ -1,13 +1,14 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -33,7 +34,7 @@ func TestConfigSetTrackingOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tracking, ok := out["TrackingOptions"].(map[string]any) require.True(t, ok, "TrackingOptions should be present in GetConfigurationSet response") @@ -74,7 +75,7 @@ func TestConfigSetDeliveryOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) delivery, ok := out["DeliveryOptions"].(map[string]any) require.True(t, ok, "DeliveryOptions should be present") @@ -113,7 +114,7 @@ func TestConfigSetSendingOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) sending, ok := out["SendingOptions"].(map[string]any) require.True(t, ok, "SendingOptions should be present") @@ -150,7 +151,7 @@ func TestConfigSetReputationOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) rep, ok := out["ReputationOptions"].(map[string]any) require.True(t, ok, "ReputationOptions should be present") @@ -187,7 +188,7 @@ func TestConfigSetSuppressionOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) supp, ok := out["SuppressionOptions"].(map[string]any) require.True(t, ok, "SuppressionOptions should be present") @@ -234,7 +235,7 @@ func TestGetConfigurationSetDefaultSendingEnabled(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) sending, ok := out["SendingOptions"].(map[string]any) require.True(t, ok, "SendingOptions always present in GetConfigurationSet") @@ -265,7 +266,7 @@ func TestConfigSetMultipleOptions(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "multi-cs", out["ConfigurationSetName"]) assert.NotNil(t, out["TrackingOptions"]) @@ -635,7 +636,7 @@ func TestGetConfigurationSet(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.csName, out["ConfigurationSetName"]) } }) @@ -668,7 +669,7 @@ func TestListConfigurationSets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) sets, ok := out["ConfigurationSets"].([]any) require.True(t, ok) diff --git a/services/sesv2/deep_flows_sdk_test.go b/services/sesv2/deep_flows_sdk_test.go index 322781aacf..c8c7f0fdb6 100644 --- a/services/sesv2/deep_flows_sdk_test.go +++ b/services/sesv2/deep_flows_sdk_test.go @@ -1,7 +1,7 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "sync" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -125,8 +126,8 @@ func TestSendEmail_SNSEventDestination_SDK(t *testing.T) { assert.Equal(t, []string{topic, topic}, pub.topics) var send, delivery map[string]any - require.NoError(t, json.Unmarshal([]byte(pub.messages[0]), &send)) - require.NoError(t, json.Unmarshal([]byte(pub.messages[1]), &delivery)) + require.NoError(t, json.Unmarshal([]byte(pub.messages[0]), &send, jsonx.V1())) + require.NoError(t, json.Unmarshal([]byte(pub.messages[1]), &delivery, jsonx.V1())) assert.Equal(t, "Send", send["eventType"]) assert.Equal(t, "Delivery", delivery["eventType"]) diff --git a/services/sesv2/deliverability_test.go b/services/sesv2/deliverability_test.go index c313bc92ff..503ab7b08f 100644 --- a/services/sesv2/deliverability_test.go +++ b/services/sesv2/deliverability_test.go @@ -1,7 +1,7 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -95,7 +96,7 @@ func TestPutDeliverabilityDashboardOption(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, true, out["DashboardEnabled"]) assert.Equal(t, "ACTIVE", out["AccountStatus"]) @@ -138,7 +139,7 @@ func TestGetDeliverabilityTestReport(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) reportID := createResp["ReportId"].(string) rec := doReq(t, h, http.MethodGet, "/v2/email/deliverability-dashboard/test-reports/"+reportID, nil) @@ -188,7 +189,7 @@ func TestGetDomainDeliverabilityCampaign(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) campaign, ok := out["DomainDeliverabilityCampaign"].(map[string]any) require.True(t, ok, "response missing DomainDeliverabilityCampaign wrapper: %s", rec.Body) @@ -267,7 +268,7 @@ func TestGetEmailAddressInsights(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) validation, ok := out["MailboxValidation"].(map[string]any) require.True(t, ok, "response missing MailboxValidation: %s", rec.Body) diff --git a/services/sesv2/email_identities_test.go b/services/sesv2/email_identities_test.go index a7a48b5bb0..0eeea5880e 100644 --- a/services/sesv2/email_identities_test.go +++ b/services/sesv2/email_identities_test.go @@ -1,7 +1,7 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -32,7 +33,7 @@ func TestPutEmailIdentityConfigurationSetAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "my-cs", out["ConfigurationSetName"]) } @@ -67,7 +68,7 @@ func TestPutEmailIdentityDkimAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) dkim, ok := out["DkimAttributes"].(map[string]any) require.True(t, ok, "DkimAttributes should be present") @@ -145,7 +146,7 @@ func TestPutEmailIdentityDkimSigningAttributes(t *testing.T) { DkimStatus string `json:"DkimStatus"` DkimTokens []string `json:"DkimTokens"` } - err := json.Unmarshal(rec.Body.Bytes(), &resp) + err := json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1()) require.NoError(t, err) assert.Equal(t, tt.wantDkimStat, resp.DkimStatus) if tt.wantTokens { @@ -177,7 +178,7 @@ func TestPutEmailIdentityFeedbackAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, false, out["FeedbackForwardingStatus"]) } @@ -214,7 +215,7 @@ func TestPutEmailIdentityMailFromAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) mailFrom, ok := out["MailFromAttributes"].(map[string]any) require.True(t, ok, "MailFromAttributes should be present after setting mail-from domain") @@ -256,7 +257,7 @@ func TestPutEmailIdentityMailFromClear(t *testing.T) { rec := doReq(t, h, http.MethodGet, "/v2/email/identities/mfclear%40example.com", nil) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // MailFromAttributes should be absent when domain is empty. assert.Nil(t, out["MailFromAttributes"]) @@ -277,7 +278,7 @@ func TestGetEmailIdentityDefaults(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out, "EmailIdentity") assert.Equal(t, "EMAIL_ADDRESS", out["IdentityType"]) @@ -304,7 +305,7 @@ func TestGetEmailIdentityDomainHasDkimTokens(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "DOMAIN", out["IdentityType"]) @@ -335,7 +336,7 @@ func TestGetEmailIdentityEmailAddressNoDkimTokens(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) dkim, ok := out["DkimAttributes"].(map[string]any) require.True(t, ok) @@ -357,7 +358,7 @@ func TestCreateEmailIdentityWithConfigSet(t *testing.T) { rec = doReq(t, h, http.MethodGet, "/v2/email/identities/tagged%40example.com", nil) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "my-cs", out["ConfigurationSetName"]) } @@ -378,7 +379,7 @@ func TestCreateEmailIdentityWithTags(t *testing.T) { rec = doReq(t, h, http.MethodGet, "/v2/email/identities/tagged2%40example.com", nil) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tags, ok := out["Tags"].([]any) require.True(t, ok, "Tags should be returned") @@ -397,7 +398,7 @@ func TestCreateDomainIdentityReturnsDkimAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Domain identities should return DkimAttributes with tokens in create response. dkim, ok := out["DkimAttributes"].(map[string]any) @@ -420,7 +421,7 @@ func TestCreateEmailAddressDoesNotReturnDkimAttributes(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Email addresses should not return DkimAttributes in create response. assert.Nil(t, out["DkimAttributes"], "Email address CreateEmailIdentity should omit DkimAttributes") @@ -442,7 +443,7 @@ func TestGetEmailIdentityPoliciesIncludedInGet(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) policies, ok := out["Policies"].(map[string]any) require.True(t, ok, "Policies should be returned in GetEmailIdentity") @@ -943,7 +944,7 @@ func TestCreateEmailIdentity(t *testing.T) { if tt.wantType != "" { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantType, out["IdentityType"]) assert.Equal(t, true, out["VerifiedForSendingStatus"]) } @@ -994,7 +995,7 @@ func TestGetEmailIdentity(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out, "EmailIdentity") } }) @@ -1027,7 +1028,7 @@ func TestListEmailIdentities(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) identities, ok := out["EmailIdentities"].([]any) require.True(t, ok) diff --git a/services/sesv2/email_templates.go b/services/sesv2/email_templates.go index 828f141b12..947dce07dc 100644 --- a/services/sesv2/email_templates.go +++ b/services/sesv2/email_templates.go @@ -1,7 +1,7 @@ package sesv2 import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -9,6 +9,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -187,7 +188,7 @@ func parseTemplateVars(data string) (map[string]string, error) { } raw := map[string]any{} - if err := json.Unmarshal([]byte(data), &raw); err != nil { + if err := json.Unmarshal([]byte(data), &raw, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: TemplateData must be valid JSON", ErrInvalidInput) } diff --git a/services/sesv2/email_templates_test.go b/services/sesv2/email_templates_test.go index 964c99b6fe..09d89eb958 100644 --- a/services/sesv2/email_templates_test.go +++ b/services/sesv2/email_templates_test.go @@ -1,13 +1,14 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -141,7 +142,7 @@ func TestGetEmailTemplate_RendersTags(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Tags, 1) assert.Equal(t, "team", resp.Tags[0].Key) assert.Equal(t, "sec", resp.Tags[0].Value) diff --git a/services/sesv2/event_publishing.go b/services/sesv2/event_publishing.go index dfd17277df..1d40b9f1f7 100644 --- a/services/sesv2/event_publishing.go +++ b/services/sesv2/event_publishing.go @@ -1,10 +1,12 @@ package sesv2 import ( - "encoding/json" + "encoding/json/v2" "net/mail" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type eventMail struct { @@ -63,7 +65,7 @@ func (b *InMemoryBackend) publishSendEvents(email Email) { } if slices.Contains(d.MatchingEventTypes, "SEND") { - payload, _ := json.Marshal(map[string]any{"eventType": "Send", "mail": m, "send": struct{}{}}) + payload, _ := json.Marshal(map[string]any{"eventType": "Send", "mail": m, "send": struct{}{}}, jsonx.V1()) _ = pub.PublishToTopic(d.SnsDestination.TopicArn, string(payload)) } @@ -71,7 +73,7 @@ func (b *InMemoryBackend) publishSendEvents(email Email) { payload, _ := json.Marshal(map[string]any{ "eventType": "Delivery", "mail": m, "delivery": eventDelivery{Timestamp: ts, Recipients: email.To}, - }) + }, jsonx.V1()) _ = pub.PublishToTopic(d.SnsDestination.TopicArn, string(payload)) } } diff --git a/services/sesv2/export_jobs_test.go b/services/sesv2/export_jobs_test.go index e82ea88f07..b9e24e9f08 100644 --- a/services/sesv2/export_jobs_test.go +++ b/services/sesv2/export_jobs_test.go @@ -1,13 +1,14 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -127,7 +128,7 @@ func TestGetExportJob(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp["JobId"].(string) rec := doRequest(t, h, http.MethodGet, "/v2/email/export-jobs/"+jobID, nil) @@ -249,7 +250,7 @@ func TestCreateExportJobWireShape(t *testing.T) { } var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID, ok := createResp["JobId"].(string) require.True(t, ok) @@ -257,7 +258,7 @@ func TestCreateExportJobWireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.wantSource, getResp["ExportSourceType"]) }) } @@ -285,7 +286,7 @@ func TestCancelExportJobAfterCreate(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp["JobId"].(string) rec := doRequest(t, h, http.MethodPut, "/v2/email/export-jobs/"+jobID+"/cancel", nil) diff --git a/services/sesv2/handler_account.go b/services/sesv2/handler_account.go index 6994f49515..1548c71be3 100644 --- a/services/sesv2/handler_account.go +++ b/services/sesv2/handler_account.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // account handlers @@ -45,7 +48,7 @@ func (h *Handler) handlePutAccountDedicatedIPWarmupAttributes(c *echo.Context) ( AutoWarmupEnabled bool `json:"AutoWarmupEnabled"` } - _ = json.NewDecoder(c.Request().Body).Decode(&in) + _ = json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()) if err := h.Backend.PutAccountDedicatedIPWarmupAttributes(in.AutoWarmupEnabled); err != nil { return nil, err @@ -67,7 +70,7 @@ type putAccountDetailsInput struct { func (h *Handler) handlePutAccountDetails(c *echo.Context) (any, error) { var in putAccountDetailsInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -90,7 +93,7 @@ type putAccountSendingInput struct { func (h *Handler) handlePutAccountSendingAttributes(c *echo.Context) (any, error) { var in putAccountSendingInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -107,7 +110,7 @@ func (h *Handler) handlePutAccountPricingAttributes(c *echo.Context) (any, error Plan string `json:"Plan"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -123,7 +126,7 @@ func (h *Handler) handlePutAccountSuppressionAttributes(c *echo.Context) (any, e SuppressedReasons []string `json:"SuppressedReasons"` } - _ = json.NewDecoder(c.Request().Body).Decode(&in) + _ = json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()) if err := h.Backend.PutAccountSuppressionAttributes(in.SuppressedReasons); err != nil { return nil, err diff --git a/services/sesv2/handler_configuration_sets.go b/services/sesv2/handler_configuration_sets.go index 35e426ea45..8acac37d63 100644 --- a/services/sesv2/handler_configuration_sets.go +++ b/services/sesv2/handler_configuration_sets.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createConfigurationSetInput struct { @@ -203,7 +206,7 @@ type listConfigurationSetsOutput struct { func (h *Handler) handleCreateConfigurationSet(c *echo.Context) (any, error) { var in createConfigurationSetInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidParameter, err.Error()) } @@ -313,7 +316,7 @@ func (h *Handler) handlePutConfigurationSetArchivingOptions( ArchiveARN string `json:"ArchiveArn"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -332,7 +335,7 @@ func (h *Handler) handlePutConfigurationSetDeliveryOptions( TLSPolicy string `json:"TlsPolicy"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -351,7 +354,7 @@ func (h *Handler) handlePutConfigurationSetReputationOptions( ReputationMetricsEnabled bool `json:"ReputationMetricsEnabled"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -369,7 +372,7 @@ func (h *Handler) handlePutConfigurationSetSendingOptions( SendingEnabled bool `json:"SendingEnabled"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -382,7 +385,7 @@ func (h *Handler) handlePutConfigurationSetSuppressionOptions( ) (any, error) { var in suppressionOptionsInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -403,7 +406,7 @@ func (h *Handler) handlePutConfigurationSetTrackingOptions( HTTPSPolicy string `json:"HttpsPolicy"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_contact_lists.go b/services/sesv2/handler_contact_lists.go index 3e81a74f9c..9c746c9a00 100644 --- a/services/sesv2/handler_contact_lists.go +++ b/services/sesv2/handler_contact_lists.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // topicInput mirrors types.Topic. @@ -38,7 +41,7 @@ type createContactListInput struct { func (h *Handler) handleCreateContactList(c *echo.Context) (any, error) { var in createContactListInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -78,7 +81,7 @@ type updateContactListInput struct { func (h *Handler) handleUpdateContactList(c *echo.Context, name string) (any, error) { var in updateContactListInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_contacts.go b/services/sesv2/handler_contacts.go index 0e4c7ee5cd..7515241c9e 100644 --- a/services/sesv2/handler_contacts.go +++ b/services/sesv2/handler_contacts.go @@ -1,12 +1,15 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/url" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createContactInput struct { @@ -19,7 +22,7 @@ type createContactInput struct { func (h *Handler) handleCreateContact(c *echo.Context, contactListName string) (any, error) { var in createContactInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -106,7 +109,7 @@ func (h *Handler) handleUpdateContact(c *echo.Context, contactListName string) ( var in updateContactInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -136,7 +139,7 @@ type listContactsInput struct { func (h *Handler) handleListContacts(c *echo.Context, contactListName string) (any, error) { var in listContactsInput - _ = json.NewDecoder(c.Request().Body).Decode(&in) + _ = json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()) var filter ContactFilter if in.Filter != nil { diff --git a/services/sesv2/handler_custom_verification_email_templates.go b/services/sesv2/handler_custom_verification_email_templates.go index c11aad56fb..c879df6040 100644 --- a/services/sesv2/handler_custom_verification_email_templates.go +++ b/services/sesv2/handler_custom_verification_email_templates.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createCustomVerificationEmailTemplateInput struct { @@ -19,7 +22,7 @@ type createCustomVerificationEmailTemplateInput struct { func (h *Handler) handleCreateCustomVerificationEmailTemplate(c *echo.Context) (any, error) { var in createCustomVerificationEmailTemplateInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -72,7 +75,7 @@ func (h *Handler) handleUpdateCustomVerificationEmailTemplate( ) (any, error) { var in updateCustomVerificationEmailTemplateInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_dedicated_ip_pools.go b/services/sesv2/handler_dedicated_ip_pools.go index 17dad99596..5bea4486f8 100644 --- a/services/sesv2/handler_dedicated_ip_pools.go +++ b/services/sesv2/handler_dedicated_ip_pools.go @@ -1,11 +1,14 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createDedicatedIPPoolInput struct { @@ -17,7 +20,7 @@ type createDedicatedIPPoolInput struct { func (h *Handler) handleCreateDedicatedIPPool(c *echo.Context) (any, error) { var in createDedicatedIPPoolInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -73,7 +76,7 @@ func (h *Handler) handlePutDedicatedIPPoolScalingAttributes( ) (any, error) { var in putDedicatedIPPoolScalingInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_dedicated_ips.go b/services/sesv2/handler_dedicated_ips.go index a2b91fdff0..f57365ccb8 100644 --- a/services/sesv2/handler_dedicated_ips.go +++ b/services/sesv2/handler_dedicated_ips.go @@ -1,11 +1,14 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetDedicatedIP(ip string) (any, error) { @@ -41,7 +44,7 @@ type putDedicatedIPInPoolInput struct { func (h *Handler) handlePutDedicatedIPInPool(c *echo.Context, ip string) (any, error) { var in putDedicatedIPInPoolInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -59,7 +62,7 @@ type putDedicatedIPWarmupInput struct { func (h *Handler) handlePutDedicatedIPWarmupAttributes(c *echo.Context, ip string) (any, error) { var in putDedicatedIPWarmupInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_deliverability.go b/services/sesv2/handler_deliverability.go index e46169d814..8f71204485 100644 --- a/services/sesv2/handler_deliverability.go +++ b/services/sesv2/handler_deliverability.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createDeliverabilityTestReportInput struct { @@ -20,7 +23,7 @@ type createDeliverabilityTestReportOutput struct { func (h *Handler) handleCreateDeliverabilityTestReport(c *echo.Context) (any, error) { var in createDeliverabilityTestReportInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -245,7 +248,7 @@ func (h *Handler) handleUpdateReputationEntityCustomerManagedStatus( ) (any, error) { var in updateReputationEntityCustomerManagedStatusInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -270,7 +273,7 @@ func (h *Handler) handleUpdateReputationEntityPolicy( ) (any, error) { var in updateReputationEntityPolicyInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_email_identities.go b/services/sesv2/handler_email_identities.go index 98bf818707..4d07ec0275 100644 --- a/services/sesv2/handler_email_identities.go +++ b/services/sesv2/handler_email_identities.go @@ -1,12 +1,15 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/url" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- request types ---- @@ -71,7 +74,7 @@ type listEmailIdentitiesOutput struct { func (h *Handler) handleCreateEmailIdentity(c *echo.Context) (any, error) { var in createEmailIdentityInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidParameter, err.Error()) } @@ -164,7 +167,11 @@ func (h *Handler) handleDeleteEmailIdentity(identity string) (any, error) { return &emptyDeleteOutput{}, nil } -func (h *Handler) handleCreateEmailIdentityPolicy(c *echo.Context, identity string) (any, error) { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleCreateEmailIdentityPolicy( + c *echo.Context, + identity string, +) (any, error) { // Extract policyName from the URL: .../identities/{identity}/policies/{policyName} segments := strings.Split(strings.TrimPrefix(c.Request().URL.Path, sesv2PathPrefix), "/") if len(segments) < policyPathMinSegments { @@ -178,7 +185,7 @@ func (h *Handler) handleCreateEmailIdentityPolicy(c *echo.Context, identity stri var in createEmailIdentityPolicyInput - if decErr := json.NewDecoder(c.Request().Body).Decode(&in); decErr != nil { + if decErr := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); decErr != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, decErr.Error()) } @@ -219,7 +226,11 @@ func (h *Handler) handleDeleteEmailIdentityPolicy(c *echo.Context, identity stri return &emptyDeleteOutput{}, nil } -func (h *Handler) handleUpdateEmailIdentityPolicy(c *echo.Context, identity string) (any, error) { +//nolint:dupl // structurally similar handlers +func (h *Handler) handleUpdateEmailIdentityPolicy( + c *echo.Context, + identity string, +) (any, error) { segments := strings.Split(strings.TrimPrefix(c.Request().URL.Path, sesv2PathPrefix), "/") if len(segments) < policyPathMinSegments { return nil, fmt.Errorf("%w: invalid policy path", ErrInvalidInput) @@ -233,7 +244,7 @@ func (h *Handler) handleUpdateEmailIdentityPolicy(c *echo.Context, identity stri var in createEmailIdentityPolicyInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -254,7 +265,7 @@ func (h *Handler) handlePutEmailIdentityConfigurationSetAttributes( ConfigurationSetName string `json:"ConfigurationSetName"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -272,7 +283,7 @@ func (h *Handler) handlePutEmailIdentityDkimAttributes( SigningEnabled bool `json:"SigningEnabled"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -307,7 +318,7 @@ func (h *Handler) handlePutEmailIdentityFeedbackAttributes( EmailForwardingEnabled bool `json:"EmailForwardingEnabled"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -326,7 +337,7 @@ func (h *Handler) handlePutEmailIdentityMailFromAttributes( BehaviorOnMxFailure string `json:"BehaviorOnMxFailure"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_email_templates.go b/services/sesv2/handler_email_templates.go index 25bae5f9cc..60df1ca005 100644 --- a/services/sesv2/handler_email_templates.go +++ b/services/sesv2/handler_email_templates.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createEmailTemplateInput struct { @@ -16,7 +19,7 @@ type createEmailTemplateInput struct { func (h *Handler) handleCreateEmailTemplate(c *echo.Context) (any, error) { var in createEmailTemplateInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -54,7 +57,7 @@ type updateEmailTemplateInput struct { func (h *Handler) handleUpdateEmailTemplate(c *echo.Context, name string) (any, error) { var in updateEmailTemplateInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -87,7 +90,7 @@ type testRenderEmailTemplateInput struct { func (h *Handler) handleTestRenderEmailTemplate(c *echo.Context, name string) (any, error) { var in testRenderEmailTemplateInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_event_destinations.go b/services/sesv2/handler_event_destinations.go index 607045887c..c5628bedb2 100644 --- a/services/sesv2/handler_event_destinations.go +++ b/services/sesv2/handler_event_destinations.go @@ -1,12 +1,15 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/url" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // eventDestinationDefinitionInput mirrors types.EventDestinationDefinition. @@ -87,7 +90,7 @@ func (h *Handler) handleCreateConfigurationSetEventDestination( ) (any, error) { var in createConfigurationSetEventDestinationInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -156,7 +159,7 @@ func (h *Handler) handleUpdateConfigurationSetEventDestination( var in updateConfigurationSetEventDestinationInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_export_jobs.go b/services/sesv2/handler_export_jobs.go index 0c13805e75..dc7092473a 100644 --- a/services/sesv2/handler_export_jobs.go +++ b/services/sesv2/handler_export_jobs.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCancelExportJob(jobID string) (any, error) { @@ -25,8 +28,8 @@ func (h *Handler) handleCancelExportJob(jobID string) (any, error) { // accepted opaquely via json.RawMessage rather than decoded into a typed // shape -- only which branch was set is used, to derive ExportSourceType. type exportDataSourceInput struct { - MetricsDataSource json.RawMessage `json:"MetricsDataSource,omitempty"` - MessageInsightsDataSource json.RawMessage `json:"MessageInsightsDataSource,omitempty"` + MetricsDataSource jsontext.Value `json:"MetricsDataSource,omitempty"` + MessageInsightsDataSource jsontext.Value `json:"MessageInsightsDataSource,omitempty"` } // sourceType validates that exactly one branch is set (real SES v2 requires @@ -65,7 +68,7 @@ type createExportJobInput struct { func (h *Handler) handleCreateExportJob(c *echo.Context) (any, error) { var in createExportJobInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_identity_certificates.go b/services/sesv2/handler_identity_certificates.go index 3a04ad1f3e..1e7a64261f 100644 --- a/services/sesv2/handler_identity_certificates.go +++ b/services/sesv2/handler_identity_certificates.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type identityCertificateOutput struct { @@ -19,7 +22,7 @@ type listEmailIdentityCertificatesOutput struct { } func decodeBody(c *echo.Context, in any) error { - if err := json.NewDecoder(c.Request().Body).Decode(in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), in, jsonx.V1()); err != nil { return fmt.Errorf("%w: invalid request body: %s", ErrInvalidParameter, err.Error()) } diff --git a/services/sesv2/handler_import_jobs.go b/services/sesv2/handler_import_jobs.go index c5fa97bcfe..3aa9e24806 100644 --- a/services/sesv2/handler_import_jobs.go +++ b/services/sesv2/handler_import_jobs.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // importDataSourceInput mirrors types.ImportDataSource. Both members are @@ -85,7 +88,7 @@ type createImportJobInput struct { func (h *Handler) handleCreateImportJob(c *echo.Context) (any, error) { var in createImportJobInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_message_insights.go b/services/sesv2/handler_message_insights.go index 250dbb23dd..df874d0538 100644 --- a/services/sesv2/handler_message_insights.go +++ b/services/sesv2/handler_message_insights.go @@ -1,13 +1,15 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type batchGetMetricDataInput struct { @@ -49,7 +51,7 @@ func epochSecondsToTime(f float64) time.Time { func (h *Handler) handleBatchGetMetricData(c *echo.Context) (any, error) { var in batchGetMetricDataInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_multi_region_endpoints.go b/services/sesv2/handler_multi_region_endpoints.go index d823ca2a58..2a4473dcbd 100644 --- a/services/sesv2/handler_multi_region_endpoints.go +++ b/services/sesv2/handler_multi_region_endpoints.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // multi-region endpoint handlers @@ -22,7 +25,7 @@ type createMultiRegionEndpointInput struct { func (h *Handler) handleCreateMultiRegionEndpoint(c *echo.Context) (any, error) { var in createMultiRegionEndpointInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_page_size.go b/services/sesv2/handler_page_size.go index f6b141a8c5..e7b3d82bcc 100644 --- a/services/sesv2/handler_page_size.go +++ b/services/sesv2/handler_page_size.go @@ -1,11 +1,14 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strconv" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func queryPageSize(c *echo.Context) int { @@ -36,7 +39,7 @@ func listRequestParams(c *echo.Context) listParams { PageSize int `json:"PageSize"` } - _ = json.NewDecoder(c.Request().Body).Decode(&in) + _ = json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()) return listParams{Filter: in.Filter, NextToken: in.NextToken, PageSize: max(in.PageSize, 0)} } diff --git a/services/sesv2/handler_send_email.go b/services/sesv2/handler_send_email.go index 3cb7ae87e4..4627213428 100644 --- a/services/sesv2/handler_send_email.go +++ b/services/sesv2/handler_send_email.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type sendEmailInput struct { @@ -69,7 +72,7 @@ type sendEmailOutput struct { func (h *Handler) handleSendEmail(c *echo.Context) (any, error) { var in sendEmailInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidParameter, err.Error()) } @@ -137,7 +140,7 @@ type sendBulkEmailInput struct { func (h *Handler) handleSendBulkEmail(c *echo.Context) (any, error) { var in sendBulkEmailInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } @@ -168,7 +171,7 @@ type sendCustomVerificationEmailInput struct { func (h *Handler) handleSendCustomVerificationEmail(c *echo.Context) (any, error) { var in sendCustomVerificationEmailInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_suppression.go b/services/sesv2/handler_suppression.go index c07f037d17..65c097e61c 100644 --- a/services/sesv2/handler_suppression.go +++ b/services/sesv2/handler_suppression.go @@ -1,12 +1,15 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strconv" "time" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // suppressed destination handlers @@ -20,7 +23,7 @@ type putSuppressedDestinationInput struct { func (h *Handler) handlePutSuppressedDestination(c *echo.Context) (any, error) { var in putSuppressedDestinationInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_tags.go b/services/sesv2/handler_tags.go index df8a19dd0b..e12af4d07d 100644 --- a/services/sesv2/handler_tags.go +++ b/services/sesv2/handler_tags.go @@ -1,11 +1,14 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type listTagsOutput struct { @@ -38,7 +41,7 @@ type tagResourceInput struct { func (h *Handler) handleTagResource(c *echo.Context) (any, error) { var in tagResourceInput - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid body", ErrInvalidInput) } diff --git a/services/sesv2/handler_tenants.go b/services/sesv2/handler_tenants.go index 7640713565..78e80d42b4 100644 --- a/services/sesv2/handler_tenants.go +++ b/services/sesv2/handler_tenants.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Tenant handlers. @@ -18,7 +21,7 @@ import ( // no {Placeholder} segments for this family at all). func decodeSESv2Body(c *echo.Context, v any) error { - if err := json.NewDecoder(c.Request().Body).Decode(v); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), v, jsonx.V1()); err != nil { return fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/handler_test.go b/services/sesv2/handler_test.go index f40cc54d34..d2d8b6cbe5 100644 --- a/services/sesv2/handler_test.go +++ b/services/sesv2/handler_test.go @@ -2,7 +2,7 @@ package sesv2_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -34,7 +35,7 @@ func doRequest( var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) @@ -366,7 +367,7 @@ func TestSESv2Handler_ErrorResponseIncludesType(t *testing.T) { assert.Equal(t, http.StatusNotFound, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal( t, "NotFoundException", @@ -399,7 +400,7 @@ func TestSESv2Handler_URLEncodedIdentity(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotContains(t, out, "EmailIdentity") // DELETE using percent-encoded path. diff --git a/services/sesv2/handler_update_configuration_set.go b/services/sesv2/handler_update_configuration_set.go index 2d33455ed8..a81d484a9a 100644 --- a/services/sesv2/handler_update_configuration_set.go +++ b/services/sesv2/handler_update_configuration_set.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // messageSecurityOptions mirrors types.MessageSecurityOptions: SigningScheme is @@ -55,7 +58,7 @@ func (h *Handler) handleUpdateConfigurationSet(c *echo.Context) (any, error) { ConfigurationSetName string `json:"ConfigurationSetName"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidParameter, err.Error()) } diff --git a/services/sesv2/handler_vdm.go b/services/sesv2/handler_vdm.go index a669266bfe..f9fa3979e3 100644 --- a/services/sesv2/handler_vdm.go +++ b/services/sesv2/handler_vdm.go @@ -1,10 +1,13 @@ package sesv2 import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handlePutAccountVdmAttributes(c *echo.Context) (any, error) { @@ -12,7 +15,7 @@ func (h *Handler) handlePutAccountVdmAttributes(c *echo.Context) (any, error) { VdmAttributes map[string]any `json:"VdmAttributes"` } - _ = json.NewDecoder(c.Request().Body).Decode(&in) + _ = json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()) if err := h.Backend.PutAccountVdmAttributes(in.VdmAttributes); err != nil { return nil, err @@ -32,7 +35,7 @@ func (h *Handler) handlePutConfigurationSetVdmOptions( } `json:"VdmOptions"` } - if err := json.NewDecoder(c.Request().Body).Decode(&in); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &in, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid request body: %s", ErrInvalidInput, err.Error()) } diff --git a/services/sesv2/import_jobs_test.go b/services/sesv2/import_jobs_test.go index e27e72f29a..8d948989c2 100644 --- a/services/sesv2/import_jobs_test.go +++ b/services/sesv2/import_jobs_test.go @@ -1,12 +1,14 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestCreateImportJob tests the CreateImportJob operation. @@ -48,7 +50,7 @@ func TestGetImportJob(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) jobID := createResp["JobId"].(string) rec := doRequest(t, h, http.MethodGet, "/v2/email/import-jobs/"+jobID, nil) @@ -175,7 +177,7 @@ func TestCreateImportJobWireShape(t *testing.T) { } var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) jobID, ok := createResp["JobId"].(string) require.True(t, ok) @@ -183,7 +185,7 @@ func TestCreateImportJobWireShape(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) dest, ok := getResp["ImportDestination"].(map[string]any) require.True(t, ok) diff --git a/services/sesv2/message_insights_test.go b/services/sesv2/message_insights_test.go index 61db9bebbd..6fc0528984 100644 --- a/services/sesv2/message_insights_test.go +++ b/services/sesv2/message_insights_test.go @@ -2,7 +2,7 @@ package sesv2_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -123,7 +124,7 @@ func TestGetMessageInsights(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp, jsonx.V1())) messageID, _ := sendResp["MessageId"].(string) require.NotEmpty(t, messageID) @@ -131,7 +132,7 @@ func TestGetMessageInsights(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, messageID, out["MessageId"]) assert.Equal(t, "sender@example.com", out["FromEmailAddress"]) diff --git a/services/sesv2/multi_region_test.go b/services/sesv2/multi_region_test.go index 67b72d4a12..21a1f1ce1c 100644 --- a/services/sesv2/multi_region_test.go +++ b/services/sesv2/multi_region_test.go @@ -2,7 +2,7 @@ package sesv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -38,7 +39,7 @@ func regionCall(t *testing.T, h *sesv2.Handler, region, method, path, body strin out := map[string]any{} if rec.Body.Len() > 0 { - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) } return out diff --git a/services/sesv2/persistence.go b/services/sesv2/persistence.go index ee03c7a1a0..9f5118d926 100644 --- a/services/sesv2/persistence.go +++ b/services/sesv2/persistence.go @@ -2,9 +2,11 @@ package sesv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -29,7 +31,7 @@ const sesv2SnapshotVersion = 1 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` EmailIdentityPolicies map[string]map[string]string `json:"emailIdentityPolicies"` ResourceTags map[string]map[string]string `json:"resourceTags"` MultiRegionEndpoints map[string]map[string]any `json:"multiRegionEndpoints"` @@ -101,7 +103,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { DeliverabilityDashboardDomains: b.deliverabilityDashboardDomains, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "sesv2: failed to marshal snapshot", "error", err) diff --git a/services/sesv2/realism_test.go b/services/sesv2/realism_test.go index 6c46e6a502..0334b717f4 100644 --- a/services/sesv2/realism_test.go +++ b/services/sesv2/realism_test.go @@ -1,13 +1,15 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func errBody(t *testing.T, body []byte) (string, string) { @@ -17,7 +19,7 @@ func errBody(t *testing.T, body []byte) (string, string) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out.Type, out.Message } diff --git a/services/sesv2/send_email_test.go b/services/sesv2/send_email_test.go index b9fe48c4c1..c8a8670c9f 100644 --- a/services/sesv2/send_email_test.go +++ b/services/sesv2/send_email_test.go @@ -1,7 +1,7 @@ package sesv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -408,7 +409,7 @@ func TestSendEmail(t *testing.T) { if tt.wantCode == http.StatusOK { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["MessageId"]) emails := h.Backend.ListEmails() diff --git a/services/sesv2/smtp_relay_test.go b/services/sesv2/smtp_relay_test.go index cebb0f6820..409ee3562c 100644 --- a/services/sesv2/smtp_relay_test.go +++ b/services/sesv2/smtp_relay_test.go @@ -3,7 +3,7 @@ package sesv2_test import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "log/slog" "mime" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/smtprelay/smtptest" "github.com/blackbirdworks/gopherstack/services/sesv2" @@ -147,7 +148,7 @@ func TestSMTPRelayDelivery(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) sess := nextSession(t, srv) msg, err := mail.ReadMessage(strings.NewReader(sess.Data)) @@ -195,7 +196,7 @@ func TestSMTPRelayRespectsRejectionAndFailure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["MessageId"]) _ = nextSession(t, srv) diff --git a/services/sesv2/store_test.go b/services/sesv2/store_test.go index dbcca5bfec..748064e0d0 100644 --- a/services/sesv2/store_test.go +++ b/services/sesv2/store_test.go @@ -2,7 +2,8 @@ package sesv2_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http/httptest" "testing" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sesv2" ) @@ -79,7 +81,7 @@ func doReq( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -110,7 +112,7 @@ func doReqQuery( var buf bytes.Buffer if body != nil { - require.NoError(t, json.NewEncoder(&buf).Encode(body)) + require.NoError(t, json.MarshalEncode(jsontext.NewEncoder(&buf, jsonx.V1()), body)) } req := httptest.NewRequest(method, path, &buf) @@ -140,7 +142,7 @@ func decodeJSON(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &m, jsonx.V1())) return m } diff --git a/services/sesv2/whitebox_test.go b/services/sesv2/whitebox_test.go index 20a9888ce3..2a1dd59e84 100644 --- a/services/sesv2/whitebox_test.go +++ b/services/sesv2/whitebox_test.go @@ -2,7 +2,7 @@ package sesv2 import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "reflect" @@ -12,6 +12,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // wantCreationTimeTagValue is the tag value newTaggableTenant/ @@ -82,7 +84,7 @@ func TestCreateOpsWithTags_CreationTimeTagsRoundTrip(t *testing.T) { func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body any) *httptest.ResponseRecorder { t.Helper() - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(method, path, bytes.NewReader(b)) diff --git a/services/shield/errors_test.go b/services/shield/errors_test.go index bb798b64f5..96b7c5c17f 100644 --- a/services/shield/errors_test.go +++ b/services/shield/errors_test.go @@ -1,7 +1,7 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -20,7 +21,7 @@ func wireErrorType(t *testing.T, body []byte) string { var env struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(body, &env)) + require.NoError(t, json.Unmarshal(body, &env, jsonx.V1())) return env.Type } diff --git a/services/shield/handler.go b/services/shield/handler.go index d95464abcb..a5fdd10c27 100644 --- a/services/shield/handler.go +++ b/services/shield/handler.go @@ -3,7 +3,7 @@ package shield import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -394,10 +395,7 @@ func shieldErrorRules() []shieldErrorRule { // treated the same as InvalidParameterException since they never reach a specific handler's own // validation. Unrecognized errors fall back to InternalErrorException/500. func classifyShieldError(err error) (string, int) { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - - if errors.As(err, &syntaxErr) || errors.As(err, &typeErr) { + if jsonx.IsSyntaxError(err) || jsonx.IsTypeError(err) { return codeInvalidParameterException, http.StatusBadRequest } @@ -415,7 +413,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: code, keyMessageField: messageWithoutCode(code, err), - }) + }, jsonx.V1()) return c.JSONBlob(status, payload) } @@ -452,7 +450,7 @@ func decodeOffsetToken(token string) (int, error) { } var t offsetToken - if unmarshalErr := json.Unmarshal(raw, &t); unmarshalErr != nil || t.O < 0 { + if unmarshalErr := json.Unmarshal(raw, &t, jsonx.V1()); unmarshalErr != nil || t.O < 0 { return 0, errInvalidPaginationToken } @@ -461,7 +459,7 @@ func decodeOffsetToken(token string) (int, error) { // encodeOffsetToken encodes an integer cursor as an opaque base64url(JSON) string. func encodeOffsetToken(offset int) string { - data, _ := json.Marshal(offsetToken{O: offset}) + data, _ := json.Marshal(offsetToken{O: offset}, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(data) } diff --git a/services/shield/handler_application_layer_automatic_response.go b/services/shield/handler_application_layer_automatic_response.go index 7803e2a560..8046c95013 100644 --- a/services/shield/handler_application_layer_automatic_response.go +++ b/services/shield/handler_application_layer_automatic_response.go @@ -1,8 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // alarAction is the nested action object in ALAR requests. @@ -31,7 +33,7 @@ func alarActionString(a alarAction) (string, error) { func (h *Handler) handleEnableApplicationLayerAutomaticResponse(body []byte) error { var req alarRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -54,7 +56,7 @@ type disableALARRequest struct { func (h *Handler) handleDisableApplicationLayerAutomaticResponse(body []byte) error { var req disableALARRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -67,7 +69,7 @@ func (h *Handler) handleDisableApplicationLayerAutomaticResponse(body []byte) er func (h *Handler) handleUpdateApplicationLayerAutomaticResponse(body []byte) error { var req alarRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/shield/handler_attacks.go b/services/shield/handler_attacks.go index 816fb1f272..c364887bf5 100644 --- a/services/shield/handler_attacks.go +++ b/services/shield/handler_attacks.go @@ -1,8 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // listAttacksTimeRange mirrors the AWS Shield TimeRange wire format for ListAttacks. @@ -24,7 +26,7 @@ type listAttacksRequest struct { func (h *Handler) handleListAttacks(body []byte) ([]byte, error) { var req listAttacksRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -89,7 +91,7 @@ func (h *Handler) handleListAttacks(body []byte) ([]byte, error) { resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // simulateAttackRequest is the request body for the __SimulateAttack endpoint (gap 25). @@ -101,7 +103,7 @@ type simulateAttackRequest struct { // handleSimulateAttack creates a synthetic attack record via the chaos endpoint (gap 25/10). func (h *Handler) handleSimulateAttack(body []byte) ([]byte, error) { var req simulateAttackRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -119,7 +121,7 @@ func (h *Handler) handleSimulateAttack(body []byte) ([]byte, error) { keyResourceArn: attack.ResourceARN, keyStartTime: floatSeconds(attack.StartTime), keyEndTime: floatSeconds(attack.EndTime), - }) + }, jsonx.V1()) } // describeAttackRequest is the request body for DescribeAttack. @@ -129,7 +131,7 @@ type describeAttackRequest struct { func (h *Handler) handleDescribeAttack(body []byte) ([]byte, error) { var req describeAttackRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -174,7 +176,7 @@ func (h *Handler) handleDescribeAttack(body []byte) ([]byte, error) { "AttackCounters": counters, "Mitigations": mitigations, }, - }) + }, jsonx.V1()) } func (h *Handler) handleDescribeAttackStatistics() ([]byte, error) { @@ -187,5 +189,5 @@ func (h *Handler) handleDescribeAttackStatistics() ([]byte, error) { "ToExclusive": float64(stats.TimeRange.ToExclusive), }, "DataItems": stats.DataItems, - }) + }, jsonx.V1()) } diff --git a/services/shield/handler_attacks_test.go b/services/shield/handler_attacks_test.go index 1d9da2956c..ba648aa825 100644 --- a/services/shield/handler_attacks_test.go +++ b/services/shield/handler_attacks_test.go @@ -1,7 +1,7 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -25,7 +26,7 @@ func TestHandler_DescribeAttackStatisticsFlatResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasWrapper := resp["AttackStatistics"] assert.False(t, hasWrapper, "response must NOT be wrapped in 'AttackStatistics'") @@ -48,7 +49,7 @@ func TestHandler_DescribeAttackStatisticsTimeRangeIsFloat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tr := resp["TimeRange"].(map[string]any) @@ -83,7 +84,7 @@ func TestHandler_ListAttacksStartTimeRangeObject(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Len(t, summaries, 1, "StartTime TimeRange object must be accepted") @@ -107,7 +108,7 @@ func TestHandler_ListAttacksEndTimeRangeObject(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Len(t, summaries, 1, "EndTime TimeRange object must be accepted") @@ -131,7 +132,7 @@ func TestHandler_ListAttacksEndTimeRangeFiltersOut(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Empty(t, summaries, "attacks after ToExclusive must be excluded") @@ -157,7 +158,7 @@ func TestHandler_ListAttacksEndTimeExclusiveBoundary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Empty(t, summaries, "an attack starting exactly at ToExclusive must be excluded, not included") @@ -181,7 +182,7 @@ func TestHandler_ListAttacksDefaultMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Len(t, summaries, 20, "omitted MaxResults must default to 20 per the documented default") @@ -209,7 +210,7 @@ func TestHandler_ListAttacksOpaquePageToken(t *testing.T) { NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.NextToken) offset := opaqueOffset(t, out.NextToken) @@ -274,7 +275,7 @@ func TestHandler_DescribeAttack(t *testing.T) { if tt.wantFields { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) attack, ok := result["Attack"].(map[string]any) require.True(t, ok) assert.Equal(t, id, attack["AttackId"]) @@ -323,7 +324,7 @@ func TestHandler_DescribeAttackStatistics(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) items, ok := result["DataItems"].([]any) require.True(t, ok) @@ -350,7 +351,7 @@ func TestHandler_AttackRichFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // No "AttackVectors" key: types.AttackDetail (shield@v1.37.4 types/types.go) // has no such member -- that's AttackSummary's field (ListAttacks), see @@ -379,7 +380,7 @@ func TestHandler_ListAttacksMultipleARNs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Len(t, summaries, 2) @@ -404,7 +405,7 @@ func TestHandler_ListAttacksPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) assert.Len(t, summaries, 2) @@ -427,7 +428,7 @@ func TestHandler_DescribeAttackStatisticsWithVolume(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["TimeRange"]) assert.NotNil(t, resp["DataItems"]) @@ -447,7 +448,7 @@ func TestHandler_DescribeAttackStatisticsEmptyWhenNoAttacks(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["DataItems"].([]any) assert.Len(t, items, 1) @@ -467,7 +468,7 @@ func TestHandler_AttackTimestampIsFloat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) atk := raw["Attack"].(map[string]any) st, ok := atk["StartTime"].(float64) @@ -494,7 +495,7 @@ func TestHandler_SimulateAttackHTTPEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["AttackId"]) assert.Equal(t, 1, shield.AttackCount(b)) @@ -533,7 +534,7 @@ func TestHandler_AttackSimulationEndToEnd(t *testing.T) { require.Equal(t, http.StatusOK, simRec.Code) var simResp map[string]any - require.NoError(t, json.Unmarshal(simRec.Body.Bytes(), &simResp)) + require.NoError(t, json.Unmarshal(simRec.Body.Bytes(), &simResp, jsonx.V1())) attackID := simResp["AttackId"].(string) // ListAttacks must show it. @@ -541,7 +542,7 @@ func TestHandler_AttackSimulationEndToEnd(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) summaries := listResp["AttackSummaries"].([]any) require.Len(t, summaries, 1) @@ -550,7 +551,7 @@ func TestHandler_AttackSimulationEndToEnd(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) atk := descResp["Attack"].(map[string]any) _, hasVectors := atk["AttackVectors"] assert.False(t, hasVectors, "types.AttackDetail has no AttackVectors member") @@ -562,7 +563,7 @@ func TestHandler_AttackSimulationEndToEnd(t *testing.T) { require.Equal(t, http.StatusOK, statsRec.Code) var statsResp map[string]any - require.NoError(t, json.Unmarshal(statsRec.Body.Bytes(), &statsResp)) + require.NoError(t, json.Unmarshal(statsRec.Body.Bytes(), &statsResp, jsonx.V1())) items := statsResp["DataItems"].([]any) require.NotEmpty(t, items) @@ -585,7 +586,7 @@ func TestHandler_ListAttacksIncludesVectors(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["AttackSummaries"].([]any) require.Len(t, summaries, 1) diff --git a/services/shield/handler_drt.go b/services/shield/handler_drt.go index 9483764cc8..5fd8dd55a6 100644 --- a/services/shield/handler_drt.go +++ b/services/shield/handler_drt.go @@ -1,8 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // associateDRTLogBucketRequest is the request body for AssociateDRTLogBucket. @@ -12,7 +14,7 @@ type associateDRTLogBucketRequest struct { func (h *Handler) handleAssociateDRTLogBucket(body []byte) error { var req associateDRTLogBucketRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -34,7 +36,7 @@ type disassociateDRTLogBucketRequest struct { func (h *Handler) handleDisassociateDRTLogBucket(body []byte) error { var req disassociateDRTLogBucketRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -52,7 +54,7 @@ type associateDRTRoleRequest struct { func (h *Handler) handleAssociateDRTRole(body []byte) error { var req associateDRTRoleRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -83,5 +85,5 @@ func (h *Handler) handleDescribeDRTAccess() ([]byte, error) { resp["RoleArn"] = access.RoleArn } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/shield/handler_drt_test.go b/services/shield/handler_drt_test.go index b813bc4507..d6abb5cee4 100644 --- a/services/shield/handler_drt_test.go +++ b/services/shield/handler_drt_test.go @@ -1,13 +1,14 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -150,7 +151,7 @@ func TestHandler_DescribeDRTAccess(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) if tt.wantRoleArn != "" { assert.Equal(t, tt.wantRoleArn, result["RoleArn"]) diff --git a/services/shield/handler_emergency_contacts.go b/services/shield/handler_emergency_contacts.go index a6fcb098ae..07b707d3dc 100644 --- a/services/shield/handler_emergency_contacts.go +++ b/services/shield/handler_emergency_contacts.go @@ -1,8 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // emergencyContactItem represents a single emergency contact in the API request/response. @@ -19,7 +21,7 @@ type associateProactiveEngagementRequest struct { func (h *Handler) handleAssociateProactiveEngagementDetails(body []byte) error { var req associateProactiveEngagementRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -57,7 +59,7 @@ type updateEmergencyContactSettingsRequest struct { func (h *Handler) handleUpdateEmergencyContactSettings(body []byte) error { var req updateEmergencyContactSettingsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -80,5 +82,5 @@ func (h *Handler) handleDescribeEmergencyContactSettings() ([]byte, error) { return json.Marshal(map[string]any{ "EmergencyContactList": items, - }) + }, jsonx.V1()) } diff --git a/services/shield/handler_health_checks.go b/services/shield/handler_health_checks.go index 30b61d4f09..5a84873fda 100644 --- a/services/shield/handler_health_checks.go +++ b/services/shield/handler_health_checks.go @@ -1,9 +1,11 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // validateHealthCheckARN checks that the ARN is a Route 53 health check ARN. @@ -27,7 +29,7 @@ type associateHealthCheckRequest struct { func (h *Handler) handleAssociateHealthCheck(body []byte) error { var req associateHealthCheckRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -54,7 +56,7 @@ type disassociateHealthCheckRequest struct { func (h *Handler) handleDisassociateHealthCheck(body []byte) error { var req disassociateHealthCheckRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/shield/handler_protection_groups.go b/services/shield/handler_protection_groups.go index 98bf2ae4c3..24e820c1ee 100644 --- a/services/shield/handler_protection_groups.go +++ b/services/shield/handler_protection_groups.go @@ -1,9 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -19,7 +20,7 @@ type createProtectionGroupRequest struct { func (h *Handler) handleCreateProtectionGroup(body []byte) error { var req createProtectionGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -50,7 +51,7 @@ type describeProtectionGroupRequest struct { func (h *Handler) handleDescribeProtectionGroup(body []byte) ([]byte, error) { var req describeProtectionGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -65,7 +66,7 @@ func (h *Handler) handleDescribeProtectionGroup(body []byte) ([]byte, error) { return json.Marshal(map[string]any{ "ProtectionGroup": protectionGroupToMap(pg), - }) + }, jsonx.V1()) } // listProtectionGroupsRequest is the request body for ListProtectionGroups. @@ -83,7 +84,7 @@ type listProtectionGroupsRequest struct { func (h *Handler) handleListProtectionGroups(body []byte) ([]byte, error) { var req listProtectionGroupsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -108,7 +109,7 @@ func (h *Handler) handleListProtectionGroups(body []byte) ([]byte, error) { } if start >= len(groups) { - return json.Marshal(map[string]any{"ProtectionGroups": []map[string]any{}}) + return json.Marshal(map[string]any{"ProtectionGroups": []map[string]any{}}, jsonx.V1()) } end := start + maxResults @@ -134,7 +135,7 @@ func (h *Handler) handleListProtectionGroups(body []byte) ([]byte, error) { resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // protectionGroupMatchesFilters returns true if pg passes all filter sets. @@ -200,7 +201,7 @@ type updateProtectionGroupRequest struct { func (h *Handler) handleUpdateProtectionGroup(body []byte) error { var req updateProtectionGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -246,7 +247,7 @@ type deleteProtectionGroupRequest struct { func (h *Handler) handleDeleteProtectionGroup(body []byte) error { var req deleteProtectionGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -266,7 +267,7 @@ type listResourcesInProtectionGroupRequest struct { func (h *Handler) handleListResourcesInProtectionGroup(body []byte) ([]byte, error) { var req listResourcesInProtectionGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -287,7 +288,7 @@ func (h *Handler) handleListResourcesInProtectionGroup(body []byte) ([]byte, err } if start >= len(arns) { - return json.Marshal(map[string]any{"ResourceArns": []string{}}) + return json.Marshal(map[string]any{"ResourceArns": []string{}}, jsonx.V1()) } end := start + maxResults @@ -306,5 +307,5 @@ func (h *Handler) handleListResourcesInProtectionGroup(body []byte) ([]byte, err resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/shield/handler_protection_groups_test.go b/services/shield/handler_protection_groups_test.go index ed5475512b..5dcd3e3a21 100644 --- a/services/shield/handler_protection_groups_test.go +++ b/services/shield/handler_protection_groups_test.go @@ -1,7 +1,7 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -181,7 +182,7 @@ func TestHandler_ListProtectionGroupsOpaquePageToken(t *testing.T) { NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.NextToken) offset := opaqueOffset(t, out.NextToken) @@ -213,7 +214,7 @@ func TestHandler_ListProtectionGroupsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ProtectionGroups"].([]any) assert.Len(t, groups, 2) @@ -247,7 +248,7 @@ func TestHandler_ListProtectionGroupsDefaultMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ProtectionGroups"].([]any) assert.Len(t, groups, 20, "omitted MaxResults must default to 20 per the documented default") @@ -281,7 +282,7 @@ func TestHandler_ListProtectionGroupsInclusionFilterByPattern(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ProtectionGroups"].([]any) assert.Len(t, groups, 1) @@ -311,7 +312,7 @@ func TestHandler_ListProtectionGroupsInclusionFilterByAggregation(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["ProtectionGroups"].([]any) assert.Len(t, groups, 1) @@ -346,7 +347,7 @@ func TestHandler_ListResourcesInProtectionGroupPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arns := resp["ResourceArns"].([]any) assert.Len(t, arns, 20, "omitted MaxResults must default to 20 per the documented default") @@ -363,7 +364,7 @@ func TestHandler_ListResourcesInProtectionGroupPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2["ResourceArns"].([]any), 5, "the remaining 5 of 25 members must be on the next page") assert.NotContains(t, resp2, "NextToken", "no further page should be signaled once all members are returned") } diff --git a/services/shield/handler_protections.go b/services/shield/handler_protections.go index ee5f27a0e1..022985f656 100644 --- a/services/shield/handler_protections.go +++ b/services/shield/handler_protections.go @@ -2,10 +2,11 @@ package shield import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -57,7 +58,7 @@ type createProtectionRequest struct { func (h *Handler) handleCreateProtection(ctx context.Context, body []byte) ([]byte, error) { var req createProtectionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -89,7 +90,7 @@ func (h *Handler) handleCreateProtection(ctx context.Context, body []byte) ([]by return json.Marshal(map[string]string{ "ProtectionId": p.ID, - }) + }, jsonx.V1()) } // describeProtectionRequest is the request body for DescribeProtection. @@ -100,7 +101,7 @@ type describeProtectionRequest struct { func (h *Handler) handleDescribeProtection(body []byte) ([]byte, error) { var req describeProtectionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +118,7 @@ func (h *Handler) handleDescribeProtection(body []byte) ([]byte, error) { return json.Marshal(map[string]any{ "Protection": protectionToMap(p, alarCfg), - }) + }, jsonx.V1()) } // deleteProtectionRequest is the request body for DeleteProtection. @@ -127,7 +128,7 @@ type deleteProtectionRequest struct { func (h *Handler) handleDeleteProtection(ctx context.Context, body []byte) error { var req deleteProtectionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -159,7 +160,7 @@ type listProtectionsRequest struct { func (h *Handler) handleListProtections(body []byte) ([]byte, error) { var req listProtectionsRequest if len(body) > 0 { - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } } @@ -183,7 +184,7 @@ func (h *Handler) handleListProtections(body []byte) ([]byte, error) { } if start >= len(protections) { - return json.Marshal(map[string]any{"Protections": []map[string]any{}}) + return json.Marshal(map[string]any{"Protections": []map[string]any{}}, jsonx.V1()) } end := start + maxResults @@ -210,7 +211,7 @@ func (h *Handler) handleListProtections(body []byte) ([]byte, error) { resp["NextToken"] = nextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // protectionMatchesFilters returns true if p passes all inclusion filter sets. diff --git a/services/shield/handler_protections_test.go b/services/shield/handler_protections_test.go index 1ac0442782..eac841da60 100644 --- a/services/shield/handler_protections_test.go +++ b/services/shield/handler_protections_test.go @@ -2,7 +2,7 @@ package shield_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -78,7 +79,7 @@ func TestHandler_CreateProtection(t *testing.T) { if tt.wantID { var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.NotEmpty(t, result["ProtectionId"]) } }) @@ -156,7 +157,7 @@ func TestHandler_DescribeProtection(t *testing.T) { if tt.wantName != "" { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) prot, ok := result["Protection"].(map[string]any) require.True(t, ok) assert.Equal(t, tt.wantName, prot["Name"]) @@ -256,7 +257,7 @@ func TestHandler_ListProtections(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) protections, ok := result["Protections"].([]any) require.True(t, ok) @@ -285,7 +286,7 @@ func TestHandler_ListProtectionsOpaquePageToken(t *testing.T) { NextToken string `json:"NextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.NotEmpty(t, out.NextToken) offset := opaqueOffset(t, out.NextToken) @@ -319,7 +320,7 @@ func TestHandler_ApplyProtectionFiltersNoBacking(t *testing.T) { Protections []map[string]any `json:"Protections"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtered)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &filtered, jsonx.V1())) assert.Len(t, filtered.Protections, 1) // Unfiltered list must still return all three. @@ -330,7 +331,7 @@ func TestHandler_ApplyProtectionFiltersNoBacking(t *testing.T) { Protections []map[string]any `json:"Protections"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &all)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &all, jsonx.V1())) assert.Len(t, all.Protections, 3) } @@ -351,7 +352,7 @@ func TestHandler_ProtectionResponseIncludesALAR(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prot := resp["Protection"].(map[string]any) alarRaw, ok := prot["ApplicationLayerAutomaticResponseConfiguration"] @@ -378,7 +379,7 @@ func TestHandler_ProtectionWithoutALARHasNoConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prot := resp["Protection"].(map[string]any) _, hasALAR := prot["ApplicationLayerAutomaticResponseConfiguration"] @@ -410,7 +411,7 @@ func TestHandler_ListProtectionsPagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prots := resp["Protections"].([]any) assert.Len(t, prots, 2) @@ -445,7 +446,7 @@ func TestHandler_ListProtectionsDefaultMaxResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prots := resp["Protections"].([]any) assert.Len(t, prots, 20, "omitted MaxResults must default to 20 per the documented default") @@ -475,7 +476,7 @@ func TestHandler_ListProtectionsNextPage(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) token := resp1["NextToken"].(string) // Second page. @@ -486,7 +487,7 @@ func TestHandler_ListProtectionsNextPage(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) prots2 := resp2["Protections"].([]any) assert.Len(t, prots2, 2) @@ -516,7 +517,7 @@ func TestHandler_ListProtectionsInclusionFilterByARN(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prots := resp["Protections"].([]any) assert.Len(t, prots, 2) @@ -542,7 +543,7 @@ func TestHandler_ListProtectionsInclusionFilterByName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prots := resp["Protections"].([]any) assert.Len(t, prots, 1) @@ -643,7 +644,7 @@ func TestHandler_ALARInListProtections(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) prots := resp["Protections"].([]any) require.Len(t, prots, 1) diff --git a/services/shield/handler_subscription.go b/services/shield/handler_subscription.go index d7d81351a6..af2c76d01b 100644 --- a/services/shield/handler_subscription.go +++ b/services/shield/handler_subscription.go @@ -2,12 +2,13 @@ package shield import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -91,7 +92,7 @@ func (h *Handler) handleDescribeSubscription() ([]byte, error) { "Limits": subscriptionResourceLimits(), "SubscriptionLimits": subscriptionLimits(), }, - }) + }, jsonx.V1()) } // updateSubscriptionRequest is the request body for UpdateSubscription. @@ -101,7 +102,7 @@ type updateSubscriptionRequest struct { func (h *Handler) handleUpdateSubscription(body []byte) error { var req updateSubscriptionRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -117,7 +118,7 @@ func (h *Handler) handleGetSubscriptionState() ([]byte, error) { return json.Marshal(map[string]string{ "SubscriptionState": state, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteSubscription() error { diff --git a/services/shield/handler_subscription_test.go b/services/shield/handler_subscription_test.go index 5ed4973029..bd4722540b 100644 --- a/services/shield/handler_subscription_test.go +++ b/services/shield/handler_subscription_test.go @@ -1,7 +1,7 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -79,7 +80,7 @@ func TestHandler_DescribeSubscription(t *testing.T) { if tt.wantField != "" { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Contains(t, result, tt.wantField) } }) @@ -102,7 +103,7 @@ func TestHandler_DescribeSubscription_TimeCommitmentInSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) sub, ok := result["Subscription"].(map[string]any) require.True(t, ok, "response must include a Subscription object") @@ -148,7 +149,7 @@ func TestHandler_GetSubscriptionState(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, tt.wantState, result["SubscriptionState"]) }) } @@ -202,7 +203,7 @@ func TestHandler_DescribeSubscriptionFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sub := resp["Subscription"].(map[string]any) assert.NotNil(t, sub["ProactiveEngagementStatus"]) @@ -233,7 +234,7 @@ func TestHandler_SubscriptionLimitsStructure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sub := resp["Subscription"].(map[string]any) limits := sub["SubscriptionLimits"].(map[string]any) @@ -255,7 +256,7 @@ func TestHandler_SubscriptionTimestampIsFloat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) sub := raw["Subscription"].(map[string]any) st, ok := sub["StartTime"].(float64) @@ -275,7 +276,7 @@ func TestHandler_SubscriptionArnFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sub := resp["Subscription"].(map[string]any) arn := sub["SubscriptionArn"].(string) @@ -397,7 +398,7 @@ func TestHandler_UpdateSubscriptionOmitAutoRenewPreservesExistingValue(t *testin require.Equal(t, 200, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) sub, ok := result["Subscription"].(map[string]any) require.True(t, ok) assert.Equal(t, shield.AutoRenewDisabled, sub["AutoRenew"]) @@ -415,7 +416,7 @@ func TestHandler_DescribeSubscriptionIncludesArn(t *testing.T) { require.Equal(t, 200, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) sub, ok := result["Subscription"].(map[string]any) require.True(t, ok) assert.Contains(t, sub, "SubscriptionArn") diff --git a/services/shield/handler_tags.go b/services/shield/handler_tags.go index fa905d7621..919ef508f9 100644 --- a/services/shield/handler_tags.go +++ b/services/shield/handler_tags.go @@ -1,9 +1,10 @@ package shield import ( - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -15,7 +16,7 @@ type tagResourceRequest struct { func (h *Handler) handleTagResource(body []byte) error { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -37,7 +38,7 @@ type listTagsForResourceRequest struct { func (h *Handler) handleListTagsForResource(body []byte) ([]byte, error) { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -52,7 +53,7 @@ func (h *Handler) handleListTagsForResource(body []byte) ([]byte, error) { return json.Marshal(map[string]any{ "Tags": tags.MapToKV(tagsMap), - }) + }, jsonx.V1()) } // untagResourceRequest is the request body for UntagResource. @@ -63,7 +64,7 @@ type untagResourceRequest struct { func (h *Handler) handleUntagResource(body []byte) error { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/shield/handler_test.go b/services/shield/handler_test.go index bb33f0b518..d39c01996d 100644 --- a/services/shield/handler_test.go +++ b/services/shield/handler_test.go @@ -3,7 +3,7 @@ package shield_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/shield" ) @@ -59,7 +60,7 @@ func doShieldRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -89,7 +90,7 @@ func opaqueOffset(t *testing.T, token string) int { O int `json:"o"` } - require.NoError(t, json.Unmarshal(raw, &obj), "token must be JSON with 'o' key") + require.NoError(t, json.Unmarshal(raw, &obj, jsonx.V1()), "token must be JSON with 'o' key") return obj.O } diff --git a/services/shield/persistence.go b/services/shield/persistence.go index 2754518c3d..265d12b363 100644 --- a/services/shield/persistence.go +++ b/services/shield/persistence.go @@ -2,7 +2,7 @@ package shield import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -41,15 +41,15 @@ type alarConfigDTO struct { // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Subscription *Subscription `json:"subscription,omitempty"` - DRTAccess *DRTAccess `json:"drtAccess,omitempty"` - AccountID string `json:"accountID"` - Region string `json:"region"` - ProactiveEngagementStatus string `json:"proactiveEngagementStatus,omitempty"` - ALARConfigs []alarConfigDTO `json:"alarConfigs,omitempty"` - EmergencyContacts []EmergencyContact `json:"emergencyContacts,omitempty"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Subscription *Subscription `json:"subscription,omitempty"` + DRTAccess *DRTAccess `json:"drtAccess,omitempty"` + AccountID string `json:"accountID"` + Region string `json:"region"` + ProactiveEngagementStatus string `json:"proactiveEngagementStatus,omitempty"` + ALARConfigs []alarConfigDTO `json:"alarConfigs,omitempty"` + EmergencyContacts []EmergencyContact `json:"emergencyContacts,omitempty"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/shield/request_validation_test.go b/services/shield/request_validation_test.go index cca6e7183e..7fd4065c56 100644 --- a/services/shield/request_validation_test.go +++ b/services/shield/request_validation_test.go @@ -1,13 +1,15 @@ package shield_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_RequestValidation(t *testing.T) { @@ -158,7 +160,7 @@ func TestHandler_RequestValidation(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantCode, out.Type) assert.Contains(t, out.Message, tt.wantMsg) }) @@ -215,7 +217,7 @@ func TestHandler_ErrorMessagesOmitCode(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantCode, out.Type) assert.NotContains(t, out.Message, tt.wantCode) assert.NotContains(t, out.Message, "invalid request:") diff --git a/services/signin/handler.go b/services/signin/handler.go index 58f0f5f6b4..128357d7d2 100644 --- a/services/signin/handler.go +++ b/services/signin/handler.go @@ -2,7 +2,7 @@ package signin import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strconv" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -310,7 +311,7 @@ func decode(body []byte, v any) error { return nil } - if err := json.Unmarshal(body, v); err != nil { + if err := json.Unmarshal(body, v, jsonx.V1()); err != nil { return validation("malformed request body") } diff --git a/services/signin/persistence.go b/services/signin/persistence.go index 119ed24a6c..313a23815a 100644 --- a/services/signin/persistence.go +++ b/services/signin/persistence.go @@ -3,7 +3,7 @@ package signin import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -14,11 +14,11 @@ import ( const signinSnapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - SigningKey string `json:"signingKey"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + SigningKey string `json:"signingKey"` + Version int `json:"version"` } // Snapshot serialises the backend state to JSON. diff --git a/services/signin/tokens.go b/services/signin/tokens.go index c5dbefb04b..1ffae56faf 100644 --- a/services/signin/tokens.go +++ b/services/signin/tokens.go @@ -7,7 +7,7 @@ import ( "crypto/sha256" "crypto/subtle" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/url" "strings" "time" @@ -15,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -107,7 +108,7 @@ func (b *InMemoryBackend) sign(payload string) string { func (b *InMemoryBackend) makeJWT(claims map[string]any) string { enc := base64.RawURLEncoding head := enc.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) - raw, _ := json.Marshal(claims) + raw, _ := json.Marshal(claims, jsonx.V1()) body := enc.EncodeToString(raw) return head + "." + body + "." + b.sign(head+"."+body) @@ -133,7 +134,7 @@ func (b *InMemoryBackend) parseJWT(tok string) (string, bool) { JTI string `json:"jti"` } - if json.Unmarshal(raw, &claims) != nil || claims.JTI == "" { + if json.Unmarshal(raw, &claims, jsonx.V1()) != nil || claims.JTI == "" { return "", false } diff --git a/services/sns/archive.go b/services/sns/archive.go index 722bafa24e..b018033976 100644 --- a/services/sns/archive.go +++ b/services/sns/archive.go @@ -1,12 +1,13 @@ package sns import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "time" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseReplayFromTimestamp parses the replayFromTimestamp field from a ReplayPolicy JSON string. @@ -20,7 +21,7 @@ func parseReplayFromTimestamp(replayPolicy string) (time.Time, error) { ReplayFromTimestamp string `json:"replayFromTimestamp"` } - if err := json.Unmarshal([]byte(replayPolicy), &p); err != nil { + if err := json.Unmarshal([]byte(replayPolicy), &p, jsonx.V1()); err != nil { return time.Time{}, fmt.Errorf( "%w: ReplayPolicy is not valid JSON: %s", ErrInvalidParameter, diff --git a/services/sns/archive_test.go b/services/sns/archive_test.go index 61e4e3a5a0..0383673c3d 100644 --- a/services/sns/archive_test.go +++ b/services/sns/archive_test.go @@ -1,7 +1,7 @@ package sns_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -228,7 +229,7 @@ func TestReplayPolicyTriggersLambdaReplay(t *testing.T) { // Verify all archived messages were replayed, in original publish order. for i, invocation := range lambda.All() { var envelope map[string]any - require.NoError(t, json.Unmarshal(invocation.Payload, &envelope)) + require.NoError(t, json.Unmarshal(invocation.Payload, &envelope, jsonx.V1())) records, _ := envelope["Records"].([]any) require.Len(t, records, 1) record, _ := records[0].(map[string]any) @@ -316,7 +317,7 @@ func TestReplayPolicyDeliversToA2AProtocols(t *testing.T) { require.Equal(t, 1, lambda.Count(), "lambda function was never invoked") var envelope map[string]any - require.NoError(t, json.Unmarshal(lambda.Last().Payload, &envelope)) + require.NoError(t, json.Unmarshal(lambda.Last().Payload, &envelope, jsonx.V1())) records, _ := envelope["Records"].([]any) require.Len(t, records, 1) record, _ := records[0].(map[string]any) @@ -345,7 +346,7 @@ func TestReplayPolicyDeliversToA2AProtocols(t *testing.T) { require.Len(t, firehose.RecordsFor(streamName), 1, "firehose stream received no record") var envelope map[string]any - require.NoError(t, json.Unmarshal(firehose.RecordsFor(streamName)[0], &envelope)) + require.NoError(t, json.Unmarshal(firehose.RecordsFor(streamName)[0], &envelope, jsonx.V1())) assert.Equal(t, archivedMessage, envelope["Message"]) }, } diff --git a/services/sns/confirmation.go b/services/sns/confirmation.go index 67ecd49aa3..2807314728 100644 --- a/services/sns/confirmation.go +++ b/services/sns/confirmation.go @@ -5,13 +5,15 @@ import ( "context" "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "strings" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -101,7 +103,7 @@ func (b *InMemoryBackend) deliverSubscriptionConfirmation( SignatureVersion: version, Signature: b.signer.signWithVersion(canonical, version), SigningCertURL: certURL, - }) + }, jsonx.V1()) if err != nil { return } diff --git a/services/sns/delivery.go b/services/sns/delivery.go index 528e3de581..4ee786458b 100644 --- a/services/sns/delivery.go +++ b/services/sns/delivery.go @@ -2,11 +2,12 @@ package sns import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -16,7 +17,7 @@ func parseTopicEffectivePolicy(topicEffectivePolicy, protocol string) *int { NumRetries *int `json:"numRetries"` } `json:"defaultHealthyRetryPolicy"` } - if err := json.Unmarshal([]byte(topicEffectivePolicy), &tp); err == nil { + if err := json.Unmarshal([]byte(topicEffectivePolicy), &tp, jsonx.V1()); err == nil { pKey := protocol if pKey == protocolHTTPS { pKey = protocolHTTP @@ -35,7 +36,7 @@ func parseSubPolicy(subPolicy string) *int { NumRetries *int `json:"numRetries"` } `json:"healthyRetryPolicy"` } - if err := json.Unmarshal([]byte(subPolicy), &sp); err == nil { + if err := json.Unmarshal([]byte(subPolicy), &sp, jsonx.V1()); err == nil { return sp.HealthyRetryPolicy.NumRetries } @@ -183,7 +184,7 @@ func buildHTTPDeliveryPayload(d httpDelivery) string { env.Subject = d.subject } - if enc, err := json.Marshal(env); err == nil { + if enc, err := json.Marshal(env, jsonx.V1()); err == nil { body = string(enc) } } @@ -256,7 +257,7 @@ func sendSubscriptionDLQ(ctx context.Context, d httpDelivery) { DeadLetterTargetArn string `json:"deadLetterTargetArn"` } - if err := json.Unmarshal([]byte(d.redrivePolicy), &policy); err != nil { + if err := json.Unmarshal([]byte(d.redrivePolicy), &policy, jsonx.V1()); err != nil { return } diff --git a/services/sns/delivery_test.go b/services/sns/delivery_test.go index b22939b1cb..86399dfd39 100644 --- a/services/sns/delivery_test.go +++ b/services/sns/delivery_test.go @@ -3,7 +3,7 @@ package sns_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "sync" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -259,7 +260,7 @@ func TestHTTPDeliveryFailureSendsToDLQ(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": dlqARN, - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err) diff --git a/services/sns/filter_match.go b/services/sns/filter_match.go index 01962ba4ec..e13f5a25ef 100644 --- a/services/sns/filter_match.go +++ b/services/sns/filter_match.go @@ -1,10 +1,13 @@ package sns import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // matchesFilterPolicyMessageBody evaluates a parsed filter policy against the @@ -15,8 +18,8 @@ func matchesFilterPolicyMessageBody(policy parsedFilterPolicy, message string) b return true } - var body map[string]json.RawMessage - if err := json.Unmarshal([]byte(message), &body); err != nil { + var body map[string]jsontext.Value + if err := json.Unmarshal([]byte(message), &body, jsonx.V1()); err != nil { return false } @@ -42,9 +45,9 @@ func matchesFilterPolicyMessageBody(policy parsedFilterPolicy, message string) b // are expanded so the condition is satisfied when ANY element matches, mirroring // AWS message-body array handling. func matchesBodyKeyConditions( - body map[string]json.RawMessage, + body map[string]jsontext.Value, key string, - conditions []json.RawMessage, + conditions []jsontext.Value, ) bool { if subPolicy, nested := nestedSubPolicy(conditions); nested { return matchesNestedBody(body[key], subPolicy) @@ -66,7 +69,7 @@ func matchesBodyKeyConditions( // matchesNestedBody applies a nested sub-policy to an object value, or to any // element when the value is an array of objects. -func matchesNestedBody(raw json.RawMessage, subPolicyJSON string) bool { +func matchesNestedBody(raw jsontext.Value, subPolicyJSON string) bool { if len(raw) == 0 { return false } @@ -76,8 +79,8 @@ func matchesNestedBody(raw json.RawMessage, subPolicyJSON string) bool { return false } - var arr []json.RawMessage - if json.Unmarshal(raw, &arr) == nil { + var arr []jsontext.Value + if json.Unmarshal(raw, &arr, jsonx.V1()) == nil { for _, elem := range arr { if matchesFilterPolicyMessageBody(sub, string(elem)) { return true @@ -94,13 +97,13 @@ func matchesNestedBody(raw json.RawMessage, subPolicyJSON string) bool { // body value: a string, number, or boolean yields one candidate; a JSON array of // scalars yields one candidate per element. A value that cannot be reduced to a // scalar yields no candidates (the key is then treated as non-matching). -func bodyMatchValues(raw json.RawMessage) []string { +func bodyMatchValues(raw jsontext.Value) []string { if v, ok := scalarBodyValue(raw); ok { return []string{v} } - var arr []json.RawMessage - if err := json.Unmarshal(raw, &arr); err == nil { + var arr []jsontext.Value + if err := json.Unmarshal(raw, &arr, jsonx.V1()); err == nil { out := make([]string, 0, len(arr)) for _, elem := range arr { if v, ok := scalarBodyValue(elem); ok { @@ -116,19 +119,19 @@ func bodyMatchValues(raw json.RawMessage) []string { // scalarBodyValue converts a single JSON scalar (string, number, or boolean) to // its string form for filter matching. It reports false for non-scalar values. -func scalarBodyValue(raw json.RawMessage) (string, bool) { +func scalarBodyValue(raw jsontext.Value) (string, bool) { var s string - if err := json.Unmarshal(raw, &s); err == nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err == nil { return s, true } - var n json.Number - if err := json.Unmarshal(raw, &n); err == nil { + var n jsonx.Number + if err := json.Unmarshal(raw, &n, jsonx.V1()); err == nil { return n.String(), true } var b bool - if err := json.Unmarshal(raw, &b); err == nil { + if err := json.Unmarshal(raw, &b, jsonx.V1()); err == nil { return strconv.FormatBool(b), true } @@ -137,7 +140,7 @@ func scalarBodyValue(raw json.RawMessage) (string, bool) { // matchesOrBody evaluates a recognised "$or" operator against a JSON message // body: it returns true when AT LEAST ONE sub-policy fully matches. -func matchesOrBody(subPolicies []json.RawMessage, message string) bool { +func matchesOrBody(subPolicies []jsontext.Value, message string) bool { for _, raw := range subPolicies { sub, err := parseFilterPolicy(string(raw)) if err != nil { @@ -183,7 +186,7 @@ func matchesParsedFilterPolicy(policy parsedFilterPolicy, attrs map[string]Messa // matched independently and the condition is satisfied if ANY element matches // (OR across elements), mirroring AWS String.Array handling. func matchesAttributeConditions( - key string, conditions []json.RawMessage, attrs map[string]MessageAttribute, + key string, conditions []jsontext.Value, attrs map[string]MessageAttribute, ) bool { attr, attrExists := attrs[key] @@ -208,7 +211,7 @@ func attributeMatchValues(attr MessageAttribute, attrExists bool) []string { if attr.DataType == "String.Array" { var elems []string - if err := json.Unmarshal([]byte(attr.StringValue), &elems); err == nil && len(elems) > 0 { + if err := json.Unmarshal([]byte(attr.StringValue), &elems, jsonx.V1()); err == nil && len(elems) > 0 { return elems } } @@ -218,7 +221,7 @@ func attributeMatchValues(attr MessageAttribute, attrExists bool) []string { // matchesOrAttributes evaluates a recognised "$or" operator against message // attributes: it returns true when AT LEAST ONE sub-policy fully matches. -func matchesOrAttributes(subPolicies []json.RawMessage, attrs map[string]MessageAttribute) bool { +func matchesOrAttributes(subPolicies []jsontext.Value, attrs map[string]MessageAttribute) bool { for _, raw := range subPolicies { sub, err := parseFilterPolicy(string(raw)) if err != nil { @@ -236,7 +239,7 @@ func matchesOrAttributes(subPolicies []json.RawMessage, attrs map[string]Message // matchObjectCondition evaluates a single JSON-object SNS filter condition such as // {"prefix": "order-"}, {"suffix": ".jpg"}, {"anything-but": [...]}, // {"equals-ignore-case": "OrderId"}, {"exists": true}, or {"numeric": [">", 0]}. -func matchObjectCondition(value string, attrExists bool, obj map[string]json.RawMessage) bool { +func matchObjectCondition(value string, attrExists bool, obj map[string]jsontext.Value) bool { // String-operand operators share the same shape: decode a single string // operand and apply a predicate. They require the attribute to exist. stringOps := map[string]func(value, operand string) bool{ @@ -254,7 +257,7 @@ func matchObjectCondition(value string, attrExists bool, obj map[string]json.Raw } var operand string - if err := json.Unmarshal(raw, &operand); err != nil { + if err := json.Unmarshal(raw, &operand, jsonx.V1()); err != nil { return false } @@ -263,7 +266,7 @@ func matchObjectCondition(value string, attrExists bool, obj map[string]json.Raw if existsRaw, ok := obj["exists"]; ok { var existsVal bool - if err := json.Unmarshal(existsRaw, &existsVal); err == nil { + if err := json.Unmarshal(existsRaw, &existsVal, jsonx.V1()); err == nil { return attrExists == existsVal } @@ -355,32 +358,32 @@ func matchCIDR(value, cidr string) bool { // // In every case the operator is satisfied only when the attribute exists and the // value does NOT match the negated condition. -func matchAnythingBut(value string, attrExists bool, raw json.RawMessage) bool { +func matchAnythingBut(value string, attrExists bool, raw jsontext.Value) bool { if !attrExists { return false } // Try as string literal. var s string - if errStr := json.Unmarshal(raw, &s); errStr == nil { + if errStr := json.Unmarshal(raw, &s, jsonx.V1()); errStr == nil { return value != s } // Try as number literal. - var n json.Number - if errNum := json.Unmarshal(raw, &n); errNum == nil { + var n jsonx.Number + if errNum := json.Unmarshal(raw, &n, jsonx.V1()); errNum == nil { return value != n.String() } // Try as array of literals. - var arr []json.RawMessage - if errArr := json.Unmarshal(raw, &arr); errArr == nil { + var arr []jsontext.Value + if errArr := json.Unmarshal(raw, &arr, jsonx.V1()); errArr == nil { return matchAnythingButArray(value, arr) } // Try as nested operator object: {"anything-but": {"prefix"|"suffix"|...: ...}}. - var obj map[string]json.RawMessage - if errObj := json.Unmarshal(raw, &obj); errObj == nil { + var obj map[string]jsontext.Value + if errObj := json.Unmarshal(raw, &obj, jsonx.V1()); errObj == nil { return matchAnythingButObject(value, obj) } @@ -390,10 +393,10 @@ func matchAnythingBut(value string, attrExists bool, raw json.RawMessage) bool { // matchAnythingButObject negates a nested string operator inside an // "anything-but" condition. It returns true when the value does NOT satisfy the // inner operator. -func matchAnythingButObject(value string, obj map[string]json.RawMessage) bool { +func matchAnythingButObject(value string, obj map[string]jsontext.Value) bool { if prefixRaw, ok := obj["prefix"]; ok { var prefix string - if err := json.Unmarshal(prefixRaw, &prefix); err == nil { + if err := json.Unmarshal(prefixRaw, &prefix, jsonx.V1()); err == nil { return !strings.HasPrefix(value, prefix) } @@ -402,7 +405,7 @@ func matchAnythingButObject(value string, obj map[string]json.RawMessage) bool { if suffixRaw, ok := obj["suffix"]; ok { var suffix string - if err := json.Unmarshal(suffixRaw, &suffix); err == nil { + if err := json.Unmarshal(suffixRaw, &suffix, jsonx.V1()); err == nil { return !strings.HasSuffix(value, suffix) } @@ -411,7 +414,7 @@ func matchAnythingButObject(value string, obj map[string]json.RawMessage) bool { if eqICaseRaw, ok := obj["equals-ignore-case"]; ok { var want string - if err := json.Unmarshal(eqICaseRaw, &want); err == nil { + if err := json.Unmarshal(eqICaseRaw, &want, jsonx.V1()); err == nil { return !strings.EqualFold(value, want) } @@ -420,7 +423,7 @@ func matchAnythingButObject(value string, obj map[string]json.RawMessage) bool { if wildcardRaw, ok := obj["wildcard"]; ok { var pattern string - if err := json.Unmarshal(wildcardRaw, &pattern); err == nil { + if err := json.Unmarshal(wildcardRaw, &pattern, jsonx.V1()); err == nil { return !matchWildcard(value, pattern) } @@ -431,10 +434,10 @@ func matchAnythingButObject(value string, obj map[string]json.RawMessage) bool { } // matchAnythingButArray checks that value does not equal any element in the "anything-but" array. -func matchAnythingButArray(value string, arr []json.RawMessage) bool { +func matchAnythingButArray(value string, arr []jsontext.Value) bool { for _, item := range arr { var sv string - if errI := json.Unmarshal(item, &sv); errI == nil { + if errI := json.Unmarshal(item, &sv, jsonx.V1()); errI == nil { if value == sv { return false } @@ -442,8 +445,8 @@ func matchAnythingButArray(value string, arr []json.RawMessage) bool { continue } - var nv json.Number - if errN := json.Unmarshal(item, &nv); errN == nil && value == nv.String() { + var nv jsonx.Number + if errN := json.Unmarshal(item, &nv, jsonx.V1()); errN == nil && value == nv.String() { return false } } @@ -453,25 +456,25 @@ func matchAnythingButArray(value string, arr []json.RawMessage) bool { // matchNumericCondition evaluates {"numeric": [op, num, ...]} conditions. // Conditions are pairs of [operator, number] and ALL pairs must be satisfied (AND semantics). -func matchNumericCondition(value string, raw json.RawMessage) bool { +func matchNumericCondition(value string, raw jsontext.Value) bool { valFloat, errParse := strconv.ParseFloat(value, 64) if errParse != nil { return false } - var conditions []json.RawMessage - if errUnm := json.Unmarshal(raw, &conditions); errUnm != nil { + var conditions []jsontext.Value + if errUnm := json.Unmarshal(raw, &conditions, jsonx.V1()); errUnm != nil { return false } for i := 0; i+1 < len(conditions); i += 2 { var op string - if errOp := json.Unmarshal(conditions[i], &op); errOp != nil { + if errOp := json.Unmarshal(conditions[i], &op, jsonx.V1()); errOp != nil { return false } - var num json.Number - if errNum := json.Unmarshal(conditions[i+1], &num); errNum != nil { + var num jsonx.Number + if errNum := json.Unmarshal(conditions[i+1], &num, jsonx.V1()); errNum != nil { return false } @@ -509,22 +512,22 @@ func numericOpMatches(op string, value, threshold float64) bool { } } -func matchCondition(value string, attrExists bool, raw json.RawMessage) bool { +func matchCondition(value string, attrExists bool, raw jsontext.Value) bool { // Object conditions: prefix, exists, anything-but, numeric. - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err == nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err == nil { return matchObjectCondition(value, attrExists, obj) } // String exact match — attribute must exist. var s string - if err := json.Unmarshal(raw, &s); err == nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err == nil { return attrExists && value == s } // Number exact match — attribute must exist. - var n json.Number - if err := json.Unmarshal(raw, &n); err == nil { + var n jsonx.Number + if err := json.Unmarshal(raw, &n, jsonx.V1()); err == nil { return attrExists && value == n.String() } @@ -532,7 +535,7 @@ func matchCondition(value string, attrExists bool, raw json.RawMessage) bool { } // matchesConditions returns true if value/existence satisfies at least one condition in the list. -func matchesConditions(value string, attrExists bool, conditions []json.RawMessage) bool { +func matchesConditions(value string, attrExists bool, conditions []jsontext.Value) bool { for _, raw := range conditions { if matchCondition(value, attrExists, raw) { return true diff --git a/services/sns/filter_match_test.go b/services/sns/filter_match_test.go index 5703f82e93..ecda758620 100644 --- a/services/sns/filter_match_test.go +++ b/services/sns/filter_match_test.go @@ -1,7 +1,7 @@ package sns_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -46,7 +47,7 @@ func TestFilterPolicyAnythingButMatchDelivery(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "blue-msg", env.Message) } case <-time.After(2 * time.Second): @@ -127,7 +128,7 @@ func TestFilterPolicyPrefixMatch(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "order-msg", env.Message) } case <-time.After(2 * time.Second): @@ -138,7 +139,7 @@ func TestFilterPolicyPrefixMatch(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { t.Fatalf("non-matching message was delivered: %s", env.Message) } case <-time.After(300 * time.Millisecond): @@ -175,7 +176,7 @@ func TestFilterPolicySuffixMatch(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "log-msg", env.Message) } case <-time.After(2 * time.Second): @@ -212,7 +213,7 @@ func TestFilterPolicyEqualsIgnoreCase(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "prod-msg", env.Message) } case <-time.After(2 * time.Second): diff --git a/services/sns/filter_policy.go b/services/sns/filter_policy.go index 4346c69d49..1b5700f154 100644 --- a/services/sns/filter_policy.go +++ b/services/sns/filter_policy.go @@ -1,11 +1,14 @@ package sns import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // parseFilterPolicy parses and validates a FilterPolicy JSON string. It returns @@ -46,8 +49,8 @@ func parseFilterPolicy(filterPolicy string) (parsedFilterPolicy, error) { ) } - var rawPolicy map[string]json.RawMessage - if err := json.Unmarshal([]byte(filterPolicy), &rawPolicy); err != nil { + var rawPolicy map[string]jsontext.Value + if err := json.Unmarshal([]byte(filterPolicy), &rawPolicy, jsonx.V1()); err != nil { return nil, fmt.Errorf( "%w: FilterPolicy is not valid JSON: %s", ErrInvalidParameter, @@ -89,15 +92,15 @@ const nestedPolicyKey = "$nestedPolicy" // parseFilterPolicyKey validates one top-level FilterPolicy key and returns its // stored conditions plus how many count toward the total-condition budget. -func parseFilterPolicyKey(key string, rawConditions json.RawMessage) ([]json.RawMessage, int, error) { +func parseFilterPolicyKey(key string, rawConditions jsontext.Value) ([]jsontext.Value, int, error) { if isNestedPolicyValue(rawConditions) { nested, err := parseNestedFilterPolicy(key, rawConditions) return nested, 0, err } - var conditions []json.RawMessage - if err := json.Unmarshal(rawConditions, &conditions); err != nil { + var conditions []jsontext.Value + if err := json.Unmarshal(rawConditions, &conditions, jsonx.V1()); err != nil { return nil, 0, fmt.Errorf( "%w: FilterPolicy attribute %q must be a JSON array", ErrInvalidParameter, key, @@ -119,7 +122,7 @@ func parseFilterPolicyKey(key string, rawConditions json.RawMessage) ([]json.Raw return conditions, len(conditions), validateConditionShapes(key, conditions) } -func isNestedPolicyValue(raw json.RawMessage) bool { +func isNestedPolicyValue(raw jsontext.Value) bool { return strings.HasPrefix(strings.TrimSpace(string(raw)), "{") } @@ -134,27 +137,27 @@ func checkConditionBudget(total int) error { return nil } -func parseNestedFilterPolicy(key string, raw json.RawMessage) ([]json.RawMessage, error) { +func parseNestedFilterPolicy(key string, raw jsontext.Value) ([]jsontext.Value, error) { if _, err := parseFilterPolicy(string(raw)); err != nil { return nil, fmt.Errorf("%w (nested key %q)", err, key) } - wrapped, err := json.Marshal(map[string]json.RawMessage{nestedPolicyKey: raw}) + wrapped, err := json.Marshal(map[string]jsontext.Value{nestedPolicyKey: raw}, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: FilterPolicy attribute %q: %s", ErrInvalidParameter, key, err.Error()) } - return []json.RawMessage{wrapped}, nil + return []jsontext.Value{wrapped}, nil } // nestedSubPolicy returns the nested sub-policy JSON stored by parseNestedFilterPolicy. -func nestedSubPolicy(conditions []json.RawMessage) (string, bool) { +func nestedSubPolicy(conditions []jsontext.Value) (string, bool) { if len(conditions) != 1 { return "", false } - var wrapper map[string]json.RawMessage - if err := json.Unmarshal(conditions[0], &wrapper); err != nil { + var wrapper map[string]jsontext.Value + if err := json.Unmarshal(conditions[0], &wrapper, jsonx.V1()); err != nil { return "", false } @@ -177,14 +180,14 @@ const minOrOperatorElems = 2 // - no element uses a reserved operator keyword as a top-level field name. // // When any rule is violated, AWS treats "$or" as an ordinary attribute key. -func isRecognisedOrOperator(elems []json.RawMessage) bool { +func isRecognisedOrOperator(elems []jsontext.Value) bool { if len(elems) < minOrOperatorElems { return false } for _, elem := range elems { - var obj map[string]json.RawMessage - if err := json.Unmarshal(elem, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(elem, &obj, jsonx.V1()); err != nil { return false } @@ -206,7 +209,7 @@ func isRecognisedOrOperator(elems []json.RawMessage) bool { // array, rejecting malformed operators or numeric operands. It returns the total // number of conditions contained across all sub-policies so the caller can // enforce the global condition cap. -func validateOrSubPolicies(elems []json.RawMessage) (int, error) { +func validateOrSubPolicies(elems []jsontext.Value) (int, error) { total := 0 for _, elem := range elems { @@ -244,10 +247,10 @@ var knownFilterPolicyOperators = map[string]struct{}{ // attribute and rejects unknown operator names and malformed numeric operand // structures. Scalar conditions (plain strings, numbers, booleans, null) and // known object operators are tolerated as-is and matched lazily at evaluation. -func validateConditionShapes(key string, conditions []json.RawMessage) error { +func validateConditionShapes(key string, conditions []jsontext.Value) error { for _, raw := range conditions { - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil { // Scalar conditions (e.g. plain strings) are valid; skip object-only checks. continue } @@ -281,9 +284,9 @@ func validateConditionShapes(key string, conditions []json.RawMessage) error { // containing a valid IPv4/IPv6 address (bare host route) or CIDR block, // rejecting it eagerly at Subscribe/SetSubscriptionAttributes time rather // than letting it silently never match at evaluation (matchCIDR). -func validateCIDROperand(key string, raw json.RawMessage) error { +func validateCIDROperand(key string, raw jsontext.Value) error { var operand string - if err := json.Unmarshal(raw, &operand); err != nil { + if err := json.Unmarshal(raw, &operand, jsonx.V1()); err != nil { return fmt.Errorf( "%w: FilterPolicy attribute %q cidr operand must be a string", ErrInvalidParameter, key, @@ -313,9 +316,9 @@ func validateCIDROperand(key string, raw json.RawMessage) error { // validateNumericOperands enforces that a "numeric" condition operand is a JSON // array of even length where each pair is (operator-string, number). -func validateNumericOperands(key string, raw json.RawMessage) error { - var operands []json.RawMessage - if err := json.Unmarshal(raw, &operands); err != nil { +func validateNumericOperands(key string, raw jsontext.Value) error { + var operands []jsontext.Value + if err := json.Unmarshal(raw, &operands, jsonx.V1()); err != nil { return fmt.Errorf( "%w: FilterPolicy attribute %q numeric operand must be a JSON array", ErrInvalidParameter, key, @@ -338,7 +341,7 @@ func validateNumericOperands(key string, raw json.RawMessage) error { for i := 0; i+1 < len(operands); i += 2 { var op string - if err := json.Unmarshal(operands[i], &op); err != nil { + if err := json.Unmarshal(operands[i], &op, jsonx.V1()); err != nil { return fmt.Errorf( "%w: FilterPolicy attribute %q numeric operator must be a string", ErrInvalidParameter, key, @@ -352,8 +355,8 @@ func validateNumericOperands(key string, raw json.RawMessage) error { ) } - var num json.Number - if err := json.Unmarshal(operands[i+1], &num); err != nil { + var num jsonx.Number + if err := json.Unmarshal(operands[i+1], &num, jsonx.V1()); err != nil { return fmt.Errorf( "%w: FilterPolicy attribute %q numeric threshold must be a number", ErrInvalidParameter, key, @@ -378,7 +381,7 @@ func validateRedrivePolicy(policy string) error { DeadLetterTargetArn string `json:"deadLetterTargetArn"` } - if err := json.Unmarshal([]byte(policy), &parsed); err != nil { + if err := json.Unmarshal([]byte(policy), &parsed, jsonx.V1()); err != nil { return fmt.Errorf( "%w: RedrivePolicy is not valid JSON: %s", ErrInvalidParameter, @@ -414,7 +417,7 @@ func (b *InMemoryBackend) checkDLQExists(policy string) error { } // Shape is already validated by validateRedrivePolicy, so an unmarshal failure // here cannot happen for a well-formed policy; ignore it and skip verification. - _ = json.Unmarshal([]byte(policy), &parsed) + _ = json.Unmarshal([]byte(policy), &parsed, jsonx.V1()) if parsed.DeadLetterTargetArn == "" { return nil } diff --git a/services/sns/golden_perf_mixed_test.go b/services/sns/golden_perf_mixed_test.go index 45d492c204..013aee3493 100644 --- a/services/sns/golden_perf_mixed_test.go +++ b/services/sns/golden_perf_mixed_test.go @@ -19,7 +19,7 @@ package sns_test // To regenerate: SNS_GOLDEN_UPDATE=1 go test -run TestGolden ./services/sns/... import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -109,7 +110,7 @@ func TestSignature_SharedAcrossHTTPSubscribers(t *testing.T) { select { case body := <-received: var env map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &env)) + require.NoError(t, json.Unmarshal([]byte(body), &env, jsonx.V1())) envelopes = append(envelopes, env) case <-time.After(2 * time.Second): t.Fatal("HTTP delivery did not arrive in time") diff --git a/services/sns/golden_perf_test.go b/services/sns/golden_perf_test.go index cd2963a7d3..d714c753b4 100644 --- a/services/sns/golden_perf_test.go +++ b/services/sns/golden_perf_test.go @@ -12,7 +12,8 @@ package sns_test // To regenerate: SNS_GOLDEN_UPDATE=1 go test -run TestGolden ./services/sns/... import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/url" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -94,7 +96,7 @@ func redactDeliveredEnvelope(t *testing.T, body []byte) []byte { t.Helper() var env map[string]any - require.NoError(t, json.Unmarshal(body, &env)) + require.NoError(t, json.Unmarshal(body, &env, jsonx.V1())) for _, k := range []string{"MessageId", "Timestamp", "Signature", "UnsubscribeURL"} { if _, ok := env[k]; ok { @@ -102,7 +104,7 @@ func redactDeliveredEnvelope(t *testing.T, body []byte) []byte { } } - out, err := json.MarshalIndent(env, "", " ") + out, err := json.Marshal(env, jsonx.V1(), jsontext.Multiline(true), jsontext.WithIndent(" ")) require.NoError(t, err) return out diff --git a/services/sns/handler_test.go b/services/sns/handler_test.go index 1c563a9279..6fbe6392f7 100644 --- a/services/sns/handler_test.go +++ b/services/sns/handler_test.go @@ -2,7 +2,7 @@ package sns_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -70,7 +71,7 @@ func extractSNSHTTPMessage(body string) string { Message string `json:"Message"` Type string `json:"Type"` } - if err := json.Unmarshal([]byte(body), &env); err == nil && env.Type == "Notification" { + if err := json.Unmarshal([]byte(body), &env, jsonx.V1()); err == nil && env.Type == "Notification" { return env.Message } diff --git a/services/sns/lambda_firehose_delivery.go b/services/sns/lambda_firehose_delivery.go index 0d146e9756..e10a0ee433 100644 --- a/services/sns/lambda_firehose_delivery.go +++ b/services/sns/lambda_firehose_delivery.go @@ -2,13 +2,14 @@ package sns import ( "context" - "encoding/json" + "encoding/json/v2" "strings" "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -87,7 +88,7 @@ func buildLambdaPayload( }, } - payload, err := json.Marshal(map[string]any{"Records": []any{record}}) + payload, err := json.Marshal(map[string]any{"Records": []any{record}}, jsonx.V1()) if err != nil { return []byte(`{"Records":[]}`) } @@ -115,7 +116,7 @@ func buildFirehoseEnvelope(ev *events.SNSPublishedEvent, sub events.SNSSubscript env.Subject = ev.Subject } - enc, err := json.Marshal(env) + enc, err := json.Marshal(env, jsonx.V1()) if err != nil { return []byte(sub.Body(ev.Message)) } @@ -357,7 +358,12 @@ func sendLambdaDLQ(ctx context.Context, sender SQSSender, redrivePolicy, body st var policy struct { DeadLetterTargetArn string `json:"deadLetterTargetArn"` } - if err := json.Unmarshal([]byte(redrivePolicy), &policy); err != nil || policy.DeadLetterTargetArn == "" { + if err := json.Unmarshal( + []byte(redrivePolicy), + &policy, + jsonx.V1(), + ); err != nil || + policy.DeadLetterTargetArn == "" { return } _ = sender.SendMessageToQueue(ctx, policy.DeadLetterTargetArn, body) diff --git a/services/sns/lambda_firehose_delivery_test.go b/services/sns/lambda_firehose_delivery_test.go index e901472d7a..56e380f843 100644 --- a/services/sns/lambda_firehose_delivery_test.go +++ b/services/sns/lambda_firehose_delivery_test.go @@ -2,7 +2,7 @@ package sns_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/url" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -149,7 +150,7 @@ func TestSNS_LambdaDelivery_InvokesFunction(t *testing.T) { // Verify payload is valid AWS SNS → Lambda envelope. var envelope map[string]any - err = json.Unmarshal(inv.Payload, &envelope) + err = json.Unmarshal(inv.Payload, &envelope, jsonx.V1()) require.NoError(t, err) records, ok := envelope["Records"].([]any) @@ -255,7 +256,7 @@ func TestSNS_FirehoseDelivery_PutsRecord(t *testing.T) { // subscription created above never sets it), so the delivered record is the // full SNS Notification JSON envelope, not the bare message body. var envelope map[string]any - require.NoError(t, json.Unmarshal(records[0], &envelope)) + require.NoError(t, json.Unmarshal(records[0], &envelope, jsonx.V1())) assert.Equal(t, "Notification", envelope["Type"]) assert.Equal(t, "firehose-message", envelope["Message"]) assert.Equal(t, topicARN, envelope["TopicArn"]) @@ -501,7 +502,7 @@ func TestFirehoseDeliveryFailureSendsToDLQ(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": dlqARN, - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err) @@ -534,7 +535,7 @@ func TestFirehoseDeliveryFailureNoSQSSenderNoError(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:dlq", - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err) @@ -623,7 +624,7 @@ func TestLambdaDeliveryFailureSendsToDLQ(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": dlqARN, - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err) @@ -656,7 +657,7 @@ func TestLambdaDeliveryFailureNoSenderNoError(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:dlq", - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err) diff --git a/services/sns/message_structure_protocols_test.go b/services/sns/message_structure_protocols_test.go index a8af37806e..8cd7ed84f0 100644 --- a/services/sns/message_structure_protocols_test.go +++ b/services/sns/message_structure_protocols_test.go @@ -1,7 +1,7 @@ package sns_test import ( - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "time" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -86,7 +87,7 @@ func receiveBody(t *testing.T, sqsClient *sqssdk.Client, queueURL string, raw bo Message string `json:"Message"` } - require.NoError(t, json.Unmarshal([]byte(body), &env)) + require.NoError(t, json.Unmarshal([]byte(body), &env, jsonx.V1())) return env.Message } @@ -222,7 +223,7 @@ func TestMessageStructureJSON_AllProtocols(t *testing.T) { } `json:"Records"` } - require.NoError(t, json.Unmarshal(lambda.Last().Payload, &lenv)) + require.NoError(t, json.Unmarshal(lambda.Last().Payload, &lenv, jsonx.V1())) require.Len(t, lenv.Records, 1) assert.Equal(t, tt.want["lambda"], lenv.Records[0].Sns.Message) @@ -237,7 +238,7 @@ func TestMessageStructureJSON_AllProtocols(t *testing.T) { Message string `json:"Message"` } - require.NoError(t, json.Unmarshal(envRecs[0], &fenv)) + require.NoError(t, json.Unmarshal(envRecs[0], &fenv, jsonx.V1())) assert.Equal(t, tt.want["firehose-env"], fenv.Message) got := map[string]string{} @@ -353,7 +354,7 @@ func TestMessageStructureJSON_Replay(t *testing.T) { } require.Equal(t, 1, lambda.Count()) - require.NoError(t, json.Unmarshal(lambda.Last().Payload, &env)) + require.NoError(t, json.Unmarshal(lambda.Last().Payload, &env, jsonx.V1())) assert.Equal(t, tt.want, env.Records[0].Sns.Message) }) }) diff --git a/services/sns/metrics.go b/services/sns/metrics.go index 3d34bed3e1..31885ed144 100644 --- a/services/sns/metrics.go +++ b/services/sns/metrics.go @@ -1,10 +1,12 @@ package sns import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "github.com/blackbirdworks/gopherstack/pkgs/cwmetric" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // AWS/SNS metrics per docs.aws.amazon.com/sns/latest/dg/sns-monitoring-using-cloudwatch.html (dimension TopicName). @@ -64,7 +66,7 @@ func classifyAttributeRejection(attrs map[string]MessageAttribute) filterRejecti } var elems []any - if json.Unmarshal([]byte(a.StringValue), &elems) != nil { + if json.Unmarshal([]byte(a.StringValue), &elems, jsonx.V1()) != nil { return rejectInvalidAttributes } } @@ -74,8 +76,8 @@ func classifyAttributeRejection(attrs map[string]MessageAttribute) filterRejecti // classifyBodyRejection names why message failed a body-scope filter policy. func classifyBodyRejection(message string) filterRejection { - var body map[string]json.RawMessage - if json.Unmarshal([]byte(message), &body) != nil { + var body map[string]jsontext.Value + if json.Unmarshal([]byte(message), &body, jsonx.V1()) != nil { return rejectInvalidBody } diff --git a/services/sns/models.go b/services/sns/models.go index 33d9d2e090..0b1a76991d 100644 --- a/services/sns/models.go +++ b/services/sns/models.go @@ -3,7 +3,7 @@ package sns import ( "context" "crypto/rsa" - "encoding/json" + "encoding/json/jsontext" "encoding/xml" "net/http" "sync" @@ -718,7 +718,7 @@ type publishTargets struct { filteredOut filteredCounts } -type parsedFilterPolicy map[string][]json.RawMessage +type parsedFilterPolicy map[string][]jsontext.Value // snsHTTPNotification is the SNS notification JSON envelope sent to HTTP/HTTPS subscribers. // When RawMessageDelivery is false, this struct is serialised as the POST body. diff --git a/services/sns/persistence.go b/services/sns/persistence.go index ac866341bc..c7e92a5cdf 100644 --- a/services/sns/persistence.go +++ b/services/sns/persistence.go @@ -2,7 +2,7 @@ package sns import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -32,7 +32,7 @@ const snsSnapshotVersion = 1 // type is not a pure keyed identity or is slice/scalar-valued -- see the // comment on registerAllTables in store_setup.go for why each one. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TopicTags map[string]*svcTags.Tags `json:"topicTags"` OptedOutPhoneNumbers map[string]bool `json:"optedOutPhoneNumbers,omitempty"` SMSAttributes map[string]string `json:"smsAttributes,omitempty"` diff --git a/services/sns/platform_endpoints.go b/services/sns/platform_endpoints.go index a9328c33b9..b1b7d80b9f 100644 --- a/services/sns/platform_endpoints.go +++ b/services/sns/platform_endpoints.go @@ -1,7 +1,7 @@ package sns import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "sort" @@ -11,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // CreatePlatformEndpoint registers a device token as an endpoint for a platform application. @@ -267,6 +268,6 @@ func (b *InMemoryBackend) fireEndpointEvent(appArn, eventAttr string, payload ma return } - msg, _ := json.Marshal(payload) + msg, _ := json.Marshal(payload, jsonx.V1()) _, _ = b.Publish(topicArn, string(msg), "", "", nil) } diff --git a/services/sns/publish.go b/services/sns/publish.go index 9694d5b40a..ec4595e797 100644 --- a/services/sns/publish.go +++ b/services/sns/publish.go @@ -2,7 +2,7 @@ package sns import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // buildMessageResolver returns a function that picks the correct message body for a given protocol, @@ -152,7 +153,7 @@ func validateStructuredMessage(message, messageStructure string) error { } var pm map[string]string - if err := json.Unmarshal([]byte(message), &pm); err != nil { + if err := json.Unmarshal([]byte(message), &pm, jsonx.V1()); err != nil { return fmt.Errorf( "%w: Invalid JSON in Message when MessageStructure is json: %s", ErrInvalidParameter, @@ -179,7 +180,7 @@ func parsePerProtocolMessages(message, messageStructure string) map[string]strin } var pm map[string]string - if err := json.Unmarshal([]byte(message), &pm); err != nil { + if err := json.Unmarshal([]byte(message), &pm, jsonx.V1()); err != nil { return nil } diff --git a/services/sns/publish_test.go b/services/sns/publish_test.go index 249191fce2..b450eb51bb 100644 --- a/services/sns/publish_test.go +++ b/services/sns/publish_test.go @@ -1,7 +1,7 @@ package sns_test import ( - "encoding/json" + "encoding/json/v2" "encoding/xml" "fmt" "io" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -742,7 +743,7 @@ func TestBatchMessageStructureJSON(t *testing.T) { case raw := <-received: // The delivered body should be the http-protocol-specific value. var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "http-specific", env.Message) } else { assert.Equal(t, "http-specific", raw) @@ -784,7 +785,7 @@ func TestBatchMessageStructureDefault(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "plain-text", env.Message) } else { assert.Equal(t, "plain-text", raw) @@ -988,7 +989,7 @@ func TestMessageStructureJSONSinglePublish(t *testing.T) { select { case raw := <-received: var env snsNotificationEnvelope - if json.Unmarshal([]byte(raw), &env) == nil { + if json.Unmarshal([]byte(raw), &env, jsonx.V1()) == nil { assert.Equal(t, "http-only", env.Message) } case <-time.After(2 * time.Second): diff --git a/services/sns/realism_sdk_test.go b/services/sns/realism_sdk_test.go index d5fdd1b650..843c02ebe1 100644 --- a/services/sns/realism_sdk_test.go +++ b/services/sns/realism_sdk_test.go @@ -1,7 +1,7 @@ package sns_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -298,7 +299,7 @@ func newConfirmationEndpoint(out chan<- map[string]string) *httptest.Server { body, _ := io.ReadAll(r.Body) var m map[string]string - if json.Unmarshal(body, &m) == nil && m["Type"] == "SubscriptionConfirmation" { + if json.Unmarshal(body, &m, jsonx.V1()) == nil && m["Type"] == "SubscriptionConfirmation" { select { case out <- m: default: diff --git a/services/sns/signing_channels_test.go b/services/sns/signing_channels_test.go index 89b691e275..4c2336e28e 100644 --- a/services/sns/signing_channels_test.go +++ b/services/sns/signing_channels_test.go @@ -7,7 +7,7 @@ import ( "crypto/sha1" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "sync" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -66,7 +67,7 @@ func TestPublishSignaturesVerifyOnEveryChannel(t *testing.T) { body, _ := io.ReadAll(r.Body) var env snsNotificationEnvelope - if json.Unmarshal(body, &env) == nil { + if json.Unmarshal(body, &env, jsonx.V1()) == nil { httpEnvs <- env } diff --git a/services/sns/signing_test.go b/services/sns/signing_test.go index 3af027acd3..a86ebae249 100644 --- a/services/sns/signing_test.go +++ b/services/sns/signing_test.go @@ -7,7 +7,7 @@ import ( "crypto/sha256" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "io" "net/http" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -76,7 +77,7 @@ func parseNotificationEnvelope(t *testing.T, body string) snsNotificationEnvelop t.Helper() var env snsNotificationEnvelope - require.NoError(t, json.Unmarshal([]byte(body), &env), "invalid SNS notification envelope") + require.NoError(t, json.Unmarshal([]byte(body), &env, jsonx.V1()), "invalid SNS notification envelope") return env } diff --git a/services/sns/subscription_attributes_test.go b/services/sns/subscription_attributes_test.go index a81f372ff7..10e8636f66 100644 --- a/services/sns/subscription_attributes_test.go +++ b/services/sns/subscription_attributes_test.go @@ -2,7 +2,7 @@ package sns_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/url" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sns" ) @@ -300,7 +301,7 @@ func TestSetSubscriptionAttributesDLQExistenceCheck(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": tt.dlqARN, - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) if tt.wantErr { @@ -327,7 +328,7 @@ func TestSetSubscriptionAttributesDLQNoCheckerSkips(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]string{ "deadLetterTargetArn": "arn:aws:sqs:us-east-1:123456789012:any-dlq", - }) + }, jsonx.V1()) err = b.SetSubscriptionAttributes(sub.SubscriptionArn, "RedrivePolicy", string(redrivePolicy)) require.NoError(t, err, "no checker means existence check is skipped") } diff --git a/services/sns/topic_attributes.go b/services/sns/topic_attributes.go index 4bd19e0e2d..229a164028 100644 --- a/services/sns/topic_attributes.go +++ b/services/sns/topic_attributes.go @@ -1,13 +1,16 @@ package sns import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "regexp" "sort" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // GetTopicAttributes returns the attributes of a topic. @@ -89,9 +92,9 @@ func (b *InMemoryBackend) GetTopicAttributes(topicArn string) (map[string]string // policyDocument mirrors the shape of an SNS access-control policy document // (docs.aws.amazon.com/sns/latest/dg/sns-access-policy-use-cases.html). type policyDocument struct { - Version string `json:"Version"` - ID string `json:"Id,omitempty"` - Statement []json.RawMessage `json:"Statement"` + Version string `json:"Version"` + ID string `json:"Id,omitempty"` + Statement []jsontext.Value `json:"Statement"` } // policyStatement is a single Allow statement built from an AddPermission grant. @@ -120,7 +123,7 @@ func mergePermissionsIntoPolicy(basePolicy, topicArn string, perms map[string]*T } var doc policyDocument - if err := json.Unmarshal([]byte(basePolicy), &doc); err != nil || doc.Version == "" { + if err := json.Unmarshal([]byte(basePolicy), &doc, jsonx.V1()); err != nil || doc.Version == "" { doc.Version = "2012-10-17" } @@ -131,13 +134,13 @@ func mergePermissionsIntoPolicy(basePolicy, topicArn string, perms map[string]*T sort.Strings(labels) - kept := make([]json.RawMessage, 0, len(doc.Statement)) + kept := make([]jsontext.Value, 0, len(doc.Statement)) for _, raw := range doc.Statement { var sid struct { Sid string `json:"Sid"` } - if err := json.Unmarshal(raw, &sid); err == nil { + if err := json.Unmarshal(raw, &sid, jsonx.V1()); err == nil { if _, isPermission := perms[sid.Sid]; isPermission { continue } @@ -147,7 +150,7 @@ func mergePermissionsIntoPolicy(basePolicy, topicArn string, perms map[string]*T } for _, label := range labels { - raw, err := json.Marshal(permissionStatement(topicArn, perms[label])) + raw, err := json.Marshal(permissionStatement(topicArn, perms[label]), jsonx.V1()) if err != nil { continue } @@ -157,7 +160,7 @@ func mergePermissionsIntoPolicy(basePolicy, topicArn string, perms map[string]*T doc.Statement = kept - out, err := json.Marshal(doc) + out, err := json.Marshal(doc, jsonx.V1()) if err != nil { return basePolicy } @@ -478,8 +481,8 @@ func validateDataProtectionPolicy(policy string) error { ) } - var doc map[string]json.RawMessage - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + var doc map[string]jsontext.Value + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return fmt.Errorf("%w: DataProtectionPolicy must be a valid JSON object", ErrInvalidParameter) } diff --git a/services/sqs/bench_protocol_test.go b/services/sqs/bench_protocol_test.go index 59280edf57..abc316e193 100644 --- a/services/sqs/bench_protocol_test.go +++ b/services/sqs/bench_protocol_test.go @@ -2,7 +2,7 @@ package sqs_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "net/url" @@ -13,6 +13,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -82,7 +83,7 @@ func jsonSendBody(b *testing.B, qURL string) []byte { "attr-one": map[string]any{"DataType": "String", "StringValue": "value-one"}, "attr-two": map[string]any{"DataType": "Number", "StringValue": "42"}, }, - }) + }, jsonx.V1()) if err != nil { b.Fatalf("marshal send body: %v", err) } @@ -144,7 +145,7 @@ func BenchmarkJSONSendMessageBatch10(b *testing.B) { } } - body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "Entries": entries}) + body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "Entries": entries}, jsonx.V1()) if err != nil { b.Fatalf("marshal batch body: %v", err) } @@ -196,7 +197,7 @@ func BenchmarkJSONReceiveMessage10_Depth10000(b *testing.B) { h := sqs.NewHandler(backend) qURL := setupReceiveDepthQueue(b, backend, "bench-json-recv-q") - body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "MaxNumberOfMessages": 10}) + body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "MaxNumberOfMessages": 10}, jsonx.V1()) if err != nil { b.Fatalf("marshal receive body: %v", err) } @@ -271,7 +272,7 @@ func BenchmarkJSONReceiveMessage10_FIFOManyGroups(b *testing.B) { h := sqs.NewHandler(backend) qURL := setupFIFOManyGroups(b, backend, "bench-json-fifo-recv-q", 1000, 10) - body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "MaxNumberOfMessages": 10}) + body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "MaxNumberOfMessages": 10}, jsonx.V1()) if err != nil { b.Fatalf("marshal receive body: %v", err) } @@ -332,7 +333,7 @@ func BenchmarkJSONDeleteMessage(b *testing.B) { bodies := make([][]byte, len(handles)) for i, rh := range handles { - body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "ReceiptHandle": rh}) + body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "ReceiptHandle": rh}, jsonx.V1()) if err != nil { b.Fatalf("marshal delete body: %v", err) } @@ -385,7 +386,7 @@ func BenchmarkJSONDeleteMessageBatch10(b *testing.B) { for i, m := range recv.Messages { entries[i] = map[string]any{"Id": strconv.Itoa(i), "ReceiptHandle": m.ReceiptHandle} } - body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "Entries": entries}) + body, err := json.Marshal(map[string]any{"QueueUrl": qURL, "Entries": entries}, jsonx.V1()) if err != nil { b.Fatalf("marshal delete batch body: %v", err) } diff --git a/services/sqs/checksums_test.go b/services/sqs/checksums_test.go index 38dfd87a18..450f01380b 100644 --- a/services/sqs/checksums_test.go +++ b/services/sqs/checksums_test.go @@ -1,12 +1,13 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -190,7 +191,7 @@ func TestComputeMD5OfMessageAttributes_FullSetUsesPrecomputed(t *testing.T) { require.Equal(t, 200, recvRec.Code) var recvResp recvResponse - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) // The MD5 must be non-empty when attributes are returned. @@ -261,7 +262,7 @@ func TestReceiveMessage_MD5Consistency(t *testing.T) { require.Equal(t, 200, recvRec.Code) var recvResp recvMD5Only - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) assert.Equal(t, tt.wantMD5, recvResp.Messages[0].MD5OfMessageAttributes, diff --git a/services/sqs/dead_letter.go b/services/sqs/dead_letter.go index bdc508a915..02b8e48cf9 100644 --- a/services/sqs/dead_letter.go +++ b/services/sqs/dead_letter.go @@ -1,20 +1,21 @@ package sqs import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "slices" "sort" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) // redrivePolicy represents the JSON structure of an SQS RedrivePolicy attribute. type redrivePolicy struct { - DeadLetterTargetArn string `json:"deadLetterTargetArn"` - MaxReceiveCount json.Number `json:"maxReceiveCount"` + DeadLetterTargetArn string `json:"deadLetterTargetArn"` + MaxReceiveCount jsonx.Number `json:"maxReceiveCount"` } // applyRedrivePolicy parses the RedrivePolicy attribute and wires up DLQ fields on q. @@ -32,11 +33,11 @@ func applyRedrivePolicy(q *Queue, attrs map[string]string, backend *InMemoryBack var pol redrivePolicy - if err := json.Unmarshal([]byte(raw), &pol); err != nil { + if err := json.Unmarshal([]byte(raw), &pol, jsonx.V1()); err != nil { return &InvalidParameterError{Message: "Invalid value for the parameter RedrivePolicy."} } - count, err := pol.MaxReceiveCount.Int64() + count, err := pol.MaxReceiveCount.Int() if err != nil || count <= 0 { return &InvalidParameterError{Message: "Invalid value for the parameter RedrivePolicy."} } @@ -72,7 +73,7 @@ func applyRedrivePolicy(q *Queue, attrs map[string]string, backend *InMemoryBack } } - q.MaxReceiveCount = int(count) + q.MaxReceiveCount = count q.dlq = dlq now := backend.now() @@ -134,7 +135,7 @@ func checkRedriveAllowPolicy(dlq *Queue, srcArn string) error { // never left with a malformed value; treat it permissively rather than // blocking on a defensive parse error. //nolint:nilerr // intentional fail-open on a defensive parse error, see comment above - if json.Unmarshal([]byte(raw), &policy) != nil { + if json.Unmarshal([]byte(raw), &policy, jsonx.V1()) != nil { return nil } @@ -178,11 +179,11 @@ func (b *InMemoryBackend) ListDeadLetterSourceQueues( } var pol redrivePolicy - if err := json.Unmarshal([]byte(raw), &pol); err != nil { + if err := json.Unmarshal([]byte(raw), &pol, jsonx.V1()); err != nil { continue } - count, err := pol.MaxReceiveCount.Int64() + count, err := pol.MaxReceiveCount.Int() if err != nil || count <= 0 { continue } diff --git a/services/sqs/dead_letter_test.go b/services/sqs/dead_letter_test.go index 8bd599ee53..dc4ed7f088 100644 --- a/services/sqs/dead_letter_test.go +++ b/services/sqs/dead_letter_test.go @@ -1,7 +1,7 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "strconv" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -29,7 +30,7 @@ func TestRedriveAllowPolicy(t *testing.T) { rap, _ := json.Marshal(map[string]any{ "redrivePermission": "byQueue", "sourceQueueArns": []string{dlqARN}, - }) + }, jsonx.V1()) _, err = b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "src", @@ -54,7 +55,7 @@ func TestRedrivePolicy_MovesToDLQAfterMaxReceiveCount(t *testing.T) { redriveJSON, _ := json.Marshal(map[string]any{ "deadLetterTargetArn": dlqARN, "maxReceiveCount": 2, - }) + }, jsonx.V1()) srcURL, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "src-with-dlq", @@ -104,7 +105,7 @@ func TestRedrivePolicy_SetViaSetQueueAttributes(t *testing.T) { redriveJSON, _ := json.Marshal(map[string]any{ "deadLetterTargetArn": dlqARN, "maxReceiveCount": 3, - }) + }, jsonx.V1()) err := b.SetQueueAttributes(&sqs.SetQueueAttributesInput{ QueueURL: srcURL, @@ -117,7 +118,7 @@ func TestRedrivePolicy_SetViaSetQueueAttributes(t *testing.T) { // Verify round-trip via GetQueueAttributes attrs := b2getAttrs(t, b, srcURL, "RedrivePolicy") var pol map[string]any - require.NoError(t, json.Unmarshal([]byte(attrs["RedrivePolicy"]), &pol)) + require.NoError(t, json.Unmarshal([]byte(attrs["RedrivePolicy"]), &pol, jsonx.V1())) assert.Equal(t, dlqARN, pol["deadLetterTargetArn"]) } @@ -130,7 +131,7 @@ func TestListDeadLetterSourceQueues_SingleSource(t *testing.T) { dlqARN := dlqAttrs["QueueArn"] makeRedriveAttr := func(arn string, count int) string { - v, _ := json.Marshal(map[string]any{"deadLetterTargetArn": arn, "maxReceiveCount": count}) + v, _ := json.Marshal(map[string]any{"deadLetterTargetArn": arn, "maxReceiveCount": count}, jsonx.V1()) return string(v) } @@ -178,7 +179,7 @@ func TestRedriveAllowPolicy_AllowAll(t *testing.T) { t.Parallel() b := b2newBackend(t) - policy, _ := json.Marshal(map[string]any{"redrivePermission": "allowAll"}) + policy, _ := json.Marshal(map[string]any{"redrivePermission": "allowAll"}, jsonx.V1()) _, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "rap-allow-all", @@ -194,7 +195,7 @@ func TestRedriveAllowPolicy_DenyAll(t *testing.T) { t.Parallel() b := b2newBackend(t) - policy, _ := json.Marshal(map[string]any{"redrivePermission": "denyAll"}) + policy, _ := json.Marshal(map[string]any{"redrivePermission": "denyAll"}, jsonx.V1()) _, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "rap-deny-all", @@ -213,7 +214,7 @@ func TestRedriveAllowPolicy_ByQueue(t *testing.T) { policy, _ := json.Marshal(map[string]any{ "redrivePermission": "byQueue", "sourceQueueArns": []string{"arn:aws:sqs:us-east-1:123456789012:src-queue"}, - }) + }, jsonx.V1()) _, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "rap-by-queue", @@ -232,7 +233,7 @@ func TestRedriveAllowPolicy_AllowAllWithArns_Rejected(t *testing.T) { policy, _ := json.Marshal(map[string]any{ "redrivePermission": "allowAll", "sourceQueueArns": []string{"arn:aws:sqs:us-east-1:123456789012:q"}, - }) + }, jsonx.V1()) _, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "rap-invalid", @@ -255,7 +256,7 @@ func TestRedriveAllowPolicy_ByQueueTooManyArns_Rejected(t *testing.T) { policy, _ := json.Marshal(map[string]any{ "redrivePermission": "byQueue", "sourceQueueArns": arns, - }) + }, jsonx.V1()) _, err := b.CreateQueue(&sqs.CreateQueueInput{ QueueName: "rap-toomany", @@ -271,7 +272,7 @@ func TestRedriveAllowPolicy_RoundTrip(t *testing.T) { t.Parallel() b := b2newBackend(t) - policy, _ := json.Marshal(map[string]any{"redrivePermission": "denyAll"}) + policy, _ := json.Marshal(map[string]any{"redrivePermission": "denyAll"}, jsonx.V1()) qURL := b2createQueue(t, b, "rap-roundtrip") require.NoError(t, b.SetQueueAttributes(&sqs.SetQueueAttributesInput{ @@ -700,7 +701,7 @@ func TestSQS_DLQRedrive_DefaultDestination(t *testing.T) { redrivePolicy, _ := json.Marshal(map[string]any{ "deadLetterTargetArn": dlqARN, "maxReceiveCount": 3, - }) + }, jsonx.V1()) _, err = b.CreateQueue(&sqs.CreateQueueInput{ QueueName: srcName, @@ -824,7 +825,7 @@ func makeRedrivePolicy(dlqARN string, maxReceiveCount int) string { b, _ := json.Marshal(map[string]any{ "deadLetterTargetArn": dlqARN, "maxReceiveCount": maxReceiveCount, - }) + }, jsonx.V1()) return string(b) } diff --git a/services/sqs/errsweep_wire_shape_test.go b/services/sqs/errsweep_wire_shape_test.go index 07d8b0515f..40d200236f 100644 --- a/services/sqs/errsweep_wire_shape_test.go +++ b/services/sqs/errsweep_wire_shape_test.go @@ -3,7 +3,7 @@ package sqs_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -17,6 +17,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/sqs" @@ -56,7 +57,7 @@ func TestSDK_GetQueueUrl_NonExistentQueue_TypedError(t *testing.T) { rawBody := rawSQSErrorBody(t, rawURL, "GetQueueUrl", `{"QueueName":"does-not-exist"}`) var decoded map[string]any - require.NoError(t, json.Unmarshal(rawBody, &decoded)) + require.NoError(t, json.Unmarshal(rawBody, &decoded, jsonx.V1())) require.Equal(t, "com.amazonaws.sqs#QueueDoesNotExist", decoded["__type"], "raw response body must carry __type; got: %s", rawBody) _, hasBareCode := decoded["code"] diff --git a/services/sqs/fifo_test.go b/services/sqs/fifo_test.go index 3a17d94fe3..a98685c0c1 100644 --- a/services/sqs/fifo_test.go +++ b/services/sqs/fifo_test.go @@ -2,7 +2,8 @@ package sqs_test import ( "encoding/hex" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -201,7 +203,7 @@ func TestSetQueueAttributesFifoQueueRejectedViaHandler(t *testing.T) { Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Contains(t, errResp.Type, "InvalidAttributeName") } @@ -344,8 +346,8 @@ func TestSendMessage_Standard_SequenceNumberAbsent(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) // Unmarshal into a raw map so we can check key presence, not just value. - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasSeqNum := raw["SequenceNumber"] assert.False(t, hasSeqNum, "standard queue SendMessage response must not contain SequenceNumber") @@ -371,7 +373,7 @@ func TestSendMessage_FIFO_SequenceNumberPresent(t *testing.T) { var createResp struct { QueueURL string `json:"QueueUrl"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rec := doRequest(t, h, "SendMessage", map[string]any{ "QueueUrl": createResp.QueueURL, @@ -380,14 +382,14 @@ func TestSendMessage_FIFO_SequenceNumberPresent(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) rawSeq, hasSeqNum := raw["SequenceNumber"] require.True(t, hasSeqNum, "FIFO queue SendMessage response must contain SequenceNumber") var seq string - require.NoError(t, json.Unmarshal(rawSeq, &seq)) + require.NoError(t, json.Unmarshal(rawSeq, &seq, jsonx.V1())) assert.NotEmpty(t, seq, "SequenceNumber must be non-empty for FIFO queue") assert.Len(t, seq, 20, "SequenceNumber must be 20 digits") } diff --git a/services/sqs/handler.go b/services/sqs/handler.go index b6f9bc4d47..d87a14964b 100644 --- a/services/sqs/handler.go +++ b/services/sqs/handler.go @@ -2,7 +2,7 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "strings" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/endpointstrategy" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -293,7 +294,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var req extractQueueURLInput - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -368,7 +369,7 @@ func (h *Handler) sqsRoute( return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // handleError writes an SQS error response using the standard error details mapping. @@ -386,7 +387,7 @@ func (h *Handler) sqsRoute( func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { errType, message, status := errorDetails(err) - payload, marshalErr := json.Marshal(jsonSQSError{Type: errType, Message: message}) + payload, marshalErr := json.Marshal(jsonSQSError{Type: errType, Message: message}, jsonx.V1()) if marshalErr != nil { return marshalErr } diff --git a/services/sqs/handler_dead_letter.go b/services/sqs/handler_dead_letter.go index 9e6747ddb8..c909c17dec 100644 --- a/services/sqs/handler_dead_letter.go +++ b/services/sqs/handler_dead_letter.go @@ -2,8 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonListDeadLetterSourceQueuesReq struct { @@ -23,7 +25,7 @@ func (h *Handler) handleListDeadLetterSourceQueues( body []byte, ) (any, error) { var req jsonListDeadLetterSourceQueuesReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_dead_letter_test.go b/services/sqs/handler_dead_letter_test.go index 6ef6f501e0..a6922bbe39 100644 --- a/services/sqs/handler_dead_letter_test.go +++ b/services/sqs/handler_dead_letter_test.go @@ -1,13 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -36,7 +37,7 @@ func TestHandlerActions_ListDeadLetterSourceQueues(t *testing.T) { var attrResp struct { Attributes map[string]string `json:"Attributes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &attrResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &attrResp, jsonx.V1())) dlqARN := attrResp.Attributes["QueueArn"] policy := `{"deadLetterTargetArn":"` + dlqARN + `","maxReceiveCount":3}` @@ -97,7 +98,7 @@ func TestHandlerActions_ListDeadLetterSourceQueues(t *testing.T) { NextToken string `json:"NextToken"` QueueURLs []string `json:"queueUrls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.QueueURLs, tt.wantURLCount) } }) diff --git a/services/sqs/handler_message_move_tasks.go b/services/sqs/handler_message_move_tasks.go index cf32fbc95c..92b5668ee5 100644 --- a/services/sqs/handler_message_move_tasks.go +++ b/services/sqs/handler_message_move_tasks.go @@ -2,8 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonStartMessageMoveTaskReq struct { @@ -54,7 +56,7 @@ func (h *Handler) handleStartMessageMoveTask( body []byte, ) (any, error) { var req jsonStartMessageMoveTaskReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -76,7 +78,7 @@ func (h *Handler) handleCancelMessageMoveTask( body []byte, ) (any, error) { var req jsonCancelMessageMoveTaskReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -99,7 +101,7 @@ func (h *Handler) handleListMessageMoveTasks( ) (any, error) { var req jsonListMessageMoveTasksReq // Body may be empty; ignore unmarshal errors. - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) out, err := h.Backend.ListMessageMoveTasks(&ListMessageMoveTasksInput{ SourceArn: req.SourceArn, diff --git a/services/sqs/handler_message_visibility.go b/services/sqs/handler_message_visibility.go index f782b511ab..bba39f76db 100644 --- a/services/sqs/handler_message_visibility.go +++ b/services/sqs/handler_message_visibility.go @@ -2,8 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonChangeVisibilityReq struct { @@ -29,7 +31,7 @@ func (h *Handler) handleChangeMessageVisibility( body []byte, ) (any, error) { var req jsonChangeVisibilityReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -52,7 +54,7 @@ func (h *Handler) handleChangeMessageVisibilityBatch( body []byte, ) (any, error) { var req jsonChangeVisibilityBatchReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_message_visibility_test.go b/services/sqs/handler_message_visibility_test.go index 348fb69c2e..b5d4e73273 100644 --- a/services/sqs/handler_message_visibility_test.go +++ b/services/sqs/handler_message_visibility_test.go @@ -1,12 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSQSHandler_ChangeMessageVisibility_InvalidJSON(t *testing.T) { @@ -73,7 +75,7 @@ func TestSQSHandler_ChangeMessageVisibility_Success(t *testing.T) { } `json:"Messages"` } - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) rec := doRequest(t, h, "ChangeMessageVisibility", map[string]any{ @@ -107,7 +109,7 @@ func TestHandlerActions_ChangeMessageVisibility(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) receipt := recvResp.Messages[0].ReceiptHandle @@ -162,7 +164,7 @@ func TestHandlerActions_ChangeMessageVisibilityBatch(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rcvRec.Body.Bytes(), &rcvResp)) + require.NoError(t, json.Unmarshal(rcvRec.Body.Bytes(), &rcvResp, jsonx.V1())) require.Len(t, rcvResp.Messages, 1) handle := rcvResp.Messages[0].ReceiptHandle @@ -213,7 +215,7 @@ func TestHandlerActions_ChangeMessageVisibilityBatch(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rcvRec.Body.Bytes(), &rcvResp)) + require.NoError(t, json.Unmarshal(rcvRec.Body.Bytes(), &rcvResp, jsonx.V1())) require.Len(t, rcvResp.Messages, 2) rec := doRequest(t, h, "ChangeMessageVisibilityBatch", map[string]any{ @@ -236,7 +238,7 @@ func TestHandlerActions_ChangeMessageVisibilityBatch(t *testing.T) { SenderFault bool `json:"SenderFault"` } `json:"Failed"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.Successful, 2) assert.ElementsMatch(t, []string{"good-1", "good-2"}, []string{body.Successful[0].ID, body.Successful[1].ID}) @@ -261,7 +263,7 @@ func TestHandlerActions_ChangeMessageVisibilityBatch(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(reRcvRec.Body.Bytes(), &reRcvResp)) + require.NoError(t, json.Unmarshal(reRcvRec.Body.Bytes(), &reRcvResp, jsonx.V1())) assert.Len(t, reRcvResp.Messages, 2) }) } diff --git a/services/sqs/handler_messages.go b/services/sqs/handler_messages.go index 599a40f6be..1168cc632e 100644 --- a/services/sqs/handler_messages.go +++ b/services/sqs/handler_messages.go @@ -2,9 +2,11 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonMsgAttr struct { @@ -113,7 +115,7 @@ func (h *Handler) handleSendMessage( body []byte, ) (any, error) { var req jsonSendMessageReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -146,7 +148,7 @@ func (h *Handler) handleReceiveMessage( body []byte, ) (any, error) { var req jsonReceiveMessageReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -237,7 +239,7 @@ func (h *Handler) handleDeleteMessage( body []byte, ) (any, error) { var req jsonDeleteMessageReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -258,7 +260,7 @@ func (h *Handler) handleSendMessageBatch( body []byte, ) (any, error) { var req jsonSendMessageBatchReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -314,7 +316,7 @@ func (h *Handler) handleDeleteMessageBatch( body []byte, ) (any, error) { var req jsonDeleteMessageBatchReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_messages_test.go b/services/sqs/handler_messages_test.go index 017633eab3..afd2341246 100644 --- a/services/sqs/handler_messages_test.go +++ b/services/sqs/handler_messages_test.go @@ -1,7 +1,7 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -28,7 +29,7 @@ func TestHandlerActions_SendMessage(t *testing.T) { MessageID string `json:"MessageId"` MD5OfMessageBody string `json:"MD5OfMessageBody"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.MessageID) assert.NotEmpty(t, resp.MD5OfMessageBody) } @@ -59,7 +60,7 @@ func TestHandlerActions_ReceiveMessage(t *testing.T) { Body string `json:"Body"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Messages, 1) assert.Equal(t, "hello", resp.Messages[0].Body) }) @@ -86,7 +87,7 @@ func TestHandlerActions_ReceiveMessage(t *testing.T) { Body string `json:"Body"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Messages, 1) }) } @@ -153,7 +154,7 @@ func TestHandlerActions_ReceiveMessageAttributeNamesFilter(t *testing.T) { MessageAttributes map[string]map[string]any `json:"MessageAttributes"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Messages, 1) assert.Len(t, resp.Messages[0].MessageAttributes, tt.wantAttrNamesLen) for _, name := range tt.wantAttrNames { @@ -181,7 +182,7 @@ func sendForMD5(t *testing.T, h *sqs.Handler, queueURL string, attrs map[string] var resp struct { MD5OfMessageAttributes string `json:"MD5OfMessageAttributes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp.MD5OfMessageAttributes } @@ -261,7 +262,7 @@ func TestHandlerActions_ReceiveMessageMD5OverSubset(t *testing.T) { MD5OfMessageAttributes string `json:"MD5OfMessageAttributes"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Messages, 1) if tt.wantEmptyMD5 { @@ -305,7 +306,7 @@ func TestHandlerActions_DeleteMessage(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) receipt := recvResp.Messages[0].ReceiptHandle @@ -354,7 +355,7 @@ func TestHandlerActions_SendMessageBatch(t *testing.T) { ID string `json:"Id"` } `json:"Successful"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Successful, 2) }) @@ -370,7 +371,7 @@ func TestHandlerActions_SendMessageBatch(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "com.amazonaws.sqs#QueueDoesNotExist", errResp.Type) }) @@ -387,7 +388,7 @@ func TestHandlerActions_SendMessageBatch(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "com.amazonaws.sqs#EmptyBatchRequest", errResp.Type) }) @@ -416,7 +417,7 @@ func TestHandlerActions_SendMessageBatch(t *testing.T) { ID string `json:"Id"` } `json:"Successful"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Successful, 10) }) } @@ -442,7 +443,7 @@ func TestHandlerActions_DeleteMessageBatch(t *testing.T) { ReceiptHandle string `json:"ReceiptHandle"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp)) + require.NoError(t, json.Unmarshal(recvRec.Body.Bytes(), &recvResp, jsonx.V1())) require.Len(t, recvResp.Messages, 1) receipt := recvResp.Messages[0].ReceiptHandle @@ -458,7 +459,7 @@ func TestHandlerActions_DeleteMessageBatch(t *testing.T) { ID string `json:"Id"` } `json:"Successful"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Successful, 1) }) @@ -474,7 +475,7 @@ func TestHandlerActions_DeleteMessageBatch(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "com.amazonaws.sqs#QueueDoesNotExist", errResp.Type) }) @@ -495,7 +496,7 @@ func TestHandlerActions_DeleteMessageBatch(t *testing.T) { ID string `json:"Id"` } `json:"Failed"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Failed, 1) }) @@ -512,7 +513,7 @@ func TestHandlerActions_DeleteMessageBatch(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "com.amazonaws.sqs#EmptyBatchRequest", errResp.Type) }) } diff --git a/services/sqs/handler_permissions.go b/services/sqs/handler_permissions.go index b77f678b52..b1c84e2292 100644 --- a/services/sqs/handler_permissions.go +++ b/services/sqs/handler_permissions.go @@ -2,8 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonAddPermissionReq struct { @@ -24,7 +26,7 @@ func (h *Handler) handleAddPermission( body []byte, ) (any, error) { var req jsonAddPermissionReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -47,7 +49,7 @@ func (h *Handler) handleRemovePermission( body []byte, ) (any, error) { var req jsonRemovePermissionReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_queue_attributes.go b/services/sqs/handler_queue_attributes.go index de8c6898ae..3b44edb589 100644 --- a/services/sqs/handler_queue_attributes.go +++ b/services/sqs/handler_queue_attributes.go @@ -2,8 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type jsonGetQueueAttributesReq struct { @@ -26,7 +28,7 @@ func (h *Handler) handleGetQueueAttributes( body []byte, ) (any, error) { var req jsonGetQueueAttributesReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -53,7 +55,7 @@ func (h *Handler) handleSetQueueAttributes( body []byte, ) (any, error) { var req jsonSetQueueAttributesReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_queues.go b/services/sqs/handler_queues.go index c3f09df6da..8dfceaf053 100644 --- a/services/sqs/handler_queues.go +++ b/services/sqs/handler_queues.go @@ -2,10 +2,11 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -46,7 +47,7 @@ func (h *Handler) handleCreateQueue( body []byte, ) (any, error) { var req jsonCreateQueueReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -92,7 +93,7 @@ func (h *Handler) handleDeleteQueue( body []byte, ) (any, error) { var req jsonQueueURLReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -112,7 +113,7 @@ func (h *Handler) handleListQueues( ) (any, error) { var req jsonListQueuesReq // ListQueues body may be empty; ignore unmarshal errors - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) region := h.requestScope(r) @@ -140,7 +141,7 @@ func (h *Handler) handleGetQueueURL( body []byte, ) (any, error) { var req jsonGetQueueURLReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -162,7 +163,7 @@ func (h *Handler) handlePurgeQueue( body []byte, ) (any, error) { var req jsonQueueURLReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_queues_test.go b/services/sqs/handler_queues_test.go index d4e847b974..53926a7edf 100644 --- a/services/sqs/handler_queues_test.go +++ b/services/sqs/handler_queues_test.go @@ -1,13 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -150,7 +151,7 @@ func TestHandlerActions_ListQueues(t *testing.T) { NextToken string `json:"NextToken"` QueueURLs []string `json:"QueueUrls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.QueueURLs, tt.wantURLCount) }) } @@ -180,7 +181,7 @@ func TestHandlerActions_ListQueues_PaginationRoundTrip(t *testing.T) { NextToken string `json:"NextToken"` QueueURLs []string `json:"QueueUrls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) allURLs = append(allURLs, resp.QueueURLs...) nextToken = resp.NextToken @@ -281,7 +282,7 @@ func TestHandlerActions_QueueManagement(t *testing.T) { var resp struct { Attributes map[string]string `json:"Attributes"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.Attributes) }) diff --git a/services/sqs/handler_tags.go b/services/sqs/handler_tags.go index 290910ad2f..2b0fc99b00 100644 --- a/services/sqs/handler_tags.go +++ b/services/sqs/handler_tags.go @@ -2,9 +2,10 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -30,7 +31,7 @@ func (h *Handler) handleTagQueue( body []byte, ) (any, error) { var req jsonTagQueueReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -51,7 +52,7 @@ func (h *Handler) handleUntagQueue( body []byte, ) (any, error) { var req jsonUntagQueueReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } @@ -72,7 +73,7 @@ func (h *Handler) handleListQueueTags( body []byte, ) (any, error) { var req jsonQueueURLReq - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, ErrUnknownAction } diff --git a/services/sqs/handler_tags_test.go b/services/sqs/handler_tags_test.go index 7e9983a23a..5cddbc4b66 100644 --- a/services/sqs/handler_tags_test.go +++ b/services/sqs/handler_tags_test.go @@ -1,12 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestSQSHandler_UntagQueue_InvalidJSON(t *testing.T) { @@ -132,7 +134,7 @@ func TestHandlerActions_TagOps(t *testing.T) { var resp struct { Tags map[string]string `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp.Tags) }) diff --git a/services/sqs/handler_test.go b/services/sqs/handler_test.go index b49869fc95..e3dc1fc0ac 100644 --- a/services/sqs/handler_test.go +++ b/services/sqs/handler_test.go @@ -2,7 +2,7 @@ package sqs_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "errors" "log/slog" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -73,7 +74,7 @@ func TestSQSHandler_Reset(t *testing.T) { QueueURLs []string `json:"QueueUrls"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp.QueueURLs, tt.name) }) } @@ -156,7 +157,7 @@ func doRequest(t *testing.T, h *sqs.Handler, action string, body any) *httptest. var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -189,7 +190,7 @@ func doCreateQueue(t *testing.T, h *sqs.Handler, name string) string { QueueURL string `json:"QueueUrl"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp.QueueURL } diff --git a/services/sqs/message_move_tasks.go b/services/sqs/message_move_tasks.go index 18f01847e9..f2a0fb6b39 100644 --- a/services/sqs/message_move_tasks.go +++ b/services/sqs/message_move_tasks.go @@ -2,7 +2,7 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "sort" "sync" "time" @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // moveTaskVisibilityTimeoutSecs is the visibility timeout used when receiving @@ -87,7 +88,7 @@ func (b *InMemoryBackend) findDefaultMoveDestinationLocked(dlqARN string) (strin var pol redrivePolicy - if err := json.Unmarshal([]byte(raw), &pol); err != nil { + if err := json.Unmarshal([]byte(raw), &pol, jsonx.V1()); err != nil { continue } diff --git a/services/sqs/message_move_tasks_test.go b/services/sqs/message_move_tasks_test.go index 5dea157ef9..8a3ced4d0b 100644 --- a/services/sqs/message_move_tasks_test.go +++ b/services/sqs/message_move_tasks_test.go @@ -1,7 +1,7 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -539,7 +540,7 @@ func TestSQS_MessageMoveTasks_Handler(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) handle, _ := startResp["TaskHandle"].(string) require.NotEmpty(t, handle) diff --git a/services/sqs/message_visibility_test.go b/services/sqs/message_visibility_test.go index 4e52f1d57c..f4b304c68c 100644 --- a/services/sqs/message_visibility_test.go +++ b/services/sqs/message_visibility_test.go @@ -1,7 +1,7 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "log/slog" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -239,7 +240,7 @@ func TestReceiveMessage_VisibilityTimeout_OutOfRange(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrType, errResp.Type) }) } @@ -282,7 +283,7 @@ func TestReceiveMessage_VisibilityTimeout_ValidBoundaries(t *testing.T) { Body string `json:"Body"` } `json:"Messages"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Messages, 1) }) } diff --git a/services/sqs/messages_test.go b/services/sqs/messages_test.go index 9dbe179c20..5e44c65d1a 100644 --- a/services/sqs/messages_test.go +++ b/services/sqs/messages_test.go @@ -3,7 +3,7 @@ package sqs_test import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "encoding/xml" "fmt" "maps" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/tags" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -1335,7 +1336,7 @@ func TestReceiveMessage_MaxNumberOfMessages_Validation(t *testing.T) { if tt.wantType != "" { var resp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp.Type) } }) diff --git a/services/sqs/permissions.go b/services/sqs/permissions.go index d3ebd9a774..3c9a2c424e 100644 --- a/services/sqs/permissions.go +++ b/services/sqs/permissions.go @@ -1,11 +1,12 @@ package sqs import ( - "encoding/json" + "encoding/json/v2" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // iamPolicyDocument represents the IAM resource policy document stored in the @@ -81,7 +82,7 @@ func buildQueueIAMPolicy(q *Queue) { Statement: stmts, } - raw, err := json.Marshal(doc) + raw, err := json.Marshal(doc, jsonx.V1()) if err != nil { return } diff --git a/services/sqs/permissions_test.go b/services/sqs/permissions_test.go index 031b31980d..1b8f0842a7 100644 --- a/services/sqs/permissions_test.go +++ b/services/sqs/permissions_test.go @@ -1,13 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -253,7 +254,7 @@ func TestAddPermission_UpdatesPolicyAttribute(t *testing.T) { assert.NotEmpty(t, policyJSON) var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(policyJSON), &doc)) + require.NoError(t, json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1())) assert.Equal(t, "2012-10-17", doc["Version"]) stmts := doc["Statement"].([]any) @@ -279,7 +280,7 @@ func TestAddPermission_WildcardAction(t *testing.T) { attrs := b2getAttrs(t, b, qURL, "Policy") var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc)) + require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc, jsonx.V1())) stmts := doc["Statement"].([]any) stmt := stmts[0].(map[string]any) actions := stmt["Action"].([]any) @@ -330,7 +331,7 @@ func TestAddPermission_MultipleStatements(t *testing.T) { attrs := b2getAttrs(t, b, qURL, "Policy") var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc)) + require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc, jsonx.V1())) stmts := doc["Statement"].([]any) assert.Len(t, stmts, 2) } @@ -357,7 +358,7 @@ func TestRemovePermission_PartialRemoval(t *testing.T) { attrs := b2getAttrs(t, b, qURL, "Policy") var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc)) + require.NoError(t, json.Unmarshal([]byte(attrs["Policy"]), &doc, jsonx.V1())) stmts := doc["Statement"].([]any) assert.Len(t, stmts, 1) stmt := stmts[0].(map[string]any) diff --git a/services/sqs/persistence.go b/services/sqs/persistence.go index a93409205d..918b8245a5 100644 --- a/services/sqs/persistence.go +++ b/services/sqs/persistence.go @@ -2,10 +2,12 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -94,21 +96,21 @@ type backendSnapshot struct { // wait-before-recreate rule for any queue deleted just before the // snapshot. Keyed by queueKey(region, name) since the deleted queue no // longer has a live Queue struct to attach this to. - RecentlyDeleted map[string]int64 `json:"recentlyDeleted,omitempty"` - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + RecentlyDeleted map[string]int64 `json:"recentlyDeleted,omitempty"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // marshalQueuesLocked marshals each queue's DTO to JSON while q.mu is held, then reassembles the JSON array. // queues must already be sorted, as [store.Table.Snapshot] returns them, to keep the wire format unchanged. -func marshalQueuesLocked(queues []*Queue) (json.RawMessage, error) { +func marshalQueuesLocked(queues []*Queue) (jsontext.Value, error) { if len(queues) == 0 { - return json.RawMessage("null"), nil + return jsontext.Value("null"), nil } - parts := make([]json.RawMessage, len(queues)) + parts := make([]jsontext.Value, len(queues)) for i, q := range queues { q.mu.Lock() @@ -135,7 +137,7 @@ func marshalQueuesLocked(queues []*Queue) (json.RawMessage, error) { IsFIFO: q.IsFIFO, FifoSeqCounter: q.fifoSeqCounter, LastPurgedAtUnixMilli: lastPurgedAtMillis, - }) + }, jsonx.V1()) q.mu.Unlock() @@ -146,7 +148,7 @@ func marshalQueuesLocked(queues []*Queue) (json.RawMessage, error) { parts[i] = data } - return json.Marshal(parts) + return json.Marshal(parts, jsonx.V1()) } // Snapshot serialises the backend state to JSON. diff --git a/services/sqs/queue_attributes.go b/services/sqs/queue_attributes.go index 51fd419c3d..8ea9aca476 100644 --- a/services/sqs/queue_attributes.go +++ b/services/sqs/queue_attributes.go @@ -1,9 +1,11 @@ package sqs import ( - "encoding/json" + "encoding/json/v2" "slices" "strconv" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // GetQueueAttributes returns queue attributes, computing dynamic ones on the fly. @@ -205,7 +207,7 @@ func validateRedriveAllowPolicy(raw string) error { SourceQueueArns []string `json:"sourceQueueArns"` } - if err := json.Unmarshal([]byte(raw), &policy); err != nil { + if err := json.Unmarshal([]byte(raw), &policy, jsonx.V1()); err != nil { return ErrInvalidAttribute } diff --git a/services/sqs/queues_test.go b/services/sqs/queues_test.go index 37666b250b..4aed59eeeb 100644 --- a/services/sqs/queues_test.go +++ b/services/sqs/queues_test.go @@ -1,7 +1,7 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -640,7 +641,7 @@ func TestCreateQueue_NameValidation(t *testing.T) { if tt.wantType != "" { var resp jsonErr - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp.Type) } }) diff --git a/services/sqs/sns_delivery.go b/services/sqs/sns_delivery.go index 85c6f24914..c92732f62d 100644 --- a/services/sqs/sns_delivery.go +++ b/services/sqs/sns_delivery.go @@ -2,7 +2,7 @@ package sqs import ( "context" - "encoding/json" + "encoding/json/v2" "strings" "time" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -145,7 +146,7 @@ func (b *InMemoryBackend) deliverToDLQ( DeadLetterTargetArn string `json:"deadLetterTargetArn"` } - if err := json.Unmarshal([]byte(redrivePolicy), &policy); err != nil { + if err := json.Unmarshal([]byte(redrivePolicy), &policy, jsonx.V1()); err != nil { return } @@ -262,7 +263,7 @@ func buildSNSEnvelope(ev *events.SNSPublishedEvent, sub events.SNSSubscriptionSn } } - b, err := json.Marshal(env) + b, err := json.Marshal(env, jsonx.V1()) if err != nil { return sub.Body(ev.Message) } diff --git a/services/sqs/sns_delivery_test.go b/services/sqs/sns_delivery_test.go index d1053453d2..793de684e0 100644 --- a/services/sqs/sns_delivery_test.go +++ b/services/sqs/sns_delivery_test.go @@ -1,13 +1,14 @@ package sqs_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/events" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" snsbackend "github.com/blackbirdworks/gopherstack/services/sns" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -147,7 +148,7 @@ func TestSNSToSQSDelivery(t *testing.T) { if tt.wantEnvType != "" { var env map[string]string - require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env)) + require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env, jsonx.V1())) assert.Equal(t, tt.wantEnvType, env["Type"]) assert.Equal(t, topic.TopicArn, env["TopicArn"]) assert.Equal(t, tt.wantEnvMessage, env["Message"]) @@ -224,7 +225,7 @@ func TestSNSToSQSRawMessageDelivery(t *testing.T) { // Ensure the body is NOT an SNS envelope (no "Type": "Notification" wrapper). var env map[string]any - if jsonErr := json.Unmarshal([]byte(out.Messages[0].Body), &env); jsonErr == nil { + if jsonErr := json.Unmarshal([]byte(out.Messages[0].Body), &env, jsonx.V1()); jsonErr == nil { assert.NotEqual(t, "Notification", env["Type"], "raw delivery body must not be SNS envelope") } @@ -313,7 +314,7 @@ func TestSNSToSQSDLQ(t *testing.T) { // DLQ receives the same body that was attempted to the failed queue. // Without RawMessageDelivery, that is the SNS envelope JSON. var env map[string]string - require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env)) + require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env, jsonx.V1())) assert.Equal(t, "Notification", env["Type"]) assert.Equal(t, tt.publishMessage, env["Message"]) } else { diff --git a/services/sqs/sns_envelope_test.go b/services/sqs/sns_envelope_test.go index 42792efe6f..9728465cc2 100644 --- a/services/sqs/sns_envelope_test.go +++ b/services/sqs/sns_envelope_test.go @@ -3,13 +3,14 @@ package sqs_test import ( "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/pem" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" snsbackend "github.com/blackbirdworks/gopherstack/services/sns" "github.com/blackbirdworks/gopherstack/services/sqs" ) @@ -46,7 +47,7 @@ func receiveOneEnvelope(t *testing.T, snsBk *snsbackend.InMemoryBackend, sqsBk * require.Len(t, out.Messages, 1) var env snsEnvelopeFields - require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env)) + require.NoError(t, json.Unmarshal([]byte(out.Messages[0].Body), &env, jsonx.V1())) return env } diff --git a/services/ssm/activations_test.go b/services/ssm/activations_test.go index 43f1f91132..d43e8d192e 100644 --- a/services/ssm/activations_test.go +++ b/services/ssm/activations_test.go @@ -2,7 +2,8 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -86,7 +88,7 @@ func TestResourceDataSync_CRUD(t *testing.T) { SyncType string `json:"SyncType"` } `json:"ResourceDataSyncItems"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&listResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &listResp, jsonx.V1())) require.Len(t, listResp.ResourceDataSyncItems, 1) item := listResp.ResourceDataSyncItems[0] assert.Equal(t, "SyncFromSource", item.SyncType) @@ -369,7 +371,7 @@ func TestStubOps_DeleteActivation(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"ActivationId": act.ActivationID}) + body, _ := json.Marshal(map[string]any{"ActivationId": act.ActivationID}, jsonx.V1()) rec := doRequest(t, h, "DeleteActivation", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -425,7 +427,7 @@ func TestCreateActivation_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateActivationOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.ActivationID) assert.NotEmpty(t, resp.ActivationCode) assert.True(t, strings.HasPrefix(resp.ActivationID, "act-")) @@ -545,18 +547,18 @@ func TestCreateActivationWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp ssm.CreateActivationOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.ActivationID) listBody, _ := json.Marshal(map[string]any{ "ResourceType": "Activation", "ResourceId": resp.ActivationID, - }) + }, jsonx.V1()) listRec := doRequest(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var tagResp ssm.ListTagsForResourceOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagResp, jsonx.V1())) assert.Len(t, tagResp.TagList, tt.tagCount) }) } @@ -839,7 +841,7 @@ func TestDeleteActivation_TableDriven(t *testing.T) { activationID = act.ActivationID } - body, _ := json.Marshal(map[string]any{"ActivationId": activationID}) + body, _ := json.Marshal(map[string]any{"ActivationId": activationID}, jsonx.V1()) rec := doRequest(t, h, "DeleteActivation", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) @@ -864,7 +866,7 @@ func TestDescribeInstanceProperties_DerivedFromActivations(t *testing.T) { var activation struct { ActivationID string `json:"ActivationId"` } - require.NoError(t, json.Unmarshal(actResp.Body.Bytes(), &activation)) + require.NoError(t, json.Unmarshal(actResp.Body.Bytes(), &activation, jsonx.V1())) require.NotEmpty(t, activation.ActivationID) propsResp := doRequest(t, h, "DescribeInstanceProperties", `{}`) diff --git a/services/ssm/agent_messages.go b/services/ssm/agent_messages.go index 21928cdf1e..bd7bb6e3cb 100644 --- a/services/ssm/agent_messages.go +++ b/services/ssm/agent_messages.go @@ -3,7 +3,7 @@ package ssm import ( "cmp" "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strconv" @@ -11,6 +11,8 @@ import ( "time" "gopkg.in/yaml.v3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // The ec2messages wire shapes below follow the open-source amazon-ssm-agent @@ -244,7 +246,10 @@ func (b *InMemoryBackend) queueAgentCancelLocked(region string, inv CommandInvoc return } - raw, _ := json.Marshal(map[string]string{"CancelMessageId": sendMessageID(inv.CommandID, inv.InstanceID)}) + raw, _ := json.Marshal( + map[string]string{"CancelMessageId": sendMessageID(inv.CommandID, inv.InstanceID)}, + jsonx.V1(), + ) q.push(&agentEnvelope{msg: AgentMessage{ CreatedDate: messageNow(), Destination: inv.InstanceID, @@ -278,7 +283,7 @@ func (b *InMemoryBackend) sendCommandPayload(region string, input *SendCommandIn payload["CloudWatchLogGroupName"] = cw.CloudWatchLogGroupName } - raw, _ := json.Marshal(payload) + raw, _ := json.Marshal(payload, jsonx.V1()) return string(raw) } @@ -290,7 +295,7 @@ func documentAsJSON(content, format string) any { if format == documentFormatYAML { err = yaml.Unmarshal([]byte(content), &doc) } else { - err = json.Unmarshal([]byte(content), &doc) + err = json.Unmarshal([]byte(content), &doc, jsonx.V1()) } if err != nil { @@ -496,7 +501,7 @@ func (b *InMemoryBackend) AgentSendReply(ctx context.Context, messageID, payload } var reply agentReply - if uerr := json.Unmarshal([]byte(payload), &reply); uerr != nil { + if uerr := json.Unmarshal([]byte(payload), &reply, jsonx.V1()); uerr != nil { return fmt.Errorf("%w: Payload is not a valid reply document", ErrValidationException) } diff --git a/services/ssm/agent_messages_test.go b/services/ssm/agent_messages_test.go index 909d276c2e..3f59f64f01 100644 --- a/services/ssm/agent_messages_test.go +++ b/services/ssm/agent_messages_test.go @@ -1,7 +1,7 @@ package ssm_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -39,7 +40,7 @@ type getMessagesResp struct { func agentCall(t *testing.T, h *ssm.Handler, op string, body any) *httptest.ResponseRecorder { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(string(raw))) @@ -61,7 +62,7 @@ func agentGetMessages(t *testing.T, h *ssm.Handler) getMessagesResp { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out getMessagesResp - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } @@ -84,7 +85,7 @@ func replyPayload(t *testing.T, docStatus string, plugin map[string]any) string "DocumentStatus": docStatus, "DocumentTraceOutput": "", "RuntimeStatus": map[string]any{"aws:runShellScript": plugin}, - }) + }, jsonx.V1()) require.NoError(t, err) return string(raw) @@ -161,7 +162,7 @@ func TestAgentPollingRunsCommandEndToEnd(t *testing.T) { } `json:"mainSteps"` } `json:"DocumentContent"` } - require.NoError(t, json.Unmarshal([]byte(msg.Payload), &payload)) + require.NoError(t, json.Unmarshal([]byte(msg.Payload), &payload, jsonx.V1())) assert.Equal(t, cmdID, payload.CommandID) assert.Equal(t, "AWS-RunShellScript", payload.DocumentName) assert.Equal(t, []string{"echo hello"}, payload.Parameters["commands"]) diff --git a/services/ssm/associations_test.go b/services/ssm/associations_test.go index ae1c265f63..6b4a5cdd16 100644 --- a/services/ssm/associations_test.go +++ b/services/ssm/associations_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -241,7 +242,7 @@ func TestStubOps_DeleteAssociation(t *testing.T) { require.NoError(t, err) body, _ := json.Marshal( - map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, + map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, jsonx.V1(), ) rec := doRequest(t, h, "DeleteAssociation", string(body)) assert.Equal(t, http.StatusOK, rec.Code) @@ -260,7 +261,7 @@ func TestStubOps_DescribeAssociation(t *testing.T) { require.NoError(t, err) body, _ := json.Marshal( - map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, + map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, jsonx.V1(), ) rec := doRequest(t, h, "DescribeAssociation", string(body)) assert.Equal(t, http.StatusOK, rec.Code) @@ -279,7 +280,7 @@ func TestStubOps_UpdateAssociation(t *testing.T) { require.NoError(t, err) body, _ := json.Marshal( - map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, + map[string]any{"AssociationId": assoc.AssociationDescription.AssociationID}, jsonx.V1(), ) rec := doRequest(t, h, "UpdateAssociation", string(body)) assert.Equal(t, http.StatusOK, rec.Code) @@ -356,7 +357,7 @@ func TestCreateAssociation_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateAssociationOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.AssociationDescription.AssociationID) assert.Equal(t, "MyDoc", resp.AssociationDescription.Name) assert.Equal(t, 1, backend.AssociationCount()) @@ -423,7 +424,7 @@ func TestCreateAssociationBatch(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateAssociationBatchOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.Successful, tt.wantSuccessful) assert.Len(t, resp.Failed, tt.wantFailed) assert.Equal(t, tt.wantSuccessful, backend.AssociationCount()) @@ -703,7 +704,7 @@ func TestDeleteAssociation_TableDriven(t *testing.T) { assocID = assoc.AssociationDescription.AssociationID } - body, _ := json.Marshal(map[string]any{"AssociationId": assocID}) + body, _ := json.Marshal(map[string]any{"AssociationId": assocID}, jsonx.V1()) rec := doRequest(t, h, "DeleteAssociation", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) diff --git a/services/ssm/automation_exec.go b/services/ssm/automation_exec.go index 5635f61fa9..b84ee1af0c 100644 --- a/services/ssm/automation_exec.go +++ b/services/ssm/automation_exec.go @@ -1,10 +1,12 @@ package ssm import ( - "encoding/json" + "encoding/json/v2" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // automationDoc is the minimal shape of an SSM automation (schemaVersion 0.3) @@ -117,7 +119,7 @@ func parseAutomationDocSteps(content string) []automationDocStep { } var doc automationDoc - if err := json.Unmarshal([]byte(content), &doc); err != nil { + if err := json.Unmarshal([]byte(content), &doc, jsonx.V1()); err != nil { return nil } diff --git a/services/ssm/automations_test.go b/services/ssm/automations_test.go index b382417165..c84e0d249b 100644 --- a/services/ssm/automations_test.go +++ b/services/ssm/automations_test.go @@ -2,7 +2,8 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -54,7 +56,7 @@ func TestChangeRequest(t *testing.T) { var startResp struct { AutomationExecutionID string `json:"AutomationExecutionId"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&startResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &startResp, jsonx.V1())) // Runbooks must round-trip on GetAutomationExecution. rec = doRequest(t, h, "GetAutomationExecution", @@ -69,7 +71,7 @@ func TestChangeRequest(t *testing.T) { } `json:"Runbooks"` } `json:"AutomationExecution"` } - require.NoError(t, json.NewDecoder(rec.Body).Decode(&getResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &getResp, jsonx.V1())) require.Len(t, getResp.AutomationExecution.Runbooks, 1) assert.Equal(t, "AWS-RunShellScript", getResp.AutomationExecution.Runbooks[0].DocumentName) assert.Equal(t, "1", getResp.AutomationExecution.Runbooks[0].MaxConcurrency) @@ -412,17 +414,17 @@ func TestAutomationExecution_ParametersRoundTrip(t *testing.T) { "DocumentName": "AWS-RunShellScript", "Parameters": tt.parameters, "Mode": tt.mode, - }) + }, jsonx.V1()) rec := doRequest(t, h, "StartAutomationExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execID := startResp["AutomationExecutionId"].(string) assert.NotEmpty(t, execID) // GetAutomationExecution should return parameters. - body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}) + body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}, jsonx.V1()) rec = doRequest(t, h, "GetAutomationExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -444,19 +446,19 @@ func TestAutomationApprovals_ApproveSignal(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execID := startResp["AutomationExecutionId"].(string) // Send Approve signal. body, _ := json.Marshal(map[string]any{ "AutomationExecutionId": execID, "SignalType": "Approve", - }) + }, jsonx.V1()) rec = doRequest(t, h, "SendAutomationSignal", string(body)) require.Equal(t, http.StatusOK, rec.Code) // GetAutomationExecution should show Approved status. - body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}) + body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}, jsonx.V1()) rec = doRequest(t, h, "GetAutomationExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "Approved") @@ -470,17 +472,17 @@ func TestAutomationApprovals_RejectSignal(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) execID := startResp["AutomationExecutionId"].(string) body, _ := json.Marshal(map[string]any{ "AutomationExecutionId": execID, "SignalType": "Reject", - }) + }, jsonx.V1()) rec = doRequest(t, h, "SendAutomationSignal", string(body)) require.Equal(t, http.StatusOK, rec.Code) - body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}) + body, _ = json.Marshal(map[string]any{"AutomationExecutionId": execID}, jsonx.V1()) rec = doRequest(t, h, "GetAutomationExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "Rejected") @@ -524,7 +526,7 @@ func TestAutomationExecution_WarningMessageAbsentFromWire(t *testing.T) { require.Equal(t, http.StatusOK, start.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(start.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(start.Body.Bytes(), &startResp, jsonx.V1())) execID, _ := startResp["AutomationExecutionId"].(string) require.NotEmpty(t, execID) @@ -542,7 +544,7 @@ func TestGetCalendarState_MissingDocumentReturnsError(t *testing.T) { body, _ := json.Marshal(map[string]any{ "CalendarNames": []string{"DoesNotExist"}, - }) + }, jsonx.V1()) rec := doRequest(t, h, "GetCalendarState", string(body)) assert.NotEqual(t, http.StatusOK, rec.Code, "missing calendar should return error") } diff --git a/services/ssm/client_token_bound_test.go b/services/ssm/client_token_bound_test.go index 92fcce6e3b..4e6e187e8b 100644 --- a/services/ssm/client_token_bound_test.go +++ b/services/ssm/client_token_bound_test.go @@ -1,13 +1,15 @@ package ssm_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -49,10 +51,10 @@ func TestClientTokenMemoIsBounded(t *testing.T) { } var snap struct { - Idempotency map[string]map[string]json.RawMessage `json:"idempotency"` + Idempotency map[string]map[string]jsontext.Value `json:"idempotency"` } - require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &snap)) + require.NoError(t, json.Unmarshal(b.Snapshot(t.Context()), &snap, jsonx.V1())) for _, recs := range snap.Idempotency { assert.LessOrEqual(t, len(recs), 1024) diff --git a/services/ssm/cloud_connector_test.go b/services/ssm/cloud_connector_test.go index 593ee62c24..22414efffd 100644 --- a/services/ssm/cloud_connector_test.go +++ b/services/ssm/cloud_connector_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -370,7 +371,7 @@ func Test_CloudConnector_HandlerDispatch(t *testing.T) { }, "DisplayName": "my-connector", "RoleArn": "arn:aws:iam::123456789012:role/r", - }) + }, jsonx.V1()) rec := doRequest(t, h, "CreateCloudConnector", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) @@ -378,15 +379,15 @@ func Test_CloudConnector_HandlerDispatch(t *testing.T) { var createResp struct { CloudConnectorID string `json:"CloudConnectorId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) require.NotEmpty(t, createResp.CloudConnectorID) - getBody, _ := json.Marshal(map[string]any{"CloudConnectorId": createResp.CloudConnectorID}) + getBody, _ := json.Marshal(map[string]any{"CloudConnectorId": createResp.CloudConnectorID}, jsonx.V1()) rec = doRequest(t, h, "GetCloudConnector", string(getBody)) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "my-connector", getResp["DisplayName"]) // CreatedAt/UpdatedAt must serialize as epoch-seconds numbers, not ISO8601 strings. _, isNumber := getResp["CreatedAt"].(float64) @@ -398,10 +399,10 @@ func Test_CloudConnector_HandlerDispatch(t *testing.T) { var errResp struct { Type string `json:"__type"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp.Type) - deleteBody, _ := json.Marshal(map[string]any{"CloudConnectorId": createResp.CloudConnectorID}) + deleteBody, _ := json.Marshal(map[string]any{"CloudConnectorId": createResp.CloudConnectorID}, jsonx.V1()) rec = doRequest(t, h, "DeleteCloudConnector", string(deleteBody)) require.Equal(t, http.StatusOK, rec.Code) } diff --git a/services/ssm/command_run_doc.go b/services/ssm/command_run_doc.go index 04d642f502..31dc383d91 100644 --- a/services/ssm/command_run_doc.go +++ b/services/ssm/command_run_doc.go @@ -2,7 +2,7 @@ package ssm import ( "cmp" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "regexp" @@ -10,6 +10,8 @@ import ( "strings" "gopkg.in/yaml.v3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -68,7 +70,7 @@ func parseScriptSteps(content, format string, params map[string][]string) ([]scr if format == documentFormatYAML { err = yaml.Unmarshal([]byte(content), &doc) } else { - err = json.Unmarshal([]byte(content), &doc) + err = json.Unmarshal([]byte(content), &doc, jsonx.V1()) } if err != nil { diff --git a/services/ssm/commands_test.go b/services/ssm/commands_test.go index d648c6b4ed..9ab8802523 100644 --- a/services/ssm/commands_test.go +++ b/services/ssm/commands_test.go @@ -2,7 +2,8 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -385,7 +387,7 @@ func TestCancelCommand_Success(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp, jsonx.V1())) cmd, ok := sendResp["Command"].(map[string]any) require.True(t, ok) @@ -454,13 +456,13 @@ func TestCancelCommand_CancelsInvocations(t *testing.T) { h, _ := newTestHandler(t) - instanceIDsJSON, _ := json.Marshal(tt.instanceIDs) + instanceIDsJSON, _ := json.Marshal(tt.instanceIDs, jsonx.V1()) sendBody := `{"DocumentName":"AWS-RunShellScript","InstanceIds":` + string(instanceIDsJSON) + `}` sendRec := doRequest(t, h, "SendCommand", sendBody) require.Equal(t, http.StatusOK, sendRec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp, jsonx.V1())) cmd := sendResp["Command"].(map[string]any) cmdID := cmd["CommandId"].(string) @@ -473,7 +475,7 @@ func TestCancelCommand_CancelsInvocations(t *testing.T) { require.Equal(t, http.StatusOK, invRec.Code) var invResp map[string]any - require.NoError(t, json.Unmarshal(invRec.Body.Bytes(), &invResp)) + require.NoError(t, json.Unmarshal(invRec.Body.Bytes(), &invResp, jsonx.V1())) assert.Equal(t, tt.wantInvocStatus, invResp["Status"]) assert.Equal(t, tt.wantInvocStatus, invResp["StatusDetails"]) } @@ -498,7 +500,7 @@ func TestCancelCommand_ScopedToInstanceIDs(t *testing.T) { require.Equal(t, http.StatusOK, sendRec.Code) var sendResp map[string]any - require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp)) + require.NoError(t, json.Unmarshal(sendRec.Body.Bytes(), &sendResp, jsonx.V1())) cmdID := sendResp["Command"].(map[string]any)["CommandId"].(string) cancelRec := doRequest(t, h, "CancelCommand", `{"CommandId":"`+cmdID+`","InstanceIds":["i-001"]}`) @@ -508,14 +510,14 @@ func TestCancelCommand_ScopedToInstanceIDs(t *testing.T) { require.Equal(t, http.StatusOK, invRec.Code) var cancelledInv map[string]any - require.NoError(t, json.Unmarshal(invRec.Body.Bytes(), &cancelledInv)) + require.NoError(t, json.Unmarshal(invRec.Body.Bytes(), &cancelledInv, jsonx.V1())) assert.Equal(t, "Cancelled", cancelledInv["Status"]) otherRec := doRequest(t, h, "GetCommandInvocation", `{"CommandId":"`+cmdID+`","InstanceId":"i-002"}`) require.Equal(t, http.StatusOK, otherRec.Code) var otherInv map[string]any - require.NoError(t, json.Unmarshal(otherRec.Body.Bytes(), &otherInv)) + require.NoError(t, json.Unmarshal(otherRec.Body.Bytes(), &otherInv, jsonx.V1())) assert.NotEqual(t, "Cancelled", otherInv["Status"], "un-named instance's invocation must not be cancelled") } @@ -887,7 +889,7 @@ func TestHandler_SendCommand(t *testing.T) { if tt.wantStatus == http.StatusOK { var out ssm.SendCommandOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.NotEmpty(t, out.Command.CommandID) assert.Equal(t, "AWS-RunShellScript", out.Command.DocumentName) } @@ -936,7 +938,7 @@ func TestHandler_ListCommands(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out ssm.ListCommandsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.Commands, tt.wantCount) }) } @@ -957,7 +959,7 @@ func TestHandler_GetCommandInvocation(t *testing.T) { t.Helper() rec := doRequest(t, h, "SendCommand", `{"DocumentName":"AWS-RunShellScript","InstanceIDs":["i-abc"]}`) var out ssm.SendCommandOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) return out.Command.CommandID }, @@ -973,7 +975,7 @@ func TestHandler_GetCommandInvocation(t *testing.T) { t.Helper() rec := doRequest(t, h, "SendCommand", `{"DocumentName":"AWS-RunShellScript","InstanceIDs":["i-abc"]}`) var out ssm.SendCommandOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) return out.Command.CommandID }, @@ -1007,7 +1009,7 @@ func TestHandler_GetCommandInvocation(t *testing.T) { if tt.wantCommandStatus != "" { var out ssm.GetCommandInvocationOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantCommandStatus, out.Status) assert.Equal(t, tt.wantCommandStatus, out.StatusDetails) } @@ -1055,7 +1057,7 @@ func TestHandler_ListCommandInvocations(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out ssm.ListCommandInvocationsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.CommandInvocations, tt.wantCount) }) } diff --git a/services/ssm/document_metadata_test.go b/services/ssm/document_metadata_test.go index e25b922b7d..7476a7b700 100644 --- a/services/ssm/document_metadata_test.go +++ b/services/ssm/document_metadata_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -143,12 +144,12 @@ func Test_DescribeDocument_OmitsContentAndHonorsVersionSelector(t *testing.T) { // "Content" field at all — assert this at the JSON level since a Go // zero-value string would round-trip identically to "field absent" in a // struct-based comparison. - createJSON, marshalErr := json.Marshal(createOut.DocumentDescription) + createJSON, marshalErr := json.Marshal(createOut.DocumentDescription, jsonx.V1()) require.NoError(t, marshalErr) assert.NotContains(t, string(createJSON), "Content", "CreateDocumentOutput.DocumentDescription must not serialize a Content field") - updateJSON, marshalErr := json.Marshal(updateOut.DocumentDescription) + updateJSON, marshalErr := json.Marshal(updateOut.DocumentDescription, jsonx.V1()) require.NoError(t, marshalErr) assert.NotContains(t, string(updateJSON), "Content", "UpdateDocumentOutput.DocumentDescription must not serialize a Content field") @@ -172,7 +173,7 @@ func Test_DescribeDocument_OmitsContentAndHonorsVersionSelector(t *testing.T) { require.NoError(t, describeErr) assert.Equal(t, tc.wantVersion, describeOut.Document.DocumentVersion) - describeJSON, jsonErr := json.Marshal(describeOut.Document) + describeJSON, jsonErr := json.Marshal(describeOut.Document, jsonx.V1()) require.NoError(t, jsonErr) assert.NotContains(t, string(describeJSON), "Content", "DescribeDocumentOutput.Document must not serialize a Content field") @@ -639,7 +640,7 @@ func TestDocumentVersions_IsDefaultVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["DocumentVersions"].([]any) require.True(t, ok, "DocumentVersions must be an array") @@ -649,7 +650,7 @@ func TestDocumentVersions_IsDefaultVersion(t *testing.T) { body, _ := json.Marshal(map[string]any{ "Name": "MyDoc", "DocumentVersion": "2", - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateDocumentDefaultVersion", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -657,7 +658,7 @@ func TestDocumentVersions_IsDefaultVersion(t *testing.T) { rec = doRequest(t, h, "ListDocumentVersions", `{"Name":"MyDoc"}`) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions = resp["DocumentVersions"].([]any) var defaultFound bool @@ -723,7 +724,7 @@ func TestDocumentVersions_TableDriven(t *testing.T) { body, _ := json.Marshal(map[string]any{ "Name": "td-doc-" + tt.name, "DocumentVersion": tt.setDefault, - }) + }, jsonx.V1()) rec := doRequest(t, h, "UpdateDocumentDefaultVersion", string(body)) require.Equal(t, http.StatusOK, rec.Code) } @@ -733,7 +734,7 @@ func TestDocumentVersions_TableDriven(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions := resp["DocumentVersions"].([]any) assert.GreaterOrEqual(t, len(versions), tt.wantVersions) diff --git a/services/ssm/document_parameters.go b/services/ssm/document_parameters.go index c033542299..79054369b0 100644 --- a/services/ssm/document_parameters.go +++ b/services/ssm/document_parameters.go @@ -1,11 +1,13 @@ package ssm import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "gopkg.in/yaml.v3" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // DocumentParameter is one declared document parameter (types.DocumentParameter). @@ -37,7 +39,7 @@ func parseDocumentParameters(content, format string) []DocumentParameter { if format == documentFormatYAML { err = yaml.Unmarshal([]byte(content), &doc) } else { - err = json.Unmarshal([]byte(content), &doc) + err = json.Unmarshal([]byte(content), &doc, jsonx.V1()) } if err != nil || len(doc.Parameters) == 0 { @@ -69,7 +71,7 @@ func documentDefaultString(v any) string { case string: return val default: - if b, err := json.Marshal(val); err == nil { + if b, err := json.Marshal(val, jsonx.V1()); err == nil { return string(b) } diff --git a/services/ssm/document_permissions_test.go b/services/ssm/document_permissions_test.go index d5dab4a8cf..c40b9bed1c 100644 --- a/services/ssm/document_permissions_test.go +++ b/services/ssm/document_permissions_test.go @@ -2,13 +2,15 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -165,7 +167,7 @@ func TestDistributorPackage_TableDriven(t *testing.T) { "Filters": []map[string]any{ {"Key": "DocumentType", "Values": []string{tt.filterType}}, }, - }) + }, jsonx.V1()) rec := doRequest(t, h, "ListDocuments", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -213,7 +215,7 @@ func TestHandler_CreateDocument(t *testing.T) { if tt.wantName != "" { var out ssm.CreateDocumentOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantName, out.DocumentDescription.Name) assert.Equal(t, "1", out.DocumentDescription.DocumentVersion) } @@ -319,7 +321,7 @@ func TestHandler_UpdateDocument(t *testing.T) { if tt.wantVer != "" { var out ssm.UpdateDocumentOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantVer, out.DocumentDescription.DocumentVersion) } }) @@ -367,7 +369,7 @@ func TestHandler_DescribeDocumentPermission(t *testing.T) { if tt.wantStatus == http.StatusOK { var out ssm.DescribeDocumentPermissionOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.AccountIDs, tt.wantLen) } }) @@ -467,7 +469,7 @@ func TestHandler_ListDocumentVersions(t *testing.T) { if tt.wantCount > 0 { var out ssm.ListDocumentVersionsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Len(t, out.DocumentVersions, tt.wantCount) } }) diff --git a/services/ssm/document_test.go b/services/ssm/document_test.go index 927f94b09c..a57c97e5f1 100644 --- a/services/ssm/document_test.go +++ b/services/ssm/document_test.go @@ -2,7 +2,8 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -458,7 +460,7 @@ func TestGetCalendarState_WithCalendarDocument(t *testing.T) { // GetCalendarState with the calendar name should succeed. body, _ := json.Marshal(map[string]any{ "CalendarNames": []string{"MyCalendar"}, - }) + }, jsonx.V1()) rec := doRequest(t, h, "GetCalendarState", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "OPEN") @@ -478,7 +480,7 @@ func TestGetCalendarState_NonCalendarDocumentReturnsError(t *testing.T) { body, _ := json.Marshal(map[string]any{ "CalendarNames": []string{"NotACalendar"}, - }) + }, jsonx.V1()) rec := doRequest(t, h, "GetCalendarState", string(body)) assert.NotEqual(t, http.StatusOK, rec.Code, "non-ChangeCalendar doc should return error") } @@ -495,7 +497,7 @@ func TestHandler_CreateDocument_Duplicate(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var errResp map[string]string - require.NoError(t, json.NewDecoder(rec2.Body).Decode(&errResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec2.Body), &errResp, jsonx.V1())) assert.Contains(t, errResp["__type"], "DocumentAlreadyExists") } func TestHandler_GetDocument(t *testing.T) { @@ -549,7 +551,7 @@ func TestHandler_GetDocument(t *testing.T) { if tt.wantDoc != "" { var out ssm.GetDocumentOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantDoc, out.Name) } }) @@ -596,7 +598,7 @@ func TestHandler_DescribeDocument(t *testing.T) { if tt.wantDoc != "" { var out ssm.DescribeDocumentOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.Equal(t, tt.wantDoc, out.Document.Name) } }) @@ -643,7 +645,7 @@ func TestHandler_ListDocuments(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out ssm.ListDocumentsOutput - require.NoError(t, json.NewDecoder(rec.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &out, jsonx.V1())) assert.GreaterOrEqual(t, len(out.DocumentIdentifiers), tt.wantMin) }) } diff --git a/services/ssm/documents.go b/services/ssm/documents.go index df1ac7fb23..3f3e8d56c4 100644 --- a/services/ssm/documents.go +++ b/services/ssm/documents.go @@ -2,7 +2,7 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -12,6 +12,7 @@ import ( "gopkg.in/yaml.v3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -1139,7 +1140,7 @@ func validateDocumentContent(docType, format, content string) error { if format == documentFormatYAML { err = yaml.Unmarshal([]byte(content), &doc) } else { - err = json.Unmarshal([]byte(content), &doc) + err = json.Unmarshal([]byte(content), &doc, jsonx.V1()) } if err != nil || doc == nil { diff --git a/services/ssm/epoch_seconds_wire_shape_test.go b/services/ssm/epoch_seconds_wire_shape_test.go index 05c94246b2..6c75f41da2 100644 --- a/services/ssm/epoch_seconds_wire_shape_test.go +++ b/services/ssm/epoch_seconds_wire_shape_test.go @@ -26,13 +26,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "regexp" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -54,7 +55,7 @@ func TestEpochSecondsWireShape_AssociationExecution(t *testing.T) { body, err := json.Marshal(ssm.AssociationExecution{ AssociationID: "assoc-1", ExecutionID: "exec-1", Status: "Success", ExecutionDate: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "ExecutionDate") } @@ -65,7 +66,7 @@ func TestEpochSecondsWireShape_MaintenanceWindowExecution(t *testing.T) { body, err := json.Marshal(ssm.MaintenanceWindowExecution{ WindowID: "mw-1", WindowExecutionID: "mwexec-1", Status: "SUCCESS", StartTime: 1_700_000_000, EndTime: 1_700_003_600, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "StartTime") numericJSONField(t, body, "EndTime") @@ -76,7 +77,7 @@ func TestEpochSecondsWireShape_InstanceInformation(t *testing.T) { body, err := json.Marshal(ssm.InstanceInformation{ InstanceID: "i-1", PingStatus: "Online", RegistrationDate: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "RegistrationDate") } @@ -86,7 +87,7 @@ func TestEpochSecondsWireShape_InstanceAssociationStatusInfo(t *testing.T) { body, err := json.Marshal(ssm.InstanceAssociationStatusInfo{ AssociationID: "assoc-1", Name: "MyDoc", Status: "Success", ExecutionDate: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "ExecutionDate") } @@ -96,7 +97,7 @@ func TestEpochSecondsWireShape_InstancePatchState(t *testing.T) { body, err := json.Marshal(ssm.InstancePatchState{ InstanceID: "i-1", PatchGroup: "grp1", Operation: "Scan", OperationStartTime: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "OperationStartTime") } @@ -106,7 +107,7 @@ func TestEpochSecondsWireShape_PatchComplianceData(t *testing.T) { body, err := json.Marshal(ssm.PatchComplianceData{ Title: "KB123", State: "INSTALLED", InstalledTime: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "InstalledTime") } @@ -117,7 +118,7 @@ func TestEpochSecondsWireShape_ResourceDataSync(t *testing.T) { body, err := json.Marshal(ssm.ResourceDataSync{ SyncName: "sync-1", SyncType: "SyncToDestination", LastStatus: "InProgress", SyncCreatedTime: 1_700_000_000, LastSyncTime: 1_700_000_100, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "SyncCreatedTime") numericJSONField(t, body, "LastSyncTime") @@ -129,7 +130,7 @@ func TestEpochSecondsWireShape_InventoryDeletion(t *testing.T) { body, err := json.Marshal(ssm.InventoryDeletion{ DeletionID: "deletion-1", TypeName: "AWS:Application", LastStatus: "Complete", DeletionStartTime: 1_700_000_000, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "DeletionStartTime") } @@ -140,7 +141,7 @@ func TestEpochSecondsWireShape_Node(t *testing.T) { body, err := json.Marshal(ssm.Node{ ID: "i-1", CaptureTime: 1_700_000_000, NodeType: &ssm.NodeType{Instance: &ssm.NodeInstanceInfo{PlatformType: "Linux"}}, - }) + }, jsonx.V1()) require.NoError(t, err) numericJSONField(t, body, "CaptureTime") } diff --git a/services/ssm/handler.go b/services/ssm/handler.go index 95dff1eac0..e7a2dea719 100644 --- a/services/ssm/handler.go +++ b/services/ssm/handler.go @@ -2,7 +2,7 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -132,7 +133,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -185,7 +186,7 @@ type ssmActionFn func(context.Context, []byte) (any, error) func jsonOp[I, O any](fn func(context.Context, *I) (O, error), checks ...func(*I) error) ssmActionFn { return func(ctx context.Context, b []byte) (any, error) { var input I - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -239,7 +240,7 @@ func (h *Handler) ssmTagOps() map[string]ssmActionFn { return map[string]ssmActionFn{ "AddTagsToResource": func(ctx context.Context, b []byte) (any, error) { var input AddTagsToResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -247,7 +248,7 @@ func (h *Handler) ssmTagOps() map[string]ssmActionFn { }, "RemoveTagsFromResource": func(ctx context.Context, b []byte) (any, error) { var input RemoveTagsFromResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -316,7 +317,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // classifySSMError maps a backend error to an HTTP status code and error type string. @@ -562,7 +563,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin Message: reqErr.Error(), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } diff --git a/services/ssm/handler_agent.go b/services/ssm/handler_agent.go index 75f095eb6e..737b6b037d 100644 --- a/services/ssm/handler_agent.go +++ b/services/ssm/handler_agent.go @@ -2,13 +2,14 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" "slices" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -42,7 +43,7 @@ func (h *Handler) agentOps() map[string]ssmActionFn { return map[string]ssmActionFn{ "GetEndpoint": func(ctx context.Context, body []byte) (any, error) { var in agentGetEndpointInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } @@ -56,7 +57,7 @@ func (h *Handler) agentOps() map[string]ssmActionFn { }, "GetMessages": func(ctx context.Context, body []byte) (any, error) { var in AgentGetMessagesInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } @@ -77,7 +78,7 @@ func (h *Handler) agentOps() map[string]ssmActionFn { }), "SendReply": func(ctx context.Context, body []byte) (any, error) { var in agentSendReplyInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } @@ -89,7 +90,7 @@ func (h *Handler) agentOps() map[string]ssmActionFn { func agentMessageOp(fn func(context.Context, *agentMessageIDInput) error) ssmActionFn { return func(ctx context.Context, body []byte) (any, error) { var in agentMessageIDInput - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } @@ -117,7 +118,7 @@ func (h *Handler) handleAgent(ctx context.Context, c *echo.Context) error { return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) }, h.handleError, ) diff --git a/services/ssm/handler_parameters_test.go b/services/ssm/handler_parameters_test.go index cf757e93b8..c9839cdb64 100644 --- a/services/ssm/handler_parameters_test.go +++ b/services/ssm/handler_parameters_test.go @@ -2,7 +2,8 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -620,9 +622,9 @@ func TestHandlerParameterListingOperations(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var out struct { - Parameters []json.RawMessage `json:"Parameters"` + Parameters []jsontext.Value `json:"Parameters"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Parameters, tt.wantCount) }) } @@ -659,7 +661,7 @@ func TestTagOperations(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var listOut ssm.ListTagsForResourceOutput - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.TagList, 2) assert.Equal(t, "env", listOut.TagList[0].Key) assert.Equal(t, "prod", listOut.TagList[0].Value) diff --git a/services/ssm/handler_test.go b/services/ssm/handler_test.go index 95b2483da7..c9a84f6e31 100644 --- a/services/ssm/handler_test.go +++ b/services/ssm/handler_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -255,7 +256,7 @@ func TestHandler_ErrorCases(t *testing.T) { if tt.wantErrTyp != "" { var errResp service.JSONErrorResponse - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantErrTyp, errResp.Type) } }) @@ -362,7 +363,7 @@ func TestHandler_GetParametersByPathViaHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out ssm.GetParametersByPathOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Parameters, 2) } @@ -381,7 +382,7 @@ func TestHandler_DescribeParametersViaHTTP(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var out ssm.DescribeParametersOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Parameters, 2) } diff --git a/services/ssm/idempotency.go b/services/ssm/idempotency.go index 11750c0697..e33fed7f7a 100644 --- a/services/ssm/idempotency.go +++ b/services/ssm/idempotency.go @@ -3,9 +3,11 @@ package ssm import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxIdempotencyRecords caps remembered tokens per region; the oldest are evicted first. @@ -20,19 +22,19 @@ type idempotencyRecord struct { } func idempotencyFingerprint(req any) string { - raw, err := json.Marshal(req) + raw, err := json.Marshal(req, jsonx.V1()) if err != nil { return "" } var m map[string]any - if json.Unmarshal(raw, &m) != nil { + if json.Unmarshal(raw, &m, jsonx.V1()) != nil { return "" } delete(m, "ClientToken") - canonical, err := json.Marshal(m) + canonical, err := json.Marshal(m, jsonx.V1()) if err != nil { return "" } diff --git a/services/ssm/inventory_test.go b/services/ssm/inventory_test.go index 3cd6ea8176..6b1b32966e 100644 --- a/services/ssm/inventory_test.go +++ b/services/ssm/inventory_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -1050,7 +1051,7 @@ func TestComplianceSummaries_AggregateByCType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ComplianceSummaryItems"].([]any) assert.NotEmpty(t, items, "should have compliance summary items") @@ -1087,7 +1088,7 @@ func TestResourceComplianceSummaries_PerResource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items := resp["ResourceComplianceSummaryItems"].([]any) assert.NotEmpty(t, items) @@ -1125,7 +1126,7 @@ func TestResourceComplianceSummaries_NonCompliantStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) for _, item := range resp["ResourceComplianceSummaryItems"].([]any) { m := item.(map[string]any) @@ -1143,7 +1144,7 @@ func TestGetInventorySchema_ReturnsBuiltInTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) schemas := resp["Schemas"].([]any) assert.NotEmpty(t, schemas, "GetInventorySchema should return built-in schema types") @@ -1187,7 +1188,7 @@ func TestGetInventorySchema_FilterByTypeName(t *testing.T) { h, _ := newTestHandler(t) - body, _ := json.Marshal(map[string]any{"TypeName": tt.typeName}) + body, _ := json.Marshal(map[string]any{"TypeName": tt.typeName}, jsonx.V1()) rec := doRequest(t, h, "GetInventorySchema", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), tt.wantContains) @@ -1219,7 +1220,7 @@ func TestInventory_TypeMergeAndLookup(t *testing.T) { body, _ := json.Marshal(map[string]any{ "InstanceId": "i-inv001", "TypeName": "AWS:Application", - }) + }, jsonx.V1()) rec := doRequest(t, h, "ListInventoryEntries", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "aws-cli") @@ -1257,7 +1258,7 @@ func TestInventory_TypeMergeAndLookup(t *testing.T) { body, _ = json.Marshal(map[string]any{ "InstanceId": "i-inv001", "TypeName": "AWS:Application", - }) + }, jsonx.V1()) rec = doRequest(t, h, "ListInventoryEntries", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "2.1") diff --git a/services/ssm/janitor.go b/services/ssm/janitor.go index 2b50f2c0c8..0843e4bdf0 100644 --- a/services/ssm/janitor.go +++ b/services/ssm/janitor.go @@ -2,10 +2,11 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "time" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/telemetry" "github.com/blackbirdworks/gopherstack/pkgs/worker" @@ -303,7 +304,7 @@ func parameterExpiresAt(policiesJSON string) (time.Time, bool) { } var policies []parameterExpirationPolicy - if err := json.Unmarshal([]byte(policiesJSON), &policies); err != nil { + if err := json.Unmarshal([]byte(policiesJSON), &policies, jsonx.V1()); err != nil { return time.Time{}, false } diff --git a/services/ssm/maintenance_window_execution_test.go b/services/ssm/maintenance_window_execution_test.go index 8f211e14e2..9b195b790f 100644 --- a/services/ssm/maintenance_window_execution_test.go +++ b/services/ssm/maintenance_window_execution_test.go @@ -2,13 +2,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -195,7 +196,7 @@ func TestCancelMaintenanceWindowExecution(t *testing.T) { body := tt.makeBody(t, b) var reqBody map[string]string - require.NoError(t, json.Unmarshal([]byte(body), &reqBody)) + require.NoError(t, json.Unmarshal([]byte(body), &reqBody, jsonx.V1())) wantExecID := reqBody["WindowExecutionId"] rec := doRequest(t, h, "CancelMaintenanceWindowExecution", body) @@ -207,7 +208,7 @@ func TestCancelMaintenanceWindowExecution(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, wantExecID, resp["WindowExecutionId"]) }) } diff --git a/services/ssm/maintenance_window_lifecycle_test.go b/services/ssm/maintenance_window_lifecycle_test.go index c6bb6d3753..953fd272c4 100644 --- a/services/ssm/maintenance_window_lifecycle_test.go +++ b/services/ssm/maintenance_window_lifecycle_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -189,7 +190,7 @@ func TestStubOps_DeleteMaintenanceWindow(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID}) + body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID}, jsonx.V1()) rec := doRequest(t, h, "DeleteMaintenanceWindow", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -208,7 +209,7 @@ func TestStubOps_GetMaintenanceWindow(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID}) + body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID}, jsonx.V1()) rec := doRequest(t, h, "GetMaintenanceWindow", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -227,7 +228,7 @@ func TestStubOps_UpdateMaintenanceWindow(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID, "Name": "updated-window"}) + body, _ := json.Marshal(map[string]any{"WindowId": mw.WindowID, "Name": "updated-window"}, jsonx.V1()) rec := doRequest(t, h, "UpdateMaintenanceWindow", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -263,7 +264,7 @@ func TestCreateMaintenanceWindow_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateMaintenanceWindowOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.WindowID) assert.True(t, strings.HasPrefix(resp.WindowID, "mw-")) assert.Equal(t, 1, backend.MaintenanceWindowCount()) @@ -296,7 +297,7 @@ func TestMaintenanceWindow_ScheduleFieldsRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var created ssm.CreateMaintenanceWindowOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) out, err := b.GetMaintenanceWindow(context.Background(), &ssm.GetMaintenanceWindowInput{WindowID: created.WindowID}) require.NoError(t, err) @@ -750,7 +751,7 @@ func TestMaintenanceWindowsForTarget_FindsByTarget(t *testing.T) { "Targets": []map[string]any{ {"Key": "InstanceIds", "Values": []string{"i-aabbcc112233"}}, }, - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterTargetWithMaintenanceWindow", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -760,7 +761,7 @@ func TestMaintenanceWindowsForTarget_FindsByTarget(t *testing.T) { "Targets": []map[string]any{ {"Key": "InstanceIds", "Values": []string{"i-aabbcc112233"}}, }, - }) + }, jsonx.V1()) rec = doRequest(t, h, "DescribeMaintenanceWindowsForTarget", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), mw.WindowID) diff --git a/services/ssm/maintenance_window_targets_test.go b/services/ssm/maintenance_window_targets_test.go index 585da73657..257aa6aa1a 100644 --- a/services/ssm/maintenance_window_targets_test.go +++ b/services/ssm/maintenance_window_targets_test.go @@ -2,13 +2,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -32,7 +33,7 @@ func TestStubOps_RegisterTargetWithMaintenanceWindow(t *testing.T) { "Targets": []map[string]any{ {"Key": "InstanceIds", "Values": []string{"i-1234567890abcdef0"}}, }, - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterTargetWithMaintenanceWindow", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -55,7 +56,7 @@ func TestStubOps_RegisterTaskWithMaintenanceWindow(t *testing.T) { "WindowId": mw.WindowID, "TaskType": "RUN_COMMAND", "TaskArn": "AWS-RunShellScript", - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterTaskWithMaintenanceWindow", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -104,19 +105,19 @@ func TestMaintenanceWindowTask_ServiceRoleArn(t *testing.T) { "ServiceRoleArn": tt.serviceRoleArn, "MaxConcurrency": tt.maxConcurrency, "MaxErrors": tt.maxErrors, - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterTaskWithMaintenanceWindow", string(body)) require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) taskID := regResp["WindowTaskId"].(string) // GetMaintenanceWindowTask should return ServiceRoleArn. body, _ = json.Marshal(map[string]any{ "WindowId": mw.WindowID, "WindowTaskId": taskID, - }) + }, jsonx.V1()) rec = doRequest(t, h, "GetMaintenanceWindowTask", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -147,12 +148,12 @@ func TestMaintenanceWindowTask_UpdateServiceRoleArn(t *testing.T) { "WindowId": mw.WindowID, "TaskType": "RUN_COMMAND", "TaskArn": "AWS-RunShellScript", - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterTaskWithMaintenanceWindow", string(body)) require.Equal(t, http.StatusOK, rec.Code) var regResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), ®Resp, jsonx.V1())) taskID := regResp["WindowTaskId"].(string) // Update with ServiceRoleArn. @@ -160,7 +161,7 @@ func TestMaintenanceWindowTask_UpdateServiceRoleArn(t *testing.T) { "WindowId": mw.WindowID, "WindowTaskId": taskID, "ServiceRoleArn": "arn:aws:iam::123456789012:role/UpdatedRole", - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateMaintenanceWindowTask", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "UpdatedRole") @@ -230,13 +231,13 @@ func TestDeregisterTargetFromMaintenanceWindow_TableDriven(t *testing.T) { body, _ := json.Marshal(map[string]any{ "WindowId": mw.WindowID, "WindowTargetId": windowTargetID, - }) + }, jsonx.V1()) rec := doRequest(t, h, "DeregisterTargetFromMaintenanceWindow", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantWindowID { assert.Equal(t, mw.WindowID, resp["WindowId"]) } @@ -310,13 +311,13 @@ func TestDeregisterTaskFromMaintenanceWindow_TableDriven(t *testing.T) { body, _ := json.Marshal(map[string]any{ "WindowId": mw.WindowID, "WindowTaskId": windowTaskID, - }) + }, jsonx.V1()) rec := doRequest(t, h, "DeregisterTaskFromMaintenanceWindow", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantWindowID { assert.Equal(t, mw.WindowID, resp["WindowId"]) } diff --git a/services/ssm/maintenance_window_test.go b/services/ssm/maintenance_window_test.go index f371bf910f..fd53c7eea8 100644 --- a/services/ssm/maintenance_window_test.go +++ b/services/ssm/maintenance_window_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -165,7 +166,7 @@ func TestPatchBaseline_DefaultOS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp ssm.CreatePatchBaselineOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.NotEmpty(t, resp.BaselineID) bl := backend.GetPatchBaselineInternal(resp.BaselineID) @@ -734,12 +735,12 @@ func TestMaintenanceWindowsForTarget_NoMatchReturnsEmpty(t *testing.T) { "Targets": []map[string]any{ {"Key": "InstanceIds", "Values": []string{"i-notregistered"}}, }, - }) + }, jsonx.V1()) rec := doRequest(t, h, "DescribeMaintenanceWindowsForTarget", string(body)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) identities := resp["WindowIdentities"].([]any) assert.Empty(t, identities) diff --git a/services/ssm/ops_items_test.go b/services/ssm/ops_items_test.go index c886985060..3dbad0e73f 100644 --- a/services/ssm/ops_items_test.go +++ b/services/ssm/ops_items_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -30,7 +31,7 @@ func TestStubOps_DeleteOpsItem(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "DeleteOpsItem", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -59,7 +60,7 @@ func TestStubOps_GetOpsItem(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "GetOpsItem", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -79,7 +80,7 @@ func TestStubOps_UpdateOpsItem(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID, "Title": "updated-item"}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID, "Title": "updated-item"}, jsonx.V1()) rec := doRequest(t, h, "UpdateOpsItem", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -114,7 +115,7 @@ func TestCreateOpsItem_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateOpsItemOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.OpsItemID) assert.NotEmpty(t, resp.OpsItemArn) assert.True(t, strings.HasPrefix(resp.OpsItemID, "oi-")) @@ -201,7 +202,7 @@ func TestAssociateOpsItemRelatedItem(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp ssm.CreateOpsItemOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) body = `{"OpsItemId":"` + createResp.OpsItemID + `","AssociationType":"RelatesTo","ResourceType":"AWS::EC2::Instance",` + @@ -215,7 +216,7 @@ func TestAssociateOpsItemRelatedItem(t *testing.T) { assert.Contains(t, rec.Body.String(), tt.wantErr) } else { var resp ssm.AssociateOpsItemRelatedItemOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.AssociationID) } }) @@ -901,7 +902,7 @@ func TestOpsItem_OperationalData_CreateAndUpdate(t *testing.T) { require.NoError(t, err) // GetOpsItem should have initial OperationalData. - body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "GetOpsItem", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "/aws/resources") @@ -915,12 +916,12 @@ func TestOpsItem_OperationalData_CreateAndUpdate(t *testing.T) { "Value": `[{"automationId":"auto-123"}]`, }, }, - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateOpsItem", string(body)) require.Equal(t, http.StatusOK, rec.Code) // GetOpsItem should have both keys. - body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec = doRequest(t, h, "GetOpsItem", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "/aws/resources") @@ -974,11 +975,11 @@ func TestOpsItem_OperationalData_TableDriven(t *testing.T) { body, _ := json.Marshal(map[string]any{ "OpsItemId": item.OpsItemID, "OperationalData": tt.updateData, - }) + }, jsonx.V1()) rec := doRequest(t, h, "UpdateOpsItem", string(body)) require.Equal(t, http.StatusOK, rec.Code) - body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec = doRequest(t, h, "GetOpsItem", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -1001,12 +1002,12 @@ func TestOpsItemEvents_TrackCreates(t *testing.T) { require.NoError(t, err) // ListOpsItemEvents for this item should return at least one event. - body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "ListOpsItemEvents", string(body)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["Summaries"].([]any) assert.NotEmpty(t, summaries, "should have at least one event after create") @@ -1027,16 +1028,16 @@ func TestOpsItemEvents_TrackUpdates(t *testing.T) { body, _ := json.Marshal(map[string]any{ "OpsItemId": item.OpsItemID, "Description": "Updated description", - }) + }, jsonx.V1()) doRequest(t, h, "UpdateOpsItem", string(body)) // ListOpsItemEvents should have multiple events now. - body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}) + body, _ = json.Marshal(map[string]any{"OpsItemId": item.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "ListOpsItemEvents", string(body)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries := resp["Summaries"].([]any) assert.GreaterOrEqual(t, len(summaries), 2, "create + update should each produce an event") @@ -1054,7 +1055,7 @@ func TestOpsItemEvents_FilterByOpsItemID(t *testing.T) { require.NoError(t, err) // Filter by item1 — should only get item1 events. - body, _ := json.Marshal(map[string]any{"OpsItemId": item1.OpsItemID}) + body, _ := json.Marshal(map[string]any{"OpsItemId": item1.OpsItemID}, jsonx.V1()) rec := doRequest(t, h, "ListOpsItemEvents", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), item1.OpsItemID) @@ -1109,7 +1110,7 @@ func TestUpdateOpsItem_TableDriven(t *testing.T) { payload := map[string]any{"OpsItemId": opsItemID} maps.Copy(payload, tt.update) - body, _ := json.Marshal(payload) + body, _ := json.Marshal(payload, jsonx.V1()) rec := doRequest(t, h, "UpdateOpsItem", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) diff --git a/services/ssm/ops_metadata_test.go b/services/ssm/ops_metadata_test.go index 8f1e2a8abf..a97328e026 100644 --- a/services/ssm/ops_metadata_test.go +++ b/services/ssm/ops_metadata_test.go @@ -2,13 +2,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -45,7 +46,7 @@ func TestStubOps_DeleteOpsMetadata(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec := doRequest(t, h, "DeleteOpsMetadata", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -61,7 +62,7 @@ func TestStubOps_GetOpsMetadata(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec := doRequest(t, h, "GetOpsMetadata", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -77,7 +78,7 @@ func TestStubOps_UpdateOpsMetadata(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec := doRequest(t, h, "UpdateOpsMetadata", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -111,7 +112,7 @@ func TestCreateOpsMetadata_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreateOpsMetadataOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.OpsMetadataArn) assert.Contains(t, resp.OpsMetadataArn, "arn:aws:ssm:") assert.Equal(t, 1, backend.OpsMetadataCount()) @@ -566,7 +567,7 @@ func TestClassifySSMErrorExtended(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantErrType, resp["__type"]) }) } @@ -592,7 +593,7 @@ func TestOpsMetadata_FullCRUD(t *testing.T) { assert.Contains(t, rec.Body.String(), "OpsMetadataList") // Get. - body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ := json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec = doRequest(t, h, "GetOpsMetadata", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "production") @@ -604,19 +605,19 @@ func TestOpsMetadata_FullCRUD(t *testing.T) { "MetadataToUpdate": map[string]any{ "version": map[string]any{"Value": "2"}, }, - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateOpsMetadata", string(body)) require.Equal(t, http.StatusOK, rec.Code) // Get updated — both keys should be present. - body, _ = json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ = json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec = doRequest(t, h, "GetOpsMetadata", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "production") assert.Contains(t, rec.Body.String(), "version") // Delete. - body, _ = json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}) + body, _ = json.Marshal(map[string]any{"OpsMetadataArn": meta.OpsMetadataArn}, jsonx.V1()) rec = doRequest(t, h, "DeleteOpsMetadata", string(body)) require.Equal(t, http.StatusOK, rec.Code) } diff --git a/services/ssm/parameter_encryption_test.go b/services/ssm/parameter_encryption_test.go index b1672cd327..589d1b4dde 100644 --- a/services/ssm/parameter_encryption_test.go +++ b/services/ssm/parameter_encryption_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -493,7 +494,7 @@ func TestSSMHandler_GetParameters(t *testing.T) { t.Helper() var out ssm.GetParametersOutput - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.Len(t, out.Parameters, 2) assert.Empty(t, out.InvalidParameters) }, @@ -512,7 +513,7 @@ func TestSSMHandler_GetParameters(t *testing.T) { t.Helper() var out ssm.GetParametersOutput - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.Len(t, out.Parameters, 1) assert.Len(t, out.InvalidParameters, 1) assert.Equal(t, "/absent", out.InvalidParameters[0]) @@ -533,7 +534,7 @@ func TestSSMHandler_GetParameters(t *testing.T) { t.Helper() var out ssm.GetParametersOutput - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) assert.Len(t, out.Parameters, 1) assert.Equal(t, "topsecret", out.Parameters[0].Value) }, @@ -708,7 +709,7 @@ func TestSecureStringHistory_WithDecryption(t *testing.T) { body, _ := json.Marshal(map[string]any{ "Name": "/secure/pwd", "WithDecryption": tt.withDecryption, - }) + }, jsonx.V1()) rec := doRequest(t, h, "GetParameterHistory", string(body)) require.Equal(t, http.StatusOK, rec.Code) diff --git a/services/ssm/parameter_labels_test.go b/services/ssm/parameter_labels_test.go index 1d8838c8fb..9a2cad438c 100644 --- a/services/ssm/parameter_labels_test.go +++ b/services/ssm/parameter_labels_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -393,7 +394,7 @@ func TestParameterLabels_VersionSpecific(t *testing.T) { "Name": "/app/key", "Labels": []string{"stable"}, "ParameterVersion": 1, - }) + }, jsonx.V1()) rec := doRequest(t, h, "LabelParameterVersion", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "ParameterVersion") @@ -403,7 +404,7 @@ func TestParameterLabels_VersionSpecific(t *testing.T) { "Name": "/app/key", "Labels": []string{"latest"}, "ParameterVersion": 2, - }) + }, jsonx.V1()) rec = doRequest(t, h, "LabelParameterVersion", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -464,7 +465,7 @@ func TestParameterLabels_UnlabelSpecificVersion(t *testing.T) { "Name": "/app/ver", "Labels": []string{"prod", "stable"}, "ParameterVersion": 1, - }) + }, jsonx.V1()) doRequest(t, h, "LabelParameterVersion", string(body)) // Remove one label. @@ -472,7 +473,7 @@ func TestParameterLabels_UnlabelSpecificVersion(t *testing.T) { "Name": "/app/ver", "Labels": []string{"prod"}, "ParameterVersion": 1, - }) + }, jsonx.V1()) rec := doRequest(t, h, "UnlabelParameterVersion", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "RemovedLabels") diff --git a/services/ssm/parameter_policy_notifications.go b/services/ssm/parameter_policy_notifications.go index 6f94899c82..1fac2e9867 100644 --- a/services/ssm/parameter_policy_notifications.go +++ b/services/ssm/parameter_policy_notifications.go @@ -2,11 +2,13 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "sort" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ParameterPolicyNotifier receives Parameter Store policy-action @@ -67,7 +69,7 @@ func parseParameterPolicies(policiesJSON string) []parameterStorePolicy { } var policies []parameterStorePolicy - if err := json.Unmarshal([]byte(policiesJSON), &policies); err != nil { + if err := json.Unmarshal([]byte(policiesJSON), &policies, jsonx.V1()); err != nil { return nil } @@ -93,7 +95,7 @@ func parameterPoliciesToWire(policiesJSON string, lastModified float64, now time wire := make([]ParameterInlinePolicy, 0, len(parsed)) for _, p := range parsed { - text, err := json.Marshal(p) + text, err := json.Marshal(p, jsonx.V1()) if err != nil { continue } diff --git a/services/ssm/parameters_handler_test.go b/services/ssm/parameters_handler_test.go index a6e5b26a4b..928b0cb2b0 100644 --- a/services/ssm/parameters_handler_test.go +++ b/services/ssm/parameters_handler_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -14,6 +14,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -77,7 +78,7 @@ func TestSSMHandler_UnknownOperation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["__type"], "UnknownOperationException") } func TestSSMHandler_InternalError(t *testing.T) { @@ -198,7 +199,7 @@ func TestSSMHandler_ValidationError(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ParameterPatternMismatchException", resp["__type"]) } @@ -216,7 +217,7 @@ func TestSSMHandler_ErrInvalidKeyID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["__type"], "UnknownOperationException") } @@ -328,14 +329,14 @@ func TestDeepCopy_Association(t *testing.T) { doRequest(t, h, "CreateDocument", `{"Name":"`+tt.docName+`","Content":"{\"schemaVersion\":\"2.2\"}"}`) - paramsJSON, _ := json.Marshal(tt.params) + paramsJSON, _ := json.Marshal(tt.params, jsonx.V1()) createBody := `{"Name":"` + tt.docName + `","Parameters":` + string(paramsJSON) + `}` rec := doRequest(t, h, "CreateAssociation", createBody) require.Equal(t, http.StatusOK, rec.Code) var createResp ssm.CreateAssociationOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) originalCmds := createResp.AssociationDescription.Parameters[tt.mutateKey] // Verify original value was stored correctly @@ -600,7 +601,7 @@ func TestAdvancedTier_AcceptsLargeValue(t *testing.T) { "Value": string(largeVal), "Type": "String", "Tier": "Advanced", - }) + }, jsonx.V1()) rec := doRequest(t, h, "PutParameter", string(body)) assert.Equal(t, http.StatusOK, rec.Code, "Advanced tier should accept 5KiB value") } @@ -620,7 +621,7 @@ func TestStandardTier_RejectsLargeValue(t *testing.T) { "Value": string(largeVal), "Type": "String", "Tier": "Standard", - }) + }, jsonx.V1()) rec := doRequest(t, h, "PutParameter", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code, "Standard tier should reject 5KiB value") } diff --git a/services/ssm/parameters_test.go b/services/ssm/parameters_test.go index f92ca2dcaf..10e5f7d32f 100644 --- a/services/ssm/parameters_test.go +++ b/services/ssm/parameters_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/kms" "github.com/blackbirdworks/gopherstack/services/ssm" @@ -557,13 +558,13 @@ func TestDeleteParameter_RegionCleanup(t *testing.T) { func postJSON(t *testing.T, h *ssm.Handler, op string, body any) (int, map[string]any) { t.Helper() - payload, err := json.Marshal(body) + payload, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, op, string(payload)) var out map[string]any - _ = json.Unmarshal(rec.Body.Bytes(), &out) + _ = json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1()) return rec.Code, out } diff --git a/services/ssm/patch_baseline_approval_rules_test.go b/services/ssm/patch_baseline_approval_rules_test.go index f5d328cad0..b9b1dcead2 100644 --- a/services/ssm/patch_baseline_approval_rules_test.go +++ b/services/ssm/patch_baseline_approval_rules_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -400,7 +401,7 @@ func TestSendCommand_RunPatchBaseline_ApprovalRules_Compliance(t *testing.T) { var created struct { BaselineID string `json:"BaselineId"` } - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) regResp := doRequest(t, h, "RegisterPatchBaselineForPatchGroup", `{ "BaselineId":"`+created.BaselineID+`","PatchGroup":"rule-group" @@ -423,7 +424,7 @@ func TestSendCommand_RunPatchBaseline_ApprovalRules_Compliance(t *testing.T) { State string `json:"State"` } `json:"Patches"` } - require.NoError(t, json.Unmarshal(patchesResp.Body.Bytes(), &patches)) + require.NoError(t, json.Unmarshal(patchesResp.Body.Bytes(), &patches, jsonx.V1())) states := map[string]string{} for _, p := range patches.Patches { diff --git a/services/ssm/patch_baselines_lifecycle_test.go b/services/ssm/patch_baselines_lifecycle_test.go index dab2db2cee..652d6edd63 100644 --- a/services/ssm/patch_baselines_lifecycle_test.go +++ b/services/ssm/patch_baselines_lifecycle_test.go @@ -2,13 +2,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -241,7 +242,7 @@ func TestPatchBaseline_OSFilter(t *testing.T) { "Filters": []map[string]any{ {"Key": "OPERATING_SYSTEM", "Values": []string{"AMAZON_LINUX_2"}}, }, - }) + }, jsonx.V1()) rec := doRequest(t, h, "DescribePatchBaselines", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "linux-baseline") @@ -252,7 +253,7 @@ func TestPatchBaseline_OSFilter(t *testing.T) { "Filters": []map[string]any{ {"Key": "OPERATING_SYSTEM", "Values": []string{"WINDOWS"}}, }, - }) + }, jsonx.V1()) rec = doRequest(t, h, "DescribePatchBaselines", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.NotContains(t, rec.Body.String(), "linux-baseline") @@ -286,7 +287,7 @@ func TestPatchBaseline_ComplianceLevelRoundTrip(t *testing.T) { require.NoError(t, err) // GetPatchBaseline should return the compliance level. - body, _ := json.Marshal(map[string]any{"BaselineId": bl.BaselineID}) + body, _ := json.Marshal(map[string]any{"BaselineId": bl.BaselineID}, jsonx.V1()) rec := doRequest(t, h, "GetPatchBaseline", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), tt.complianceLevel) @@ -295,7 +296,7 @@ func TestPatchBaseline_ComplianceLevelRoundTrip(t *testing.T) { body, _ = json.Marshal(map[string]any{ "BaselineId": bl.BaselineID, "ApprovedPatchesComplianceLevel": "LOW", - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdatePatchBaseline", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "LOW") @@ -318,7 +319,7 @@ func TestPatchGroups_RegisterAndLookup(t *testing.T) { body, _ := json.Marshal(map[string]any{ "BaselineId": bl.BaselineID, "PatchGroup": "ubuntu-servers", - }) + }, jsonx.V1()) rec := doRequest(t, h, "RegisterPatchBaselineForPatchGroup", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -328,7 +329,7 @@ func TestPatchGroups_RegisterAndLookup(t *testing.T) { assert.Contains(t, rec.Body.String(), "ubuntu-servers") // GetPatchBaselineForPatchGroup should return the baseline. - body, _ = json.Marshal(map[string]any{"PatchGroup": "ubuntu-servers"}) + body, _ = json.Marshal(map[string]any{"PatchGroup": "ubuntu-servers"}, jsonx.V1()) rec = doRequest(t, h, "GetPatchBaselineForPatchGroup", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), bl.BaselineID) @@ -337,7 +338,7 @@ func TestPatchGroups_RegisterAndLookup(t *testing.T) { body, _ = json.Marshal(map[string]any{ "BaselineId": bl.BaselineID, "PatchGroup": "ubuntu-servers", - }) + }, jsonx.V1()) rec = doRequest(t, h, "DeregisterPatchBaselineForPatchGroup", string(body)) require.Equal(t, http.StatusOK, rec.Code) } @@ -380,14 +381,14 @@ func TestDeregisterPatchBaselineForPatchGroup_TableDriven(t *testing.T) { body, _ := json.Marshal(map[string]any{ "BaselineId": tt.baselineID, "PatchGroup": tt.patchGroup, - }) + }, jsonx.V1()) rec := doRequest(t, h, "DeregisterPatchBaselineForPatchGroup", string(body)) require.Equal(t, tt.wantStatus, rec.Code) if tt.wantStatus == http.StatusOK && tt.wantBaselineID != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantBaselineID, resp["BaselineId"]) assert.Equal(t, tt.wantPatchGroup, resp["PatchGroup"]) } @@ -408,7 +409,7 @@ func TestDescribeAvailablePatches_SeededCatalog(t *testing.T) { Name string `json:"Name"` } `json:"Patches"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out.Patches, "the catalog must be seeded with real built-in patches") } @@ -432,7 +433,7 @@ func TestSendCommand_RunPatchBaseline_PopulatesInstanceState(t *testing.T) { var created struct { BaselineID string `json:"BaselineId"` } - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &created, jsonx.V1())) require.NotEmpty(t, created.BaselineID) regResp := doRequest(t, h, "RegisterPatchBaselineForPatchGroup", `{ @@ -457,7 +458,7 @@ func TestSendCommand_RunPatchBaseline_PopulatesInstanceState(t *testing.T) { PatchGroup string `json:"PatchGroup"` } `json:"InstancePatchStates"` } - require.NoError(t, json.Unmarshal(stateResp.Body.Bytes(), &states)) + require.NoError(t, json.Unmarshal(stateResp.Body.Bytes(), &states, jsonx.V1())) require.Len(t, states.InstancePatchStates, 1) assert.Equal(t, "i-patchtest", states.InstancePatchStates[0].InstanceID) assert.Equal(t, created.BaselineID, states.InstancePatchStates[0].BaselineID) @@ -472,7 +473,7 @@ func TestSendCommand_RunPatchBaseline_PopulatesInstanceState(t *testing.T) { State string `json:"State"` } `json:"Patches"` } - require.NoError(t, json.Unmarshal(patchesResp.Body.Bytes(), &patches)) + require.NoError(t, json.Unmarshal(patchesResp.Body.Bytes(), &patches, jsonx.V1())) require.NotEmpty(t, patches.Patches) var sawInstalled bool diff --git a/services/ssm/patch_baselines_test.go b/services/ssm/patch_baselines_test.go index 48a9b10245..c0875b469f 100644 --- a/services/ssm/patch_baselines_test.go +++ b/services/ssm/patch_baselines_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -295,7 +296,7 @@ func TestStubOps_DeletePatchBaseline(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID}) + body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID}, jsonx.V1()) rec := doRequest(t, h, "DeletePatchBaseline", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -344,7 +345,7 @@ func TestStubOps_GetPatchBaseline(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID}) + body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID}, jsonx.V1()) rec := doRequest(t, h, "GetPatchBaseline", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -361,7 +362,7 @@ func TestStubOps_UpdatePatchBaseline(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID, "Name": "updated-baseline"}) + body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID, "Name": "updated-baseline"}, jsonx.V1()) rec := doRequest(t, h, "UpdatePatchBaseline", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -378,7 +379,7 @@ func TestStubOps_RegisterPatchBaselineForPatchGroup(t *testing.T) { }) require.NoError(t, err) - body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID, "PatchGroup": "test-group"}) + body, _ := json.Marshal(map[string]any{"BaselineId": pb.BaselineID, "PatchGroup": "test-group"}, jsonx.V1()) rec := doRequest(t, h, "RegisterPatchBaselineForPatchGroup", string(body)) assert.Equal(t, http.StatusOK, rec.Code) } @@ -498,7 +499,7 @@ func TestCreatePatchBaseline_Success(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp ssm.CreatePatchBaselineOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp.BaselineID) assert.True(t, strings.HasPrefix(resp.BaselineID, "pb-")) assert.Equal(t, 1, backend.PatchBaselineCount()) @@ -540,7 +541,7 @@ func TestPatchBaseline_ApprovalRulesGlobalFiltersSourcesRoundTrip(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var created ssm.CreatePatchBaselineOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) out, err := b.GetPatchBaseline(context.Background(), &ssm.GetPatchBaselineInput{BaselineID: created.BaselineID}) require.NoError(t, err) @@ -577,7 +578,7 @@ func TestCreatePatchBaseline_DefaultsGlobalFiltersAndApprovalRulesNonNil(t *test require.Equal(t, http.StatusOK, rec.Code) var created ssm.CreatePatchBaselineOutput - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) out, err := b.GetPatchBaseline(context.Background(), &ssm.GetPatchBaselineInput{BaselineID: created.BaselineID}) require.NoError(t, err) @@ -591,7 +592,7 @@ func TestCreatePatchBaseline_DefaultsGlobalFiltersAndApprovalRulesNonNil(t *test require.Equal(t, http.StatusOK, rawRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rawRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rawRec.Body.Bytes(), &raw, jsonx.V1())) assert.Contains(t, raw, "GlobalFilters", "GlobalFilters key must be present, not omitted") assert.Contains(t, raw, "ApprovalRules", "ApprovalRules key must be present, not omitted") } diff --git a/services/ssm/persistence.go b/services/ssm/persistence.go index 9ddfc360a8..401de17991 100644 --- a/services/ssm/persistence.go +++ b/services/ssm/persistence.go @@ -2,10 +2,12 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/tags" @@ -37,7 +39,7 @@ const ssmSnapshotVersion = 1 // store_setup.go's package doc for why each one doesn't fit Table's // func(*V) string keying requirement. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` History map[string]map[string][]ParameterHistory `json:"history"` Tags map[string]map[string]*tags.Tags `json:"tags"` DocumentVersions map[string]map[string][]DocumentVersion `json:"document_versions"` @@ -221,7 +223,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NotifiedParameterPolicies: b.notifiedParameterPolicies, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ssm: failed to marshal snapshot", "error", err) @@ -240,7 +242,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { // silently dropped instead of restored. Each table name has the shape // "/" (see store_setup.go's getOrCreateTable); regions // never contain "/", so splitting on the first one recovers both parts. -func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]json.RawMessage) { +func (b *InMemoryBackend) preRegisterSnapshotTables(tables map[string]jsontext.Value) { for key := range tables { name, region, found := strings.Cut(key, "/") if !found { diff --git a/services/ssm/public_parameters.go b/services/ssm/public_parameters.go index c839819e23..b847b4ef92 100644 --- a/services/ssm/public_parameters.go +++ b/services/ssm/public_parameters.go @@ -2,7 +2,7 @@ package ssm import ( _ "embed" // public_parameters.json - "encoding/json" + "encoding/json/v2" "slices" "sort" "strconv" @@ -10,6 +10,7 @@ import ( "sync" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) //go:embed public_parameters.json @@ -45,7 +46,7 @@ type publicCatalog struct { //nolint:gochecknoglobals // read-only catalog built once from embedded data var loadPublicCatalog = sync.OnceValue(func() *publicCatalog { var f publicParametersFile - if err := json.Unmarshal(publicParametersJSON, &f); err != nil { + if err := json.Unmarshal(publicParametersJSON, &f, jsonx.V1()); err != nil { panic("ssm: corrupt embedded public_parameters.json: " + err.Error()) } diff --git a/services/ssm/public_parameters_sdk_test.go b/services/ssm/public_parameters_sdk_test.go index 2068b233b8..bb3f045e88 100644 --- a/services/ssm/public_parameters_sdk_test.go +++ b/services/ssm/public_parameters_sdk_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "strings" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -60,7 +61,7 @@ func TestGetParameter_PublicCatalog_SDK(t *testing.T) { t.Helper() var doc map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(p.Value)), &doc)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(p.Value)), &doc, jsonx.V1())) assert.Contains(t, doc, "image_id") assert.Contains(t, doc, "ecs_agent_version") }}, @@ -474,7 +475,7 @@ func TestSecretsManagerReference_SDK(t *testing.T) { assert.Contains(t, aws.ToString(p.ARN), ":parameter/aws/reference/secretsmanager/") var src map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(p.SourceResult)), &src)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(p.SourceResult)), &src, jsonx.V1())) assert.Equal(t, secretARN, src["ARN"]) assert.Equal(t, "db", src["Name"]) assert.Equal(t, tc.wantValue, src["SecretString"]) diff --git a/services/ssm/resource_policies_test.go b/services/ssm/resource_policies_test.go index d646e65ae4..ba020167d1 100644 --- a/services/ssm/resource_policies_test.go +++ b/services/ssm/resource_policies_test.go @@ -1,7 +1,7 @@ package ssm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -69,19 +70,19 @@ func TestResourcePolicies_FullCRUD(t *testing.T) { body, _ := json.Marshal(map[string]any{ "ResourceArn": tt.resourceArn, "Policy": tt.policy, - }) + }, jsonx.V1()) rec := doRequest(t, h, "PutResourcePolicy", string(body)) require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) policyID := putResp["PolicyId"].(string) policyHash := putResp["PolicyHash"].(string) assert.NotEmpty(t, policyID) assert.NotEmpty(t, policyHash) // Get policies. - body, _ = json.Marshal(map[string]any{"ResourceArn": tt.resourceArn}) + body, _ = json.Marshal(map[string]any{"ResourceArn": tt.resourceArn}, jsonx.V1()) rec = doRequest(t, h, "GetResourcePolicies", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), policyID) @@ -91,12 +92,12 @@ func TestResourcePolicies_FullCRUD(t *testing.T) { "ResourceArn": tt.resourceArn, "PolicyId": policyID, "PolicyHash": policyHash, - }) + }, jsonx.V1()) rec = doRequest(t, h, "DeleteResourcePolicy", string(body)) require.Equal(t, http.StatusOK, rec.Code) // Get policies — should be empty. - body, _ = json.Marshal(map[string]any{"ResourceArn": tt.resourceArn}) + body, _ = json.Marshal(map[string]any{"ResourceArn": tt.resourceArn}, jsonx.V1()) rec = doRequest(t, h, "GetResourcePolicies", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), `"Policies":[]`) diff --git a/services/ssm/secret_references.go b/services/ssm/secret_references.go index 50993458ff..8a8636678e 100644 --- a/services/ssm/secret_references.go +++ b/services/ssm/secret_references.go @@ -2,7 +2,7 @@ package ssm import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "regexp" @@ -10,6 +10,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -146,7 +147,7 @@ func (b *InMemoryBackend) getSecretReference( value = string(secret.SecretBinary) } - source, err := json.Marshal(secretSourceResult(*secret)) + source, err := json.Marshal(secretSourceResult(*secret), jsonx.V1()) if err != nil { return ParameterOutput{}, fmt.Errorf("marshal secret source result: %w", err) } diff --git a/services/ssm/service_settings_test.go b/services/ssm/service_settings_test.go index be11b23c97..7f25ac3ee1 100644 --- a/services/ssm/service_settings_test.go +++ b/services/ssm/service_settings_test.go @@ -1,7 +1,7 @@ package ssm_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -101,7 +102,7 @@ func TestServiceSettings_LifecycleRoundTrip(t *testing.T) { h, _ := newTestHandler(t) // Default state. - body, _ := json.Marshal(map[string]any{"SettingId": tt.settingID}) + body, _ := json.Marshal(map[string]any{"SettingId": tt.settingID}, jsonx.V1()) rec := doRequest(t, h, "GetServiceSetting", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "Default") @@ -110,24 +111,24 @@ func TestServiceSettings_LifecycleRoundTrip(t *testing.T) { body, _ = json.Marshal(map[string]any{ "SettingId": tt.settingID, "SettingValue": tt.setValue, - }) + }, jsonx.V1()) rec = doRequest(t, h, "UpdateServiceSetting", string(body)) require.Equal(t, http.StatusOK, rec.Code) // Get updated. - body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}) + body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}, jsonx.V1()) rec = doRequest(t, h, "GetServiceSetting", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), tt.setValue) assert.Contains(t, rec.Body.String(), "Customized") // Reset. - body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}) + body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}, jsonx.V1()) rec = doRequest(t, h, "ResetServiceSetting", string(body)) require.Equal(t, http.StatusOK, rec.Code) // Default again. - body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}) + body, _ = json.Marshal(map[string]any{"SettingId": tt.settingID}, jsonx.V1()) rec = doRequest(t, h, "GetServiceSetting", string(body)) require.Equal(t, http.StatusOK, rec.Code) assert.Contains(t, rec.Body.String(), "Default") diff --git a/services/ssm/sessions_test.go b/services/ssm/sessions_test.go index 23dcd2f217..c9be4a9702 100644 --- a/services/ssm/sessions_test.go +++ b/services/ssm/sessions_test.go @@ -2,7 +2,7 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -108,7 +109,7 @@ func TestStartSession_DefaultsToStandardAccessType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) sessions := out["Sessions"].([]any) require.Len(t, sessions, 1) assert.Equal(t, "Standard", sessions[0].(map[string]any)["AccessType"]) @@ -208,12 +209,12 @@ func TestSession_Parameters_RoundTrip(t *testing.T) { body, _ := json.Marshal(map[string]any{ "Target": "i-1234567890abcdef0", "Parameters": tt.parameters, - }) + }, jsonx.V1()) rec := doRequest(t, h, "StartSession", string(body)) require.Equal(t, http.StatusOK, rec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) assert.NotEmpty(t, startResp["SessionId"]) // DescribeSessions should show the session, but never a @@ -250,7 +251,7 @@ func TestSession_StateFilter(t *testing.T) { assert.NotContains(t, rec.Body.String(), sess.SessionID) // Terminate session. - body, _ := json.Marshal(map[string]any{"SessionId": sess.SessionID}) + body, _ := json.Marshal(map[string]any{"SessionId": sess.SessionID}, jsonx.V1()) doRequest(t, h, "TerminateSession", string(body)) // Filter by Active — should NOT find the terminated session. diff --git a/services/ssm/ssmmessages_port_test.go b/services/ssm/ssmmessages_port_test.go index a0d658b3c1..22ee923f9f 100644 --- a/services/ssm/ssmmessages_port_test.go +++ b/services/ssm/ssmmessages_port_test.go @@ -3,7 +3,7 @@ package ssm_test import ( "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "io" "net" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -79,7 +80,7 @@ func portHandshake(t *testing.T, c *dcClient, acked map[int64]bool, wantProps ma require.Equal(t, ssm.PayloadHandshakeRequest, req.PayloadType) var hr ssm.HandshakeRequestPayload - require.NoError(t, json.Unmarshal(req.Payload, &hr)) + require.NoError(t, json.Unmarshal(req.Payload, &hr, jsonx.V1())) require.Len(t, hr.RequestedClientActions, 1) assert.Equal(t, "2.3.0.0", hr.AgentVersion) assert.Equal(t, "Port", hr.RequestedClientActions[0].ActionParameters["SessionType"]) @@ -88,7 +89,7 @@ func portHandshake(t *testing.T, c *dcClient, acked map[int64]bool, wantProps ma resp, _ := json.Marshal(ssm.HandshakeResponsePayload{ ClientVersion: "1.2.0.0", ProcessedClientActions: []ssm.ProcessedClientAction{{ActionType: "SessionType", ActionStatus: 1}}, - }) + }, jsonx.V1()) c.input(ssm.PayloadHandshakeResponse, resp) done := c.nextNonAck(acked) @@ -291,7 +292,7 @@ func TestSessionPortForwardingErrors(t *testing.T) { resp, _ := json.Marshal(ssm.HandshakeResponsePayload{ ProcessedClientActions: []ssm.ProcessedClientAction{{ActionType: "SessionType", ActionStatus: 1}}, - }) + }, jsonx.V1()) c.input(ssm.PayloadHandshakeResponse, resp) done := c.nextNonAck(map[int64]bool{}) diff --git a/services/ssm/ssmmessages_server.go b/services/ssm/ssmmessages_server.go index 2a376d9b47..72fb314e54 100644 --- a/services/ssm/ssmmessages_server.go +++ b/services/ssm/ssmmessages_server.go @@ -3,7 +3,7 @@ package ssm import ( "context" "encoding/binary" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net" @@ -16,6 +16,7 @@ import ( "github.com/gorilla/websocket" "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -107,7 +108,7 @@ func (d *dataChannel) serve(parent context.Context) { _ = d.ws.SetReadDeadline(time.Time{}) var open OpenDataChannelInput - if jerr := json.Unmarshal(raw, &open); jerr != nil { + if jerr := json.Unmarshal(raw, &open, jsonx.V1()); jerr != nil { d.reject("malformed OpenDataChannel request") return @@ -228,7 +229,7 @@ func (d *dataChannel) process(ctx context.Context, m *StreamMessage) bool { return d.completeHandshake(ctx, m.Payload) case PayloadSize: var sz SizePayload - if json.Unmarshal(m.Payload, &sz) == nil && sz.Cols > 0 && sz.Rows > 0 { + if json.Unmarshal(m.Payload, &sz, jsonx.V1()) == nil && sz.Cols > 0 && sz.Rows > 0 { d.cols, d.rows = sz.Cols, sz.Rows if d.shell != nil { _ = d.shell.Resize(ctx, sz.Cols, sz.Rows) @@ -270,14 +271,14 @@ func (d *dataChannel) sendHandshakeRequest() error { "Properties": props, }, }}, - }) + }, jsonx.V1()) return d.sendOutput(PayloadHandshakeRequest, body) } func (d *dataChannel) completeHandshake(ctx context.Context, payload []byte) bool { var resp HandshakeResponsePayload - if err := json.Unmarshal(payload, &resp); err != nil { + if err := json.Unmarshal(payload, &resp, jsonx.V1()); err != nil { d.endSession("Malformed handshake response.") return true @@ -291,7 +292,7 @@ func (d *dataChannel) completeHandshake(ctx context.Context, payload []byte) boo } } - done, _ := json.Marshal(HandshakeCompletePayload{HandshakeTimeToComplete: int64(time.Since(d.started))}) + done, _ := json.Marshal(HandshakeCompletePayload{HandshakeTimeToComplete: int64(time.Since(d.started))}, jsonx.V1()) if d.sendOutput(PayloadHandshakeComplete, done) != nil { return true } @@ -392,7 +393,7 @@ func (d *dataChannel) finish(reason string) { SessionID: d.sessionID, SchemaVersion: msgSchemaVersion, Output: reason, - }) + }, jsonx.V1()) _ = d.send(NewStreamMessage(MsgChannelClosed, 0, 0, body)) d.closeNormal() @@ -410,7 +411,7 @@ func (d *dataChannel) sendAck(m *StreamMessage) error { body, _ := json.Marshal(AckPayload{ MessageType: m.MessageType, MessageID: m.MessageID.String(), SequenceNumber: m.SequenceNumber, IsSequentialMessage: true, - }) + }, jsonx.V1()) ack := NewStreamMessage(MsgAcknowledge, 0, 0, body) ack.Flags = ackFlags diff --git a/services/ssm/ssmmessages_test.go b/services/ssm/ssmmessages_test.go index 7cb7bec9d3..99ebd3fc1e 100644 --- a/services/ssm/ssmmessages_test.go +++ b/services/ssm/ssmmessages_test.go @@ -4,7 +4,8 @@ import ( "context" "crypto/sha256" "encoding/binary" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -188,7 +190,7 @@ func newServer(t *testing.T, withShell bool) *httptest.Server { func ssmCall(t *testing.T, srv *httptest.Server, op string, in any) map[string]any { t.Helper() - body, err := json.Marshal(in) + body, err := json.Marshal(in, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, srv.URL, strings.NewReader(string(body))) @@ -202,7 +204,7 @@ func ssmCall(t *testing.T, srv *httptest.Server, op string, in any) map[string]a defer resp.Body.Close() var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) return out } @@ -232,7 +234,7 @@ func (c *dcClient) open(token string) { b, _ := json.Marshal(ssm.OpenDataChannelInput{ MessageSchemaVersion: "1.0", RequestID: uuid.NewString(), TokenValue: token, ClientID: uuid.NewString(), ClientVersion: "1.2.0.0", - }) + }, jsonx.V1()) require.NoError(c.t, c.ws.WriteMessage(websocket.TextMessage, b)) } @@ -280,7 +282,7 @@ func (c *dcClient) nextNonAck(acked map[int64]bool) *ssm.StreamMessage { } var a ssm.AckPayload - require.NoError(c.t, json.Unmarshal(m.Payload, &a)) + require.NoError(c.t, json.Unmarshal(m.Payload, &a, jsonx.V1())) acked[a.SequenceNumber] = true } } @@ -296,7 +298,7 @@ func (c *dcClient) handshake(acked map[int64]bool) { resp, _ := json.Marshal(ssm.HandshakeResponsePayload{ ClientVersion: "1.2.0.0", ProcessedClientActions: []ssm.ProcessedClientAction{{ActionType: "SessionType", ActionStatus: 1}}, - }) + }, jsonx.V1()) c.input(ssm.PayloadHandshakeResponse, resp) done := c.nextNonAck(acked) diff --git a/services/ssm/tags_test.go b/services/ssm/tags_test.go index 046a820d67..89475cb661 100644 --- a/services/ssm/tags_test.go +++ b/services/ssm/tags_test.go @@ -2,13 +2,14 @@ package ssm_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/ssm" ) @@ -49,7 +50,7 @@ func TestTagsForNonParameterResources(t *testing.T) { "ResourceType": tt.resourceType, "ResourceId": tt.resourceID, "Tags": tt.tags, - }) + }, jsonx.V1()) addRec := doRequest(t, h, "AddTagsToResource", string(addBody)) require.Equal(t, http.StatusOK, addRec.Code) @@ -57,13 +58,13 @@ func TestTagsForNonParameterResources(t *testing.T) { listBody, _ := json.Marshal(map[string]any{ "ResourceType": tt.resourceType, "ResourceId": tt.resourceID, - }) + }, jsonx.V1()) listRec := doRequest(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var listResp ssm.ListTagsForResourceOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp.TagList, tt.wantTagCount) }) } @@ -109,25 +110,25 @@ func TestRemoveTagsForNonParameter(t *testing.T) { "ResourceType": tt.resourceType, "ResourceId": tt.resourceID, "Tags": tt.addTags, - }) + }, jsonx.V1()) require.Equal(t, http.StatusOK, doRequest(t, h, "AddTagsToResource", string(addBody)).Code) removeBody, _ := json.Marshal(map[string]any{ "ResourceType": tt.resourceType, "ResourceId": tt.resourceID, "TagKeys": tt.removeKeys, - }) + }, jsonx.V1()) require.Equal(t, http.StatusOK, doRequest(t, h, "RemoveTagsFromResource", string(removeBody)).Code) listBody, _ := json.Marshal(map[string]any{ "ResourceType": tt.resourceType, "ResourceId": tt.resourceID, - }) + }, jsonx.V1()) listRec := doRequest(t, h, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var resp ssm.ListTagsForResourceOutput - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp.TagList, tt.wantRemain) }) } diff --git a/services/ssoadmin/application_auth_methods.go b/services/ssoadmin/application_auth_methods.go index 59d91248ff..e3244c4804 100644 --- a/services/ssoadmin/application_auth_methods.go +++ b/services/ssoadmin/application_auth_methods.go @@ -1,7 +1,7 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" @@ -32,7 +32,7 @@ func (b *InMemoryBackend) DeleteApplicationAuthenticationMethod(applicationArn, // authMethodType must be IAM; body is the full AuthenticationMethod JSON object. func (b *InMemoryBackend) PutApplicationAuthenticationMethod( applicationArn, authMethodType string, - body json.RawMessage, + body jsontext.Value, ) error { b.mu.Lock("PutApplicationAuthenticationMethod") defer b.mu.Unlock() @@ -45,10 +45,10 @@ func (b *InMemoryBackend) PutApplicationAuthenticationMethod( return ErrApplicationNotFound } if b.applicationAuthMethods[applicationArn] == nil { - b.applicationAuthMethods[applicationArn] = make(map[string]json.RawMessage) + b.applicationAuthMethods[applicationArn] = make(map[string]jsontext.Value) } if len(body) == 0 { - body = json.RawMessage("null") + body = jsontext.Value("null") } b.applicationAuthMethods[applicationArn][authMethodType] = body @@ -66,7 +66,7 @@ func (b *InMemoryBackend) ListApplicationAuthenticationMethods(applicationArn st methods := b.applicationAuthMethods[applicationArn] result := make([]AuthMethod, 0, len(methods)) for mType, body := range methods { - bodyCopy := make(json.RawMessage, len(body)) + bodyCopy := make(jsontext.Value, len(body)) copy(bodyCopy, body) result = append(result, AuthMethod{AuthMethodType: mType, Body: bodyCopy}) } @@ -78,7 +78,7 @@ func (b *InMemoryBackend) ListApplicationAuthenticationMethods(applicationArn st // GetApplicationAuthenticationMethod returns a specific auth method body. func (b *InMemoryBackend) GetApplicationAuthenticationMethod( applicationArn, authMethodType string, -) (json.RawMessage, error) { +) (jsontext.Value, error) { b.mu.RLock("GetApplicationAuthenticationMethod") defer b.mu.RUnlock() @@ -93,7 +93,7 @@ func (b *InMemoryBackend) GetApplicationAuthenticationMethod( if !exists { return nil, ErrAuthMethodNotFound } - result := make(json.RawMessage, len(body)) + result := make(jsontext.Value, len(body)) copy(result, body) return result, nil diff --git a/services/ssoadmin/application_grants.go b/services/ssoadmin/application_grants.go index 6032a1c4b0..25f6183235 100644 --- a/services/ssoadmin/application_grants.go +++ b/services/ssoadmin/application_grants.go @@ -1,7 +1,7 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" "fmt" "sort" @@ -22,7 +22,7 @@ func isValidGrantType(grantType string) bool { } // PutApplicationGrant stores a typed grant with its full body. -func (b *InMemoryBackend) PutApplicationGrant(applicationArn, grantType string, body json.RawMessage) error { +func (b *InMemoryBackend) PutApplicationGrant(applicationArn, grantType string, body jsontext.Value) error { b.mu.Lock("PutApplicationGrant") defer b.mu.Unlock() @@ -35,10 +35,10 @@ func (b *InMemoryBackend) PutApplicationGrant(applicationArn, grantType string, return ErrApplicationNotFound } if b.applicationGrants[applicationArn] == nil { - b.applicationGrants[applicationArn] = make(map[string]json.RawMessage) + b.applicationGrants[applicationArn] = make(map[string]jsontext.Value) } if len(body) == 0 { - body = json.RawMessage("null") + body = jsontext.Value("null") } b.applicationGrants[applicationArn][grantType] = body @@ -76,7 +76,7 @@ func (b *InMemoryBackend) ListApplicationGrants(applicationArn string) ([]Applic grants := b.applicationGrants[applicationArn] result := make([]ApplicationGrant, 0, len(grants)) for gType, body := range grants { - bodyCopy := make(json.RawMessage, len(body)) + bodyCopy := make(jsontext.Value, len(body)) copy(bodyCopy, body) result = append(result, ApplicationGrant{GrantType: gType, Grant: bodyCopy}) } @@ -86,7 +86,7 @@ func (b *InMemoryBackend) ListApplicationGrants(applicationArn string) ([]Applic } // GetApplicationGrant returns a specific grant body. -func (b *InMemoryBackend) GetApplicationGrant(applicationArn, grantType string) (json.RawMessage, error) { +func (b *InMemoryBackend) GetApplicationGrant(applicationArn, grantType string) (jsontext.Value, error) { b.mu.RLock("GetApplicationGrant") defer b.mu.RUnlock() @@ -101,7 +101,7 @@ func (b *InMemoryBackend) GetApplicationGrant(applicationArn, grantType string) if !exists { return nil, ErrGrantNotFound } - result := make(json.RawMessage, len(body)) + result := make(jsontext.Value, len(body)) copy(result, body) return result, nil diff --git a/services/ssoadmin/handler.go b/services/ssoadmin/handler.go index 5d71526cd3..b729c874da 100644 --- a/services/ssoadmin/handler.go +++ b/services/ssoadmin/handler.go @@ -2,7 +2,7 @@ package ssoadmin import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -94,7 +95,7 @@ func listApplicationItems[T any]( ApplicationArn string `json:"ApplicationArn"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -232,7 +233,7 @@ func listProvisioningStatusMetadata[T any]( NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -268,7 +269,7 @@ func listPermissionSetSubItems[T any]( NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -427,7 +428,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { var req struct { InstanceArn string `json:"InstanceArn"` } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -736,13 +737,11 @@ func toPermissionSetProvisioningStatusMetadataView(s *ProvisioningStatus) permis } func writeJSON(c *echo.Context, status int, v any) error { - data, err := json.Marshal(v) + data, err := json.Marshal(v, jsonx.V1()) if err != nil { return err } - c.Response().Header().Set("Content-Type", "application/x-amz-json-1.1") - c.Response().WriteHeader(status) - _, _ = c.Response().Write(data) + httputils.WriteAmzJSON11Bytes(c.Response(), status, data) return nil } @@ -754,7 +753,7 @@ func checkPaging(body []byte) string { NextToken *string `json:"NextToken"` } - if json.Unmarshal(body, &req) != nil { + if json.Unmarshal(body, &req, jsonx.V1()) != nil { return "" } diff --git a/services/ssoadmin/handler_account_assignments.go b/services/ssoadmin/handler_account_assignments.go index 95939f8e4f..d5df8bb8c6 100644 --- a/services/ssoadmin/handler_account_assignments.go +++ b/services/ssoadmin/handler_account_assignments.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type assignmentView struct { @@ -23,7 +25,7 @@ func (h *Handler) handleCreateAccountAssignment(c *echo.Context, body []byte) er PrincipalID string `json:"PrincipalId"` PrincipalType string `json:"PrincipalType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -71,7 +73,7 @@ func (h *Handler) handleDescribeAccountAssignmentCreationStatus(c *echo.Context, InstanceArn string `json:"InstanceArn"` AccountAssignmentCreationRequestID string `json:"AccountAssignmentCreationRequestId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -97,7 +99,7 @@ func (h *Handler) handleDeleteAccountAssignment(c *echo.Context, body []byte) er PrincipalID string `json:"PrincipalId"` PrincipalType string `json:"PrincipalType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.PrincipalType != "" && req.PrincipalType != principalTypeUser && req.PrincipalType != principalTypeGroup { @@ -130,7 +132,7 @@ func (h *Handler) handleDescribeAccountAssignmentDeletionStatus(c *echo.Context, InstanceArn string `json:"InstanceArn"` AccountAssignmentDeletionRequestID string `json:"AccountAssignmentDeletionRequestId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -155,7 +157,7 @@ func (h *Handler) handleListAccountAssignments(c *echo.Context, body []byte) err NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -212,7 +214,7 @@ func (h *Handler) handleListAccountAssignmentsForPrincipal(c *echo.Context, body NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_application_access_scopes.go b/services/ssoadmin/handler_application_access_scopes.go index 2356be03a5..e2155be79b 100644 --- a/services/ssoadmin/handler_application_access_scopes.go +++ b/services/ssoadmin/handler_application_access_scopes.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDeleteApplicationAccessScope(c *echo.Context, body []byte) error { @@ -12,7 +14,7 @@ func (h *Handler) handleDeleteApplicationAccessScope(c *echo.Context, body []byt ApplicationArn string `json:"ApplicationArn"` Scope string `json:"Scope"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -29,7 +31,7 @@ func (h *Handler) handleListApplicationAccessScopes(c *echo.Context, body []byte NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } scopes, err := h.Backend.ListApplicationAccessScopes(req.ApplicationArn) @@ -53,7 +55,7 @@ func (h *Handler) handlePutApplicationAccessScope(c *echo.Context, body []byte) Scope string `json:"Scope"` AuthorizedTargets []string `json:"AuthorizedTargets"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if err := h.Backend.PutApplicationAccessScope(req.ApplicationArn, req.Scope, req.AuthorizedTargets); err != nil { @@ -68,7 +70,7 @@ func (h *Handler) handleGetApplicationAccessScope(c *echo.Context, body []byte) ApplicationArn string `json:"ApplicationArn"` Scope string `json:"Scope"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { diff --git a/services/ssoadmin/handler_application_assignments.go b/services/ssoadmin/handler_application_assignments.go index 2aedb891ec..666318d4b1 100644 --- a/services/ssoadmin/handler_application_assignments.go +++ b/services/ssoadmin/handler_application_assignments.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateApplicationAssignment(c *echo.Context, body []byte) error { @@ -13,7 +15,7 @@ func (h *Handler) handleCreateApplicationAssignment(c *echo.Context, body []byte PrincipalID string `json:"PrincipalId"` PrincipalType string `json:"PrincipalType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -34,7 +36,7 @@ func (h *Handler) handleDeleteApplicationAssignment(c *echo.Context, body []byte PrincipalID string `json:"PrincipalId"` PrincipalType string `json:"PrincipalType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -55,7 +57,7 @@ func (h *Handler) handleDescribeApplicationAssignment(c *echo.Context, body []by PrincipalID string `json:"PrincipalId"` PrincipalType string `json:"PrincipalType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -82,7 +84,7 @@ func (h *Handler) handleListApplicationAssignments(c *echo.Context, body []byte) NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } assignments, err := h.Backend.ListApplicationAssignments(req.ApplicationArn) @@ -121,7 +123,7 @@ func (h *Handler) handlePutApplicationAssignmentConfiguration(c *echo.Context, b ApplicationArn string `json:"ApplicationArn"` AssignmentRequired bool `json:"AssignmentRequired"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -142,7 +144,7 @@ func (h *Handler) handlePutApplicationSessionConfiguration(c *echo.Context, body ApplicationArn string `json:"ApplicationArn"` UserBackgroundSessionApplicationStatus string `json:"UserBackgroundSessionApplicationStatus"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if err := h.Backend.PutApplicationSessionConfiguration( @@ -158,7 +160,7 @@ func (h *Handler) handleGetApplicationAssignmentConfiguration(c *echo.Context, b var req struct { ApplicationArn string `json:"ApplicationArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { @@ -178,7 +180,7 @@ func (h *Handler) handleGetApplicationSessionConfiguration(c *echo.Context, body var req struct { ApplicationArn string `json:"ApplicationArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { @@ -209,7 +211,7 @@ func (h *Handler) handleListApplicationAssignmentsForPrincipal(c *echo.Context, NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_application_auth_methods.go b/services/ssoadmin/handler_application_auth_methods.go index 60902b2b95..1a11703e35 100644 --- a/services/ssoadmin/handler_application_auth_methods.go +++ b/services/ssoadmin/handler_application_auth_methods.go @@ -1,10 +1,13 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDeleteApplicationAuthenticationMethod(c *echo.Context, body []byte) error { @@ -12,7 +15,7 @@ func (h *Handler) handleDeleteApplicationAuthenticationMethod(c *echo.Context, b ApplicationArn string `json:"ApplicationArn"` AuthMethodType string `json:"AuthenticationMethodType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -35,12 +38,12 @@ func (h *Handler) handleListApplicationAuthenticationMethods(c *echo.Context, bo func (h *Handler) handlePutApplicationAuthenticationMethod(c *echo.Context, body []byte) error { var req struct { - ApplicationArn string `json:"ApplicationArn"` - AuthMethodType string `json:"AuthenticationMethodType"` - AuthenticationType string `json:"AuthenticationType"` - AuthenticationMethod json.RawMessage `json:"AuthenticationMethod"` + ApplicationArn string `json:"ApplicationArn"` + AuthMethodType string `json:"AuthenticationMethodType"` + AuthenticationType string `json:"AuthenticationType"` + AuthenticationMethod jsontext.Value `json:"AuthenticationMethod"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } methodType := req.AuthMethodType @@ -61,7 +64,7 @@ func (h *Handler) handleGetApplicationAuthenticationMethod(c *echo.Context, body ApplicationArn string `json:"ApplicationArn"` AuthenticationMethodType string `json:"AuthenticationMethodType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { diff --git a/services/ssoadmin/handler_application_auth_methods_test.go b/services/ssoadmin/handler_application_auth_methods_test.go index a21abc9868..a898327ee6 100644 --- a/services/ssoadmin/handler_application_auth_methods_test.go +++ b/services/ssoadmin/handler_application_auth_methods_test.go @@ -1,12 +1,15 @@ package ssoadmin_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestApplicationAuthMethodStructured verifies PutApplicationAuthenticationMethod stores structured body. @@ -41,13 +44,13 @@ func TestApplicationAuthMethodStructured(t *testing.T) { instanceArn := createInstance(t, h, "auth-method-inst") appArn := createApplication(t, h, instanceArn, "AuthMethodApp") - bodyBytes, err := json.Marshal(tt.authBody) + bodyBytes, err := json.Marshal(tt.authBody, jsonx.V1()) require.NoError(t, err) rec := doRequest(t, h, "PutApplicationAuthenticationMethod", map[string]any{ "ApplicationArn": appArn, "AuthenticationMethodType": tt.authMethodType, - "AuthenticationMethod": json.RawMessage(bodyBytes), + "AuthenticationMethod": jsontext.Value(bodyBytes), }) assert.Equal(t, tt.wantStatus, rec.Code) }) diff --git a/services/ssoadmin/handler_application_grants.go b/services/ssoadmin/handler_application_grants.go index 9e99fced9c..a6d7b99445 100644 --- a/services/ssoadmin/handler_application_grants.go +++ b/services/ssoadmin/handler_application_grants.go @@ -1,10 +1,13 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDeleteApplicationGrant(c *echo.Context, body []byte) error { @@ -12,7 +15,7 @@ func (h *Handler) handleDeleteApplicationGrant(c *echo.Context, body []byte) err ApplicationArn string `json:"ApplicationArn"` GrantType string `json:"GrantType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if err := h.Backend.DeleteApplicationGrant(req.ApplicationArn, req.GrantType); err != nil { @@ -34,11 +37,11 @@ func (h *Handler) handleListApplicationGrants(c *echo.Context, body []byte) erro func (h *Handler) handlePutApplicationGrant(c *echo.Context, body []byte) error { var req struct { - ApplicationArn string `json:"ApplicationArn"` - GrantType string `json:"GrantType"` - Grant json.RawMessage `json:"Grant"` + ApplicationArn string `json:"ApplicationArn"` + GrantType string `json:"GrantType"` + Grant jsontext.Value `json:"Grant"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if err := h.Backend.PutApplicationGrant(req.ApplicationArn, req.GrantType, req.Grant); err != nil { @@ -53,7 +56,7 @@ func (h *Handler) handleGetApplicationGrant(c *echo.Context, body []byte) error ApplicationArn string `json:"ApplicationArn"` GrantType string `json:"GrantType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { diff --git a/services/ssoadmin/handler_applications.go b/services/ssoadmin/handler_applications.go index 3d1bda9f02..525f499c87 100644 --- a/services/ssoadmin/handler_applications.go +++ b/services/ssoadmin/handler_applications.go @@ -1,12 +1,14 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type applicationView struct { @@ -40,7 +42,7 @@ func (h *Handler) handleCreateApplication(c *echo.Context, body []byte) error { ClientToken string `json:"ClientToken"` Tags []tagView `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -101,7 +103,7 @@ func (h *Handler) handleDeleteApplication(c *echo.Context, body []byte) error { var req struct { ApplicationArn string `json:"ApplicationArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.ApplicationArn == "" { @@ -119,7 +121,7 @@ func (h *Handler) handleDescribeApplication(c *echo.Context, body []byte) error var req struct { ApplicationArn string `json:"ApplicationArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -163,7 +165,7 @@ func (h *Handler) handleDescribeApplicationProvider(c *echo.Context, body []byte var req struct { ApplicationProviderArn string `json:"ApplicationProviderArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -184,7 +186,7 @@ func (h *Handler) handleListApplicationProviders(c *echo.Context, body []byte) e NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -221,7 +223,7 @@ func (h *Handler) handleListApplications(c *echo.Context, body []byte) error { NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } apps := h.Backend.ListApplications(req.InstanceArn) @@ -276,7 +278,7 @@ func (h *Handler) handleUpdateApplication(c *echo.Context, body []byte) error { Description string `json:"Description"` Status string `json:"Status"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/ssoadmin/handler_inline_policies.go b/services/ssoadmin/handler_inline_policies.go index e737873cc0..a9bb68673c 100644 --- a/services/ssoadmin/handler_inline_policies.go +++ b/services/ssoadmin/handler_inline_policies.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handlePutInlinePolicyToPermissionSet(c *echo.Context, body []byte) error { @@ -13,7 +15,7 @@ func (h *Handler) handlePutInlinePolicyToPermissionSet(c *echo.Context, body []b PermissionSetArn string `json:"PermissionSetArn"` InlinePolicy string `json:"InlinePolicy"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -33,7 +35,7 @@ func (h *Handler) handleGetInlinePolicyForPermissionSet(c *echo.Context, body [] InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -52,7 +54,7 @@ func (h *Handler) handleDeleteInlinePolicyFromPermissionSet(c *echo.Context, bod InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/ssoadmin/handler_instance_access_control.go b/services/ssoadmin/handler_instance_access_control.go index d71ff604e0..c9cdcb1fed 100644 --- a/services/ssoadmin/handler_instance_access_control.go +++ b/services/ssoadmin/handler_instance_access_control.go @@ -1,11 +1,13 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleCreateInstanceAccessControlAttributeConfiguration(c *echo.Context, body []byte) error { @@ -20,7 +22,7 @@ func (h *Handler) handleCreateInstanceAccessControlAttributeConfiguration(c *ech } `json:"AccessControlAttributes"` } `json:"InstanceAccessControlAttributeConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -52,7 +54,7 @@ func (h *Handler) handleDeleteInstanceAccessControlAttributeConfiguration(c *ech var req struct { InstanceArn string `json:"InstanceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if err := h.Backend.DeleteInstanceAccessControlAttributeConfiguration(req.InstanceArn); err != nil { @@ -66,7 +68,7 @@ func (h *Handler) handleDescribeInstanceAccessControlAttributeConfiguration(c *e var req struct { InstanceArn string `json:"InstanceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } cfg, err := h.Backend.DescribeInstanceAccessControlAttributeConfiguration(req.InstanceArn) @@ -105,7 +107,7 @@ func (h *Handler) handleUpdateInstanceAccessControlAttributeConfiguration(c *ech } `json:"AccessControlAttributes"` } `json:"InstanceAccessControlAttributeConfiguration"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_instances.go b/services/ssoadmin/handler_instances.go index 34f221512d..a30948a3ff 100644 --- a/services/ssoadmin/handler_instances.go +++ b/services/ssoadmin/handler_instances.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type instanceView struct { @@ -34,7 +36,7 @@ func (h *Handler) handleListInstances(c *echo.Context, body []byte) error { } // Body is optional for ListInstances; ignore unmarshal errors on empty/garbage. if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } instances := h.Backend.ListInstances() @@ -78,7 +80,7 @@ func (h *Handler) handleCreateInstance(c *echo.Context, body []byte) error { ClientToken string `json:"ClientToken"` Tags []tagView `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -105,7 +107,7 @@ func (h *Handler) handleDescribeInstance(c *echo.Context, body []byte) error { var req struct { InstanceArn string `json:"InstanceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -162,7 +164,7 @@ func (h *Handler) handleDeleteInstance(c *echo.Context, body []byte) error { var req struct { InstanceArn string `json:"InstanceArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -186,7 +188,7 @@ func (h *Handler) handleUpdateInstance(c *echo.Context, body []byte) error { InstanceArn string `json:"InstanceArn"` Name string `json:"Name"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_managed_policies.go b/services/ssoadmin/handler_managed_policies.go index 65560d35aa..ddf0837cf7 100644 --- a/services/ssoadmin/handler_managed_policies.go +++ b/services/ssoadmin/handler_managed_policies.go @@ -1,11 +1,13 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type managedPolicyView struct { @@ -19,7 +21,7 @@ func (h *Handler) handleAttachManagedPolicyToPermissionSet(c *echo.Context, body PermissionSetArn string `json:"PermissionSetArn"` ManagedPolicyArn string `json:"ManagedPolicyArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -59,7 +61,7 @@ func (h *Handler) handleDetachManagedPolicyFromPermissionSet(c *echo.Context, bo PermissionSetArn string `json:"PermissionSetArn"` ManagedPolicyArn string `json:"ManagedPolicyArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -106,7 +108,7 @@ func (h *Handler) handleAttachCustomerManagedPolicyReferenceToPermissionSet(c *e PermissionSetArn string `json:"PermissionSetArn"` CustomerManagedPolicyReference customerManagedPolicyReferenceView `json:"CustomerManagedPolicyReference"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -152,7 +154,7 @@ func (h *Handler) handleDetachCustomerManagedPolicyReferenceFromPermissionSet( PermissionSetArn string `json:"PermissionSetArn"` CustomerManagedPolicyReference customerManagedPolicyReferenceView `json:"CustomerManagedPolicyReference"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_permission_sets.go b/services/ssoadmin/handler_permission_sets.go index acf5ebefd6..2753d7dbf1 100644 --- a/services/ssoadmin/handler_permission_sets.go +++ b/services/ssoadmin/handler_permission_sets.go @@ -1,12 +1,14 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type permissionSetView struct { @@ -27,7 +29,7 @@ func (h *Handler) handleCreatePermissionSet(c *echo.Context, body []byte) error RelayState string `json:"RelayState"` Tags []tagView `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -85,7 +87,7 @@ func (h *Handler) handleDescribePermissionSet(c *echo.Context, body []byte) erro InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -112,7 +114,7 @@ func (h *Handler) handleListPermissionSets(c *echo.Context, body []byte) error { NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -139,7 +141,7 @@ func (h *Handler) handleDeletePermissionSet(c *echo.Context, body []byte) error InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -167,7 +169,7 @@ func (h *Handler) handleUpdatePermissionSet(c *echo.Context, body []byte) error SessionDuration string `json:"SessionDuration"` RelayState string `json:"RelayState"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -191,7 +193,7 @@ func (h *Handler) handleProvisionPermissionSet(c *echo.Context, body []byte) err TargetType string `json:"TargetType"` TargetID string `json:"TargetId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -248,7 +250,7 @@ func (h *Handler) handleDescribePermissionSetProvisioningStatus( InstanceArn string `json:"InstanceArn"` ProvisionPermissionSetRequestID string `json:"ProvisionPermissionSetRequestId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -290,7 +292,7 @@ func (h *Handler) handleListPermissionSetsProvisionedToAccount(c *echo.Context, NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -338,7 +340,7 @@ func (h *Handler) handleListAccountsForProvisionedPermissionSet( NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_permissions_boundary.go b/services/ssoadmin/handler_permissions_boundary.go index 9122b2b3f9..045651695f 100644 --- a/services/ssoadmin/handler_permissions_boundary.go +++ b/services/ssoadmin/handler_permissions_boundary.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handlePutPermissionsBoundaryToPermissionSet(c *echo.Context, body []byte) error { @@ -19,7 +21,7 @@ func (h *Handler) handlePutPermissionsBoundaryToPermissionSet(c *echo.Context, b InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -49,7 +51,7 @@ func (h *Handler) handleGetPermissionsBoundaryForPermissionSet(c *echo.Context, InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -79,7 +81,7 @@ func (h *Handler) handleDeletePermissionsBoundaryFromPermissionSet(c *echo.Conte InstanceArn string `json:"InstanceArn"` PermissionSetArn string `json:"PermissionSetArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_regions.go b/services/ssoadmin/handler_regions.go index c055a88e99..566180910e 100644 --- a/services/ssoadmin/handler_regions.go +++ b/services/ssoadmin/handler_regions.go @@ -1,10 +1,12 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleAddRegion(c *echo.Context, body []byte) error { @@ -12,7 +14,7 @@ func (h *Handler) handleAddRegion(c *echo.Context, body []byte) error { InstanceArn string `json:"InstanceArn"` RegionName string `json:"RegionName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -31,7 +33,7 @@ func (h *Handler) handleRemoveRegion(c *echo.Context, body []byte) error { InstanceArn string `json:"InstanceArn"` RegionName string `json:"RegionName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -56,7 +58,7 @@ func (h *Handler) handleListRegions(c *echo.Context, body []byte) error { NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -100,7 +102,7 @@ func (h *Handler) handleDescribeRegion(c *echo.Context, body []byte) error { InstanceArn string `json:"InstanceArn"` RegionName string `json:"RegionName"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { diff --git a/services/ssoadmin/handler_tags.go b/services/ssoadmin/handler_tags.go index 099f2ab0bc..6463dad0b4 100644 --- a/services/ssoadmin/handler_tags.go +++ b/services/ssoadmin/handler_tags.go @@ -1,12 +1,14 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "sort" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { @@ -15,7 +17,7 @@ func (h *Handler) handleTagResource(c *echo.Context, body []byte) error { ResourceArn string `json:"ResourceArn"` Tags []tagView `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -42,7 +44,7 @@ func (h *Handler) handleUntagResource(c *echo.Context, body []byte) error { ResourceArn string `json:"ResourceArn"` TagKeys []string `json:"TagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -59,7 +61,7 @@ func (h *Handler) handleListTagsForResource(c *echo.Context, body []byte) error ResourceArn string `json:"ResourceArn"` NextToken string `json:"NextToken"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/ssoadmin/handler_test.go b/services/ssoadmin/handler_test.go index 6cb7a7dcdd..c6635e08c5 100644 --- a/services/ssoadmin/handler_test.go +++ b/services/ssoadmin/handler_test.go @@ -2,7 +2,7 @@ package ssoadmin_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/ssoadmin" ) @@ -33,7 +34,7 @@ func doRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -53,7 +54,7 @@ func doRequest( func parseResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -444,7 +445,7 @@ func TestHandler_ExtractResource(t *testing.T) { var bodyBytes []byte if tt.body != nil { var err error - bodyBytes, err = json.Marshal(tt.body) + bodyBytes, err = json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/ssoadmin/handler_trusted_token_issuers.go b/services/ssoadmin/handler_trusted_token_issuers.go index fd1dfb54ec..0d37f2c0a0 100644 --- a/services/ssoadmin/handler_trusted_token_issuers.go +++ b/services/ssoadmin/handler_trusted_token_issuers.go @@ -1,11 +1,13 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // trustedTokenIssuerView is the wire shape for types.TrustedTokenIssuerMetadata @@ -34,7 +36,7 @@ func (h *Handler) handleCreateTrustedTokenIssuer(c *echo.Context, body []byte) e ClientToken string `json:"ClientToken"` Tags []tagView `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.InstanceArn == "" { @@ -93,7 +95,7 @@ func (h *Handler) handleDeleteTrustedTokenIssuer(c *echo.Context, body []byte) e var req struct { TrustedTokenIssuerArn string `json:"TrustedTokenIssuerArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } if req.TrustedTokenIssuerArn == "" { @@ -110,7 +112,7 @@ func (h *Handler) handleDescribeTrustedTokenIssuer(c *echo.Context, body []byte) var req struct { TrustedTokenIssuerArn string `json:"TrustedTokenIssuerArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } @@ -153,7 +155,7 @@ func (h *Handler) handleListTrustedTokenIssuers(c *echo.Context, body []byte) er NextToken string `json:"NextToken"` MaxResults int `json:"MaxResults"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } issuers := h.Backend.ListTrustedTokenIssuers(req.InstanceArn) @@ -189,7 +191,7 @@ func (h *Handler) handleUpdateTrustedTokenIssuer(c *echo.Context, body []byte) e Name string `json:"Name"` TrustedTokenIssuerType string `json:"TrustedTokenIssuerType"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return writeError(c, http.StatusBadRequest, "ValidationException", "invalid request body") } diff --git a/services/ssoadmin/interfaces.go b/services/ssoadmin/interfaces.go index 2643333c8e..3dd9aa1c0a 100644 --- a/services/ssoadmin/interfaces.go +++ b/services/ssoadmin/interfaces.go @@ -2,7 +2,7 @@ package ssoadmin import ( "context" - "encoding/json" + "encoding/json/jsontext" ) // StorageBackend is the interface for SSO Admin storage operations. @@ -74,9 +74,9 @@ type StorageBackend interface { PutApplicationAccessScope(applicationArn, scope string, authorizedTargets []string) error PutApplicationAssignmentConfiguration(applicationArn string, assignmentRequired bool) error // PutApplicationAuthenticationMethod stores a structured auth method body; authMethodType must be IAM. - PutApplicationAuthenticationMethod(applicationArn, authMethodType string, body json.RawMessage) error + PutApplicationAuthenticationMethod(applicationArn, authMethodType string, body jsontext.Value) error // PutApplicationGrant stores a structured grant body; grantType must be a valid AWS grant type enum value. - PutApplicationGrant(applicationArn, grantType string, body json.RawMessage) error + PutApplicationGrant(applicationArn, grantType string, body jsontext.Value) error // PutApplicationSessionConfiguration sets the UserBackgroundSessionApplicationStatus // (ENABLED or DISABLED) for an application. Matches the real // PutApplicationSessionConfigurationInput wire shape -- this is NOT a @@ -116,8 +116,8 @@ type StorageBackend interface { GetApplicationAssignmentConfiguration(applicationArn string) (bool, error) GetApplicationSessionConfiguration(applicationArn string) (string, error) GetApplicationAccessScope(applicationArn, scope string) ([]string, error) - GetApplicationAuthenticationMethod(applicationArn, authMethodType string) (json.RawMessage, error) - GetApplicationGrant(applicationArn, grantType string) (json.RawMessage, error) + GetApplicationAuthenticationMethod(applicationArn, authMethodType string) (jsontext.Value, error) + GetApplicationGrant(applicationArn, grantType string) (jsontext.Value, error) ListCustomerManagedPolicyReferencesInPermissionSet( instanceArn, permissionSetArn string, ) ([]CustomerManagedPolicyReference, error) diff --git a/services/ssoadmin/models.go b/services/ssoadmin/models.go index b39cb1d11e..e9b98cefd4 100644 --- a/services/ssoadmin/models.go +++ b/services/ssoadmin/models.go @@ -1,7 +1,7 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" "regexp" "time" ) @@ -348,12 +348,12 @@ type ScopeDetails struct { // AuthMethod holds a typed authentication method with its full structured body. type AuthMethod struct { - AuthMethodType string `json:"AuthenticationMethodType"` - Body json.RawMessage `json:"AuthenticationMethod"` + AuthMethodType string `json:"AuthenticationMethodType"` + Body jsontext.Value `json:"AuthenticationMethod"` } // ApplicationGrant holds a typed grant with its structured body. type ApplicationGrant struct { - GrantType string `json:"GrantType"` - Grant json.RawMessage `json:"Grant"` + GrantType string `json:"GrantType"` + Grant jsontext.Value `json:"Grant"` } diff --git a/services/ssoadmin/multi_region_test.go b/services/ssoadmin/multi_region_test.go index 6cc2def6c8..dca0aee1fa 100644 --- a/services/ssoadmin/multi_region_test.go +++ b/services/ssoadmin/multi_region_test.go @@ -2,7 +2,8 @@ package ssoadmin_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/ssoadmin" ) @@ -172,8 +174,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/ssoadmin/persistence.go b/services/ssoadmin/persistence.go index 840e985b40..0b944935a3 100644 --- a/services/ssoadmin/persistence.go +++ b/services/ssoadmin/persistence.go @@ -2,11 +2,13 @@ package ssoadmin import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -74,7 +76,7 @@ func newDirtyDTORegistry() ( // newer) build of this backend as though it were the current shape; see // Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Assignments map[string][]*AccountAssignment `json:"assignments"` AssignmentCreationIDs map[string]string `json:"assignmentCreationIDs"` ProvisionedAt map[string]time.Time `json:"provisionedAt"` @@ -82,8 +84,8 @@ type backendSnapshot struct { CustomerManagedPolicies map[string][]CustomerManagedPolicyReference `json:"customerManagedPolicies"` ApplicationAssignments map[string][]*ApplicationAssignment `json:"applicationAssignments"` ApplicationScopes map[string]map[string][]string `json:"applicationScopes"` - ApplicationAuthMethods map[string]map[string]json.RawMessage `json:"applicationAuthMethods"` - ApplicationGrants map[string]map[string]json.RawMessage `json:"applicationGrants"` + ApplicationAuthMethods map[string]map[string]jsontext.Value `json:"applicationAuthMethods"` + ApplicationGrants map[string]map[string]jsontext.Value `json:"applicationGrants"` ApplicationAssignConfig map[string]bool `json:"applicationAssignConfig"` ApplicationSessions map[string]string `json:"applicationSessions"` AccountID string `json:"accountID"` @@ -140,7 +142,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Region: b.region, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "ssoadmin: failed to marshal snapshot", "error", err) @@ -182,8 +184,8 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { b.customerManagedPolicies = make(map[string][]CustomerManagedPolicyReference) b.applicationAssignments = make(map[string][]*ApplicationAssignment) b.applicationScopes = make(map[string]map[string][]string) - b.applicationAuthMethods = make(map[string]map[string]json.RawMessage) - b.applicationGrants = make(map[string]map[string]json.RawMessage) + b.applicationAuthMethods = make(map[string]map[string]jsontext.Value) + b.applicationGrants = make(map[string]map[string]jsontext.Value) b.applicationAssignConfig = make(map[string]bool) b.applicationSessions = make(map[string]string) b.seedDefaultInstance() @@ -222,7 +224,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // permissionBoundaries) from tables via the ephemeral DTO registry, // converting each DTO back to its live type. The caller MUST hold b.mu for // writing. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtoReg, acaDTOs, pbDTOs := newDirtyDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { diff --git a/services/ssoadmin/store.go b/services/ssoadmin/store.go index ed35b44a08..ce79de0a4b 100644 --- a/services/ssoadmin/store.go +++ b/services/ssoadmin/store.go @@ -1,7 +1,7 @@ package ssoadmin import ( - "encoding/json" + "encoding/json/jsontext" "time" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -34,9 +34,9 @@ type InMemoryBackend struct { applicationAssignments map[string][]*ApplicationAssignment applicationScopes map[string]map[string][]string // applicationAuthMethods stores per-app authentication methods: appArn → methodType → full JSON body. - applicationAuthMethods map[string]map[string]json.RawMessage + applicationAuthMethods map[string]map[string]jsontext.Value // applicationGrants stores per-app grants: appArn → grantType → full JSON body. - applicationGrants map[string]map[string]json.RawMessage + applicationGrants map[string]map[string]jsontext.Value applicationAssignConfig map[string]bool applicationSessions map[string]string instanceACAs *store.Table[ABACConfig] @@ -81,8 +81,8 @@ func NewInMemoryBackend(accountID, region string) *InMemoryBackend { customerManagedPolicies: make(map[string][]CustomerManagedPolicyReference), applicationAssignments: make(map[string][]*ApplicationAssignment), applicationScopes: make(map[string]map[string][]string), - applicationAuthMethods: make(map[string]map[string]json.RawMessage), - applicationGrants: make(map[string]map[string]json.RawMessage), + applicationAuthMethods: make(map[string]map[string]jsontext.Value), + applicationGrants: make(map[string]map[string]jsontext.Value), applicationAssignConfig: make(map[string]bool), applicationSessions: make(map[string]string), mu: lockmetrics.New("ssoadmin"), @@ -115,8 +115,8 @@ func (b *InMemoryBackend) Reset() { b.customerManagedPolicies = make(map[string][]CustomerManagedPolicyReference) b.applicationAssignments = make(map[string][]*ApplicationAssignment) b.applicationScopes = make(map[string]map[string][]string) - b.applicationAuthMethods = make(map[string]map[string]json.RawMessage) - b.applicationGrants = make(map[string]map[string]json.RawMessage) + b.applicationAuthMethods = make(map[string]map[string]jsontext.Value) + b.applicationGrants = make(map[string]map[string]jsontext.Value) b.applicationAssignConfig = make(map[string]bool) b.applicationSessions = make(map[string]string) // Re-seed the default instance. diff --git a/services/ssooidc/handler.go b/services/ssooidc/handler.go index a51f5b51e0..3db3cd1c0d 100644 --- a/services/ssooidc/handler.go +++ b/services/ssooidc/handler.go @@ -1,7 +1,7 @@ package ssooidc import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -151,7 +152,7 @@ func decodeBody(c *echo.Context, v any) *APIError { return nil } - if json.Unmarshal(body, v) != nil { + if json.Unmarshal(body, v, jsonx.V1()) != nil { return errInvalidRequest("the request body is not valid JSON") } diff --git a/services/ssooidc/persistence.go b/services/ssooidc/persistence.go index 61ed403621..8d0624a1ac 100644 --- a/services/ssooidc/persistence.go +++ b/services/ssooidc/persistence.go @@ -2,7 +2,7 @@ package ssooidc import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,8 +12,8 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + Version int `json:"version"` } // Snapshot serializes the backend state. It implements persistence.Persistable. diff --git a/services/stepfunctions/aliases_test.go b/services/stepfunctions/aliases_test.go index 876b30c698..bf1bd81bdb 100644 --- a/services/stepfunctions/aliases_test.go +++ b/services/stepfunctions/aliases_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -29,7 +30,7 @@ func createSFNStateMachineCov( require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn, _ := resp["stateMachineArn"].(string) require.NotEmpty(t, arn) @@ -299,14 +300,14 @@ func TestCreateStateMachineAlias_RoutingViaHandler(t *testing.T) { "routingConfiguration": []map[string]any{ {"stateMachineVersionArn": "arn:fake:version:1", "weight": 50}, }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachineAlias", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // CHANGED (orphan-code sweep, gopherstack-yatn): this assertion previously // checked for "InvalidRoutingConfiguration", which is not a real sfn SDK // error type -- CreateStateMachineAlias's own deserializeOpError switch diff --git a/services/stepfunctions/asl/distributed_scope.go b/services/stepfunctions/asl/distributed_scope.go index e6d7ab3298..40517f53bc 100644 --- a/services/stepfunctions/asl/distributed_scope.go +++ b/services/stepfunctions/asl/distributed_scope.go @@ -1,11 +1,13 @@ package asl import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "regexp" "slices" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var varRefRe = regexp.MustCompile( @@ -30,13 +32,13 @@ func checkDistributedScope(mapName string, states map[string]*State, outer map[s continue } - b, err := json.Marshal(st) + b, err := json.Marshal(st, jsonx.V1()) if err != nil { return fmt.Errorf("%w: state %q: %w", ErrParseError, name, err) } var doc any - if err = json.Unmarshal(b, &doc); err != nil { + if err = json.Unmarshal(b, &doc, jsonx.V1()); err != nil { return fmt.Errorf("%w: state %q: %w", ErrParseError, name, err) } diff --git a/services/stepfunctions/asl/executor.go b/services/stepfunctions/asl/executor.go index c1071f55c4..dcd5de891d 100644 --- a/services/stepfunctions/asl/executor.go +++ b/services/stepfunctions/asl/executor.go @@ -7,7 +7,8 @@ import ( cryptorand "crypto/rand" "encoding/base64" "encoding/csv" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -26,6 +27,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/arn" "github.com/blackbirdworks/gopherstack/pkgs/awstime" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/roleauth" ) @@ -747,7 +749,7 @@ func (e *Executor) Execute( ) (*ExecutionResult, error) { var input any if inputJSON != "" { - if err := json.Unmarshal([]byte(inputJSON), &input); err != nil { + if err := json.Unmarshal([]byte(inputJSON), &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid input JSON: %w", err) } } @@ -981,7 +983,7 @@ func (e *Executor) executeState( func (e *Executor) executePass(state *State, input any) (string, any, error) { if len(state.Result) > 0 { var result any - if err := json.Unmarshal(state.Result, &result); err != nil { + if err := json.Unmarshal(state.Result, &result, jsonx.V1()); err != nil { return "", nil, fmt.Errorf("invalid Pass Result: %w", err) } @@ -1381,7 +1383,7 @@ func (e *Executor) invokeTaskAttempt( func parseCallbackOutput(output string) any { var result any if output != "" { - if err := json.Unmarshal([]byte(output), &result); err == nil { + if err := json.Unmarshal([]byte(output), &result, jsonx.V1()); err == nil { return result } } @@ -1679,7 +1681,7 @@ func (e *Executor) invokeActivityTask(ctx context.Context, state *State, input a } var result any - if unmarshalErr := json.Unmarshal([]byte(output), &result); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal([]byte(output), &result, jsonx.V1()); unmarshalErr != nil { // Non-JSON output is valid; return the raw string. Intentionally ignoring unmarshalErr. return output, nil //nolint:nilerr // non-JSON activity output is valid; return as string } @@ -1697,7 +1699,7 @@ func (e *Executor) invokeLambdaTask(ctx context.Context, state *State, input any return nil, err } - payload, err := json.Marshal(input) + payload, err := json.Marshal(input, jsonx.V1()) if err != nil { return nil, fmt.Errorf("failed to marshal task input: %w", err) } @@ -1723,7 +1725,7 @@ func (e *Executor) invokeLambdaTask(ctx context.Context, state *State, input any var result any // Non-JSON Lambda responses are valid; fall back to raw string. - if json.Unmarshal(respBytes, &result) != nil { + if json.Unmarshal(respBytes, &result, jsonx.V1()) != nil { result = string(respBytes) } @@ -2538,7 +2540,7 @@ func batchItems(items []any, maxItemsPerBatch, maxInputBytesPerBatch int) []any return batches } -func (e *Executor) batchInput(b *ItemBatcher) json.RawMessage { +func (e *Executor) batchInput(b *ItemBatcher) jsontext.Value { if e.jxMapCfg != nil { return e.jxMapCfg.batchInput } @@ -2552,10 +2554,10 @@ func (e *Executor) batchInput(b *ItemBatcher) json.RawMessage { // input-output-itembatcher.html, "Batch input"). Previously each batch was // passed through as a bare array, which is not the shape AWS sends to child // executions. -func wrapItemBatcherBatches(rawBatches []any, batchInput json.RawMessage) ([]any, error) { +func wrapItemBatcherBatches(rawBatches []any, batchInput jsontext.Value) ([]any, error) { var decodedBatchInput any if len(batchInput) > 0 { - if err := json.Unmarshal(batchInput, &decodedBatchInput); err != nil { + if err := json.Unmarshal(batchInput, &decodedBatchInput, jsonx.V1()); err != nil { return nil, fmt.Errorf("ItemBatcher.BatchInput error: %w", err) } } @@ -2576,7 +2578,7 @@ func wrapItemBatcherBatches(rawBatches []any, batchInput json.RawMessage) ([]any func applyMapItemSelector( slot *atomic.Pointer[parsedTemplate], - itemSelector json.RawMessage, + itemSelector jsontext.Value, items []any, vars varFunc, ) ([]any, error) { @@ -2984,7 +2986,7 @@ func (e *Executor) resolveS3InventoryManifest( cfg *ReaderConfig, ) ([]any, error) { var manifest s3InventoryManifest - if err := json.Unmarshal(manifestData, &manifest); err != nil { + if err := json.Unmarshal(manifestData, &manifest, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: manifest.json: %w", ErrItemReaderInvalidData, err) } @@ -3078,7 +3080,7 @@ func decodeJSONItems(data []byte, cfg *ReaderConfig) ([]any, error) { } var doc any - if err := json.Unmarshal(data, &doc); err != nil { + if err := json.Unmarshal(data, &doc, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrItemReaderInvalidData, err) } @@ -3155,7 +3157,7 @@ func stepJSONPointer(cur any, tok string) (any, error) { func decodeJSONAuto(data []byte) ([]any, error) { var arr []any - if jsonErr := json.Unmarshal(data, &arr); jsonErr == nil { + if jsonErr := json.Unmarshal(data, &arr, jsonx.V1()); jsonErr == nil { return arr, nil } @@ -3174,7 +3176,7 @@ func decodeJSONLines(data []byte) ([]any, error) { } var item any - if jsonErr := json.Unmarshal([]byte(line), &item); jsonErr != nil { + if jsonErr := json.Unmarshal([]byte(line), &item, jsonx.V1()); jsonErr != nil { return nil, fmt.Errorf("%w: %w", ErrItemReaderInvalidData, jsonErr) } @@ -4469,7 +4471,7 @@ func marshalInput(v any) string { return "{}" } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "{}" } @@ -4485,13 +4487,13 @@ type parsedTemplate struct { } // loadTemplate returns the slot's parsed template, decoding raw on first use. -func loadTemplate(slot *atomic.Pointer[parsedTemplate], raw json.RawMessage) *parsedTemplate { +func loadTemplate(slot *atomic.Pointer[parsedTemplate], raw jsontext.Value) *parsedTemplate { if pt := slot.Load(); pt != nil { return pt } pt := &parsedTemplate{usesContext: bytes.Contains(raw, []byte("$$"))} - if err := json.Unmarshal(raw, &pt.tmpl); err != nil { + if err := json.Unmarshal(raw, &pt.tmpl, jsonx.V1()); err != nil { pt.err = fmt.Errorf("invalid template: %w", err) } diff --git a/services/stepfunctions/asl/executor_test.go b/services/stepfunctions/asl/executor_test.go index 3535599a67..c654d4d0b5 100644 --- a/services/stepfunctions/asl/executor_test.go +++ b/services/stepfunctions/asl/executor_test.go @@ -2,7 +2,7 @@ package asl_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync/atomic" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -2112,7 +2113,7 @@ func TestExecutor_UnknownStateType(t *testing.T) { // Parse will succeed but execute will fail on unknown type. var sm asl.StateMachine - require.NoError(t, json.Unmarshal([]byte(tt.def), &sm)) + require.NoError(t, json.Unmarshal([]byte(tt.def), &sm, jsonx.V1())) exec := asl.NewExecutor(&sm, nil, nil) _, err := exec.Execute(t.Context(), "test", tt.input) require.Error(t, err) diff --git a/services/stepfunctions/asl/intrinsics.go b/services/stepfunctions/asl/intrinsics.go index ac8b113e46..8988b9b6bc 100644 --- a/services/stepfunctions/asl/intrinsics.go +++ b/services/stepfunctions/asl/intrinsics.go @@ -6,7 +6,7 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "hash" @@ -15,6 +15,8 @@ import ( "slices" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Sentinel errors for intrinsic function evaluation. @@ -260,7 +262,7 @@ func intrinsicFormat(args []any) (any, error) { case nil: result.WriteString("null") default: - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, fmt.Errorf("States.Format: cannot serialize argument: %w", err) } @@ -289,7 +291,7 @@ func intrinsicStringToJSON(args []any) (any, error) { } var result any - if err := json.Unmarshal([]byte(s), &result); err != nil { + if err := json.Unmarshal([]byte(s), &result, jsonx.V1()); err != nil { return nil, fmt.Errorf("States.StringToJson: invalid JSON: %w", err) } @@ -302,7 +304,7 @@ func intrinsicJSONToString(args []any) (any, error) { return nil, ErrStatesJSONToStringRequiresArg } - b, err := json.Marshal(args[0]) + b, err := json.Marshal(args[0], jsonx.V1()) if err != nil { return nil, fmt.Errorf("States.JsonToString: cannot serialize: %w", err) } diff --git a/services/stepfunctions/asl/item_reader_parquet.go b/services/stepfunctions/asl/item_reader_parquet.go index f98cec716a..2f6dde0f6e 100644 --- a/services/stepfunctions/asl/item_reader_parquet.go +++ b/services/stepfunctions/asl/item_reader_parquet.go @@ -2,10 +2,11 @@ package asl import ( "encoding/binary" - "encoding/json" + "encoding/json/v2" "errors" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -66,13 +67,13 @@ func decodeParquetItems(data []byte) ([]any, error) { // normalizeJSONNumbers round-trips items so numbers carry the float64 type // every other ItemReader decoder yields. func normalizeJSONNumbers(items []any) ([]any, error) { - raw, err := json.Marshal(items) + raw, err := json.Marshal(items, jsonx.V1()) if err != nil { return nil, fmt.Errorf("%w: %w", ErrItemReaderInvalidData, err) } var out []any - if err = json.Unmarshal(raw, &out); err != nil { + if err = json.Unmarshal(raw, &out, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrItemReaderInvalidData, err) } diff --git a/services/stepfunctions/asl/jsonata.go b/services/stepfunctions/asl/jsonata.go index 978adea651..2349a97a6a 100644 --- a/services/stepfunctions/asl/jsonata.go +++ b/services/stepfunctions/asl/jsonata.go @@ -2,7 +2,7 @@ package asl import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -11,6 +11,8 @@ import ( "time" "github.com/recolabs/gnata" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -114,13 +116,13 @@ func normalizeJSONata(v any) (any, error) { return v, nil } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return nil, queryEvalError(fmt.Errorf("%w: result is not valid JSON: %w", errJSONataEval, err)) } var out any - if err = json.Unmarshal(b, &out); err != nil { + if err = json.Unmarshal(b, &out, jsonx.V1()); err != nil { return nil, queryEvalError(err) } diff --git a/services/stepfunctions/asl/jsonata_exec.go b/services/stepfunctions/asl/jsonata_exec.go index 4aa9ad90a8..ed6736a581 100644 --- a/services/stepfunctions/asl/jsonata_exec.go +++ b/services/stepfunctions/asl/jsonata_exec.go @@ -2,13 +2,16 @@ package asl import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" "math" "slices" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // runJSONataState runs one JSONata-mode state (Arguments, body, then the @@ -396,7 +399,7 @@ func (e *Executor) jxItemSelector(state *State, items []any) ([]any, error) { type jxMapConfig struct { readerParams map[string]any writerParams map[string]any - batchInput json.RawMessage + batchInput jsontext.Value maxItems int maxBytes int } @@ -445,7 +448,7 @@ func (s *jxScope) resolveBatcher(b *ItemBatcher, cfg *jxMapConfig) error { return err } - if cfg.batchInput, err = json.Marshal(v); err != nil { + if cfg.batchInput, err = json.Marshal(v, jsonx.V1()); err != nil { return queryEvalError(err) } diff --git a/services/stepfunctions/asl/jsonata_validate.go b/services/stepfunctions/asl/jsonata_validate.go index c0517b57c5..d6603a346f 100644 --- a/services/stepfunctions/asl/jsonata_validate.go +++ b/services/stepfunctions/asl/jsonata_validate.go @@ -1,12 +1,15 @@ package asl import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "regexp" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -144,20 +147,20 @@ func walkNested(name string, st *State, lang string, inner map[string]struct{}) return nil } -func decodeAssign(raw json.RawMessage) (map[string]any, error) { +func decodeAssign(raw jsontext.Value) (map[string]any, error) { if len(raw) == 0 { return nil, nil //nolint:nilnil // field absent } var m map[string]any - if err := json.Unmarshal(raw, &m); err != nil || m == nil { + if err := json.Unmarshal(raw, &m, jsonx.V1()); err != nil || m == nil { return nil, fmt.Errorf("%w: Assign must be a JSON object", ErrParseError) } return m, nil } -func assignNames(raw json.RawMessage, into map[string]struct{}) error { +func assignNames(raw jsontext.Value, into map[string]struct{}) error { m, err := decodeAssign(raw) if err != nil { return err @@ -373,9 +376,9 @@ func rejectJSONPathFields(name string, st *State) error { return nil } -func decodeRaw(name, field string, raw json.RawMessage) (any, error) { +func decodeRaw(name, field string, raw jsontext.Value) (any, error) { var v any - if err := json.Unmarshal(raw, &v); err != nil { + if err := json.Unmarshal(raw, &v, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: state %q: invalid %s: %w", ErrParseError, name, field, err) } @@ -487,7 +490,7 @@ func (j *jxState) prepareFields(name string, st *State) error { dst *any set *bool field string - raw json.RawMessage + raw jsontext.Value ctx exprCtx }{ {&j.args, &j.hasArgs, "Arguments", st.Arguments, plain}, @@ -519,7 +522,7 @@ func (j *jxState) prepareFields(name string, st *State) error { return j.prepareRest(name, st, exprCtx{result: hasResult}) } -func readerArguments(st *State) json.RawMessage { +func readerArguments(st *State) jsontext.Value { if st.ItemReader == nil { return nil } @@ -527,7 +530,7 @@ func readerArguments(st *State) json.RawMessage { return st.ItemReader.Arguments } -func writerArguments(st *State) json.RawMessage { +func writerArguments(st *State) jsontext.Value { if st.ResultWriter == nil { return nil } @@ -535,7 +538,7 @@ func writerArguments(st *State) json.RawMessage { return st.ResultWriter.Arguments } -func batchInputRaw(st *State) json.RawMessage { +func batchInputRaw(st *State) jsontext.Value { if st.ItemBatcher == nil { return nil } diff --git a/services/stepfunctions/asl/mock.go b/services/stepfunctions/asl/mock.go index 82ff496a15..23d2d4c4ff 100644 --- a/services/stepfunctions/asl/mock.go +++ b/services/stepfunctions/asl/mock.go @@ -1,7 +1,8 @@ package asl import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "os" @@ -9,6 +10,8 @@ import ( "strconv" "strings" "sync/atomic" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Mock config errors. @@ -29,7 +32,7 @@ type MockConfig struct { type mockStep struct { throw *mockThrow - ret json.RawMessage + ret jsontext.Value lo int hi int } @@ -40,8 +43,8 @@ type mockThrow struct { } type mockFileStep struct { - Throw *mockThrow `json:"Throw"` - Return json.RawMessage `json:"Return"` + Throw *mockThrow `json:"Throw"` + Return jsontext.Value `json:"Return"` } type mockFile struct { @@ -64,7 +67,7 @@ func LoadMockConfig(path string) (*MockConfig, error) { // ParseMockConfig validates mock configuration JSON. func ParseMockConfig(data []byte) (*MockConfig, error) { var f mockFile - if err := json.Unmarshal(data, &f); err != nil { + if err := json.Unmarshal(data, &f, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrMockConfigInvalid, err) } @@ -204,7 +207,7 @@ func (r *MockRun) invoke(stateName string) (any, bool, error) { } var out any - if uerr := json.Unmarshal(s.ret, &out); uerr != nil { + if uerr := json.Unmarshal(s.ret, &out, jsonx.V1()); uerr != nil { return nil, true, &FailError{ErrCode: errCodeStatesRuntime, Cause: uerr.Error()} } diff --git a/services/stepfunctions/asl/parser.go b/services/stepfunctions/asl/parser.go index 864f7de619..f96daa3cb0 100644 --- a/services/stepfunctions/asl/parser.go +++ b/services/stepfunctions/asl/parser.go @@ -4,10 +4,13 @@ package asl import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "sync/atomic" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrParseError is returned when the state machine definition cannot be parsed. @@ -70,37 +73,37 @@ type ItemBatcher struct { maxItemsExpr string maxBytesExpr string // BatchInput is fixed JSON merged into every batch (input-output-itembatcher.html). - BatchInput json.RawMessage `json:"BatchInput,omitempty"` + BatchInput jsontext.Value `json:"BatchInput,omitempty"` // MaxItemsPerBatch and MaxInputBytesPerBatch: zero means no limit. MaxItemsPerBatch int `json:"MaxItemsPerBatch,omitempty"` MaxInputBytesPerBatch int `json:"MaxInputBytesPerBatch,omitempty"` } type itemBatcherWire struct { - MaxItemsPerBatchPath string `json:"MaxItemsPerBatchPath,omitempty"` - MaxInputBytesPerBatchPath string `json:"MaxInputBytesPerBatchPath,omitempty"` - MaxItemsPerBatch json.RawMessage `json:"MaxItemsPerBatch,omitempty"` - MaxInputBytesPerBatch json.RawMessage `json:"MaxInputBytesPerBatch,omitempty"` - BatchInput json.RawMessage `json:"BatchInput,omitempty"` + MaxItemsPerBatchPath string `json:"MaxItemsPerBatchPath,omitempty"` + MaxInputBytesPerBatchPath string `json:"MaxInputBytesPerBatchPath,omitempty"` + MaxItemsPerBatch jsontext.Value `json:"MaxItemsPerBatch,omitempty"` + MaxInputBytesPerBatch jsontext.Value `json:"MaxInputBytesPerBatch,omitempty"` + BatchInput jsontext.Value `json:"BatchInput,omitempty"` } // decodeBatchLimit reads an integer limit, or a JSONata string into expr. -func decodeBatchLimit(raw json.RawMessage, n *int, expr *string) error { +func decodeBatchLimit(raw jsontext.Value, n *int, expr *string) error { if len(raw) == 0 { return nil } if raw[0] == '"' { - return json.Unmarshal(raw, expr) + return json.Unmarshal(raw, expr, jsonx.V1()) } - return json.Unmarshal(raw, n) + return json.Unmarshal(raw, n, jsonx.V1()) } // UnmarshalJSON accepts a JSONata string for either batch limit. func (b *ItemBatcher) UnmarshalJSON(data []byte) error { var w itemBatcherWire - if err := json.Unmarshal(data, &w); err != nil { + if err := json.Unmarshal(data, &w, jsonx.V1()); err != nil { return err } @@ -134,15 +137,15 @@ func (b ItemBatcher) MarshalJSON() ([]byte, error) { return nil, err } - return json.Marshal(w) + return json.Marshal(w, jsonx.V1()) } -func encodeBatchLimit(n int, expr string) (json.RawMessage, error) { +func encodeBatchLimit(n int, expr string) (jsontext.Value, error) { switch { case expr != "": - return json.Marshal(expr) + return json.Marshal(expr, jsonx.V1()) case n != 0: - return json.Marshal(n) + return json.Marshal(n, jsonx.V1()) } return nil, nil @@ -150,10 +153,10 @@ func encodeBatchLimit(n int, expr string) (json.RawMessage, error) { // ItemReader configures reading items from S3 for a Map state's Distributed Map. type ItemReader struct { - Parameters map[string]any `json:"Parameters,omitempty"` - ReaderConfig *ReaderConfig `json:"ReaderConfig,omitempty"` - Resource string `json:"Resource,omitempty"` - Arguments json.RawMessage `json:"Arguments,omitempty"` + Parameters map[string]any `json:"Parameters,omitempty"` + ReaderConfig *ReaderConfig `json:"ReaderConfig,omitempty"` + Resource string `json:"Resource,omitempty"` + Arguments jsontext.Value `json:"Arguments,omitempty"` } // ReaderConfig describes how the ItemReader should interpret S3 object data. @@ -196,7 +199,7 @@ type ResultWriter struct { Parameters map[string]any `json:"Parameters,omitempty"` WriterConfig *ResultWriterConfig `json:"WriterConfig,omitempty"` Resource string `json:"Resource,omitempty"` - Arguments json.RawMessage `json:"Arguments,omitempty"` + Arguments jsontext.Value `json:"Arguments,omitempty"` } // ResultWriterConfig is ResultWriter.WriterConfig: Transformation @@ -215,22 +218,22 @@ type State struct { jx *jxState assignVals map[string]any // numExprs holds JSONata strings for fields that are integers in JSONPath. - Assign json.RawMessage `json:"Assign,omitempty"` - Arguments json.RawMessage `json:"Arguments,omitempty"` - Output json.RawMessage `json:"Output,omitempty"` - Items json.RawMessage `json:"Items,omitempty"` + Assign jsontext.Value `json:"Assign,omitempty"` + Arguments jsontext.Value `json:"Arguments,omitempty"` + Output jsontext.Value `json:"Output,omitempty"` + Items jsontext.Value `json:"Items,omitempty"` numExprs map[string]string QueryLanguage string `json:"QueryLanguage,omitempty"` lang string - Iterator *StateMachine `json:"Iterator,omitempty"` - ItemProcessor *StateMachine `json:"ItemProcessor,omitempty"` - ItemBatcher *ItemBatcher `json:"ItemBatcher,omitempty"` - ItemReader *ItemReader `json:"ItemReader,omitempty"` - ResultWriter *ResultWriter `json:"ResultWriter,omitempty"` - ItemSelector json.RawMessage `json:"ItemSelector,omitempty"` - SecondsPath string `json:"SecondsPath,omitempty"` - TimestampPath string `json:"TimestampPath,omitempty"` - ItemsPath string `json:"ItemsPath,omitempty"` + Iterator *StateMachine `json:"Iterator,omitempty"` + ItemProcessor *StateMachine `json:"ItemProcessor,omitempty"` + ItemBatcher *ItemBatcher `json:"ItemBatcher,omitempty"` + ItemReader *ItemReader `json:"ItemReader,omitempty"` + ResultWriter *ResultWriter `json:"ResultWriter,omitempty"` + ItemSelector jsontext.Value `json:"ItemSelector,omitempty"` + SecondsPath string `json:"SecondsPath,omitempty"` + TimestampPath string `json:"TimestampPath,omitempty"` + ItemsPath string `json:"ItemsPath,omitempty"` // ToleratedFailureCount/Percentage (and their *Path variants) bound how many // Map iterations may fail before the Map state itself fails with // States.ExceedToleratedFailureThreshold. AWS supports these only for @@ -261,20 +264,20 @@ type State struct { // reference-path siblings, mutually exclusive with them and resolved against // the Task state's pre-Parameters input (AWS docs: // amazon-states-language-task-state.html, GlueJobTask example). - TimeoutSecondsPath string `json:"TimeoutSecondsPath,omitempty"` - HeartbeatSecondsPath string `json:"HeartbeatSecondsPath,omitempty"` - Retry []Retrier `json:"Retry,omitempty"` - Catch []Catcher `json:"Catch,omitempty"` - Choices []ChoiceRule `json:"Choices,omitempty"` - Result json.RawMessage `json:"Result,omitempty"` - Branches []Branch `json:"Branches,omitempty"` - Parameters json.RawMessage `json:"Parameters,omitempty"` - ResultSelector json.RawMessage `json:"ResultSelector,omitempty"` - TimeoutSeconds int `json:"TimeoutSeconds,omitempty"` - HeartbeatSeconds int `json:"HeartbeatSeconds,omitempty"` - Seconds int `json:"Seconds,omitempty"` - MaxConcurrency int `json:"MaxConcurrency,omitempty"` - End bool `json:"End,omitempty"` + TimeoutSecondsPath string `json:"TimeoutSecondsPath,omitempty"` + HeartbeatSecondsPath string `json:"HeartbeatSecondsPath,omitempty"` + Retry []Retrier `json:"Retry,omitempty"` + Catch []Catcher `json:"Catch,omitempty"` + Choices []ChoiceRule `json:"Choices,omitempty"` + Result jsontext.Value `json:"Result,omitempty"` + Branches []Branch `json:"Branches,omitempty"` + Parameters jsontext.Value `json:"Parameters,omitempty"` + ResultSelector jsontext.Value `json:"ResultSelector,omitempty"` + TimeoutSeconds int `json:"TimeoutSeconds,omitempty"` + HeartbeatSeconds int `json:"HeartbeatSeconds,omitempty"` + Seconds int `json:"Seconds,omitempty"` + MaxConcurrency int `json:"MaxConcurrency,omitempty"` + End bool `json:"End,omitempty"` } // Retrier defines retry behavior for a Task state on error. @@ -294,8 +297,8 @@ type Retrier struct { // Catcher defines catch behavior for a Task state on error. type Catcher struct { - Assign json.RawMessage `json:"Assign,omitempty"` - Output json.RawMessage `json:"Output,omitempty"` + Assign jsontext.Value `json:"Assign,omitempty"` + Output jsontext.Value `json:"Output,omitempty"` assignVals map[string]any outputVal any Next string `json:"Next"` @@ -314,8 +317,8 @@ type Branch struct { // ChoiceRule represents a single condition/transition in a Choice state. type ChoiceRule struct { // Condition and Assign are the JSONata-mode rule fields. - Condition string `json:"Condition,omitempty"` - Assign json.RawMessage `json:"Assign,omitempty"` + Condition string `json:"Condition,omitempty"` + Assign jsontext.Value `json:"Assign,omitempty"` assignVals map[string]any // Numeric comparisons NumericEquals *float64 `json:"NumericEquals,omitempty"` @@ -386,7 +389,7 @@ func ParseIsolatedState(definition string) (*StateMachine, error) { func parse(definition string, checkTransitions bool) (*StateMachine, error) { var sm StateMachine - if err := json.Unmarshal([]byte(definition), &sm); err != nil { + if err := json.Unmarshal([]byte(definition), &sm, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", ErrParseError, err) } @@ -610,7 +613,7 @@ func (s *State) UnmarshalJSON(b []byte) error { } } - if err := json.Unmarshal(b, (*stateAlias)(s)); err != nil { + if err := json.Unmarshal(b, (*stateAlias)(s), jsonx.V1()); err != nil { return err } @@ -622,8 +625,8 @@ func (s *State) UnmarshalJSON(b []byte) error { // liftNumExprs removes string-valued integer fields from the state object and // returns them separately; the object is returned untouched if there are none. func liftNumExprs(b []byte) ([]byte, map[string]string, error) { - var raw map[string]json.RawMessage - if err := json.Unmarshal(b, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(b, &raw, jsonx.V1()); err != nil { return nil, nil, err } @@ -636,7 +639,7 @@ func liftNumExprs(b []byte) ([]byte, map[string]string, error) { } var str string - if err := json.Unmarshal(v, &str); err != nil { + if err := json.Unmarshal(v, &str, jsonx.V1()); err != nil { return nil, nil, err } @@ -652,7 +655,7 @@ func liftNumExprs(b []byte) ([]byte, map[string]string, error) { return b, nil, nil } - out, err := json.Marshal(raw) + out, err := json.Marshal(raw, jsonx.V1()) return out, exprs, err } diff --git a/services/stepfunctions/asl/result_writer.go b/services/stepfunctions/asl/result_writer.go index f2f101f8d6..06b51ab3d3 100644 --- a/services/stepfunctions/asl/result_writer.go +++ b/services/stepfunctions/asl/result_writer.go @@ -3,11 +3,12 @@ package asl import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -172,7 +173,7 @@ func (e *Executor) exportMapResults( DestinationBucket: bucket, MapRunArn: mapRunARN, ResultFiles: files, - }) + }, jsonx.V1()) if err != nil { return nil, 0, fmt.Errorf("ResultWriter: marshal manifest: %w", err) } @@ -267,7 +268,7 @@ func buildMapItemRecord( } func stringifyJSON(v any) string { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return aslNullLiteral } @@ -358,7 +359,7 @@ func encodeJSONLines(entries []any) ([]byte, error) { buf.WriteByte('\n') } - b, err := json.Marshal(e) + b, err := json.Marshal(e, jsonx.V1()) if err != nil { return nil, fmt.Errorf("marshal JSONL entry: %w", err) } @@ -501,7 +502,7 @@ func (e *Executor) writeMapResultFile( if outputType == outputTypeJSONL { data, err = encodeJSONLines(entries) } else { - data, err = json.Marshal(entries) + data, err = json.Marshal(entries, jsonx.V1()) } if err != nil { diff --git a/services/stepfunctions/asl/sdk_integration.go b/services/stepfunctions/asl/sdk_integration.go index 0f912a5203..8d2aeee2d9 100644 --- a/services/stepfunctions/asl/sdk_integration.go +++ b/services/stepfunctions/asl/sdk_integration.go @@ -3,9 +3,12 @@ package asl import ( "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -150,7 +153,7 @@ func (e *Executor) invokeLambdaOptimized(ctx context.Context, input any) (any, e var payload []byte if raw, ok := params["Payload"]; ok { - b, err := json.Marshal(raw) + b, err := json.Marshal(raw, jsonx.V1()) if err != nil { return nil, &FailError{ErrCode: errCodeStatesRuntime, Cause: err.Error()} } @@ -176,7 +179,7 @@ func (e *Executor) invokeLambdaOptimized(ctx context.Context, input any) (any, e if len(bytes.TrimSpace(resp)) > 0 { var parsed any - if json.Unmarshal(resp, &parsed) != nil { + if json.Unmarshal(resp, &parsed, jsonx.V1()) != nil { parsed = string(resp) } @@ -222,11 +225,11 @@ func lambdaFunctionError(resp []byte) error { } var probe struct { - ErrorMessage *json.RawMessage `json:"errorMessage"` - ErrorType *string `json:"errorType"` + ErrorMessage *jsontext.Value `json:"errorMessage"` + ErrorType *string `json:"errorType"` } - if json.Unmarshal(trimmed, &probe) == nil && (probe.ErrorMessage != nil || probe.ErrorType != nil) { + if json.Unmarshal(trimmed, &probe, jsonx.V1()) == nil && (probe.ErrorMessage != nil || probe.ErrorType != nil) { code := lambdaErrUnknown if probe.ErrorType != nil && *probe.ErrorType != "" { code = *probe.ErrorType diff --git a/services/stepfunctions/asl/variables.go b/services/stepfunctions/asl/variables.go index 28a52f7c4c..e8769890a9 100644 --- a/services/stepfunctions/asl/variables.go +++ b/services/stepfunctions/asl/variables.go @@ -1,12 +1,14 @@ package asl import ( - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" "strings" "unicode" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // maxVariableNameLen is the AWS limit ("Variable name syntax"). @@ -75,7 +77,7 @@ func (e *Executor) setVars(vals map[string]any) error { assignTotal := 0 for name, v := range vals { - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return dataLimitError("variable %q cannot be serialized: %v", name, err) } diff --git a/services/stepfunctions/distributed_map.go b/services/stepfunctions/distributed_map.go index 59ef3a566a..4480a4a310 100644 --- a/services/stepfunctions/distributed_map.go +++ b/services/stepfunctions/distributed_map.go @@ -2,10 +2,11 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -89,7 +90,7 @@ func marshalDistributedMapInput(item any) string { return "{}" } - b, err := json.Marshal(item) + b, err := json.Marshal(item, jsonx.V1()) if err != nil { return "{}" } diff --git a/services/stepfunctions/eks_integration.go b/services/stepfunctions/eks_integration.go index c860f18033..47f846a676 100644 --- a/services/stepfunctions/eks_integration.go +++ b/services/stepfunctions/eks_integration.go @@ -6,7 +6,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -18,6 +18,7 @@ import ( v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/smithy-go" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -105,7 +106,7 @@ func invokeModelOutput(out any) any { } var parsed any - if json.Unmarshal(raw, &parsed) != nil { + if json.Unmarshal(raw, &parsed, jsonx.V1()) != nil { parsed = string(raw) } @@ -183,7 +184,7 @@ func (s eksCallSpec) request(ctx context.Context, token string) (*http.Request, var body io.Reader if s.body != nil { - b, err := json.Marshal(s.body) + b, err := json.Marshal(s.body, jsonx.V1()) if err != nil { return nil, runtimeFailure("invalid RequestBody: %v", err) } @@ -279,7 +280,7 @@ func doEKSCall(client *http.Client, req *http.Request) (any, error) { } var parsed any - if json.Unmarshal(data, &parsed) != nil { + if json.Unmarshal(data, &parsed, jsonx.V1()) != nil { parsed = string(data) } @@ -287,7 +288,7 @@ func doEKSCall(client *http.Client, req *http.Request) (any, error) { return map[string]any{"ResponseBody": parsed}, nil } - cause, _ := json.Marshal(parsed) + cause, _ := json.Marshal(parsed, jsonx.V1()) return nil, &asl.FailError{ErrCode: errCodeTaskFailed, Cause: string(cause)} } diff --git a/services/stepfunctions/error_path_sweep_test.go b/services/stepfunctions/error_path_sweep_test.go index d1b1f3e5b2..0d888256c3 100644 --- a/services/stepfunctions/error_path_sweep_test.go +++ b/services/stepfunctions/error_path_sweep_test.go @@ -1,7 +1,7 @@ package stepfunctions_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -267,13 +268,13 @@ func Test_TagResource_TooManyTags_WireType(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": tagList, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(t.Context(), t, h, e, "TagResource", string(body)) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TooManyTags", resp["__type"]) } @@ -313,7 +314,7 @@ func Test_CreateStateMachineAlias_ErrorCodes(t *testing.T) { ) var pubResp map[string]any - require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &pubResp)) + require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &pubResp, jsonx.V1())) versionARN, _ := pubResp["stateMachineVersionArn"].(string) require.NotEmpty(t, versionARN) @@ -338,7 +339,7 @@ func Test_CreateStateMachineAlias_ErrorCodes(t *testing.T) { rec := sfnPost(t.Context(), t, h, e, "CreateStateMachineAlias", tc.body(h, e)) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tc.wantType, resp["__type"]) }) } diff --git a/services/stepfunctions/execution_history.go b/services/stepfunctions/execution_history.go index 99959dc8a8..88d576d4e9 100644 --- a/services/stepfunctions/execution_history.go +++ b/services/stepfunctions/execution_history.go @@ -1,11 +1,12 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/v2" "fmt" "sort" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -307,7 +308,7 @@ func historyValueToJSON(v any) string { return "" } - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) if err != nil { return "" } diff --git a/services/stepfunctions/execution_history_test.go b/services/stepfunctions/execution_history_test.go index c910f7c59e..3d493da6e6 100644 --- a/services/stepfunctions/execution_history_test.go +++ b/services/stepfunctions/execution_history_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -252,7 +253,7 @@ func TestHandler_GetExecutionHistory(t *testing.T) { if tt.wantEvents > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["events"].([]any), tt.wantEvents) } }) diff --git a/services/stepfunctions/executions.go b/services/stepfunctions/executions.go index 941d98ee38..4f6fdb4878 100644 --- a/services/stepfunctions/executions.go +++ b/services/stepfunctions/executions.go @@ -2,7 +2,8 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "slices" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/apptrace" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -272,7 +274,7 @@ func finalizeSyncExecutionResult( return syncResult } - outputBytes, _ := json.Marshal(result.Output) + outputBytes, _ := json.Marshal(result.Output, jsonx.V1()) syncResult.Status = statusSucceeded syncResult.Output = string(outputBytes) @@ -674,7 +676,7 @@ func (b *InMemoryBackend) finalizeExecutionRecordLocked( return } - outputBytes, _ := json.Marshal(result.Output) + outputBytes, _ := json.Marshal(result.Output, jsonx.V1()) exec.Status = statusSucceeded exec.Output = string(outputBytes) exec.OutputDetails = &CloudWatchEventsExecutionDataDetails{Included: true} @@ -1142,7 +1144,7 @@ func validateExecutionInput(input string) error { return fmt.Errorf("%w: input exceeds %d bytes", ErrInvalidExecutionInput, maxExecutionInputBytes) } - if input != "" && !json.Valid([]byte(input)) { + if input != "" && !jsontext.Value([]byte(input)).IsValid(jsonx.V1()) { return fmt.Errorf("%w: input is not valid JSON", ErrInvalidExecutionInput) } diff --git a/services/stepfunctions/handler.go b/services/stepfunctions/handler.go index 46a6e6b03b..22a28165d8 100644 --- a/services/stepfunctions/handler.go +++ b/services/stepfunctions/handler.go @@ -2,7 +2,7 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -194,7 +195,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var data map[string]any - if uerr := json.Unmarshal(body, &data); uerr != nil { + if uerr := json.Unmarshal(body, &data, jsonx.V1()); uerr != nil { return "" } @@ -249,14 +250,14 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) case "CreateStateMachine": resp, err := h.createStateMachineAction(ctx, body) if err != nil { return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) case "ListStateMachines": return h.listStateMachinesAction(ctx, body) case "CreateActivity": @@ -265,14 +266,14 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) case "ListActivities": resp, err := h.handleListActivities(ctx, body) if err != nil { return nil, err } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } fn, ok := h.dispatchTable()[action] @@ -285,7 +286,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(response) + return json.Marshal(response, jsonx.V1()) } // handleError writes a standardized JSON error response. @@ -311,7 +312,7 @@ func (h *Handler) handleError( Message: errorMessage(reqErr), } - payload, _ := json.Marshal(errResp) + payload, _ := json.Marshal(errResp, jsonx.V1()) return c.JSONBlob(statusCode, payload) } @@ -458,7 +459,7 @@ const actionStartExecution = "StartExecution" func (h *Handler) listStateMachinesAction(ctx context.Context, body []byte) ([]byte, error) { var input listStateMachinesInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } @@ -472,5 +473,5 @@ func (h *Handler) listStateMachinesAction(ctx context.Context, body []byte) ([]b items[i] = newStateMachineListItem(&sms[i]) } - return json.Marshal(&listStateMachinesOutput{StateMachines: items, NextToken: next}) + return json.Marshal(&listStateMachinesOutput{StateMachines: items, NextToken: next}, jsonx.V1()) } diff --git a/services/stepfunctions/handler_activities.go b/services/stepfunctions/handler_activities.go index cf92f3419b..427f10cfa1 100644 --- a/services/stepfunctions/handler_activities.go +++ b/services/stepfunctions/handler_activities.go @@ -2,7 +2,9 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createActivityInput struct { @@ -100,7 +102,7 @@ func (h *Handler) activityActions() map[string]actionFn { func (h *Handler) handleCreateActivity(ctx context.Context, b []byte) (any, error) { var input createActivityInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -131,7 +133,7 @@ func (h *Handler) handleCreateActivity(ctx context.Context, b []byte) (any, erro func (h *Handler) handleDeleteActivity(b []byte) (any, error) { var input deleteActivityInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -154,7 +156,7 @@ func (h *Handler) handleDeleteActivity(b []byte) (any, error) { func (h *Handler) handleDescribeActivity(b []byte) (any, error) { var input describeActivityInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -163,7 +165,7 @@ func (h *Handler) handleDescribeActivity(b []byte) (any, error) { func (h *Handler) handleListActivities(ctx context.Context, b []byte) (any, error) { var input listActivitiesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -182,7 +184,7 @@ func (h *Handler) handleListActivities(ctx context.Context, b []byte) (any, erro func (h *Handler) handleSendTaskSuccess(b []byte) (any, error) { var input sendTaskSuccessInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -195,7 +197,7 @@ func (h *Handler) handleSendTaskSuccess(b []byte) (any, error) { func (h *Handler) handleSendTaskFailure(b []byte) (any, error) { var input sendTaskFailureInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -208,7 +210,7 @@ func (h *Handler) handleSendTaskFailure(b []byte) (any, error) { func (h *Handler) handleSendTaskHeartbeat(b []byte) (any, error) { var input sendTaskHeartbeatInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -222,7 +224,7 @@ func (h *Handler) handleSendTaskHeartbeat(b []byte) (any, error) { // handleGetActivityTask handles GetActivityTask with context support for long-polling. func (h *Handler) handleGetActivityTask(ctx context.Context, body []byte) ([]byte, error) { var input getActivityTaskInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } @@ -232,11 +234,11 @@ func (h *Handler) handleGetActivityTask(ctx context.Context, body []byte) ([]byt } if task == nil { - return json.Marshal(&getActivityTaskOutput{}) + return json.Marshal(&getActivityTaskOutput{}, jsonx.V1()) } return json.Marshal(&getActivityTaskOutput{ TaskToken: task.TaskToken, Input: task.Input, - }) + }, jsonx.V1()) } diff --git a/services/stepfunctions/handler_activities_test.go b/services/stepfunctions/handler_activities_test.go index 167ce463b6..ea681a9e5e 100644 --- a/services/stepfunctions/handler_activities_test.go +++ b/services/stepfunctions/handler_activities_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -104,7 +105,7 @@ func TestHandler_ActivityOperations(t *testing.T) { rec := sfnPost(ctx, t, h, e, "CreateActivity", `{"name":"setup-act-`+tt.name+`"}`) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) actARN = resp["activityArn"].(string) } case "CreateActivity": @@ -162,7 +163,7 @@ func TestHandler_CreateAndListActivities(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) activities, ok := resp["activities"].([]any) require.True(t, ok) @@ -249,7 +250,7 @@ func TestHandler_SendTaskSuccess_WithRealToken(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var actResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &actResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &actResp, jsonx.V1())) actARN := actResp["activityArn"].(string) // Enqueue a task by calling InvokeActivity from the backend. @@ -276,7 +277,7 @@ func TestHandler_SendTaskSuccess_WithRealToken(t *testing.T) { body, _ := json.Marshal(map[string]string{ "taskToken": task.TaskToken, "output": tt.output, - }) + }, jsonx.V1()) rec = sfnPost(ctx, t, h, e, "SendTaskSuccess", string(body)) assert.Equal(t, http.StatusOK, rec.Code) @@ -315,19 +316,19 @@ func TestHandler_GetActivityTask_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var actResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &actResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &actResp, jsonx.V1())) actARN := actResp["activityArn"].(string) // GetActivityTask with a short timeout — should return empty. pollCtx, cancel := context.WithTimeout(ctx, tt.timeout) defer cancel() - body, _ := json.Marshal(map[string]string{"activityArn": actARN, "workerName": "w1"}) + body, _ := json.Marshal(map[string]string{"activityArn": actARN, "workerName": "w1"}, jsonx.V1()) rec = sfnPost(pollCtx, t, h, e, "GetActivityTask", string(body)) assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // With no task available, taskToken should be absent or empty. token, _ := resp["taskToken"].(string) assert.Empty(t, token) @@ -402,20 +403,20 @@ func TestHandler_StartSyncExecution_HTTP(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var smResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp, jsonx.V1())) smARN := smResp["stateMachineArn"].(string) body, _ := json.Marshal(map[string]string{ "stateMachineArn": smARN, "name": "sync-exec-" + tt.name, "input": `{}`, - }) + }, jsonx.V1()) rec = sfnPost(ctx, t, h, e, "StartSyncExecution", string(body)) assert.Equal(t, tt.wantCode, rec.Code) if tt.wantStatus != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantStatus, resp["status"]) } }) @@ -466,7 +467,7 @@ func TestHandler_Reset(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["tags"]) }) } @@ -718,7 +719,7 @@ func TestActivityName_Validation(t *testing.T) { ctx := t.Context() h, e := newSFNHandler(t) - body, err := json.Marshal(map[string]string{"name": tt.actName}) + body, err := json.Marshal(map[string]string{"name": tt.actName}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateActivity", string(body)) @@ -1429,23 +1430,23 @@ func TestCreateActivity_EncryptionConfiguration(t *testing.T) { "kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/test-key", "kmsDataKeyReusePeriodSeconds": 300, }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateActivity", string(body)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) actARN, _ := createResp["activityArn"].(string) require.NotEmpty(t, actARN) - descBody, _ := json.Marshal(map[string]string{"activityArn": actARN}) + descBody, _ := json.Marshal(map[string]string{"activityArn": actARN}, jsonx.V1()) descRec := sfnPost(ctx, t, h, e, "DescribeActivity", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) encCfg, ok := descResp["encryptionConfiguration"].(map[string]any) require.True(t, ok, "expected encryptionConfiguration echoed back on DescribeActivity") assert.Equal(t, "CUSTOMER_MANAGED_KMS_KEY", encCfg["type"]) @@ -1465,23 +1466,23 @@ func TestCreateActivity_InlineTags(t *testing.T) { "tags": []map[string]string{ {"key": "env", "value": "prod"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateActivity", string(body)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) actARN, _ := createResp["activityArn"].(string) require.NotEmpty(t, actARN) - tagsBody, _ := json.Marshal(map[string]string{"resourceArn": actARN}) + tagsBody, _ := json.Marshal(map[string]string{"resourceArn": actARN}, jsonx.V1()) tagsRec := sfnPost(ctx, t, h, e, "ListTagsForResource", string(tagsBody)) require.Equal(t, http.StatusOK, tagsRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tagList, _ := tagsResp["tags"].([]any) require.Len(t, tagList, 1) @@ -1499,32 +1500,32 @@ func TestDeleteActivity_ClearsTags(t *testing.T) { ctx := t.Context() h, e := newSFNHandler(t) - createBody, _ := json.Marshal(map[string]any{"name": "del-tagged-activity"}) + createBody, _ := json.Marshal(map[string]any{"name": "del-tagged-activity"}, jsonx.V1()) rec := sfnPost(ctx, t, h, e, "CreateActivity", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) actARN, _ := createResp["activityArn"].(string) require.NotEmpty(t, actARN) tagBody, _ := json.Marshal(map[string]any{ "resourceArn": actARN, "tags": []map[string]string{{"key": "k", "value": "v"}}, - }) + }, jsonx.V1()) tagRec := sfnPost(ctx, t, h, e, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, tagRec.Code) - delBody, _ := json.Marshal(map[string]string{"activityArn": actARN}) + delBody, _ := json.Marshal(map[string]string{"activityArn": actARN}, jsonx.V1()) delRec := sfnPost(ctx, t, h, e, "DeleteActivity", string(delBody)) require.Equal(t, http.StatusOK, delRec.Code) - tagsBody, _ := json.Marshal(map[string]string{"resourceArn": actARN}) + tagsBody, _ := json.Marshal(map[string]string{"resourceArn": actARN}, jsonx.V1()) tagsRec := sfnPost(ctx, t, h, e, "ListTagsForResource", string(tagsBody)) require.Equal(t, http.StatusOK, tagsRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(tagsRec.Body.Bytes(), &tagsResp, jsonx.V1())) tagList, _ := tagsResp["tags"].([]any) assert.Empty(t, tagList, "tags must be cleared after DeleteActivity") } diff --git a/services/stepfunctions/handler_aliases.go b/services/stepfunctions/handler_aliases.go index 084fee5320..85cbeb3dce 100644 --- a/services/stepfunctions/handler_aliases.go +++ b/services/stepfunctions/handler_aliases.go @@ -1,7 +1,9 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // createStateMachineAliasInput has no StateMachineArn member: the real @@ -95,7 +97,7 @@ func (h *Handler) aliasActions() map[string]actionFn { func (h *Handler) handleCreateStateMachineAlias(b []byte) (any, error) { var input createStateMachineAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -111,7 +113,7 @@ func (h *Handler) handleCreateStateMachineAlias(b []byte) (any, error) { func (h *Handler) handleUpdateStateMachineAlias(b []byte) (any, error) { var input updateStateMachineAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -127,7 +129,7 @@ func (h *Handler) handleUpdateStateMachineAlias(b []byte) (any, error) { func (h *Handler) handleDeleteStateMachineAlias(b []byte) (any, error) { var input deleteStateMachineAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -140,7 +142,7 @@ func (h *Handler) handleDeleteStateMachineAlias(b []byte) (any, error) { func (h *Handler) handleDescribeStateMachineAlias(b []byte) (any, error) { var input describeStateMachineAliasInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -154,7 +156,7 @@ func (h *Handler) handleDescribeStateMachineAlias(b []byte) (any, error) { func (h *Handler) handleListStateMachineAliases(b []byte) (any, error) { var input listStateMachineAliasesInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/stepfunctions/handler_executions.go b/services/stepfunctions/handler_executions.go index 3b94fbd13f..f764031652 100644 --- a/services/stepfunctions/handler_executions.go +++ b/services/stepfunctions/handler_executions.go @@ -2,8 +2,10 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type startExecutionInput struct { @@ -150,7 +152,7 @@ func (h *Handler) executionActions() map[string]actionFn { func (h *Handler) handleRedriveExecution(b []byte) (any, error) { var input redriveExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -164,7 +166,7 @@ func (h *Handler) handleRedriveExecution(b []byte) (any, error) { func (h *Handler) handleDescribeStateMachineForExecution(b []byte) (any, error) { var input describeStateMachineForExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -189,7 +191,7 @@ func (h *Handler) handleStartExecution(b []byte) (any, error) { func (h *Handler) handleStartExecutionCtx(ctx context.Context, b []byte) (any, error) { var input startExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -209,7 +211,7 @@ func (h *Handler) handleStartExecutionCtx(ctx context.Context, b []byte) (any, e func (h *Handler) handleStartSyncExecution(b []byte) (any, error) { var input startSyncExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -218,7 +220,7 @@ func (h *Handler) handleStartSyncExecution(b []byte) (any, error) { func (h *Handler) handleStopExecution(b []byte) (any, error) { var input stopExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -236,7 +238,7 @@ func (h *Handler) handleStopExecution(b []byte) (any, error) { func (h *Handler) handleDescribeExecution(b []byte) (any, error) { var input describeExecutionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -264,7 +266,7 @@ func validateRedriveFilter(filter string, hasMapRun bool) error { func (h *Handler) handleListExecutions(b []byte) (any, error) { var input listExecutionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -313,7 +315,7 @@ func (h *Handler) handleListExecutions(b []byte) (any, error) { func (h *Handler) handleGetExecutionHistory(b []byte) (any, error) { var input getExecutionHistoryInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/stepfunctions/handler_executions_test.go b/services/stepfunctions/handler_executions_test.go index c5b46a5248..2a204579d3 100644 --- a/services/stepfunctions/handler_executions_test.go +++ b/services/stepfunctions/handler_executions_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -42,7 +43,7 @@ func TestHandler_StartExecution(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["executionArn"].(string), "exec1") }, }, @@ -239,7 +240,7 @@ func TestHandler_ListExecutions(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["executions"].([]any), tt.wantCount) }) } @@ -292,7 +293,7 @@ func TestExecutionName_Validation(t *testing.T) { "stateMachineArn": smARN, "name": tt.execName, "input": "{}", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "StartExecution", string(body)) @@ -312,14 +313,14 @@ func TestStartExecution_ResponseContainsARNAndStartDate(t *testing.T) { "stateMachineArn": smArn, "name": "start-resp-exec", "input": "{}", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "StartExecution", string(body)) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["executionArn"]) assert.Greater(t, out["startDate"].(float64), float64(0)) @@ -339,7 +340,7 @@ func TestListExecutions_OrderedByStartDateDesc_ViaHandler(t *testing.T) { "stateMachineArn": smARN, "name": name, "input": "{}", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "StartExecution", string(body)) @@ -347,14 +348,14 @@ func TestListExecutions_OrderedByStartDateDesc_ViaHandler(t *testing.T) { time.Sleep(5 * time.Millisecond) } - listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}) + listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "ListExecutions", string(listBody)) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rawExecs, _ := resp["executions"].([]any) require.Len(t, rawExecs, 3) @@ -381,7 +382,7 @@ func TestSFN_DescribeStateMachineForExecution(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) execARN, _ := resp["executionArn"].(string) require.NotEmpty(t, execARN) diff --git a/services/stepfunctions/handler_map_runs.go b/services/stepfunctions/handler_map_runs.go index 9c023f4061..1e3934901d 100644 --- a/services/stepfunctions/handler_map_runs.go +++ b/services/stepfunctions/handler_map_runs.go @@ -1,7 +1,9 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type describeMapRunInput struct { @@ -53,7 +55,7 @@ func (h *Handler) mapRunActions() map[string]actionFn { return map[string]actionFn{ "DescribeMapRun": func(b []byte) (any, error) { var input describeMapRunInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -61,7 +63,7 @@ func (h *Handler) mapRunActions() map[string]actionFn { }, "ListMapRuns": func(b []byte) (any, error) { var input listMapRunsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -86,7 +88,7 @@ func (h *Handler) mapRunActions() map[string]actionFn { }, "UpdateMapRun": func(b []byte) (any, error) { var input updateMapRunInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/stepfunctions/handler_state_machine_versions.go b/services/stepfunctions/handler_state_machine_versions.go index 0a7e613ed2..e3b7a5c48a 100644 --- a/services/stepfunctions/handler_state_machine_versions.go +++ b/services/stepfunctions/handler_state_machine_versions.go @@ -1,7 +1,9 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/v2" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type publishStateMachineVersionInput struct { @@ -47,7 +49,7 @@ func (h *Handler) versionActions() map[string]actionFn { return map[string]actionFn{ "PublishStateMachineVersion": func(b []byte) (any, error) { var input publishStateMachineVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -59,7 +61,7 @@ func (h *Handler) versionActions() map[string]actionFn { }, "DeleteStateMachineVersion": func(b []byte) (any, error) { var input deleteStateMachineVersionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteStateMachineVersion(input.StateMachineVersionArn); err != nil { @@ -70,7 +72,7 @@ func (h *Handler) versionActions() map[string]actionFn { }, "ListStateMachineVersions": func(b []byte) (any, error) { var input listStateMachineVersionsInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/stepfunctions/handler_state_machines.go b/services/stepfunctions/handler_state_machines.go index 2de5167950..0846429d8f 100644 --- a/services/stepfunctions/handler_state_machines.go +++ b/services/stepfunctions/handler_state_machines.go @@ -2,9 +2,11 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "maps" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type createStateMachineInput struct { @@ -89,7 +91,7 @@ type updateStateMachineOutput struct { // configuration and inline tags when supplied in the request body. func (h *Handler) createStateMachineAction(ctx context.Context, b []byte) (any, error) { var input createStateMachineInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -153,7 +155,7 @@ func (h *Handler) createStateMachineAction(ctx context.Context, b []byte) (any, // configuration when supplied in the request body. func (h *Handler) updateStateMachineAction(b []byte) (any, error) { var input updateStateMachineInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -211,7 +213,7 @@ func (h *Handler) stateMachineActions() map[string]actionFn { // CreateStateMachine and ListStateMachines are handled in dispatch (ctx-aware). "DeleteStateMachine": func(b []byte) (any, error) { var input deleteStateMachineInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } if err := h.Backend.DeleteStateMachine(input.StateMachineArn); err != nil { @@ -230,7 +232,7 @@ func (h *Handler) stateMachineActions() map[string]actionFn { }, "DescribeStateMachine": func(b []byte) (any, error) { var input describeStateMachineInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } diff --git a/services/stepfunctions/handler_state_machines_test.go b/services/stepfunctions/handler_state_machines_test.go index 59b132e987..961c742a0c 100644 --- a/services/stepfunctions/handler_state_machines_test.go +++ b/services/stepfunctions/handler_state_machines_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -33,7 +34,7 @@ func TestHandler_CreateStateMachine(t *testing.T) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["stateMachineArn"].(string), "test-sm") }, }, @@ -149,7 +150,7 @@ func TestHandler_StartExecution_StateMachineDeleting(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) smArn, ok := createResp["stateMachineArn"].(string) require.True(t, ok) @@ -165,7 +166,7 @@ func TestHandler_StartExecution_StateMachineDeleting(t *testing.T) { require.Equal(t, http.StatusOK, runningRec.Code) var runningDesc map[string]any - require.NoError(t, json.Unmarshal(runningRec.Body.Bytes(), &runningDesc)) + require.NoError(t, json.Unmarshal(runningRec.Body.Bytes(), &runningDesc, jsonx.V1())) require.Equal(t, "RUNNING", runningDesc["status"]) delRec := sfnPost(ctx, t, h, e, "DeleteStateMachine", `{"stateMachineArn":"`+smArn+`"}`) @@ -175,7 +176,7 @@ func TestHandler_StartExecution_StateMachineDeleting(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code, "DELETING state machine must still be describable") var desc map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1())) assert.Equal(t, "DELETING", desc["status"]) startRec := sfnPost(ctx, t, h, e, "StartExecution", @@ -183,7 +184,7 @@ func TestHandler_StartExecution_StateMachineDeleting(t *testing.T) { assert.Equal(t, http.StatusConflict, startRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "StateMachineDeleting", errResp["__type"]) } @@ -219,7 +220,7 @@ func TestHandler_ListStateMachines(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Len(t, resp["stateMachines"].([]any), tt.wantCount) }) } @@ -245,7 +246,7 @@ func TestHandler_DescribeStateMachine(t *testing.T) { makeSMBody("desc-sm", validPassDef, "EXPRESS")) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["stateMachineArn"].(string) }, @@ -255,7 +256,7 @@ func TestHandler_DescribeStateMachine(t *testing.T) { t.Helper() var sm map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sm)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &sm, jsonx.V1())) assert.Equal(t, "EXPRESS", sm["type"]) }, }, @@ -344,7 +345,7 @@ func TestStateMachineName_Validation(t *testing.T) { "name": tt.smName, "definition": sfnPassDefinition, "roleArn": "arn:aws:iam::123456789012:role/role", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) @@ -352,7 +353,7 @@ func TestStateMachineName_Validation(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidName", resp["__type"]) } }) @@ -405,24 +406,24 @@ func TestCreateStateMachine_InlineTags(t *testing.T) { reqBody["tags"] = tt.tags } - body, err := json.Marshal(reqBody) + body, err := json.Marshal(reqBody, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) smARN, _ := createResp["stateMachineArn"].(string) require.NotEmpty(t, smARN) // Verify tags were applied. - listBody, _ := json.Marshal(map[string]string{"resourceArn": smARN}) + listBody, _ := json.Marshal(map[string]string{"resourceArn": smARN}, jsonx.V1()) listRec := sfnPost(ctx, t, h, e, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var tagsResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsResp, jsonx.V1())) rawTags, _ := tagsResp["tags"].([]any) got := make(map[string]string, len(rawTags)) @@ -485,24 +486,24 @@ func TestCreateStateMachine_EncryptionConfiguration(t *testing.T) { reqBody["encryptionConfiguration"] = tt.encCfg } - body, err := json.Marshal(reqBody) + body, err := json.Marshal(reqBody, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) smARN, _ := createResp["stateMachineArn"].(string) require.NotEmpty(t, smARN) // Describe to verify encryption config is stored. - descBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}) + descBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) descRec := sfnPost(ctx, t, h, e, "DescribeStateMachine", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) if tt.wantType == "" { // No encryption config present in response or nil. @@ -531,19 +532,19 @@ func TestUpdateStateMachine_EncryptionConfiguration(t *testing.T) { "type": "CUSTOMER_MANAGED_KMS_KEY", "kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key", }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(body)) assert.Equal(t, http.StatusOK, rec.Code) // Verify via describe. - descBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}) + descBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) descRec := sfnPost(ctx, t, h, e, "DescribeStateMachine", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) enc, ok := descResp["encryptionConfiguration"].(map[string]any) require.True(t, ok) assert.Equal(t, "CUSTOMER_MANAGED_KMS_KEY", enc["type"]) @@ -583,14 +584,14 @@ func TestCreateStateMachine_WithPublish(t *testing.T) { "definition": sfnPassDefinition, "roleArn": "arn:aws:iam::123456789012:role/role", "publish": tt.publish, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) smARN, _ := createResp["stateMachineArn"].(string) require.NotEmpty(t, smARN) @@ -606,12 +607,12 @@ func TestCreateStateMachine_WithPublish(t *testing.T) { } // Check versions. - versBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}) + versBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) versRec := sfnPost(ctx, t, h, e, "ListStateMachineVersions", string(versBody)) require.Equal(t, http.StatusOK, versRec.Code) var versResp map[string]any - require.NoError(t, json.Unmarshal(versRec.Body.Bytes(), &versResp)) + require.NoError(t, json.Unmarshal(versRec.Body.Bytes(), &versResp, jsonx.V1())) versions, _ := versResp["stateMachineVersions"].([]any) if tt.wantVersion { @@ -636,23 +637,23 @@ func TestUpdateStateMachine_WithPublish(t *testing.T) { "stateMachineArn": smARN, "definition": newDef, "publish": true, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["revisionId"], "expected a non-empty revisionId after update") assert.NotEmpty(t, updateResp["stateMachineVersionArn"], "expected stateMachineVersionArn when publish=true") - versBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}) + versBody, _ := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) versRec := sfnPost(ctx, t, h, e, "ListStateMachineVersions", string(versBody)) require.Equal(t, http.StatusOK, versRec.Code) var versResp map[string]any - require.NoError(t, json.Unmarshal(versRec.Body.Bytes(), &versResp)) + require.NoError(t, json.Unmarshal(versRec.Body.Bytes(), &versResp, jsonx.V1())) versions, _ := versResp["stateMachineVersions"].([]any) assert.Len(t, versions, 1, "expected one version after update with publish=true") } @@ -671,14 +672,14 @@ func TestUpdateStateMachine_NoPublish_NoVersionArn(t *testing.T) { body, err := json.Marshal(map[string]any{ "stateMachineArn": smARN, "definition": newDef, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["revisionId"]) _, hasVersionArn := updateResp["stateMachineVersionArn"] assert.False(t, hasVersionArn, "expected no stateMachineVersionArn when publish=false") @@ -699,7 +700,7 @@ func TestUpdateStateMachine_VersionDescriptionRequiresPublish(t *testing.T) { "definition": `{"StartAt":"S","States":{"S":{"Type":"Succeed"}}}`, "versionDescription": "should be rejected", "publish": false, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(body)) @@ -747,7 +748,7 @@ func TestCreateStateMachine_ResponseContainsARNAndDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.NotEmpty(t, out["stateMachineArn"]) assert.Greater(t, out["creationDate"].(float64), float64(0)) @@ -765,18 +766,18 @@ func TestCreateStateMachine_Idempotent_ViaHandler(t *testing.T) { "name": "idem-handler-sm", "definition": sfnPassDefinition, "roleArn": "arn:aws:iam::123456789012:role/role", - }) + }, jsonx.V1()) require.NoError(t, err) rec1 := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) rec2 := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, resp1["stateMachineArn"], resp2["stateMachineArn"]) } @@ -831,7 +832,7 @@ func TestCreateStateMachineRequiresRoleArn(t *testing.T) { h, e := newSFNHandler(t) ctx := context.Background() - body, err := json.Marshal(tt.body) + body, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) @@ -840,7 +841,7 @@ func TestCreateStateMachineRequiresRoleArn(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"], "error type for case %q", tt.name) } @@ -880,7 +881,7 @@ func TestCreateStateMachine_NameValidation(t *testing.T) { "name": tt.smName, "definition": validPassDef, "roleArn": "arn:aws:iam::123456789012:role/role", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) @@ -888,7 +889,7 @@ func TestCreateStateMachine_NameValidation(t *testing.T) { if tt.wantType != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) @@ -930,14 +931,14 @@ func TestUpdateStateMachine_ArnValidation(t *testing.T) { body, err := json.Marshal(map[string]any{ "stateMachineArn": tt.arn, "definition": validPassDef, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(body)) assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) }) } @@ -1025,14 +1026,14 @@ func TestHandler_UpdateStateMachine(t *testing.T) { body, err = json.Marshal(map[string]any{ "stateMachineArn": smARN, "roleArn": "arn:aws:iam::123456789012:role/new-role", - }) + }, jsonx.V1()) require.NoError(t, err) } else { var err error body, err = json.Marshal(map[string]any{ "stateMachineArn": smARN, "definition": sfnPassDefinition, - }) + }, jsonx.V1()) require.NoError(t, err) } @@ -1040,7 +1041,7 @@ func TestHandler_UpdateStateMachine(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "updateDate") }) } diff --git a/services/stepfunctions/handler_tags.go b/services/stepfunctions/handler_tags.go index 0efef17690..0b9d36c2e9 100644 --- a/services/stepfunctions/handler_tags.go +++ b/services/stepfunctions/handler_tags.go @@ -1,8 +1,10 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type sfnListTagsForResourceInput struct { @@ -82,7 +84,7 @@ func (h *Handler) stateMachineTagActions() map[string]actionFn { return map[string]actionFn{ "ListTagsForResource": func(b []byte) (any, error) { var input sfnListTagsForResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -96,7 +98,7 @@ func (h *Handler) stateMachineTagActions() map[string]actionFn { }, "TagResource": func(b []byte) (any, error) { var input sfnTagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -116,7 +118,7 @@ func (h *Handler) stateMachineTagActions() map[string]actionFn { }, "UntagResource": func(b []byte) (any, error) { var input sfnUntagResourceInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } h.removeTags(input.ResourceArn, input.TagKeys) diff --git a/services/stepfunctions/handler_test.go b/services/stepfunctions/handler_test.go index 07817e1896..e8ca9fa534 100644 --- a/services/stepfunctions/handler_test.go +++ b/services/stepfunctions/handler_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -67,7 +68,7 @@ func makeSMBody(name, def, smType string) string { input["type"] = smType } - b, err := json.Marshal(input) + b, err := json.Marshal(input, jsonx.V1()) if err != nil { panic(err) } @@ -83,7 +84,7 @@ func createSM(ctx context.Context, t *testing.T, h *stepfunctions.Handler, e *ec require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["stateMachineArn"].(string) } @@ -102,7 +103,7 @@ func startExec( require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["executionArn"].(string) } @@ -148,7 +149,7 @@ func TestHandler_Routing(t *testing.T) { t.Helper() var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.Contains(t, ops, "CreateStateMachine") }, }, @@ -252,14 +253,14 @@ func TestHandler_Shutdown_CancelsRunningExecutions(t *testing.T) { var smResp struct { StateMachineArn string `json:"stateMachineArn"` } - require.NoError(t, json.Unmarshal(smRec.Body.Bytes(), &smResp)) + require.NoError(t, json.Unmarshal(smRec.Body.Bytes(), &smResp, jsonx.V1())) // Start an execution. execBody, err := json.Marshal(map[string]string{ "stateMachineArn": smResp.StateMachineArn, "name": "exec-1", "input": "{}", - }) + }, jsonx.V1()) require.NoError(t, err) execRec := sfnPost(ctx, t, h, e, "StartExecution", string(execBody)) @@ -268,7 +269,7 @@ func TestHandler_Shutdown_CancelsRunningExecutions(t *testing.T) { var execResp struct { ExecutionArn string `json:"executionArn"` } - require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp)) + require.NoError(t, json.Unmarshal(execRec.Body.Bytes(), &execResp, jsonx.V1())) // Shutdown should cancel the long-running Wait execution and return promptly. h.Shutdown(t.Context()) @@ -276,7 +277,10 @@ func TestHandler_Shutdown_CancelsRunningExecutions(t *testing.T) { // After Shutdown, the cancelled execution goroutine should eventually // transition the execution out of RUNNING (to FAILED due to cancellation). require.Eventuallyf(t, func() bool { - descBody, marshalErr := json.Marshal(map[string]string{"executionArn": execResp.ExecutionArn}) + descBody, marshalErr := json.Marshal( + map[string]string{"executionArn": execResp.ExecutionArn}, + jsonx.V1(), + ) if marshalErr != nil { return false } @@ -290,7 +294,8 @@ func TestHandler_Shutdown_CancelsRunningExecutions(t *testing.T) { Status string `json:"status"` } - return json.Unmarshal(descRec.Body.Bytes(), &descResp) == nil && descResp.Status != "RUNNING" + return json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1()) == nil && + descResp.Status != "RUNNING" }, 5*time.Second, 50*time.Millisecond, "[%s] execution should leave RUNNING state after Shutdown", tt.name) }) @@ -331,7 +336,7 @@ func TestClassifyError_InvalidExecutionInput(t *testing.T) { body, err := json.Marshal(map[string]string{ "stateMachineArn": smARN, "input": tt.input, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "StartExecution", string(body)) @@ -339,7 +344,7 @@ func TestClassifyError_InvalidExecutionInput(t *testing.T) { if tt.wantErr { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidExecutionInput", resp["__type"]) } else { assert.Equal(t, http.StatusOK, rec.Code) @@ -384,7 +389,7 @@ func TestClassifyError_InvalidArn(t *testing.T) { "name": "arn-check-sm-" + tt.name, "definition": sfnPassDefinition, "roleArn": tt.roleArn, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(body)) @@ -392,7 +397,7 @@ func TestClassifyError_InvalidArn(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidArn", resp["__type"]) } }) @@ -438,7 +443,7 @@ func TestHTTP_StateMachineAlreadyExists_Returns409(t *testing.T) { assert.Equal(t, http.StatusConflict, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "StateMachineAlreadyExists", out["__type"]) } @@ -453,7 +458,7 @@ func TestHTTP_InvalidDefinition_Returns400(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "InvalidDefinition", out["__type"]) } diff --git a/services/stepfunctions/handler_tracing_logging_test.go b/services/stepfunctions/handler_tracing_logging_test.go index d95559eacc..b4e4328c26 100644 --- a/services/stepfunctions/handler_tracing_logging_test.go +++ b/services/stepfunctions/handler_tracing_logging_test.go @@ -1,12 +1,14 @@ package stepfunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateStateMachine_PropagatesTracingAndLogging(t *testing.T) { @@ -30,7 +32,7 @@ func TestHandler_CreateStateMachine_PropagatesTracingAndLogging(t *testing.T) { }, } - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) ctx := t.Context() @@ -40,19 +42,19 @@ func TestHandler_CreateStateMachine_PropagatesTracingAndLogging(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) arnStr, _ := createOut["stateMachineArn"].(string) require.NotEmpty(t, arnStr) - descBody, err := json.Marshal(map[string]any{"stateMachineArn": arnStr}) + descBody, err := json.Marshal(map[string]any{"stateMachineArn": arnStr}, jsonx.V1()) require.NoError(t, err) descRec := sfnPost(ctx, t, h, e, "DescribeStateMachine", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) tracing, _ := descOut["tracingConfiguration"].(map[string]any) require.NotNil(t, tracing) @@ -74,19 +76,19 @@ func TestHandler_UpdateStateMachine_PropagatesTracing(t *testing.T) { updBody, err := json.Marshal(map[string]any{ "stateMachineArn": arn, "tracingConfiguration": map[string]any{"enabled": true}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(updBody)) require.Equal(t, http.StatusOK, rec.Code) - descBody, _ := json.Marshal(map[string]any{"stateMachineArn": arn}) + descBody, _ := json.Marshal(map[string]any{"stateMachineArn": arn}, jsonx.V1()) descRec := sfnPost(ctx, t, h, e, "DescribeStateMachine", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) tracing, _ := descOut["tracingConfiguration"].(map[string]any) require.NotNil(t, tracing) diff --git a/services/stepfunctions/handler_util.go b/services/stepfunctions/handler_util.go index 54705da4f1..143eb89ff3 100644 --- a/services/stepfunctions/handler_util.go +++ b/services/stepfunctions/handler_util.go @@ -1,10 +1,12 @@ package stepfunctions import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/ptrconv" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -39,7 +41,7 @@ func (h *Handler) utilActions() map[string]actionFn { return map[string]actionFn{ "ValidateStateMachineDefinition": func(b []byte) (any, error) { var input validateStateMachineDefinitionInput - if err := json.Unmarshal(b, &input); err != nil { + if err := json.Unmarshal(b, &input, jsonx.V1()); err != nil { return nil, err } @@ -132,11 +134,11 @@ func testStateMockRun(m *testStateMock, stateName string) (*asl.MockRun, error) var step map[string]any if m.Result != nil { - if !json.Valid([]byte(*m.Result)) { + if !jsontext.Value([]byte(*m.Result)).IsValid(jsonx.V1()) { return nil, fmt.Errorf("%w: mock result is not valid JSON", ErrValidation) } - step = map[string]any{"Return": json.RawMessage(*m.Result)} + step = map[string]any{"Return": jsontext.Value(*m.Result)} } else { if m.ErrorOutput.Error == nil || *m.ErrorOutput.Error == "" { return nil, fmt.Errorf("%w: mock errorOutput.error is required", ErrValidation) @@ -152,7 +154,7 @@ func testStateMockRun(m *testStateMock, stateName string) (*asl.MockRun, error) "StateMachines": map[string]any{ "s": map[string]any{"TestCases": map[string]any{"t": map[string]string{stateName: "m"}}}, }, - }) + }, jsonx.V1()) if err != nil { return nil, err } @@ -194,7 +196,7 @@ func inspectionDataFor(level string, executor *asl.Executor, trace *httpTrace) m } for key, value := range executor.Inspection() { - if raw, err := json.Marshal(value); err == nil { + if raw, err := json.Marshal(value, jsonx.V1()); err == nil { out[key] = string(raw) } } @@ -208,23 +210,23 @@ func validInspectionLevel(level string) bool { // detachNext swaps Next for End:true so TestState can run a non-terminal // state without a synthetic next state, returning the new definition and Next. -func detachNext(definition, stateName string, raw json.RawMessage) (string, string) { +func detachNext(definition, stateName string, raw jsontext.Value) (string, string) { var ( next string - rawState map[string]json.RawMessage + rawState map[string]jsontext.Value ) - if json.Unmarshal(raw, &rawState) != nil { + if json.Unmarshal(raw, &rawState, jsonx.V1()) != nil { return definition, next } if nextRaw, hasNext := rawState["Next"]; hasNext { - _ = json.Unmarshal(nextRaw, &next) + _ = json.Unmarshal(nextRaw, &next, jsonx.V1()) delete(rawState, "Next") - rawState["End"] = json.RawMessage(`true`) + rawState["End"] = jsontext.Value(`true`) } - if modified, err := json.Marshal(map[string]any{stateName: rawState}); err == nil { + if modified, err := json.Marshal(map[string]any{stateName: rawState}, jsonx.V1()); err == nil { definition = string(modified) } @@ -234,7 +236,7 @@ func detachNext(definition, stateName string, raw json.RawMessage) (string, stri // handleTestState executes a single state definition in isolation and returns its output. func (h *Handler) handleTestState(body []byte) (any, error) { var input testStateInput - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, err } @@ -243,13 +245,13 @@ func (h *Handler) handleTestState(body []byte) (any, error) { } // Wrap the state definition in a minimal state machine. - var states map[string]json.RawMessage - if err := json.Unmarshal([]byte(input.Definition), &states); err != nil { + var states map[string]jsontext.Value + if err := json.Unmarshal([]byte(input.Definition), &states, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid state definition JSON: %w", ErrInvalidDefinition, err) } if _, bare := states["Type"]; bare { - states = map[string]json.RawMessage{bareStateName: json.RawMessage(input.Definition)} + states = map[string]jsontext.Value{bareStateName: jsontext.Value(input.Definition)} input.Definition = fmt.Sprintf(`{%q:%s}`, bareStateName, input.Definition) } @@ -326,7 +328,7 @@ func testStateResult( } } - outputBytes, _ := json.Marshal(result.Output) + outputBytes, _ := json.Marshal(result.Output, jsonx.V1()) out := &testStateOutput{ Status: "SUCCEEDED", Output: string(outputBytes), NextState: nextState, InspectionData: inspection, @@ -342,7 +344,7 @@ func testStateResult( // configureTestStateExecutor applies the request's mock, context and // stateConfiguration to executor, rejecting combinations AWS rejects. func configureTestStateExecutor( - executor *asl.Executor, input *testStateInput, stateName string, rawState json.RawMessage, + executor *asl.Executor, input *testStateInput, stateName string, rawState jsontext.Value, ) error { if input.Context != nil && input.Mock == nil { return fmt.Errorf("%w: context may only be specified together with a mock", ErrValidation) @@ -350,7 +352,7 @@ func configureTestStateExecutor( if input.Context != nil { var ctxObj map[string]any - if err := json.Unmarshal([]byte(*input.Context), &ctxObj); err != nil { + if err := json.Unmarshal([]byte(*input.Context), &ctxObj, jsonx.V1()); err != nil { return fmt.Errorf("%w: context must be a JSON object", ErrValidation) } @@ -369,7 +371,7 @@ func configureTestStateExecutor( Type string `json:"Type"` } - _ = json.Unmarshal(rawState, &typed) + _ = json.Unmarshal(rawState, &typed, jsonx.V1()) if typed.Type != stateTypeTask && typed.Type != stateTypeMap && typed.Type != stateTypeParallel { return fmt.Errorf("%w: mock is supported for Task, Map and Parallel states only", ErrValidation) @@ -386,20 +388,20 @@ func configureTestStateExecutor( } // isHTTPTaskState reports whether raw is a Task state calling http:invoke. -func isHTTPTaskState(raw json.RawMessage) bool { +func isHTTPTaskState(raw jsontext.Value) bool { var st struct { Type string `json:"Type"` Resource string `json:"Resource"` } - _ = json.Unmarshal(raw, &st) + _ = json.Unmarshal(raw, &st, jsonx.V1()) return st.Type == stateTypeTask && strings.HasSuffix(st.Resource, ":::http:invoke") } // validateTestStateTrace rejects the TRACE and revealSecrets combinations AWS // rejects (test-state-isolation.html, "TRACE inspectionLevel"). -func validateTestStateTrace(input *testStateInput, raw json.RawMessage) error { +func validateTestStateTrace(input *testStateInput, raw jsontext.Value) error { if input.InspectionLevel == inspectionTrace && !isHTTPTaskState(raw) { return fmt.Errorf("%w: inspectionLevel TRACE is only applicable to an HTTP Task state", ErrValidation) } diff --git a/services/stepfunctions/handler_util_test.go b/services/stepfunctions/handler_util_test.go index 2b918337dc..464c6726f5 100644 --- a/services/stepfunctions/handler_util_test.go +++ b/services/stepfunctions/handler_util_test.go @@ -2,12 +2,14 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ValidateStateMachineDefinition(t *testing.T) { @@ -55,14 +57,14 @@ func TestHandler_ValidateStateMachineDefinition(t *testing.T) { ctx := t.Context() h, e := newSFNHandler(t) - reqBody, err := json.Marshal(map[string]string{"definition": tt.definition}) + reqBody, err := json.Marshal(map[string]string{"definition": tt.definition}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "ValidateStateMachineDefinition", string(reqBody)) assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantResult, resp["result"]) diag, ok := resp["diagnostics"].([]any) @@ -83,7 +85,7 @@ func TestValidateStateMachineDefinition_Valid(t *testing.T) { ctx := t.Context() h, e := newSFNHandler(t) - body, err := json.Marshal(map[string]any{"definition": validPassDef}) + body, err := json.Marshal(map[string]any{"definition": validPassDef}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "ValidateStateMachineDefinition", string(body)) @@ -96,7 +98,7 @@ func TestValidateStateMachineDefinition_Invalid(t *testing.T) { ctx := t.Context() h, e := newSFNHandler(t) - body, err := json.Marshal(map[string]any{"definition": `{"not":"valid-asl"}`}) + body, err := json.Marshal(map[string]any{"definition": `{"not":"valid-asl"}`}, jsonx.V1()) require.NoError(t, err) // AWS returns 200 with Result:"FAIL" and diagnostics — not a 4xx. @@ -104,7 +106,7 @@ func TestValidateStateMachineDefinition_Invalid(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "FAIL", out["result"]) } @@ -168,7 +170,7 @@ func TestTestState_NextState(t *testing.T) { body, err := json.Marshal(map[string]any{ "definition": tt.definition, "input": tt.input, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TestState", string(body)) @@ -179,7 +181,7 @@ func TestTestState_NextState(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantStatus, resp["status"]) @@ -223,14 +225,14 @@ func TestValidateStateMachineDefinition(t *testing.T) { t.Parallel() h, e := newSFNHandler(t) - body, err := json.Marshal(map[string]string{"definition": tt.def}) + body, err := json.Marshal(map[string]string{"definition": tt.def}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(t.Context(), t, h, e, "ValidateStateMachineDefinition", string(body)) assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantResult, resp["result"]) }) } diff --git a/services/stepfunctions/http_task.go b/services/stepfunctions/http_task.go index f0c6f6e920..4124487f94 100644 --- a/services/stepfunctions/http_task.go +++ b/services/stepfunctions/http_task.go @@ -3,7 +3,7 @@ package stepfunctions import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -19,6 +19,7 @@ import ( "unicode/utf8" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -374,7 +375,7 @@ func mergeHTTPBody(spec httpSpec, connBody []NameValue) ([]byte, error) { } } - out, err := json.Marshal(body) + out, err := json.Marshal(body, jsonx.V1()) if err != nil { return nil, runtimeFailure("RequestBody is not serializable") } @@ -455,7 +456,7 @@ func fetchOAuthToken(ctx context.Context, client *http.Client, o *OAuthCredentia AccessToken string `json:"access_token"` } - if json.Unmarshal(data, &tok) != nil || tok.AccessToken == "" { + if json.Unmarshal(data, &tok, jsonx.V1()) != nil || tok.AccessToken == "" { return "", errOAuthToken } @@ -510,7 +511,7 @@ func doHTTP(client *http.Client, req *http.Request, trace *httpTrace) (any, erro return out, nil } - cause, _ := json.Marshal(out) + cause, _ := json.Marshal(out, jsonx.V1()) return nil, &asl.FailError{ErrCode: httpStatusPrefix + strconv.Itoa(resp.StatusCode), Cause: string(cause)} } @@ -559,7 +560,7 @@ func httpOutput(resp *http.Response, data []byte) map[string]any { media, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type")) if media == "application/json" || strings.HasSuffix(media, "+json") { var parsed any - if json.Unmarshal(data, &parsed) == nil { + if json.Unmarshal(data, &parsed, jsonx.V1()) == nil { body = parsed } } diff --git a/services/stepfunctions/http_trace.go b/services/stepfunctions/http_trace.go index 93c05ee1de..1255f3d32f 100644 --- a/services/stepfunctions/http_trace.go +++ b/services/stepfunctions/http_trace.go @@ -3,11 +3,13 @@ package stepfunctions import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "strconv" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const traceMask = "***" @@ -79,7 +81,7 @@ func (t *httpTrace) headers(h http.Header, dropAuth string) string { flat[k] = t.scrub(strings.Join(vs, ",")) } - raw, _ := json.Marshal(flat) + raw, _ := json.Marshal(flat, jsonx.V1()) return string(raw) } diff --git a/services/stepfunctions/integrations.go b/services/stepfunctions/integrations.go index 4f03132ff2..a8f5ce9534 100644 --- a/services/stepfunctions/integrations.go +++ b/services/stepfunctions/integrations.go @@ -3,7 +3,7 @@ package stepfunctions import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" @@ -12,6 +12,7 @@ import ( awss3 "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" dynamodbpkg "github.com/blackbirdworks/gopherstack/services/dynamodb" ecsbackend "github.com/blackbirdworks/gopherstack/services/ecs" gluebackend "github.com/blackbirdworks/gopherstack/services/glue" @@ -182,12 +183,12 @@ func (a *s3ResultWriterAdapter) PutObjectBytes(ctx context.Context, bucket, key // convertViaJSON converts a value to a target type by marshaling to JSON and back. func convertViaJSON(input, target any) error { - b, err := json.Marshal(input) + b, err := json.Marshal(input, jsonx.V1()) if err != nil { return err } - return json.Unmarshal(b, target) + return json.Unmarshal(b, target, jsonx.V1()) } // outputToAny converts a typed SDK output struct to an untyped map via JSON round-trip. diff --git a/services/stepfunctions/item_reader_parquet_test.go b/services/stepfunctions/item_reader_parquet_test.go index 5ff112abad..cae59ca1e0 100644 --- a/services/stepfunctions/item_reader_parquet_test.go +++ b/services/stepfunctions/item_reader_parquet_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" @@ -10,6 +10,7 @@ import ( sfntypes "github.com/aws/aws-sdk-go-v2/service/sfn/types" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/parquetio" ) @@ -159,7 +160,7 @@ func TestItemReader_ParquetFlattenAndAthena(t *testing.T) { require.Equal(t, sfntypes.ExecutionStatusSucceeded, status, "error=%s cause=%s", errCode, cause) var items []map[string]any - require.NoError(t, json.Unmarshal([]byte(output), &items)) + require.NoError(t, json.Unmarshal([]byte(output), &items, jsonx.V1())) assert.Len(t, items, 6) }) } diff --git a/services/stepfunctions/item_reader_s3_resource_test.go b/services/stepfunctions/item_reader_s3_resource_test.go index 4051b5a7ce..1740e7a729 100644 --- a/services/stepfunctions/item_reader_s3_resource_test.go +++ b/services/stepfunctions/item_reader_s3_resource_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" s3pkg "github.com/blackbirdworks/gopherstack/services/s3" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -126,7 +127,7 @@ func TestItemReader_S3ListObjectsV2(t *testing.T) { require.Equal(t, sfntypes.ExecutionStatusSucceeded, status, "error=%s cause=%s", errCode, cause) var items []map[string]any - require.NoError(t, json.Unmarshal([]byte(output), &items)) + require.NoError(t, json.Unmarshal([]byte(output), &items, jsonx.V1())) require.Len(t, items, 2) assert.Equal(t, "data/a.txt", items[0]["Key"]) @@ -217,7 +218,7 @@ func TestItemReader_S3Manifest(t *testing.T) { "files": []map[string]any{{"key": dataKey, "size": len(inventoryCSV)}}, } - b, err := json.Marshal(manifest) + b, err := json.Marshal(manifest, jsonx.V1()) require.NoError(t, err) return string(b) @@ -242,7 +243,7 @@ func TestItemReader_S3Manifest(t *testing.T) { require.Equal(t, sfntypes.ExecutionStatusSucceeded, status, "error=%s cause=%s", errCode, cause) var items []map[string]any - require.NoError(t, json.Unmarshal([]byte(output), &items)) + require.NoError(t, json.Unmarshal([]byte(output), &items, jsonx.V1())) require.Len(t, items, 2) assert.Equal(t, map[string]any{ "Bucket": "src-bucket", "Key": "csvDataset/titles.csv", @@ -269,7 +270,7 @@ func TestItemReader_S3Manifest(t *testing.T) { require.Equal(t, sfntypes.ExecutionStatusSucceeded, status, "error=%s cause=%s", errCode, cause) var items []map[string]any - require.NoError(t, json.Unmarshal([]byte(output), &items)) + require.NoError(t, json.Unmarshal([]byte(output), &items, jsonx.V1())) require.Len(t, items, 2) assert.Equal(t, "imageDataset/pic.jpg", items[1]["Key"]) }) @@ -295,7 +296,7 @@ func TestItemReader_S3Manifest(t *testing.T) { require.Equal(t, sfntypes.ExecutionStatusSucceeded, status, "error=%s cause=%s", errCode, cause) var items []map[string]any - require.NoError(t, json.Unmarshal([]byte(output), &items)) + require.NoError(t, json.Unmarshal([]byte(output), &items, jsonx.V1())) require.Len(t, items, 1) assert.Equal(t, "csvDataset/titles.csv", items[0]["Key"]) }) diff --git a/services/stepfunctions/jsonata_distributed_map_test.go b/services/stepfunctions/jsonata_distributed_map_test.go index d403b25215..1c2c159b07 100644 --- a/services/stepfunctions/jsonata_distributed_map_test.go +++ b/services/stepfunctions/jsonata_distributed_map_test.go @@ -2,13 +2,14 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -260,7 +261,7 @@ func runS3ArgumentsCase(t *testing.T, reader, writer, input, wantOutput string, } var out mapExportOutput - require.NoError(t, json.Unmarshal([]byte(d.Output), &out)) + require.NoError(t, json.Unmarshal([]byte(d.Output), &out, jsonx.V1())) assert.Equal(t, "data-bucket", out.ResultWriterDetails.Bucket) assert.NotEmpty(t, getS3ObjectBytes(t, s3Bk, "data-bucket", out.ResultWriterDetails.Key)) } diff --git a/services/stepfunctions/list_executions_golden_test.go b/services/stepfunctions/list_executions_golden_test.go index 95c63e645a..697ce63e67 100644 --- a/services/stepfunctions/list_executions_golden_test.go +++ b/services/stepfunctions/list_executions_golden_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "os" @@ -15,6 +15,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -99,7 +100,7 @@ func TestListExecutions_PageGolden(t *testing.T) { var resp struct { NextToken string `json:"nextToken"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if resp.NextToken == "" { break diff --git a/services/stepfunctions/map_runs_test.go b/services/stepfunctions/map_runs_test.go index 2612f72f0c..a18a4c5b8a 100644 --- a/services/stepfunctions/map_runs_test.go +++ b/services/stepfunctions/map_runs_test.go @@ -2,13 +2,14 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -43,14 +44,14 @@ func TestListMapRuns_ReturnsEmptyList(t *testing.T) { smARN := createSM(ctx, t, h, e, "no-maprun-sm") execARN := startExec(ctx, t, h, e, smARN, "no-maprun-exec") - listBody, err := json.Marshal(map[string]any{"executionArn": execARN}) + listBody, err := json.Marshal(map[string]any{"executionArn": execARN}, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "ListMapRuns", string(listBody)) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) mapRuns, _ := out["mapRuns"].([]any) assert.Empty(t, mapRuns) @@ -118,24 +119,24 @@ func TestMapRunStorage(t *testing.T) { "definition": mapIterStateDef, "roleArn": "arn:aws:iam::123456789012:role/test", "type": "EXPRESS", - }) + }, jsonx.V1()) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var smResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp, jsonx.V1())) smARN := smResp["stateMachineArn"].(string) syncBody, _ := json.Marshal(map[string]any{ "stateMachineArn": smARN, "name": "sync-exec-1", "input": `[1,2,3]`, - }) + }, jsonx.V1()) syncRec := sfnPost(ctx, t, h, e, "StartSyncExecution", string(syncBody)) require.Equal(t, http.StatusOK, syncRec.Code) var syncResp map[string]any - require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp)) + require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp, jsonx.V1())) execARN := syncResp["executionArn"].(string) assert.GreaterOrEqual(t, bk.MapRunCountForTest(execARN), 1, @@ -156,35 +157,35 @@ func TestMapRunStorage(t *testing.T) { "definition": mapIterStateDef, "roleArn": "arn:aws:iam::123456789012:role/test", "type": "EXPRESS", - }) + }, jsonx.V1()) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var smResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp, jsonx.V1())) smARN := smResp["stateMachineArn"].(string) syncBody, _ := json.Marshal(map[string]any{ "stateMachineArn": smARN, "name": "sync-exec-2", "input": `[1,2]`, - }) + }, jsonx.V1()) syncRec := sfnPost(ctx, t, h, e, "StartSyncExecution", string(syncBody)) require.Equal(t, http.StatusOK, syncRec.Code) var syncResp map[string]any - require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp)) + require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp, jsonx.V1())) execARN := syncResp["executionArn"].(string) require.GreaterOrEqual(t, bk.MapRunCountForTest(execARN), 1) // ListMapRuns via handler. - listBody, _ := json.Marshal(map[string]any{"executionArn": execARN}) + listBody, _ := json.Marshal(map[string]any{"executionArn": execARN}, jsonx.V1()) listRec := sfnPost(ctx, t, h, e, "ListMapRuns", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) mapRuns, _ := listResp["mapRuns"].([]any) require.NotEmpty(t, mapRuns, "expected non-empty mapRuns list") @@ -192,12 +193,12 @@ func TestMapRunStorage(t *testing.T) { mapRunARN, _ := firstRun["mapRunArn"].(string) require.NotEmpty(t, mapRunARN) - descBody, _ := json.Marshal(map[string]any{"mapRunArn": mapRunARN}) + descBody, _ := json.Marshal(map[string]any{"mapRunArn": mapRunARN}, jsonx.V1()) descRec := sfnPost(ctx, t, h, e, "DescribeMapRun", string(descBody)) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) assert.Equal(t, mapRunARN, descResp["mapRunArn"]) assert.Equal(t, execARN, descResp["executionArn"]) assert.Equal(t, "SUCCEEDED", descResp["status"]) @@ -217,34 +218,34 @@ func TestMapRunStorage(t *testing.T) { "definition": mapIterStateDef, "roleArn": "arn:aws:iam::123456789012:role/test", "type": "EXPRESS", - }) + }, jsonx.V1()) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var smResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &smResp, jsonx.V1())) smARN := smResp["stateMachineArn"].(string) syncBody, _ := json.Marshal(map[string]any{ "stateMachineArn": smARN, "name": "sync-exec-3b", "input": `[1]`, - }) + }, jsonx.V1()) syncRec := sfnPost(ctx, t, h, e, "StartSyncExecution", string(syncBody)) require.Equal(t, http.StatusOK, syncRec.Code) var syncResp map[string]any - require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp)) + require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &syncResp, jsonx.V1())) execARN := syncResp["executionArn"].(string) require.GreaterOrEqual(t, bk.MapRunCountForTest(execARN), 1) - listBody, _ := json.Marshal(map[string]any{"executionArn": execARN}) + listBody, _ := json.Marshal(map[string]any{"executionArn": execARN}, jsonx.V1()) listRec := sfnPost(ctx, t, h, e, "ListMapRuns", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) mapRuns, _ := listResp["mapRuns"].([]any) require.NotEmpty(t, mapRuns) @@ -254,12 +255,12 @@ func TestMapRunStorage(t *testing.T) { updateBody, _ := json.Marshal(map[string]any{ "mapRunArn": mapRunARN, "maxConcurrency": 10, - }) + }, jsonx.V1()) updateRec := sfnPost(ctx, t, h, e, "UpdateMapRun", string(updateBody)) require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) maxConc, _ := updateResp["maxConcurrency"].(float64) assert.InEpsilon(t, float64(10), maxConc, 1e-9) }, diff --git a/services/stepfunctions/overwide_list_test.go b/services/stepfunctions/overwide_list_test.go index 24bb88c00c..1f17da1d5e 100644 --- a/services/stepfunctions/overwide_list_test.go +++ b/services/stepfunctions/overwide_list_test.go @@ -2,13 +2,16 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestOverWideListResponses asserts on the raw response body that every @@ -55,14 +58,14 @@ func TestOverWideListResponses(t *testing.T) { "tracingConfiguration": map[string]any{"enabled": true}, "loggingConfiguration": map[string]any{"level": "ALL"}, "encryptionConfiguration": map[string]any{"type": "AWS_OWNED_KEY"}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) smARN, _ := created["stateMachineArn"].(string) require.NotEmpty(t, smARN) @@ -71,7 +74,7 @@ func TestOverWideListResponses(t *testing.T) { "stateMachineArn": smARN, "definition": validPassDef, "roleArn": "arn:aws:iam::123456789012:role/test", - }) + }, jsonx.V1()) require.NoError(t, err) updRec := sfnPost(ctx, t, h, e, "UpdateStateMachine", string(updateBody)) @@ -100,7 +103,7 @@ func TestOverWideListResponses(t *testing.T) { "type": "CUSTOMER_MANAGED_KMS_KEY", "kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/test-key", }, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateActivity", string(createBody)) @@ -128,18 +131,18 @@ func TestOverWideListResponses(t *testing.T) { "stateMachineArn": smARN, "name": "wide-exec", "input": `{"secret":"do-not-leak"}`, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "StartExecution", string(execBody)) require.Equal(t, http.StatusOK, rec.Code) var started map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &started, jsonx.V1())) execARN, _ := started["executionArn"].(string) require.NotEmpty(t, execARN) - descBody, err := json.Marshal(map[string]string{"executionArn": execARN}) + descBody, err := json.Marshal(map[string]string{"executionArn": execARN}, jsonx.V1()) require.NoError(t, err) require.Eventuallyf(t, func() bool { @@ -149,14 +152,14 @@ func TestOverWideListResponses(t *testing.T) { } var desc map[string]any - if unmarshalErr := json.Unmarshal(descRec.Body.Bytes(), &desc); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(descRec.Body.Bytes(), &desc, jsonx.V1()); unmarshalErr != nil { return false } return desc["status"] != "RUNNING" }, 5*time.Second, 20*time.Millisecond, "execution should finish") - listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}) + listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListExecutions", string(listBody)) @@ -187,14 +190,14 @@ func TestOverWideListResponses(t *testing.T) { "definition": mapIterStateDef, "roleArn": "arn:aws:iam::123456789012:role/test", "type": "EXPRESS", - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, rec.Code) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) smARN, _ := created["stateMachineArn"].(string) require.NotEmpty(t, smARN) @@ -202,25 +205,25 @@ func TestOverWideListResponses(t *testing.T) { "stateMachineArn": smARN, "name": "wide-maprun-exec", "input": `[1,2,3]`, - }) + }, jsonx.V1()) require.NoError(t, err) syncRec := sfnPost(ctx, t, h, e, "StartSyncExecution", string(syncBody)) require.Equal(t, http.StatusOK, syncRec.Code) var synced map[string]any - require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &synced)) + require.NoError(t, json.Unmarshal(syncRec.Body.Bytes(), &synced, jsonx.V1())) execARN, _ := synced["executionArn"].(string) require.NotEmpty(t, execARN) - listBody, err := json.Marshal(map[string]any{"executionArn": execARN}) + listBody, err := json.Marshal(map[string]any{"executionArn": execARN}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListMapRuns", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var listed map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listed)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listed, jsonx.V1())) runs, _ := listed["mapRuns"].([]any) require.NotEmpty(t, runs, "expected at least one MapRun") first, _ := runs[0].(map[string]any) @@ -234,7 +237,7 @@ func TestOverWideListResponses(t *testing.T) { "maxConcurrency": 5, "toleratedFailureCount": 1, "toleratedFailurePercentage": 10.0, - }) + }, jsonx.V1()) require.NoError(t, err) updRec := sfnPost(ctx, t, h, e, "UpdateMapRun", string(updateBody)) @@ -261,14 +264,14 @@ func TestOverWideListResponses(t *testing.T) { h, e := newSFNHandler(t) smARN := createSM(ctx, t, h, e, "wide-alias-sm") - pubBody, err := json.Marshal(map[string]any{"stateMachineArn": smARN}) + pubBody, err := json.Marshal(map[string]any{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) pubRec := sfnPost(ctx, t, h, e, "PublishStateMachineVersion", string(pubBody)) require.Equal(t, http.StatusOK, pubRec.Code) var published map[string]any - require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &published)) + require.NoError(t, json.Unmarshal(pubRec.Body.Bytes(), &published, jsonx.V1())) versionARN, _ := published["stateMachineVersionArn"].(string) require.NotEmpty(t, versionARN) @@ -279,14 +282,14 @@ func TestOverWideListResponses(t *testing.T) { "routingConfiguration": []map[string]any{ {"stateMachineVersionArn": versionARN, "weight": 100}, }, - }) + }, jsonx.V1()) require.NoError(t, err) aliasRec := sfnPost(ctx, t, h, e, "CreateStateMachineAlias", string(createBody)) require.Equal(t, http.StatusOK, aliasRec.Code) var alias map[string]any - require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &alias)) + require.NoError(t, json.Unmarshal(aliasRec.Body.Bytes(), &alias, jsonx.V1())) aliasARN, _ := alias["stateMachineAliasArn"].(string) require.NotEmpty(t, aliasARN) @@ -294,13 +297,13 @@ func TestOverWideListResponses(t *testing.T) { updateBody, err := json.Marshal(map[string]any{ "stateMachineAliasArn": aliasARN, "description": "updated", - }) + }, jsonx.V1()) require.NoError(t, err) updRec := sfnPost(ctx, t, h, e, "UpdateStateMachineAlias", string(updateBody)) require.Equal(t, http.StatusOK, updRec.Code) - listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}) + listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListStateMachineAliases", string(listBody)) @@ -331,13 +334,13 @@ func TestOverWideListResponses(t *testing.T) { "stateMachineArn": smARN, "description": "leaks if present", "revisionId": "leaks-if-present", - }) + }, jsonx.V1()) require.NoError(t, err) pubRec := sfnPost(ctx, t, h, e, "PublishStateMachineVersion", string(pubBody)) require.Equal(t, http.StatusOK, pubRec.Code) - listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}) + listBody, err := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListStateMachineVersions", string(listBody)) @@ -354,14 +357,14 @@ func TestOverWideListResponses(t *testing.T) { body := tt.raw(t) - var resp map[string]json.RawMessage - require.NoError(t, json.Unmarshal(body, &resp)) + var resp map[string]jsontext.Value + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) itemsRaw, ok := resp[tt.itemsKey] require.True(t, ok, "response missing %q key", tt.itemsKey) - var items []map[string]json.RawMessage - require.NoError(t, json.Unmarshal(itemsRaw, &items)) + var items []map[string]jsontext.Value + require.NoError(t, json.Unmarshal(itemsRaw, &items, jsonx.V1())) require.NotEmpty(t, items, "expected at least one item in %q", tt.itemsKey) for _, item := range items { diff --git a/services/stepfunctions/persistence.go b/services/stepfunctions/persistence.go index b678eed6a4..8522628345 100644 --- a/services/stepfunctions/persistence.go +++ b/services/stepfunctions/persistence.go @@ -2,9 +2,11 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" "github.com/blackbirdworks/gopherstack/pkgs/store" @@ -93,17 +95,17 @@ func (b *InMemoryBackend) newPersistedDTORegistry() ( // guards against decoding a snapshot from an incompatible (older or newer) // build of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // handlerSnapshot wraps the backend snapshot together with handler-level state // (tags) so both are persisted and restored together. type handlerSnapshot struct { Tags map[string]map[string]string `json:"tags,omitempty"` - Backend json.RawMessage `json:"backend"` + Backend jsontext.Value `json:"backend"` } // Snapshot serialises the backend state to JSON. @@ -329,7 +331,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { Snapshot(ctx context.Context) []byte } - var backendBytes json.RawMessage + var backendBytes jsontext.Value if s, ok := h.Backend.(snapshotter); ok { backendBytes = s.Snapshot(ctx) } @@ -346,7 +348,7 @@ func (h *Handler) Snapshot(ctx context.Context) []byte { Tags: tagsData, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { return backendBytes } diff --git a/services/stepfunctions/result_writer_test.go b/services/stepfunctions/result_writer_test.go index 900a5d0f21..2a0cb654f5 100644 --- a/services/stepfunctions/result_writer_test.go +++ b/services/stepfunctions/result_writer_test.go @@ -2,7 +2,8 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "log/slog" "strings" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" s3pkg "github.com/blackbirdworks/gopherstack/services/s3" "github.com/blackbirdworks/gopherstack/services/stepfunctions" @@ -198,7 +200,7 @@ func TestDistributedMapResultWriter(t *testing.T) { require.Equal(t, "SUCCEEDED", d.Status, "cause=%s error=%s", d.Cause, d.Error) var out mapExportOutput - require.NoError(t, json.Unmarshal([]byte(d.Output), &out)) + require.NoError(t, json.Unmarshal([]byte(d.Output), &out, jsonx.V1())) assert.Equal(t, bucket, out.ResultWriterDetails.Bucket) assert.True(t, strings.HasPrefix(out.ResultWriterDetails.Key, "jobs/")) assert.True(t, strings.HasSuffix(out.ResultWriterDetails.Key, "manifest.json")) @@ -207,7 +209,7 @@ func TestDistributedMapResultWriter(t *testing.T) { manifestBytes := getS3ObjectBytes(t, s3Bk, bucket, out.ResultWriterDetails.Key) var manifest resultManifest - require.NoError(t, json.Unmarshal(manifestBytes, &manifest)) + require.NoError(t, json.Unmarshal(manifestBytes, &manifest, jsonx.V1())) assert.Equal(t, bucket, manifest.DestinationBucket) assert.Equal(t, out.MapRunArn, manifest.MapRunArn) require.Len(t, manifest.ResultFiles.Succeeded, 1) @@ -219,7 +221,7 @@ func TestDistributedMapResultWriter(t *testing.T) { succeededBytes := getS3ObjectBytes(t, s3Bk, bucket, manifest.ResultFiles.Succeeded[0].Key) var records []map[string]any - require.NoError(t, json.Unmarshal(succeededBytes, &records)) + require.NoError(t, json.Unmarshal(succeededBytes, &records, jsonx.V1())) require.Len(t, records, 3) assert.Equal(t, "SUCCEEDED", records[0]["Status"]) // Default Transformation (NONE, since ResultWriter exports without a @@ -255,7 +257,7 @@ func TestDistributedMapResultWriter(t *testing.T) { require.Equal(t, "SUCCEEDED", d.Status, "cause=%s error=%s", d.Cause, d.Error) var arr []float64 - require.NoError(t, json.Unmarshal([]byte(d.Output), &arr)) + require.NoError(t, json.Unmarshal([]byte(d.Output), &arr, jsonx.V1())) assert.Equal(t, []float64{1, 2, 3}, arr) runs, _, err := b.ListMapRuns(exec.ExecutionArn, "", 0) @@ -292,7 +294,7 @@ func TestDistributedMapResultWriter(t *testing.T) { // NONE, same as the writes-to-S3 case -- the missing S3 writer only // changes whether the formatted result is exported, not its shape. var records []map[string]any - require.NoError(t, json.Unmarshal([]byte(d.Output), &records)) + require.NoError(t, json.Unmarshal([]byte(d.Output), &records, jsonx.V1())) require.Len(t, records, 3) assert.Equal(t, "SUCCEEDED", records[0]["Status"]) assert.Equal(t, "1", records[0]["Input"]) @@ -401,7 +403,7 @@ func startWriterConfigExecution( // assertTransformationEntries checks succeeded-file/preview entries against // what each Transformation must produce for three items whose child output // is always [10, 20] (AWS docs: input-output-resultwriter.html). -func assertTransformationEntries(t *testing.T, transformation string, raw []json.RawMessage) { +func assertTransformationEntries(t *testing.T, transformation string, raw []jsontext.Value) { t.Helper() switch transformation { @@ -411,7 +413,7 @@ func assertTransformationEntries(t *testing.T, transformation string, raw []json want := []float64{10, 20, 10, 20, 10, 20} for i, r := range raw { var v float64 - require.NoError(t, json.Unmarshal(r, &v)) + require.NoError(t, json.Unmarshal(r, &v, jsonx.V1())) assert.InDelta(t, want[i], v, 0) } case "COMPACT": @@ -419,7 +421,7 @@ func assertTransformationEntries(t *testing.T, transformation string, raw []json for _, r := range raw { var v []float64 - require.NoError(t, json.Unmarshal(r, &v)) + require.NoError(t, json.Unmarshal(r, &v, jsonx.V1())) assert.Equal(t, []float64{10, 20}, v) } default: // NONE @@ -427,7 +429,7 @@ func assertTransformationEntries(t *testing.T, transformation string, raw []json for _, r := range raw { var rec map[string]any - require.NoError(t, json.Unmarshal(r, &rec)) + require.NoError(t, json.Unmarshal(r, &rec, jsonx.V1())) assert.Equal(t, "SUCCEEDED", rec["Status"]) assert.Equal(t, "[10,20]", rec["Output"], "NONE stringifies Output like a real DescribeExecution") assert.Equal(t, true, rec["InputDetails"].(map[string]any)["Included"]) @@ -438,21 +440,21 @@ func assertTransformationEntries(t *testing.T, transformation string, raw []json // decodeResultEntries parses a SUCCEEDED_0.json/FAILED_0.json file's bytes // into individual JSON values, per OutputType: JSON is one array, JSONL is // one value per newline. -func decodeResultEntries(t *testing.T, data []byte, outputType string) []json.RawMessage { +func decodeResultEntries(t *testing.T, data []byte, outputType string) []jsontext.Value { t.Helper() if outputType != "JSONL" { - var arr []json.RawMessage - require.NoError(t, json.Unmarshal(data, &arr)) + var arr []jsontext.Value + require.NoError(t, json.Unmarshal(data, &arr, jsonx.V1())) return arr } lines := strings.Split(strings.TrimSpace(string(data)), "\n") - raw := make([]json.RawMessage, len(lines)) + raw := make([]jsontext.Value, len(lines)) for i, l := range lines { - raw[i] = json.RawMessage(l) + raw[i] = jsontext.Value(l) } return raw @@ -488,7 +490,7 @@ func TestDistributedMapResultWriter_TransformationOutputType(t *testing.T) { "cause=%s error=%s", aws.ToString(desc.Cause), aws.ToString(desc.Error)) var out mapExportOutput - require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out, jsonx.V1())) require.Equal(t, bucket, out.ResultWriterDetails.Bucket) assert.True(t, strings.HasPrefix(out.ResultWriterDetails.Key, "jobs/")) assert.True(t, strings.HasSuffix(out.ResultWriterDetails.Key, "manifest.json")) @@ -496,7 +498,7 @@ func TestDistributedMapResultWriter_TransformationOutputType(t *testing.T) { manifestBytes := getS3ObjectBytes(t, s3Bk, bucket, out.ResultWriterDetails.Key) var manifest resultManifest - require.NoError(t, json.Unmarshal(manifestBytes, &manifest)) + require.NoError(t, json.Unmarshal(manifestBytes, &manifest, jsonx.V1())) require.Len(t, manifest.ResultFiles.Succeeded, 1) assert.Empty(t, manifest.ResultFiles.Failed) assert.True(t, strings.HasSuffix(manifest.ResultFiles.Succeeded[0].Key, "SUCCEEDED_0.json")) @@ -566,26 +568,26 @@ func TestDistributedMapResultWriter_FailedItemsKeepFullRecord(t *testing.T) { ) var out mapExportOutput - require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out, jsonx.V1())) manifestBytes := getS3ObjectBytes(t, s3Bk, bucket, out.ResultWriterDetails.Key) var manifest resultManifest - require.NoError(t, json.Unmarshal(manifestBytes, &manifest)) + require.NoError(t, json.Unmarshal(manifestBytes, &manifest, jsonx.V1())) require.Len(t, manifest.ResultFiles.Succeeded, 1) require.Len(t, manifest.ResultFiles.Failed, 1) succeededBytes := getS3ObjectBytes(t, s3Bk, bucket, manifest.ResultFiles.Succeeded[0].Key) var succeeded [][]float64 - require.NoError(t, json.Unmarshal(succeededBytes, &succeeded)) + require.NoError(t, json.Unmarshal(succeededBytes, &succeeded, jsonx.V1())) require.Len(t, succeeded, 2, "2 of 3 items succeed") assert.Equal(t, []float64{10, 20}, succeeded[0]) failedBytes := getS3ObjectBytes(t, s3Bk, bucket, manifest.ResultFiles.Failed[0].Key) var failed []map[string]any - require.NoError(t, json.Unmarshal(failedBytes, &failed)) + require.NoError(t, json.Unmarshal(failedBytes, &failed, jsonx.V1())) require.Len(t, failed, 1) assert.Equal(t, "FAILED", failed[0]["Status"]) assert.Equal(t, "2", failed[0]["Input"]) @@ -636,18 +638,18 @@ func TestDistributedMapResultWriter_DistributedChildIdentity(t *testing.T) { "cause=%s error=%s", aws.ToString(desc.Cause), aws.ToString(desc.Error)) var out mapExportOutput - require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(desc.Output)), &out, jsonx.V1())) manifestBytes := getS3ObjectBytes(t, s3Bk, bucket, out.ResultWriterDetails.Key) var manifest resultManifest - require.NoError(t, json.Unmarshal(manifestBytes, &manifest)) + require.NoError(t, json.Unmarshal(manifestBytes, &manifest, jsonx.V1())) require.Len(t, manifest.ResultFiles.Succeeded, 1) succeededBytes := getS3ObjectBytes(t, s3Bk, bucket, manifest.ResultFiles.Succeeded[0].Key) var records []map[string]any - require.NoError(t, json.Unmarshal(succeededBytes, &records)) + require.NoError(t, json.Unmarshal(succeededBytes, &records, jsonx.V1())) require.Len(t, records, 3) seen := map[string]bool{} @@ -694,20 +696,20 @@ func TestDistributedMapResultWriter_PreviewWithoutExport(t *testing.T) { if tt.outputType == "JSONL" { var jsonl string - require.NoError(t, json.Unmarshal([]byte(output), &jsonl)) + require.NoError(t, json.Unmarshal([]byte(output), &jsonl, jsonx.V1())) lines := strings.Split(jsonl, "\n") require.Len(t, lines, 6) var v float64 - require.NoError(t, json.Unmarshal([]byte(lines[0]), &v)) + require.NoError(t, json.Unmarshal([]byte(lines[0]), &v, jsonx.V1())) assert.InDelta(t, 10, v, 0) return } var arr []float64 - require.NoError(t, json.Unmarshal([]byte(output), &arr)) + require.NoError(t, json.Unmarshal([]byte(output), &arr, jsonx.V1())) assert.Equal(t, []float64{10, 20, 10, 20, 10, 20}, arr) }) } diff --git a/services/stepfunctions/s3_item_reader_test.go b/services/stepfunctions/s3_item_reader_test.go index cb29db16d2..f4bc6d167b 100644 --- a/services/stepfunctions/s3_item_reader_test.go +++ b/services/stepfunctions/s3_item_reader_test.go @@ -2,13 +2,14 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -73,7 +74,7 @@ func TestStartExecution_MapItemReader_S3(t *testing.T) { require.Equal(t, "SUCCEEDED", described.Status, "cause=%s error=%s", described.Cause, described.Error) var output []map[string]any - require.NoError(t, json.Unmarshal([]byte(described.Output), &output)) + require.NoError(t, json.Unmarshal([]byte(described.Output), &output, jsonx.V1())) assert.Len(t, output, 3, "expected one output entry per S3-sourced item") }) } diff --git a/services/stepfunctions/sdk_convert.go b/services/stepfunctions/sdk_convert.go index 2e614db78e..7cc20736f0 100644 --- a/services/stepfunctions/sdk_convert.go +++ b/services/stepfunctions/sdk_convert.go @@ -2,7 +2,7 @@ package stepfunctions import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -10,6 +10,8 @@ import ( "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -63,7 +65,7 @@ func streamString(src any) string { return s } - b, _ := json.Marshal(src) + b, _ := json.Marshal(src, jsonx.V1()) return string(b) } diff --git a/services/stepfunctions/sdk_integration.go b/services/stepfunctions/sdk_integration.go index 377ecd46e8..f3c735160f 100644 --- a/services/stepfunctions/sdk_integration.go +++ b/services/stepfunctions/sdk_integration.go @@ -3,7 +3,7 @@ package stepfunctions import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -20,6 +20,7 @@ import ( "github.com/aws/smithy-go" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/safemap" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire" @@ -418,7 +419,7 @@ func withJSONString(m map[string]any, key string, blob bool) map[string]any { raw, isStr := val.(string) if !isStr { - b, _ := json.Marshal(val) + b, _ := json.Marshal(val, jsonx.V1()) raw = string(b) } diff --git a/services/stepfunctions/sdk_sync.go b/services/stepfunctions/sdk_sync.go index 557373c4a0..4086c1954d 100644 --- a/services/stepfunctions/sdk_sync.go +++ b/services/stepfunctions/sdk_sync.go @@ -2,10 +2,11 @@ package stepfunctions import ( "context" - "encoding/json" + "encoding/json/v2" "slices" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/asl" ) @@ -172,7 +173,7 @@ func runTaskFailures(call asl.SDKCall, out any) error { return nil } - cause, _ := json.Marshal(failures) + cause, _ := json.Marshal(failures, jsonx.V1()) return &asl.FailError{ErrCode: "AmazonECS.Unknown", Cause: string(cause)} } @@ -186,7 +187,7 @@ func executionSyncResult(p map[string]any, pattern string) any { if s, ok := p["Output"].(string); ok { var parsed any - if json.Unmarshal([]byte(s), &parsed) == nil { + if json.Unmarshal([]byte(s), &parsed, jsonx.V1()) == nil { p["Output"] = parsed } } @@ -257,7 +258,7 @@ func finishSync(req syncRequest, poll map[string]any, state string) (any, error) return result, nil } - cause, _ := json.Marshal(result) + cause, _ := json.Marshal(result, jsonx.V1()) return nil, &asl.FailError{ErrCode: errCodeTaskFailed, Cause: string(cause)} } diff --git a/services/stepfunctions/sdkwire/call_test.go b/services/stepfunctions/sdkwire/call_test.go index cb1f7326a1..fb0fa2dcbd 100644 --- a/services/stepfunctions/sdkwire/call_test.go +++ b/services/stepfunctions/sdkwire/call_test.go @@ -2,7 +2,7 @@ package sdkwire_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire" ) @@ -85,7 +86,7 @@ func TestCall_Request(t *testing.T) { require.NoError(t, err) var got map[string]any - require.NoError(t, json.Unmarshal(seen, &got)) + require.NoError(t, json.Unmarshal(seen, &got, jsonx.V1())) for k, v := range tt.wantJSON { assert.Equal(t, v, got[k], k) @@ -107,7 +108,7 @@ func TestCall_IdempotencyToken(t *testing.T) { require.NoError(t, err) var got map[string]any - require.NoError(t, json.Unmarshal(seen, &got)) + require.NoError(t, json.Unmarshal(seen, &got, jsonx.V1())) assert.NotEmpty(t, got["clientToken"]) } diff --git a/services/stepfunctions/sdkwire/legacy.go b/services/stepfunctions/sdkwire/legacy.go index caa13817fe..16457a0e69 100644 --- a/services/stepfunctions/sdkwire/legacy.go +++ b/services/stepfunctions/sdkwire/legacy.go @@ -3,7 +3,8 @@ package sdkwire import ( "compress/gzip" "embed" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" @@ -11,6 +12,7 @@ import ( "github.com/aws/smithy-go/traits" smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions/sdkwire/ir" ) @@ -36,7 +38,7 @@ func legacyService(key, signing, userAgentAPI string, protocol protocolFor) *Ser } var m ir.Model - if derr := json.NewDecoder(zr).Decode(&m); derr != nil { + if derr := json.UnmarshalDecode(jsontext.NewDecoder(zr), &m, jsonx.V1()); derr != nil { panic(fmt.Sprintf("sdkwire: model %s: %v", key, derr)) } diff --git a/services/stepfunctions/sdkwire/parity_whitebox_test.go b/services/stepfunctions/sdkwire/parity_whitebox_test.go index c437ea0dfd..e2583852d2 100644 --- a/services/stepfunctions/sdkwire/parity_whitebox_test.go +++ b/services/stepfunctions/sdkwire/parity_whitebox_test.go @@ -3,7 +3,7 @@ package sdkwire import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -25,6 +25,8 @@ import ( "github.com/aws/smithy-go/transport/http/protocol/restxml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const testEndpoint = "http://sfn-sdk-integration.local" @@ -166,7 +168,7 @@ func decodeJSONBody(raw []byte) (any, bool) { var v any - return v, json.Unmarshal(raw, &v) == nil + return v, json.Unmarshal(raw, &v, jsonx.V1()) == nil } func bodyDiff(contentType string, want, got []byte) string { diff --git a/services/stepfunctions/sdkwire/schemair/encode.go b/services/stepfunctions/sdkwire/schemair/encode.go index 780df9a83f..7e80d25ea7 100644 --- a/services/stepfunctions/sdkwire/schemair/encode.go +++ b/services/stepfunctions/sdkwire/schemair/encode.go @@ -3,12 +3,14 @@ package schemair import ( "bytes" "compress/gzip" - "encoding/json" + "encoding/json/v2" "fmt" "reflect" "sort" "github.com/aws/smithy-go" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // OpRef is an operation's schemas as the SDK package declares them. @@ -99,7 +101,7 @@ func (e *encoder) addOp(m *Model, ref OpRef) (Op, error) { // Write gzips the model as JSON. func (m *Model) Write() ([]byte, error) { - raw, err := json.Marshal(m) + raw, err := json.Marshal(m, jsonx.V1()) if err != nil { return nil, err } @@ -198,7 +200,7 @@ func (e *encoder) traits(s *smithy.Schema, direct bool) ([]Trait, error) { continue } - raw, err := json.Marshal(t) + raw, err := json.Marshal(t, jsonx.V1()) if err != nil { return nil, err } diff --git a/services/stepfunctions/sdkwire/schemair/schemair.go b/services/stepfunctions/sdkwire/schemair/schemair.go index ba59e7b8e4..f7ee9feb89 100644 --- a/services/stepfunctions/sdkwire/schemair/schemair.go +++ b/services/stepfunctions/sdkwire/schemair/schemair.go @@ -5,7 +5,8 @@ package schemair import ( "compress/gzip" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +15,8 @@ import ( "github.com/aws/smithy-go" "github.com/aws/smithy-go/prelude" "github.com/aws/smithy-go/traits" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ErrModel marks an invalid or mismatched schema model. @@ -58,8 +61,8 @@ type Op struct { // Trait is a smithy-go trait by codec name and JSON-encoded fields. type Trait struct { - Name string `json:"n"` - Value json.RawMessage `json:"v,omitempty"` + Name string `json:"n"` + Value jsontext.Value `json:"v,omitempty"` } // Operation is a decoded operation. @@ -161,7 +164,7 @@ func Read(r io.Reader) (*Model, error) { defer zr.Close() var m Model - if derr := json.NewDecoder(zr).Decode(&m); derr != nil { + if derr := json.UnmarshalDecode(jsontext.NewDecoder(zr), &m, jsonx.V1()); derr != nil { return nil, fmt.Errorf("schemair: %w", derr) } @@ -245,7 +248,7 @@ func decodeTraits(cs map[string]codec, in []Trait) ([]smithy.Trait, error) { v := reflect.New(c.typ) if len(t.Value) > 0 { - if err := json.Unmarshal(t.Value, v.Interface()); err != nil { + if err := json.Unmarshal(t.Value, v.Interface(), jsonx.V1()); err != nil { return nil, fmt.Errorf("schemair: trait %s: %w", t.Name, err) } } diff --git a/services/stepfunctions/state_machine_versions_test.go b/services/stepfunctions/state_machine_versions_test.go index 1f2a6fbf63..bbe3da7dfe 100644 --- a/services/stepfunctions/state_machine_versions_test.go +++ b/services/stepfunctions/state_machine_versions_test.go @@ -2,7 +2,7 @@ package stepfunctions_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/stepfunctions" ) @@ -172,7 +173,7 @@ func TestHandler_ListStateMachineVersions(t *testing.T) { body := tt.body if body == "" { - b, err := json.Marshal(map[string]string{"stateMachineArn": smARN}) + b, err := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, err) body = string(b) } @@ -181,7 +182,7 @@ func TestHandler_ListStateMachineVersions(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "stateMachineVersions") }) } @@ -271,24 +272,24 @@ func TestListStateMachineVersions(t *testing.T) { "definition": validPassDef, "roleArn": "arn:aws:iam::000:role/r", "type": "STANDARD", - }) + }, jsonx.V1()) require.NoError(t, err) createRec := sfnPost(t.Context(), t, h, e, "CreateStateMachine", string(createBody)) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) smARN := createResp["stateMachineArn"].(string) - listBody, merr := json.Marshal(map[string]string{"stateMachineArn": smARN}) + listBody, merr := json.Marshal(map[string]string{"stateMachineArn": smARN}, jsonx.V1()) require.NoError(t, merr) rec := sfnPost(t.Context(), t, h, e, "ListStateMachineVersions", string(listBody)) assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["stateMachineVersions"].([]any) require.True(t, ok, "stateMachineVersions should be an array") assert.Empty(t, versions) diff --git a/services/stepfunctions/tags_test.go b/services/stepfunctions/tags_test.go index 53e082481f..a02ef80d48 100644 --- a/services/stepfunctions/tags_test.go +++ b/services/stepfunctions/tags_test.go @@ -1,13 +1,15 @@ package stepfunctions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagResource(t *testing.T) { @@ -67,7 +69,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].([]any) assert.NotEmpty(t, tags) }) @@ -112,7 +114,7 @@ func TestHandler_UntagResource(t *testing.T) { listRec := sfnPost(ctx, t, h, e, "ListTagsForResource", `{"resourceArn":"`+arn+`"}`) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].([]any) assert.Len(t, tags, tt.wantTagCount) @@ -140,24 +142,24 @@ func TestTags_CreateWithInlineTags(t *testing.T) { }, } - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "CreateStateMachine", string(raw)) require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) arnStr := out["stateMachineArn"].(string) - listBody, err := json.Marshal(map[string]any{"resourceArn": arnStr}) + listBody, err := json.Marshal(map[string]any{"resourceArn": arnStr}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var tags map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tags)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tags, jsonx.V1())) tagList, _ := tags["tags"].([]any) assert.Len(t, tagList, 2) @@ -173,21 +175,21 @@ func TestTags_TagAndUntag(t *testing.T) { tagBody, err := json.Marshal(map[string]any{ "resourceArn": arnStr, "tags": []map[string]string{{"key": "k1", "value": "v1"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(tagBody)) require.Equal(t, http.StatusOK, rec.Code) // Verify - listBody, err := json.Marshal(map[string]any{"resourceArn": arnStr}) + listBody, err := json.Marshal(map[string]any{"resourceArn": arnStr}, jsonx.V1()) require.NoError(t, err) listRec := sfnPost(ctx, t, h, e, "ListTagsForResource", string(listBody)) require.Equal(t, http.StatusOK, listRec.Code) var tagOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagOut, jsonx.V1())) tagList, _ := tagOut["tags"].([]any) assert.Len(t, tagList, 1) @@ -196,7 +198,7 @@ func TestTags_TagAndUntag(t *testing.T) { untagBody, err := json.Marshal(map[string]any{ "resourceArn": arnStr, "tagKeys": []string{"k1"}, - }) + }, jsonx.V1()) require.NoError(t, err) untagRec := sfnPost(ctx, t, h, e, "UntagResource", string(untagBody)) @@ -206,7 +208,7 @@ func TestTags_TagAndUntag(t *testing.T) { require.Equal(t, http.StatusOK, listRec2.Code) var after map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &after, jsonx.V1())) tagList2, _ := after["tags"].([]any) assert.Empty(t, tagList2) @@ -225,14 +227,14 @@ func TestTagResource_KeyTooLong_Error(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": []map[string]string{{"key": longKey, "value": "val"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TagPolicyViolation", resp["__type"]) } @@ -246,14 +248,14 @@ func TestTagResource_EmptyKey_Error(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": []map[string]string{{"key": "", "value": "val"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TagPolicyViolation", resp["__type"]) } @@ -268,14 +270,14 @@ func TestTagResource_ValueTooLong_Error(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": []map[string]string{{"key": "mykey", "value": longVal}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TagPolicyViolation", resp["__type"]) } @@ -297,7 +299,7 @@ func TestTagResource_MaxTagsExceeded_Error(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": batch, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) require.Equal(t, http.StatusOK, rec.Code) @@ -307,14 +309,14 @@ func TestTagResource_MaxTagsExceeded_Error(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": []map[string]string{{"key": "overflow-key", "value": "val"}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // AWS: TagResource's own error switch models TooManyTags for exceeding // the per-resource tag limit -- "TagPolicyViolation" names no type // anywhere in this SDK. @@ -345,7 +347,7 @@ func TestTagResource_ValidTagsAccepted(t *testing.T) { body, err := json.Marshal(map[string]any{ "resourceArn": smARN, "tags": []map[string]string{{"key": tt.key, "value": tt.value}}, - }) + }, jsonx.V1()) require.NoError(t, err) rec := sfnPost(ctx, t, h, e, "TagResource", string(body)) @@ -395,7 +397,7 @@ func TestHandler_GetTags_EmptyAndNonEmpty(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := resp["tags"].([]any) assert.Len(t, tags, tt.wantTagsLen) diff --git a/services/sts/persistence.go b/services/sts/persistence.go index 7a6d9befe3..5051e465e3 100644 --- a/services/sts/persistence.go +++ b/services/sts/persistence.go @@ -2,9 +2,10 @@ package sts import ( "context" - "encoding/json" + "encoding/json/v2" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -45,7 +46,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { OpsGetFedToken: b.cntGetFederationToken.Load(), } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "sts: failed to snapshot backend state", "error", err) diff --git a/services/sts/token_validation.go b/services/sts/token_validation.go index 8525f58b5a..f7063bd935 100644 --- a/services/sts/token_validation.go +++ b/services/sts/token_validation.go @@ -2,7 +2,7 @@ package sts import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/xml" "errors" "fmt" @@ -10,6 +10,8 @@ import ( "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // clockSkewLeeway is the tolerance applied to JWT/SAML temporal claims so that @@ -87,7 +89,7 @@ func parseJWTPayloadClaims(token string) map[string]any { } var claims map[string]any - if err = json.Unmarshal(decoded, &claims); err != nil { + if err = json.Unmarshal(decoded, &claims, jsonx.V1()); err != nil { return nil } @@ -215,7 +217,7 @@ func jwtTimeClaim(claims map[string]any, key string) (time.Time, bool, error) { switch n := v.(type) { case float64: return time.Unix(int64(n), 0).UTC(), true, nil - case json.Number: + case jsonx.Number: secs, err := n.Int64() if err != nil { return time.Time{}, false, fmt.Errorf( diff --git a/services/sts/token_validation_test.go b/services/sts/token_validation_test.go index 38c4b41cde..a30711461f 100644 --- a/services/sts/token_validation_test.go +++ b/services/sts/token_validation_test.go @@ -2,7 +2,7 @@ package sts_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "testing" "time" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sts" ) @@ -19,7 +20,7 @@ func makeUnitJWT(t *testing.T, claims map[string]any) string { header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`)) - payload, err := json.Marshal(claims) + payload, err := json.Marshal(claims, jsonx.V1()) if err != nil { t.Fatalf("marshal claims: %v", err) } @@ -232,7 +233,7 @@ func TestJWTTimeClaim(t *testing.T) { }, { name: "json_number", - claims: map[string]any{"exp": json.Number("2000")}, + claims: map[string]any{"exp": jsonx.Number("2000")}, key: "exp", wantOK: true, wantEpoch: 2000, diff --git a/services/sts/trust_policy.go b/services/sts/trust_policy.go index f7ddf906eb..dd08ba8275 100644 --- a/services/sts/trust_policy.go +++ b/services/sts/trust_policy.go @@ -2,7 +2,8 @@ package sts import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" @@ -10,6 +11,7 @@ import ( "time" "github.com/blackbirdworks/gopherstack/pkgs/condeval" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -126,10 +128,10 @@ func (e trustEval) conditionValue(key string) (string, bool) { // trustStatementDoc is a single parsed trust-policy statement. type trustStatementDoc struct { - Condition map[string]map[string]json.RawMessage `json:"Condition"` - Effect string `json:"Effect"` - Principal json.RawMessage `json:"Principal"` - Action json.RawMessage `json:"Action"` + Condition map[string]map[string]jsontext.Value `json:"Condition"` + Effect string `json:"Effect"` + Principal jsontext.Value `json:"Principal"` + Action jsontext.Value `json:"Action"` } // principalSet is the parsed Principal element of a statement. @@ -147,7 +149,7 @@ type trustPolicy struct { // trustStatement is a single statement in a trust policy. type trustStatement struct { - Condition map[string]map[string]json.RawMessage `json:"Condition"` + Condition map[string]map[string]jsontext.Value `json:"Condition"` } // evaluateAssumeRoleTrust evaluates a role's trust policy against the caller @@ -213,10 +215,10 @@ func parseTrustStatements(policyJSON string) ([]trustStatementDoc, bool) { } var doc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if err := json.Unmarshal([]byte(policyJSON), &doc); err != nil { + if err := json.Unmarshal([]byte(policyJSON), &doc, jsonx.V1()); err != nil { return nil, false } @@ -227,7 +229,7 @@ func parseTrustStatements(policyJSON string) ([]trustStatementDoc, bool) { if raw[0] == '[' { var arr []trustStatementDoc - if err := json.Unmarshal(raw, &arr); err != nil { + if err := json.Unmarshal(raw, &arr, jsonx.V1()); err != nil { return nil, false } @@ -235,7 +237,7 @@ func parseTrustStatements(policyJSON string) ([]trustStatementDoc, bool) { } var single trustStatementDoc - if err := json.Unmarshal(raw, &single); err != nil { + if err := json.Unmarshal(raw, &single, jsonx.V1()); err != nil { return nil, false } @@ -244,7 +246,7 @@ func parseTrustStatements(policyJSON string) ([]trustStatementDoc, bool) { // trimLeadingSpace drops leading ASCII whitespace from a raw JSON message so the // first significant byte can be inspected without importing bytes helpers. -func trimLeadingSpace(raw json.RawMessage) json.RawMessage { +func trimLeadingSpace(raw jsontext.Value) jsontext.Value { i := 0 for i < len(raw) { switch raw[i] { @@ -260,7 +262,7 @@ func trimLeadingSpace(raw json.RawMessage) json.RawMessage { // actionMatches reports whether the requested action is granted by the // statement Action element (a string or array; wildcards supported). -func actionMatches(raw json.RawMessage, requested string) bool { +func actionMatches(raw jsontext.Value, requested string) bool { if len(raw) == 0 { // A statement without an explicit Action is treated as matching so that // condition-only test policies (which omit Action) keep working. @@ -282,7 +284,7 @@ func actionMatches(raw json.RawMessage, requested string) bool { } // principalMatches reports whether the caller satisfies the statement Principal. -func principalMatches(raw json.RawMessage, ev trustEval) bool { +func principalMatches(raw jsontext.Value, ev trustEval) bool { if len(raw) == 0 { // No Principal specified. Real trust policies always name a principal, // but ExternalId-only test fixtures omit it; treat as matching so those @@ -320,11 +322,11 @@ func principalMatches(raw json.RawMessage, ev trustEval) bool { // parsePrincipal parses the Principal element, which may be the string "*" or an // object keyed by AWS / Federated / Service / CanonicalUser. -func parsePrincipal(raw json.RawMessage) principalSet { +func parsePrincipal(raw jsontext.Value) principalSet { var out principalSet var asString string - if err := json.Unmarshal(raw, &asString); err == nil { + if err := json.Unmarshal(raw, &asString, jsonx.V1()); err == nil { if asString == "*" { out.wildcard = true } else { @@ -334,8 +336,8 @@ func parsePrincipal(raw json.RawMessage) principalSet { return out } - var obj map[string]json.RawMessage - if err := json.Unmarshal(raw, &obj); err != nil { + var obj map[string]jsontext.Value + if err := json.Unmarshal(raw, &obj, jsonx.V1()); err != nil { return out } @@ -476,7 +478,7 @@ func assumedRoleArnMatches(callerArn, account, roleName string) bool { // conditionsSatisfied reports whether every condition in a statement holds for // the caller context. Unmodelled keys and operators are treated as satisfied. -func conditionsSatisfied(cond map[string]map[string]json.RawMessage, ev trustEval) bool { +func conditionsSatisfied(cond map[string]map[string]jsontext.Value, ev trustEval) bool { for op, kv := range cond { for key, raw := range kv { if !conditionOperatorHolds(op, key, raw, ev) { @@ -497,7 +499,7 @@ func conditionsSatisfied(cond map[string]map[string]json.RawMessage, ev trustEva // cannot evaluate does not silently break existing callers. Both fallback // paths log loudly at WARN so the gap is discoverable at runtime instead of // silent -- that is the fix for gopherstack-yg95, not a change of default. -func conditionOperatorHolds(op, key string, raw json.RawMessage, ev trustEval) bool { +func conditionOperatorHolds(op, key string, raw jsontext.Value, ev trustEval) bool { // AWS docs: IfExists may suffix any operator except Null (which already // tests presence), so "NullIfExists" is left as an unrecognized operator. //nolint:lll // AWS doc URL, cannot be split @@ -615,7 +617,7 @@ func dateOperatorHolds(normOp string, want []string, actual string) bool { // be present. Unlike every other operator, Null tests key existence, not the // key's value, so it is driven by conditionValue's known result rather than a // string/bool comparison against actual. -func nullConditionHolds(raw json.RawMessage, known bool) bool { +func nullConditionHolds(raw jsontext.Value, known bool) bool { for _, v := range extractStringValues(raw) { wantAbsent, err := strconv.ParseBool(v) if err == nil && wantAbsent == !known { @@ -728,7 +730,7 @@ func validateExternalID(trustPolicyJSON, externalID string) error { // policy leaves tp with a zero value (empty Statements), so no ExternalId // condition will be found and the call proceeds without validation — the // permissive behaviour is intentional for a mock implementation. - _ = json.Unmarshal([]byte(trustPolicyJSON), &tp) + _ = json.Unmarshal([]byte(trustPolicyJSON), &tp, jsonx.V1()) var hasExternalIDCondition bool @@ -757,7 +759,7 @@ func validateExternalID(trustPolicyJSON, externalID string) error { // requiredExternalIDs extracts all sts:ExternalId values from a trust-statement Condition map. // Returns nil when no ExternalId condition is present. -func requiredExternalIDs(condition map[string]map[string]json.RawMessage) []string { +func requiredExternalIDs(condition map[string]map[string]jsontext.Value) []string { for condOp, condMap := range condition { if !strings.EqualFold(condOp, "StringEquals") && !strings.EqualFold(condOp, "StringLike") { continue @@ -791,7 +793,7 @@ func validateMFACondition(trustPolicyJSON string, mfaPresent bool) error { // Unmarshal errors leave tp with a zero value (empty Statements): a malformed // trust policy is treated permissively, same as validateExternalID. - _ = json.Unmarshal([]byte(trustPolicyJSON), &tp) + _ = json.Unmarshal([]byte(trustPolicyJSON), &tp, jsonx.V1()) var hasMFACondition bool @@ -821,7 +823,7 @@ func validateMFACondition(trustPolicyJSON string, mfaPresent bool) error { // requiredMFAPresent extracts the required aws:MultiFactorAuthPresent Bool value // from a trust-statement Condition map. The second result reports whether the // condition is present at all. -func requiredMFAPresent(condition map[string]map[string]json.RawMessage) (bool, bool) { +func requiredMFAPresent(condition map[string]map[string]jsontext.Value) (bool, bool) { for condOp, condMap := range condition { if normalizeConditionOp(condOp) != condOperatorBool { continue @@ -845,14 +847,14 @@ func requiredMFAPresent(condition map[string]map[string]json.RawMessage) (bool, // extractStringValues unmarshals a JSON RawMessage that may be either a string // or an array of strings and returns the values as a Go string slice. -func extractStringValues(raw json.RawMessage) []string { +func extractStringValues(raw jsontext.Value) []string { var single string - if err := json.Unmarshal(raw, &single); err == nil { + if err := json.Unmarshal(raw, &single, jsonx.V1()); err == nil { return []string{single} } var many []string - if err := json.Unmarshal(raw, &many); err == nil { + if err := json.Unmarshal(raw, &many, jsonx.V1()); err == nil { return many } diff --git a/services/sts/validation.go b/services/sts/validation.go index 6578a4cd91..fc2679cbfa 100644 --- a/services/sts/validation.go +++ b/services/sts/validation.go @@ -1,11 +1,13 @@ package sts import ( - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // roleSessionNameRe is the AWS-allowed character set for RoleSessionName/FederationToken Name. @@ -198,7 +200,7 @@ func validateInlinePolicy(policy string) error { } var doc map[string]any - if err := json.Unmarshal([]byte(policy), &doc); err != nil { + if err := json.Unmarshal([]byte(policy), &doc, jsonx.V1()); err != nil { return fmt.Errorf("%w: %w", ErrMalformedPolicyDocument, err) } @@ -306,7 +308,7 @@ func validateJWTNotExpired(token string) error { switch v := exp.(type) { case float64: expTime = time.Unix(int64(v), 0).UTC() - case json.Number: + case jsonx.Number: n, err := v.Int64() if err != nil { return err diff --git a/services/sts/web_identity.go b/services/sts/web_identity.go index e2d93964eb..1e037ec9c0 100644 --- a/services/sts/web_identity.go +++ b/services/sts/web_identity.go @@ -2,13 +2,15 @@ package sts import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // webIdentitySubjectPlaceholder is returned when the sub claim cannot be extracted from the token. @@ -479,7 +481,7 @@ func (b *InMemoryBackend) GetWebIdentityToken( claims[jwtClaimTags] = tagMap } - payload, err := json.Marshal(claims) + payload, err := json.Marshal(claims, jsonx.V1()) if err != nil { return nil, fmt.Errorf("build token payload: %w", err) } diff --git a/services/sts/web_identity_irsa_test.go b/services/sts/web_identity_irsa_test.go index e74d29212a..4fd339ec4d 100644 --- a/services/sts/web_identity_irsa_test.go +++ b/services/sts/web_identity_irsa_test.go @@ -6,7 +6,7 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "strings" "testing" "time" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sts" ) @@ -53,10 +54,10 @@ func (l irsaOIDCLookup) OIDCProviderClientIDs(issuer string) ([]string, bool) { func signIRSAToken(t *testing.T, key *rsa.PrivateKey, claims map[string]any) string { t.Helper() - hdr, err := json.Marshal(map[string]string{"alg": "RS256", "kid": "k"}) + hdr, err := json.Marshal(map[string]string{"alg": "RS256", "kid": "k"}, jsonx.V1()) require.NoError(t, err) - body, err := json.Marshal(claims) + body, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) signing := base64.RawURLEncoding.EncodeToString(hdr) + "." + base64.RawURLEncoding.EncodeToString(body) @@ -77,7 +78,7 @@ func irsaTrustPolicy(cond string) string { func forgeIRSAPayload(t *testing.T, token string, claims map[string]any) string { t.Helper() - forged, err := json.Marshal(claims) + forged, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) parts := strings.Split(token, ".") @@ -89,7 +90,7 @@ func forgeIRSAPayload(t *testing.T, token string, claims map[string]any) string func unsignedIRSAToken(t *testing.T, claims map[string]any) string { t.Helper() - body, err := json.Marshal(claims) + body, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) return base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`)) + "." + diff --git a/services/sts/web_identity_test.go b/services/sts/web_identity_test.go index b9457e16c8..697294bbd1 100644 --- a/services/sts/web_identity_test.go +++ b/services/sts/web_identity_test.go @@ -2,7 +2,7 @@ package sts_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "encoding/xml" "net/http" "net/url" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/sts" ) @@ -41,7 +42,7 @@ func buildJWT(t *testing.T, claims map[string]any) string { t.Helper() header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"mock","typ":"JWT"}`)) - payload, err := json.Marshal(claims) + payload, err := json.Marshal(claims, jsonx.V1()) require.NoError(t, err) return header + "." + base64.RawURLEncoding.EncodeToString(payload) + ".fakesig" @@ -52,7 +53,7 @@ func buildParityJWT(t *testing.T, expUnix int64) string { t.Helper() header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none","typ":"JWT"}`)) - payload, err := json.Marshal(map[string]any{"sub": "test", "exp": expUnix}) + payload, err := json.Marshal(map[string]any{"sub": "test", "exp": expUnix}, jsonx.V1()) require.NoError(t, err) return header + "." + base64.RawURLEncoding.EncodeToString(payload) + "." @@ -69,7 +70,7 @@ func parseJWTClaims(t *testing.T, tokenStr string) map[string]any { require.NoError(t, err) var claims map[string]any - require.NoError(t, json.Unmarshal(payloadBytes, &claims)) + require.NoError(t, json.Unmarshal(payloadBytes, &claims, jsonx.V1())) return claims } diff --git a/services/sts/web_identity_verify.go b/services/sts/web_identity_verify.go index 5f3e137886..c06b2e5ffa 100644 --- a/services/sts/web_identity_verify.go +++ b/services/sts/web_identity_verify.go @@ -5,10 +5,12 @@ import ( "crypto/rsa" "crypto/sha256" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const jwtAlgRS256 = "RS256" @@ -37,7 +39,7 @@ func verifyRS256(token string, keys []*rsa.PublicKey) error { Alg string `json:"alg"` } - if err = json.Unmarshal(rawHdr, &hdr); err != nil || hdr.Alg != jwtAlgRS256 { + if err = json.Unmarshal(rawHdr, &hdr, jsonx.V1()); err != nil || hdr.Alg != jwtAlgRS256 { return fmt.Errorf("%w: unsupported JWT signing algorithm", ErrInvalidIdentityToken) } diff --git a/services/support/cases.go b/services/support/cases.go index 6da4dd5c0a..3ea1463308 100644 --- a/services/support/cases.go +++ b/services/support/cases.go @@ -2,7 +2,7 @@ package support import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "slices" "sort" @@ -10,6 +10,8 @@ import ( "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -382,7 +384,7 @@ func pageOffset(token string) (int, error) { } var t pageTokenJSON - if unmarshalErr := json.Unmarshal(decoded, &t); unmarshalErr != nil || t.Offset < 0 { + if unmarshalErr := json.Unmarshal(decoded, &t, jsonx.V1()); unmarshalErr != nil || t.Offset < 0 { return 0, fmt.Errorf("%w: invalid nextToken", ErrValidation) } @@ -390,7 +392,7 @@ func pageOffset(token string) (int, error) { } func encodePageToken(offset int) string { - data, _ := json.Marshal(pageTokenJSON{Offset: offset}) + data, _ := json.Marshal(pageTokenJSON{Offset: offset}, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(data) } diff --git a/services/support/cases_test.go b/services/support/cases_test.go index 7b9d67dcf0..125bb77968 100644 --- a/services/support/cases_test.go +++ b/services/support/cases_test.go @@ -2,7 +2,7 @@ package support_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/support" ) @@ -25,7 +26,7 @@ func opaqueToken(t *testing.T, token string) int { O int `json:"o"` } - require.NoError(t, json.Unmarshal(raw, &obj), "token must be JSON with 'o' key") + require.NoError(t, json.Unmarshal(raw, &obj, jsonx.V1()), "token must be JSON with 'o' key") return obj.O } diff --git a/services/support/handler.go b/services/support/handler.go index af7d7163ac..cdcbe1ffdd 100644 --- a/services/support/handler.go +++ b/services/support/handler.go @@ -2,7 +2,7 @@ package support import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -152,7 +153,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req extractSupportResourceInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.CaseID != "" { return req.CaseID @@ -231,7 +232,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // handleError writes an AWS JSON-1.1 error envelope: the "__type" field is @@ -256,7 +257,7 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin payload, marshalErr := json.Marshal(service.JSONErrorResponse{ Type: errType, Message: errorMessage(errType, err), - }) + }, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } @@ -269,9 +270,6 @@ func (h *Handler) handleError(ctx context.Context, c *echo.Context, action strin // resolveErrorType maps a backend error to the wire "__type" exception name // and HTTP status a real AWS Support (awsjson1.1) response would carry. func resolveErrorType(err error) (string, int) { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, ErrNotFound): return "CaseIdNotFound", http.StatusBadRequest @@ -294,7 +292,7 @@ func resolveErrorType(err error) (string, int) { case errors.Is(err, ErrDescribeAttachmentLimitExceeded): return "DescribeAttachmentLimitExceeded", http.StatusBadRequest case errors.Is(err, ErrValidation), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return "ValidationException", http.StatusBadRequest default: return "InternalServerError", http.StatusInternalServerError diff --git a/services/support/handler_test.go b/services/support/handler_test.go index 1ffe2462be..62bf8de29c 100644 --- a/services/support/handler_test.go +++ b/services/support/handler_test.go @@ -2,7 +2,7 @@ package support_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "maps" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/support" ) @@ -24,7 +25,7 @@ func newTestSupportHandler(t *testing.T) *support.Handler { func doSupportRequest(t *testing.T, h *support.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -47,7 +48,7 @@ func decodeSupportResponse(t *testing.T, rec *httptest.ResponseRecorder) map[str t.Helper() var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) return result } diff --git a/services/support/persistence.go b/services/support/persistence.go index f9b9e00574..634948bd81 100644 --- a/services/support/persistence.go +++ b/services/support/persistence.go @@ -2,10 +2,12 @@ package support import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" @@ -55,7 +57,7 @@ func newAttachmentSetDTORegistry() (*store.Registry, *store.Table[attachmentSetS // is order-sensitive and left a plain map -- see store_setup.go -- so it is // persisted here directly instead of through a store.Table. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Communications map[string][]Communication `json:"communications"` NextDisplayID uint64 `json:"nextDisplayId"` Version int `json:"version"` @@ -96,7 +98,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { NextDisplayID: b.nextDisplayID, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "support: snapshot marshal failed", "error", err) @@ -161,7 +163,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreAttachmentSetsLocked rebuilds the "dirty" attachmentSets table from // tables via the ephemeral DTO registry, converting each DTO back to its live // type. The caller MUST hold b.mu for writing. -func (b *InMemoryBackend) restoreAttachmentSetsLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreAttachmentSetsLocked(tables map[string]jsontext.Value) error { dtoReg, asDTOs := newAttachmentSetDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("support: restore snapshot DTO tables: %w", err) diff --git a/services/support/whitebox_test.go b/services/support/whitebox_test.go index 0964201d54..4654c03405 100644 --- a/services/support/whitebox_test.go +++ b/services/support/whitebox_test.go @@ -2,7 +2,7 @@ package support import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -12,12 +12,14 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doWhiteboxSupportRequest(t *testing.T, h *Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -184,6 +186,6 @@ func TestSupport_CreateCase_CaseCreationLimitExceeded_WireType(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "CaseCreationLimitExceeded", result["__type"]) } diff --git a/services/swf/decision_lifecycle_test.go b/services/swf/decision_lifecycle_test.go index aad14c2a07..288c3407ed 100644 --- a/services/swf/decision_lifecycle_test.go +++ b/services/swf/decision_lifecycle_test.go @@ -1,13 +1,14 @@ package swf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -597,7 +598,7 @@ func TestRespondDecisionTask_ViaHandler(t *testing.T) { if tt.wantExecStatus != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Verify via describe - pick the right domain/workflowID var domainName, wfID string if tt.wantExecStatus == "COMPLETED" { diff --git a/services/swf/decision_orchestration_test.go b/services/swf/decision_orchestration_test.go index 06068e2dcc..43b41e6462 100644 --- a/services/swf/decision_orchestration_test.go +++ b/services/swf/decision_orchestration_test.go @@ -7,13 +7,14 @@ package swf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -80,7 +81,7 @@ func TestStartChildWorkflowExecutionDecision_Success(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) openCounts, ok := body["openCounts"].(map[string]any) require.True(t, ok) assert.InDelta(t, 1, openCounts["openChildWorkflowExecutions"], 0) diff --git a/services/swf/handler.go b/services/swf/handler.go index 78e04381e0..912871bfa7 100644 --- a/services/swf/handler.go +++ b/services/swf/handler.go @@ -2,7 +2,7 @@ package swf import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -136,7 +137,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { return "" } var req extractSWFResourceInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.Name != "" { return req.Name } @@ -211,13 +212,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - code := http.StatusInternalServerError var errType string @@ -253,7 +251,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err code = http.StatusBadRequest errType = "OperationNotPermittedFault" case errors.Is(err, errInvalidRequest), errors.Is(err, ErrUnknownOperation), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): code = http.StatusBadRequest } diff --git a/services/swf/handler_activity_types_test.go b/services/swf/handler_activity_types_test.go index 66802b5b61..29a189feea 100644 --- a/services/swf/handler_activity_types_test.go +++ b/services/swf/handler_activity_types_test.go @@ -1,13 +1,14 @@ package swf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -119,7 +120,7 @@ func TestHandler_DeprecateActivityType(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) diff --git a/services/swf/handler_test.go b/services/swf/handler_test.go index 5553f9c97c..8a9b0555a4 100644 --- a/services/swf/handler_test.go +++ b/services/swf/handler_test.go @@ -2,7 +2,7 @@ package swf_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http" "net/http/httptest" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -29,7 +30,7 @@ func doSWFRequest(t *testing.T, h *swf.Handler, action string, body any) *httpte var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } else { bodyBytes = []byte("{}") @@ -54,7 +55,7 @@ func parseSWFResp(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -252,7 +253,7 @@ func TestSWFHandler_Actions(t *testing.T) { if tt.wantNotEmptyField != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantNotEmptyField]) } }) @@ -578,7 +579,7 @@ func TestSWFHandler_ErrorTypes(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) }) } diff --git a/services/swf/handler_workflow_types_test.go b/services/swf/handler_workflow_types_test.go index 73dbbc9e5b..7d8cd30ad6 100644 --- a/services/swf/handler_workflow_types_test.go +++ b/services/swf/handler_workflow_types_test.go @@ -1,12 +1,14 @@ package swf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListWorkflowTypes_Pagination(t *testing.T) { @@ -251,7 +253,7 @@ func TestHandler_DeprecateWorkflowType(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) diff --git a/services/swf/lambda_tasks.go b/services/swf/lambda_tasks.go index 7656935f08..1455d89539 100644 --- a/services/swf/lambda_tasks.go +++ b/services/swf/lambda_tasks.go @@ -2,11 +2,13 @@ package swf import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "slices" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -189,7 +191,7 @@ func lambdaFailureDetails(functionError string, payload []byte) (string, string) ErrorType string `json:"errorType"` } - if json.Unmarshal(payload, &body) == nil && body.ErrorType != "" { + if json.Unmarshal(payload, &body, jsonx.V1()) == nil && body.ErrorType != "" { return body.ErrorType, string(payload) } diff --git a/services/swf/models.go b/services/swf/models.go index e5b2ec11b3..03f3de479b 100644 --- a/services/swf/models.go +++ b/services/swf/models.go @@ -1,9 +1,12 @@ package swf import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "maps" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -118,24 +121,24 @@ func (e HistoryEvent) MarshalJSON() ([]byte, error) { } maps.Copy(m, e.Attributes) - return json.Marshal(m) + return json.Marshal(m, jsonx.V1()) } // UnmarshalJSON restores a HistoryEvent from its JSON representation, // capturing any unknown keys (the attributes block) into Attributes. func (e *HistoryEvent) UnmarshalJSON(data []byte) error { - var raw map[string]json.RawMessage - if err := json.Unmarshal(data, &raw); err != nil { + var raw map[string]jsontext.Value + if err := json.Unmarshal(data, &raw, jsonx.V1()); err != nil { return err } if v, ok := raw["eventType"]; ok { - _ = json.Unmarshal(v, &e.EventType) + _ = json.Unmarshal(v, &e.EventType, jsonx.V1()) } if v, ok := raw["eventId"]; ok { - _ = json.Unmarshal(v, &e.EventID) + _ = json.Unmarshal(v, &e.EventID, jsonx.V1()) } if v, ok := raw["eventTimestamp"]; ok { - _ = json.Unmarshal(v, &e.Timestamp) + _ = json.Unmarshal(v, &e.Timestamp, jsonx.V1()) } known := map[string]bool{ "eventType": true, "eventId": true, "eventTimestamp": true, @@ -144,7 +147,7 @@ func (e *HistoryEvent) UnmarshalJSON(data []byte) error { for k, v := range raw { if !known[k] { var x any - _ = json.Unmarshal(v, &x) + _ = json.Unmarshal(v, &x, jsonx.V1()) e.Attributes[k] = x } } diff --git a/services/swf/multi_region_test.go b/services/swf/multi_region_test.go index 4a137d5c0d..f79de1272a 100644 --- a/services/swf/multi_region_test.go +++ b/services/swf/multi_region_test.go @@ -2,7 +2,8 @@ package swf_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -180,8 +182,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/swf/persistence.go b/services/swf/persistence.go index 2b082cb974..b67b04e7ae 100644 --- a/services/swf/persistence.go +++ b/services/swf/persistence.go @@ -2,7 +2,7 @@ package swf import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -78,7 +78,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // activeDecisionTasks. History, ActivityQueues, and DecisionQueues stay plain // maps by design (order-sensitive) and are written as-is. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` History map[string][]HistoryEvent `json:"history,omitempty"` Tags map[string]map[string]string `json:"tags,omitempty"` // ActivityQueues/DecisionQueues are the pending FIFO task lists, persisted in order. @@ -284,7 +284,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // activeDecisionTasks; see store_setup.go's registerAllTables doc) from // tables via the ephemeral DTO registry, factored out of Restore to keep // Restore's own cognitive complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreDirtyTablesLocked(tables map[string]jsontext.Value) error { dtos := buildPersistenceDTORegistry() if err := dtos.registry.RestoreAll(tables); err != nil { return fmt.Errorf("swf: restore snapshot DTO tables: %w", err) diff --git a/services/swf/persistence_test.go b/services/swf/persistence_test.go index dc6db5daf0..bf5453f4da 100644 --- a/services/swf/persistence_test.go +++ b/services/swf/persistence_test.go @@ -1,13 +1,14 @@ package swf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/swf" ) @@ -375,7 +376,7 @@ func TestSnapshotRestore_ChildLinkAndOpenTimers(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var body map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) openCounts, ok := body["openCounts"].(map[string]any) require.True(t, ok) assert.InDelta( diff --git a/services/textract/handler.go b/services/textract/handler.go index d957c29a66..733abeac74 100644 --- a/services/textract/handler.go +++ b/services/textract/handler.go @@ -2,7 +2,7 @@ package textract import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -203,7 +204,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req documentLocationInput - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) bucket := req.DocumentLocation.S3Object.Bucket key := req.DocumentLocation.S3Object.Name @@ -280,7 +281,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // Operation name constants for the ops referenced by @@ -330,9 +331,6 @@ var opsWithoutValidationException = map[string]bool{ //nolint:gochecknoglobals / } func (h *Handler) handleError(_ context.Context, c *echo.Context, action string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var code string var status int @@ -345,7 +343,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, action string, code, status = "InvalidS3ObjectException", http.StatusBadRequest case errors.Is(err, ErrValidation), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): if opsWithoutValidationException[action] { code, status = "InvalidParameterException", http.StatusBadRequest } else { @@ -355,7 +353,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, action string, code, status = "InternalServerError", http.StatusInternalServerError } - payload, marshalErr := json.Marshal(service.JSONErrorResponse{Type: code, Message: errMessage(err)}) + payload, marshalErr := json.Marshal(service.JSONErrorResponse{Type: code, Message: errMessage(err)}, jsonx.V1()) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") } diff --git a/services/textract/handler_adapter_versions_test.go b/services/textract/handler_adapter_versions_test.go index ac9c2983a1..7937dc6588 100644 --- a/services/textract/handler_adapter_versions_test.go +++ b/services/textract/handler_adapter_versions_test.go @@ -1,7 +1,7 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -38,7 +39,7 @@ func TestHandler_AdapterVersionLifecycle(t *testing.T) { require.Equal(t, tt.wantStatus, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] // CreateAdapterVersion @@ -59,7 +60,7 @@ func TestHandler_AdapterVersionLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] assert.NotEmpty(t, adapterVersion) assert.Equal(t, adapterID, createVersionResp["AdapterId"]) @@ -73,7 +74,7 @@ func TestHandler_AdapterVersionLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, getVersionRec.Code) var getVersionResp map[string]any - require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp)) + require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp, jsonx.V1())) assert.Equal(t, adapterID, getVersionResp["AdapterId"]) assert.Equal(t, adapterVersion, getVersionResp["AdapterVersion"]) assert.Equal(t, "ACTIVE", getVersionResp["Status"]) @@ -152,7 +153,7 @@ func TestHandler_CreateAdapterVersion_RequiresDatasetAndOutputConfig(t *testing. require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) body := map[string]any{"AdapterId": createAdapterResp["AdapterId"]} if tt.datasetConfig != nil { @@ -229,7 +230,7 @@ func TestHandler_CreateAdapterVersion_DatasetConfig(t *testing.T) { require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] createVersionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -248,7 +249,7 @@ func TestHandler_CreateAdapterVersion_DatasetConfig(t *testing.T) { require.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] getVersionRec := doTextractRequest(t, h, "GetAdapterVersion", map[string]any{ @@ -258,7 +259,7 @@ func TestHandler_CreateAdapterVersion_DatasetConfig(t *testing.T) { require.Equal(t, http.StatusOK, getVersionRec.Code) var getVersionResp map[string]any - require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp)) + require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp, jsonx.V1())) datasetConfig, ok := getVersionResp["DatasetConfig"].(map[string]any) require.True(t, ok, "GetAdapterVersion should include DatasetConfig") @@ -287,7 +288,7 @@ func TestHandler_AdapterVersion_InProgressThenActive(t *testing.T) { require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] createVersionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -305,7 +306,7 @@ func TestHandler_AdapterVersion_InProgressThenActive(t *testing.T) { require.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] // Immediately check: should be CREATION_IN_PROGRESS. @@ -316,7 +317,7 @@ func TestHandler_AdapterVersion_InProgressThenActive(t *testing.T) { require.Equal(t, http.StatusOK, getRec1.Code) var getResp1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1, jsonx.V1())) assert.Equal(t, "CREATION_IN_PROGRESS", getResp1["Status"]) // After delay, should be ACTIVE. @@ -330,7 +331,7 @@ func TestHandler_AdapterVersion_InProgressThenActive(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var getResp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2, jsonx.V1())) assert.Equal(t, "ACTIVE", getResp2["Status"]) }) } @@ -352,7 +353,7 @@ func TestHandler_AdapterVersion_EvaluationMetrics(t *testing.T) { require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] createVersionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -370,7 +371,7 @@ func TestHandler_AdapterVersion_EvaluationMetrics(t *testing.T) { require.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] getVersionRec := doTextractRequest(t, h, "GetAdapterVersion", map[string]any{ @@ -380,7 +381,7 @@ func TestHandler_AdapterVersion_EvaluationMetrics(t *testing.T) { require.Equal(t, http.StatusOK, getVersionRec.Code) var getVersionResp map[string]any - require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp)) + require.NoError(t, json.Unmarshal(getVersionRec.Body.Bytes(), &getVersionResp, jsonx.V1())) evalMetricsList, ok := getVersionResp["EvaluationMetrics"].([]any) require.True(t, ok, "GetAdapterVersion should include EvaluationMetrics as a list") @@ -417,7 +418,7 @@ func TestHandler_DeleteAdapterVersion_GetReturnsErrorAfterDelete(t *testing.T) { }) require.Equal(t, http.StatusOK, createAdapterRec.Code) var adapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &adapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &adapterResp, jsonx.V1())) adapterID := adapterResp["AdapterId"] createVerRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -434,7 +435,7 @@ func TestHandler_DeleteAdapterVersion_GetReturnsErrorAfterDelete(t *testing.T) { }) require.Equal(t, http.StatusOK, createVerRec.Code) var verResp map[string]string - require.NoError(t, json.Unmarshal(createVerRec.Body.Bytes(), &verResp)) + require.NoError(t, json.Unmarshal(createVerRec.Body.Bytes(), &verResp, jsonx.V1())) version := verResp["AdapterVersion"] require.NotEmpty(t, version) @@ -456,7 +457,7 @@ func TestHandler_DeleteAdapterVersion_GetReturnsErrorAfterDelete(t *testing.T) { }) assert.Equal(t, http.StatusBadRequest, getAfterRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(getAfterRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(getAfterRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"], "deleted version must return ResourceNotFoundException") } @@ -472,7 +473,7 @@ func TestHandler_ListAdapterVersions_HappyPath(t *testing.T) { "FeatureTypes": []string{"FORMS"}, }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] for range 3 { @@ -496,7 +497,7 @@ func TestHandler_ListAdapterVersions_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["AdapterVersions"].([]any) assert.True(t, ok) assert.Len(t, versions, 3) @@ -541,7 +542,7 @@ func TestHandler_ListAdapterVersions_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] verRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -565,7 +566,7 @@ func TestHandler_ListAdapterVersions_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) versions, ok := resp["AdapterVersions"].([]any) require.True(t, ok) @@ -604,7 +605,7 @@ func TestHandler_AdapterVersion_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] createVersionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -622,7 +623,7 @@ func TestHandler_AdapterVersion_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] getRec := doTextractRequest(t, h, "GetAdapterVersion", map[string]any{ @@ -632,7 +633,7 @@ func TestHandler_AdapterVersion_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ct, ok := getResp["CreationTime"].(float64) assert.True(t, ok, "GetAdapterVersion CreationTime must be a JSON number") assert.Positive(t, ct) @@ -641,7 +642,7 @@ func TestHandler_AdapterVersion_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) versions, ok := listResp["AdapterVersions"].([]any) require.True(t, ok) require.NotEmpty(t, versions) @@ -670,7 +671,7 @@ func TestHandler_ListAdapterVersions_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] for range 3 { @@ -699,7 +700,7 @@ func TestHandler_ListAdapterVersions_Pagination(t *testing.T) { NextToken string `json:"NextToken"` AdapterVersions []map[string]any `json:"AdapterVersions"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) assert.Len(t, resp1.AdapterVersions, 2) require.NotEmpty(t, resp1.NextToken, "first page must return a NextToken") @@ -713,7 +714,7 @@ func TestHandler_ListAdapterVersions_Pagination(t *testing.T) { NextToken string `json:"NextToken"` AdapterVersions []map[string]any `json:"AdapterVersions"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2.AdapterVersions, 1, "remaining 1 version on the second page") assert.Empty(t, resp2.NextToken, "no more pages") } diff --git a/services/textract/handler_adapters_test.go b/services/textract/handler_adapters_test.go index 3aca1cc31e..d1bdd937ec 100644 --- a/services/textract/handler_adapters_test.go +++ b/services/textract/handler_adapters_test.go @@ -1,7 +1,7 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -55,7 +56,7 @@ func TestHandler_AdapterLifecycle(t *testing.T) { } var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] assert.NotEmpty(t, adapterID) @@ -65,7 +66,7 @@ func TestHandler_AdapterLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, tt.adapterName, getResp["AdapterName"]) assert.Equal(t, tt.description, getResp["Description"]) @@ -114,7 +115,7 @@ func TestHandler_GetAdapter_IncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) getRec := doTextractRequest(t, h, "GetAdapter", map[string]any{ "AdapterId": createResp["AdapterId"], @@ -122,7 +123,7 @@ func TestHandler_GetAdapter_IncludesTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) tags, hasTags := getResp["Tags"].(map[string]any) assert.True(t, hasTags, "GetAdapter must include Tags") @@ -151,7 +152,7 @@ func TestHandler_DeleteAdapter_CascadesVersions(t *testing.T) { require.Equal(t, http.StatusOK, createResp.Code) var cr map[string]string - require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &cr)) + require.NoError(t, json.Unmarshal(createResp.Body.Bytes(), &cr, jsonx.V1())) adapterID := cr["AdapterId"] // Create a version @@ -170,7 +171,7 @@ func TestHandler_DeleteAdapter_CascadesVersions(t *testing.T) { require.Equal(t, http.StatusOK, versionResp.Code) var vr map[string]string - require.NoError(t, json.Unmarshal(versionResp.Body.Bytes(), &vr)) + require.NoError(t, json.Unmarshal(versionResp.Body.Bytes(), &vr, jsonx.V1())) adapterVersion := vr["AdapterVersion"] // Delete adapter should cascade to versions @@ -217,13 +218,13 @@ func TestHandler_CreateAdapter_ClientRequestTokenDedup(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) rec2 := doTextractRequest(t, h, "CreateAdapter", body) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, resp1["AdapterId"], resp2["AdapterId"], "same token should return same AdapterId") } @@ -246,7 +247,7 @@ func TestHandler_UpdateAdapter_PreservesNameAndFeatureTypes(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] require.NotEmpty(t, adapterID) @@ -258,7 +259,7 @@ func TestHandler_UpdateAdapter_PreservesNameAndFeatureTypes(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "immutable-name", updateResp["AdapterName"], "AdapterName must be unchanged after UpdateAdapter") @@ -375,7 +376,7 @@ func TestHandler_UpdateAdapter_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] updateRec := doTextractRequest(t, h, "UpdateAdapter", map[string]any{ @@ -386,7 +387,7 @@ func TestHandler_UpdateAdapter_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, "updated description", updateResp["Description"]) assert.Equal(t, "ENABLED", updateResp["AutoUpdate"]) @@ -406,7 +407,7 @@ func TestHandler_UpdateAdapter_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -468,7 +469,7 @@ func TestHandler_UpdateAdapter_PartialUpdateSemantics(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] body := map[string]any{"AdapterId": adapterID} @@ -482,7 +483,7 @@ func TestHandler_UpdateAdapter_PartialUpdateSemantics(t *testing.T) { } var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.Equal(t, tt.wantName, updateResp["AdapterName"]) assert.Equal(t, tt.wantDesc, updateResp["Description"]) }) @@ -507,7 +508,7 @@ func TestHandler_ListAdapters_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) adapters, ok := resp["Adapters"].([]any) assert.True(t, ok) assert.Len(t, adapters, 3) @@ -533,7 +534,7 @@ func TestHandler_ListAdapters_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) adapters, ok := resp["Adapters"].([]any) require.True(t, ok) @@ -569,14 +570,14 @@ func TestHandler_Adapter_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] getRec := doTextractRequest(t, h, "GetAdapter", map[string]any{"AdapterId": adapterID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ct, ok := getResp["CreationTime"].(float64) assert.True(t, ok, "GetAdapter CreationTime must be a JSON number") assert.Positive(t, ct) @@ -588,7 +589,7 @@ func TestHandler_Adapter_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) uct, ok := updateResp["CreationTime"].(float64) assert.True(t, ok, "UpdateAdapter CreationTime must be a JSON number") assert.Positive(t, uct) @@ -597,7 +598,7 @@ func TestHandler_Adapter_CreationTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) adapters, ok := listResp["Adapters"].([]any) require.True(t, ok) require.NotEmpty(t, adapters) @@ -633,7 +634,7 @@ func TestHandler_ListAdapters_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Adapters []map[string]any `json:"Adapters"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) assert.Len(t, resp1.Adapters, 2) require.NotEmpty(t, resp1.NextToken, "first page must return a NextToken") @@ -644,7 +645,7 @@ func TestHandler_ListAdapters_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Adapters []map[string]any `json:"Adapters"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2.Adapters, 1, "remaining 1 adapter on the second page") assert.Empty(t, resp2.NextToken, "no more pages") } diff --git a/services/textract/handler_analyze_document_config_test.go b/services/textract/handler_analyze_document_config_test.go index 27a015b638..2b8c757eef 100644 --- a/services/textract/handler_analyze_document_config_test.go +++ b/services/textract/handler_analyze_document_config_test.go @@ -1,7 +1,7 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -103,7 +104,7 @@ func TestAnalyzeDocument_AdaptersConfig(t *testing.T) { } var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, tt.wantType, errResp["__type"]) }) } @@ -159,7 +160,7 @@ func TestAnalyzeDocument_HumanLoopConfig(t *testing.T) { var out struct { HumanLoopActivationOutput *struct{} `json:"HumanLoopActivationOutput"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Nil( t, out.HumanLoopActivationOutput, @@ -189,7 +190,7 @@ func newTestHandlerT(t *testing.T) *testHandlerT { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) versionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ "AdapterId": createResp["AdapterId"], @@ -206,7 +207,7 @@ func newTestHandlerT(t *testing.T) *testHandlerT { require.Equal(t, http.StatusOK, versionRec.Code) var versionResp map[string]string - require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &versionResp)) + require.NoError(t, json.Unmarshal(versionRec.Body.Bytes(), &versionResp, jsonx.V1())) return &testHandlerT{ h: h, diff --git a/services/textract/handler_document_analysis_test.go b/services/textract/handler_document_analysis_test.go index b4be85e43d..b185d2c0b5 100644 --- a/services/textract/handler_document_analysis_test.go +++ b/services/textract/handler_document_analysis_test.go @@ -2,7 +2,7 @@ package textract_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -27,7 +28,7 @@ func startDocumentAnalysisJob(t *testing.T, h *textract.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobID := resp["JobId"] require.NotEmpty(t, jobID) @@ -46,7 +47,7 @@ func startTextDetectionJob(t *testing.T, h *textract.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) jobID := resp["JobId"] require.NotEmpty(t, jobID) @@ -58,7 +59,7 @@ func blocksOfType(t *testing.T, body []byte, blockType string) []map[string]any t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) raw, ok := resp["Blocks"].([]any) require.True(t, ok, "response should have Blocks array") @@ -121,7 +122,7 @@ func TestHandler_AnalyzeDocument(t *testing.T) { if tt.wantBlocks { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) blocks, ok := resp["Blocks"].([]any) assert.True(t, ok) assert.NotEmpty(t, blocks) @@ -164,7 +165,7 @@ func TestHandler_StartAndGetDocumentAnalysis(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] assert.NotEmpty(t, jobID) @@ -174,7 +175,7 @@ func TestHandler_StartAndGetDocumentAnalysis(t *testing.T) { assert.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) blocks, ok := getResp["Blocks"].([]any) assert.True(t, ok) @@ -235,7 +236,7 @@ func TestHandler_GetDocumentAnalysis_RejectsTextDetectionJobID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidJobIdException", resp["__type"], "cross-type access must return InvalidJobIdException, not a different error") } @@ -475,7 +476,7 @@ func TestHandler_AnalyzeDocument_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "1.0", resp["AnalyzeDocumentModelVersion"]) } @@ -494,7 +495,7 @@ func TestHandler_AnalyzeDocument_DocumentMetadataPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) meta, ok := m["DocumentMetadata"].(map[string]any) require.True(t, ok, "AnalyzeDocument response must have DocumentMetadata") pages, ok2 := meta["Pages"].(float64) @@ -518,7 +519,7 @@ func TestHandler_AnalyzeDocument_BytesInput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, ok := m["Blocks"].([]any) assert.True(t, ok && len(blocks) > 0, "AnalyzeDocument with Bytes must return Blocks") } @@ -539,7 +540,7 @@ func TestHandler_GetDocumentAnalysis_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetDocumentAnalysis", map[string]any{ @@ -549,7 +550,7 @@ func TestHandler_GetDocumentAnalysis_PaginationNextToken(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) blocks, _ := getResp["Blocks"].([]any) assert.Len(t, blocks, 2) @@ -574,14 +575,14 @@ func TestHandler_GetDocumentAnalysis_BlocksHaveGeometry(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetDocumentAnalysis", map[string]any{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) blocks, _ := getResp["Blocks"].([]any) require.NotEmpty(t, blocks) @@ -619,14 +620,14 @@ func TestHandler_GetDocumentAnalysis_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetDocumentAnalysis", map[string]string{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m, jsonx.V1())) ver, ok := m["AnalyzeDocumentModelVersion"].(string) assert.True(t, ok, "GetDocumentAnalysis must have AnalyzeDocumentModelVersion") assert.Equal(t, "1.0", ver) @@ -647,14 +648,14 @@ func TestHandler_GetDocumentAnalysis_JobStatusSucceeded(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetDocumentAnalysis", map[string]string{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m, jsonx.V1())) assert.Equal(t, "SUCCEEDED", m["JobStatus"], "synchronous backend job must SUCCEED immediately") } @@ -679,7 +680,7 @@ func TestHandler_StartDocumentAnalysis_AsyncInProgressThenSucceeded(t *testing.T require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] // Immediately after start, job should be IN_PROGRESS. @@ -687,7 +688,7 @@ func TestHandler_StartDocumentAnalysis_AsyncInProgressThenSucceeded(t *testing.T require.Equal(t, http.StatusOK, getRec1.Code) var getResp1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", getResp1["JobStatus"]) // After delay, job should be SUCCEEDED. @@ -698,7 +699,7 @@ func TestHandler_StartDocumentAnalysis_AsyncInProgressThenSucceeded(t *testing.T require.Equal(t, http.StatusOK, getRec2.Code) var getResp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp2["JobStatus"]) }) } @@ -725,7 +726,7 @@ func TestHandler_StartDocumentAnalysis_AsyncInitialStatusInProgress(t *testing.T require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] require.NotEmpty(t, jobID) @@ -734,7 +735,7 @@ func TestHandler_StartDocumentAnalysis_AsyncInitialStatusInProgress(t *testing.T require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "IN_PROGRESS", getResp["JobStatus"], "job must be IN_PROGRESS immediately after Start when async delay is non-zero") } @@ -758,14 +759,14 @@ func TestHandler_StartDocumentAnalysis_ClientRequestTokenIdempotency(t *testing. require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) jobID1 := resp1["JobId"] rec2 := doTextractRequest(t, h, "StartDocumentAnalysis", startBody) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) jobID2 := resp2["JobId"] assert.Equal(t, jobID1, jobID2, "same ClientRequestToken should return the same JobId") @@ -786,7 +787,7 @@ func TestHandler_StartDocumentAnalysis_JobIdNonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) jobID, ok := m["JobId"].(string) assert.True(t, ok, "StartDocumentAnalysis response must have string JobId") assert.NotEmpty(t, jobID, "JobId must not be empty") @@ -879,7 +880,7 @@ func TestHandler_AnalyzeDocumentAndStart_ValidationErrorsUseInvalidParameterExce require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidParameterException", errResp["__type"]) }) } diff --git a/services/textract/handler_document_detection_test.go b/services/textract/handler_document_detection_test.go index c754be8aec..eff4ae4bd1 100644 --- a/services/textract/handler_document_detection_test.go +++ b/services/textract/handler_document_detection_test.go @@ -1,13 +1,15 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DetectDocumentText(t *testing.T) { @@ -76,7 +78,7 @@ func TestHandler_StartAndGetDocumentTextDetection(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] assert.NotEmpty(t, jobID) @@ -85,7 +87,7 @@ func TestHandler_StartAndGetDocumentTextDetection(t *testing.T) { assert.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) }) } @@ -143,7 +145,7 @@ func TestHandler_GetDocumentTextDetection_RejectsAnalysisJobID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidJobIdException", resp["__type"], "cross-type access must return InvalidJobIdException") } @@ -192,7 +194,7 @@ func TestHandler_DetectDocumentText_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "1.0", resp["DetectDocumentTextModelVersion"]) } @@ -212,7 +214,7 @@ func TestHandler_GetDocumentTextDetection_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] // Get first page with MaxResults=3. @@ -223,7 +225,7 @@ func TestHandler_GetDocumentTextDetection_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, getRec1.Code) var getResp1 map[string]any - require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1)) + require.NoError(t, json.Unmarshal(getRec1.Body.Bytes(), &getResp1, jsonx.V1())) blocks1, _ := getResp1["Blocks"].([]any) assert.Len(t, blocks1, 3, "first page should have 3 blocks") @@ -240,7 +242,7 @@ func TestHandler_GetDocumentTextDetection_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, getRec2.Code) var getResp2 map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &getResp2, jsonx.V1())) blocks2, _ := getResp2["Blocks"].([]any) assert.NotEmpty(t, blocks2, "second page should have remaining blocks") @@ -260,7 +262,7 @@ func TestHandler_DetectDocumentText_BlockIDsAreUUIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, ok := m["Blocks"].([]any) require.True(t, ok) require.NotEmpty(t, blocks) @@ -291,7 +293,7 @@ func TestHandler_DetectDocumentText_ConfidenceInRange(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, _ := m["Blocks"].([]any) require.NotEmpty(t, blocks) @@ -325,7 +327,7 @@ func TestHandler_DetectDocumentText_WordHasTextType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, _ := m["Blocks"].([]any) require.NotEmpty(t, blocks) @@ -361,7 +363,7 @@ func TestHandler_DetectDocumentText_PageIsSet(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, _ := m["Blocks"].([]any) require.NotEmpty(t, blocks) @@ -392,7 +394,7 @@ func TestHandler_DetectDocumentText_GeometryBoundingBoxValid(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, _ := m["Blocks"].([]any) require.NotEmpty(t, blocks) @@ -439,7 +441,7 @@ func TestHandler_DetectDocumentText_DocumentMetadataPages(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) meta, ok := m["DocumentMetadata"].(map[string]any) require.True(t, ok, "response must have DocumentMetadata") pages, ok2 := meta["Pages"].(float64) @@ -462,7 +464,7 @@ func TestHandler_DetectDocumentText_BytesInput(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) blocks, ok := m["Blocks"].([]any) assert.True(t, ok && len(blocks) > 0, "DetectDocumentText with Bytes must return Blocks") } @@ -481,14 +483,14 @@ func TestHandler_GetDocumentTextDetection_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetDocumentTextDetection", map[string]string{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m, jsonx.V1())) ver, ok := m["DetectDocumentTextModelVersion"].(string) assert.True(t, ok, "GetDocumentTextDetection must have DetectDocumentTextModelVersion") assert.Equal(t, "1.0", ver) @@ -511,7 +513,7 @@ func TestHandler_StartDocumentTextDetection_ClientRequestTokenIdempotency(t *tes require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) jobID1 := resp1["JobId"] require.NotEmpty(t, jobID1) @@ -519,7 +521,7 @@ func TestHandler_StartDocumentTextDetection_ClientRequestTokenIdempotency(t *tes require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, jobID1, resp2["JobId"], "same ClientRequestToken must return same JobId") } diff --git a/services/textract/handler_expense_analysis_test.go b/services/textract/handler_expense_analysis_test.go index fa360a6412..38cd0e58b4 100644 --- a/services/textract/handler_expense_analysis_test.go +++ b/services/textract/handler_expense_analysis_test.go @@ -1,13 +1,14 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -57,7 +58,7 @@ func TestHandler_AnalyzeExpense(t *testing.T) { if tt.wantDocs { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["ExpenseDocuments"].([]any) assert.True(t, ok) assert.NotEmpty(t, docs) @@ -110,7 +111,7 @@ func TestHandler_StartExpenseAnalysis_ClientRequestTokenIdempotency(t *testing.T require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) jobID1 := resp1["JobId"] require.NotEmpty(t, jobID1) @@ -118,7 +119,7 @@ func TestHandler_StartExpenseAnalysis_ClientRequestTokenIdempotency(t *testing.T require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, jobID1, resp2["JobId"], "same ClientRequestToken must return same JobId") assert.Equal(t, 1, textract.ExpenseJobCount(h.Backend.(*textract.InMemoryBackend))) @@ -140,7 +141,7 @@ func TestHandler_StartExpenseAnalysis_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] require.NotEmpty(t, jobID) @@ -148,7 +149,7 @@ func TestHandler_StartExpenseAnalysis_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) assert.Equal(t, "1.0", getResp["AnalyzeExpenseModelVersion"]) docs, ok := getResp["ExpenseDocuments"].([]any) @@ -185,7 +186,7 @@ func TestHandler_AnalyzeExpense_SummaryFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["ExpenseDocuments"].([]any) require.True(t, ok) @@ -232,7 +233,7 @@ func TestHandler_AnalyzeExpense_LineItemGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, _ := resp["ExpenseDocuments"].([]any) require.NotEmpty(t, docs) @@ -264,7 +265,7 @@ func TestHandler_AnalyzeExpense_ExpenseIndex(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) docs, ok := m["ExpenseDocuments"].([]any) require.True(t, ok) require.NotEmpty(t, docs) @@ -290,7 +291,7 @@ func TestHandler_GetExpenseAnalysis_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] require.NotEmpty(t, jobID) @@ -298,7 +299,7 @@ func TestHandler_GetExpenseAnalysis_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m, jsonx.V1())) ver, ok := m["AnalyzeExpenseModelVersion"].(string) assert.True(t, ok, "GetExpenseAnalysis must have AnalyzeExpenseModelVersion") assert.Equal(t, "1.0", ver) @@ -344,14 +345,14 @@ func TestHandler_GetExpenseAnalysis_RejectsDocumentJobID(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetExpenseAnalysis", map[string]any{"JobId": jobID}) assert.Equal(t, http.StatusBadRequest, getRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidJobIdException", errResp["__type"], "expense analysis must not accept a document job ID") }) @@ -376,7 +377,7 @@ func TestHandler_AnalyzeExpense_CurrencyAndTypeWireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["ExpenseDocuments"].([]any) require.True(t, ok) @@ -453,7 +454,7 @@ func TestHandler_GetExpenseAnalysis_Pagination(t *testing.T) { NextToken string `json:"NextToken"` ExpenseDocuments []map[string]any `json:"ExpenseDocuments"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) assert.Len(t, resp1.ExpenseDocuments, 2) require.NotEmpty(t, resp1.NextToken, "first page must return a NextToken") @@ -467,7 +468,7 @@ func TestHandler_GetExpenseAnalysis_Pagination(t *testing.T) { NextToken string `json:"NextToken"` ExpenseDocuments []map[string]any `json:"ExpenseDocuments"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2.ExpenseDocuments, 3, "remaining 3 documents on the second page") assert.Empty(t, resp2.NextToken, "no more pages") } diff --git a/services/textract/handler_id_analysis_test.go b/services/textract/handler_id_analysis_test.go index f939855920..717705bbd5 100644 --- a/services/textract/handler_id_analysis_test.go +++ b/services/textract/handler_id_analysis_test.go @@ -1,12 +1,14 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_AnalyzeID(t *testing.T) { @@ -57,7 +59,7 @@ func TestHandler_AnalyzeID(t *testing.T) { if !tt.wantErr { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["IdentityDocuments"].([]any) assert.True(t, ok) assert.Len(t, docs, 2) @@ -83,7 +85,7 @@ func TestHandler_AnalyzeID_IdentityDocumentFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["IdentityDocuments"].([]any) require.True(t, ok) @@ -133,7 +135,7 @@ func TestHandler_AnalyzeID_NormalizedValueDate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, _ := resp["IdentityDocuments"].([]any) doc, _ := docs[0].(map[string]any) @@ -180,7 +182,7 @@ func TestHandler_AnalyzeID_DocumentIndex(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) docs, ok := m["IdentityDocuments"].([]any) require.True(t, ok) require.NotEmpty(t, docs) @@ -206,7 +208,7 @@ func TestHandler_AnalyzeID_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) ver, ok := m["AnalyzeIDModelVersion"].(string) assert.True(t, ok, "AnalyzeID response must have AnalyzeIDModelVersion") assert.Equal(t, "1.0", ver) @@ -227,7 +229,7 @@ func TestHandler_AnalyzeID_MultiPageDocumentIndex(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) docs, ok := m["IdentityDocuments"].([]any) require.True(t, ok) require.Len(t, docs, 2, "two DocumentPages should yield two IdentityDocuments") @@ -277,7 +279,7 @@ func TestHandler_AnalyzeID_DocumentMetadataPagesMatchesInputCount(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) meta, _ := resp["DocumentMetadata"].(map[string]any) gotPages, _ := meta["Pages"].(float64) assert.InDelta(t, float64(tc.pageCount), gotPages, 0, @@ -310,7 +312,7 @@ func TestHandler_AnalyzeID_NoFabricatedGeometry(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) docs, ok := resp["IdentityDocuments"].([]any) require.True(t, ok) diff --git a/services/textract/handler_lending_analysis_test.go b/services/textract/handler_lending_analysis_test.go index 32260ae93e..703b712df7 100644 --- a/services/textract/handler_lending_analysis_test.go +++ b/services/textract/handler_lending_analysis_test.go @@ -1,13 +1,14 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -55,7 +56,7 @@ func TestHandler_StartLendingAnalysis_ClientRequestTokenIdempotency(t *testing.T require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]string - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) jobID1 := resp1["JobId"] require.NotEmpty(t, jobID1) @@ -63,7 +64,7 @@ func TestHandler_StartLendingAnalysis_ClientRequestTokenIdempotency(t *testing.T require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, jobID1, resp2["JobId"], "same ClientRequestToken must return same JobId") assert.Equal(t, 1, textract.LendingJobCount(h.Backend.(*textract.InMemoryBackend))) @@ -83,7 +84,7 @@ func TestHandler_GetLendingAnalysisSummary_IncludesWarnings(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] textract.AddLendingJobInternal(b, &textract.LendingJob{ @@ -103,7 +104,7 @@ func TestHandler_GetLendingAnalysisSummary_IncludesWarnings(t *testing.T) { Pages []int `json:"Pages"` } `json:"Warnings"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Warnings, 1) assert.Equal(t, "InvalidPageException", resp.Warnings[0].ErrorCode) } @@ -123,14 +124,14 @@ func TestHandler_GetLendingAnalysis_ExpandedLendingResult(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetLendingAnalysis", map[string]any{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) results, ok := getResp["Results"].([]any) @@ -163,14 +164,14 @@ func TestHandler_GetLendingAnalysisSummary_PopulatedSummary(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetLendingAnalysisSummary", map[string]any{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) summary, ok := getResp["Summary"].(map[string]any) @@ -195,14 +196,14 @@ func TestHandler_GetLendingAnalysis_ModelVersion(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetLendingAnalysis", map[string]string{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var m map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &m, jsonx.V1())) ver, ok := m["AnalyzeLendingModelVersion"].(string) assert.True(t, ok, "GetLendingAnalysis must have AnalyzeLendingModelVersion") assert.Equal(t, "1.0", ver) @@ -224,7 +225,7 @@ func TestHandler_StartLendingAnalysis_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] require.NotEmpty(t, jobID) @@ -232,7 +233,7 @@ func TestHandler_StartLendingAnalysis_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", getResp["JobStatus"]) } @@ -264,14 +265,14 @@ func TestHandler_GetLendingAnalysisSummary_HappyPath(t *testing.T) { }) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] rec := doTextractRequest(t, h, "GetLendingAnalysisSummary", map[string]string{"JobId": jobID}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "SUCCEEDED", resp["JobStatus"]) assert.Equal(t, "1.0", resp["AnalyzeLendingModelVersion"]) } @@ -285,7 +286,7 @@ func TestHandler_GetLendingAnalysisSummary_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidJobIdException", errResp["__type"]) } @@ -308,14 +309,14 @@ func TestHandler_GetLendingAnalysis_LendingFieldWireShape(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetLendingAnalysis", map[string]any{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) results, ok := getResp["Results"].([]any) require.True(t, ok) @@ -372,14 +373,14 @@ func TestHandler_GetLendingAnalysis_PageClassificationUsesPrediction(t *testing. require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]string - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) jobID := startResp["JobId"] getRec := doTextractRequest(t, h, "GetLendingAnalysis", map[string]any{"JobId": jobID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) results, ok := getResp["Results"].([]any) require.True(t, ok) @@ -441,7 +442,7 @@ func TestHandler_GetLendingAnalysis_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Results []map[string]any `json:"Results"` } - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) assert.Len(t, resp1.Results, 2) require.NotEmpty(t, resp1.NextToken, "first page must return a NextToken") @@ -455,7 +456,7 @@ func TestHandler_GetLendingAnalysis_Pagination(t *testing.T) { NextToken string `json:"NextToken"` Results []map[string]any `json:"Results"` } - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Len(t, resp2.Results, 3, "remaining 3 results on the second page") assert.Empty(t, resp2.NextToken, "no more pages") } diff --git a/services/textract/handler_tags_test.go b/services/textract/handler_tags_test.go index ef98543190..c345450b8c 100644 --- a/services/textract/handler_tags_test.go +++ b/services/textract/handler_tags_test.go @@ -1,13 +1,14 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -22,7 +23,7 @@ func TestHandler_TagResource_HappyPath(t *testing.T) { "FeatureTypes": []string{"FORMS"}, }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] tagRec := doTextractRequest(t, h, "TagResource", map[string]any{ @@ -37,7 +38,7 @@ func TestHandler_TagResource_HappyPath(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) assert.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -56,7 +57,7 @@ func TestHandler_UntagResource_HappyPath(t *testing.T) { "Tags": map[string]string{"env": "dev", "team": "ml"}, }) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] untagRec := doTextractRequest(t, h, "UntagResource", map[string]any{ @@ -69,7 +70,7 @@ func TestHandler_UntagResource_HappyPath(t *testing.T) { "ResourceARN": adapterID, }) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["Tags"].(map[string]any) _, hasEnv := tags["env"] assert.False(t, hasEnv) @@ -93,7 +94,7 @@ func TestHandler_TagResource_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -129,7 +130,7 @@ func TestHandler_UntagResourceAndListTagsForResource_NotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) }) } @@ -150,7 +151,7 @@ func TestHandler_TagResource_ByARNRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]string - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) adapterID := createResp["AdapterId"] // Build an ARN using the backend helper. @@ -172,7 +173,7 @@ func TestHandler_TagResource_ByARNRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "prod", tags["env"]) @@ -194,7 +195,7 @@ func TestHandler_TagResource_AdapterVersionARN(t *testing.T) { require.Equal(t, http.StatusOK, createAdapterRec.Code) var createAdapterResp map[string]string - require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp)) + require.NoError(t, json.Unmarshal(createAdapterRec.Body.Bytes(), &createAdapterResp, jsonx.V1())) adapterID := createAdapterResp["AdapterId"] createVersionRec := doTextractRequest(t, h, "CreateAdapterVersion", map[string]any{ @@ -212,7 +213,7 @@ func TestHandler_TagResource_AdapterVersionARN(t *testing.T) { require.Equal(t, http.StatusOK, createVersionRec.Code) var createVersionResp map[string]string - require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp)) + require.NoError(t, json.Unmarshal(createVersionRec.Body.Bytes(), &createVersionResp, jsonx.V1())) adapterVersion := createVersionResp["AdapterVersion"] versionARN := b.BuildAdapterVersionARN(adapterID, adapterVersion) @@ -230,7 +231,7 @@ func TestHandler_TagResource_AdapterVersionARN(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].(map[string]any) require.True(t, ok) assert.Equal(t, "v1", tags["version-tag"]) diff --git a/services/textract/handler_test.go b/services/textract/handler_test.go index 196f205ebe..00f8f59d45 100644 --- a/services/textract/handler_test.go +++ b/services/textract/handler_test.go @@ -2,7 +2,7 @@ package textract_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -34,7 +35,7 @@ func doTextractRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -122,7 +123,7 @@ func TestHandler_UnknownAction_ErrorEnvelope(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ValidationException", errResp["__type"]) } @@ -214,7 +215,7 @@ func TestHandler_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) @@ -380,7 +381,7 @@ func TestHandler_HandleError_AdapterNotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -397,7 +398,7 @@ func TestHandler_HandleError_JobNotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidJobIdException", errResp["__type"]) } @@ -520,7 +521,7 @@ func TestHandler_ErrorEnvelope_TypeAndMessage(t *testing.T) { assert.GreaterOrEqual(t, rec.Code, http.StatusBadRequest) var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) assert.Equal(t, tt.wantType, m["__type"], "__type must match expected error code") assert.NotEmpty(t, m["message"], "message must be present in error envelope") }) diff --git a/services/textract/job_output_test.go b/services/textract/job_output_test.go index d2c45a3029..8cafc9579d 100644 --- a/services/textract/job_output_test.go +++ b/services/textract/job_output_test.go @@ -2,7 +2,7 @@ package textract_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "sync" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -109,7 +110,7 @@ func TestAsyncJobWritesOutputConfig(t *testing.T) { assert.Contains(t, w.objects, "out/pre/"+id+"/.s3_access_check") var page map[string]any - require.NoError(t, json.Unmarshal(w.objects["out/pre/"+id+"/1"], &page)) + require.NoError(t, json.Unmarshal(w.objects["out/pre/"+id+"/1"], &page, jsonx.V1())) assert.Contains(t, page, tt.wantKey) assert.Contains(t, page, "DocumentMetadata") diff --git a/services/textract/persistence.go b/services/textract/persistence.go index e00de2a504..390bb84291 100644 --- a/services/textract/persistence.go +++ b/services/textract/persistence.go @@ -2,7 +2,7 @@ package textract import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "time" @@ -117,7 +117,7 @@ func newDTORegistry() *dtoRegistry { // values are strings, not *T, so they do not fit store.Table's // keyed-by-identity-value shape and are unaffected by this conversion. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ClientTokenToJobID map[string]map[string]string `json:"clientTokenToJobId,omitempty"` AdapterClientTokenToID map[string]map[string]string `json:"adapterClientTokenToId,omitempty"` ExpenseClientTokenToJobID map[string]map[string]string `json:"expenseClientTokenToJobId,omitempty"` diff --git a/services/textract/persistence_test.go b/services/textract/persistence_test.go index b849634a86..929095bd94 100644 --- a/services/textract/persistence_test.go +++ b/services/textract/persistence_test.go @@ -2,11 +2,14 @@ package textract //nolint:testpackage // needs access to the unexported region c import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Test_PersistenceRoundTrip proves that every store.Table-backed resource @@ -201,11 +204,11 @@ func Test_PersistenceVersionMismatch(t *testing.T) { rewrite: func(t *testing.T, snap []byte) []byte { t.Helper() - var m map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &m)) + var m map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &m, jsonx.V1())) delete(m, "version") - out, err := json.Marshal(m) + out, err := json.Marshal(m, jsonx.V1()) require.NoError(t, err) return out @@ -264,16 +267,16 @@ func Test_PersistenceVersionMismatch(t *testing.T) { func rewriteSnapshotVersion(t *testing.T, snap []byte, version int) []byte { t.Helper() - var m map[string]json.RawMessage + var m map[string]jsontext.Value - require.NoError(t, json.Unmarshal(snap, &m)) + require.NoError(t, json.Unmarshal(snap, &m, jsonx.V1())) - versionJSON, err := json.Marshal(version) + versionJSON, err := json.Marshal(version, jsonx.V1()) require.NoError(t, err) m["version"] = versionJSON - out, err := json.Marshal(m) + out, err := json.Marshal(m, jsonx.V1()) require.NoError(t, err) return out diff --git a/services/textract/realism_test.go b/services/textract/realism_test.go index 2afde33da9..745a624b08 100644 --- a/services/textract/realism_test.go +++ b/services/textract/realism_test.go @@ -1,13 +1,15 @@ package textract_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestRealism_RequestValidation(t *testing.T) { @@ -92,7 +94,7 @@ func TestRealism_RequestValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) assert.Contains(t, resp["message"], tt.wantMsg) assert.NotContains(t, resp["message"], "Exception: ") diff --git a/services/textract/s3_object_test.go b/services/textract/s3_object_test.go index a96e632326..1082f40a0a 100644 --- a/services/textract/s3_object_test.go +++ b/services/textract/s3_object_test.go @@ -2,7 +2,7 @@ package textract_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "net/http" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/textract" ) @@ -100,7 +101,7 @@ func TestSyncDocumentOps_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doTextractRequest(t, h, tt.action, tt.body("good-bucket", "file.pdf")) @@ -125,7 +126,7 @@ func TestAnalyzeID_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doTextractRequest(t, h, "AnalyzeID", map[string]any{ @@ -174,7 +175,7 @@ func TestStartOps_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doTextractRequest(t, h, tt.action, body("good-bucket")) @@ -230,7 +231,7 @@ func createAdapterForS3Test(t *testing.T, h *textract.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["AdapterId"] } @@ -263,7 +264,7 @@ func TestCreateAdapterVersion_S3ObjectValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidS3ObjectException", resp["__type"]) rec = doTextractRequest(t, h, "CreateAdapterVersion", diff --git a/services/textract/s3_output.go b/services/textract/s3_output.go index c7beeb3244..32196112b7 100644 --- a/services/textract/s3_output.go +++ b/services/textract/s3_output.go @@ -3,13 +3,14 @@ package textract import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "path" "strings" "github.com/aws/aws-sdk-go-v2/aws" awss3 "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" s3backend "github.com/blackbirdworks/gopherstack/services/s3" ) @@ -87,7 +88,7 @@ func (b *InMemoryBackend) writeJobOutputLocked(cfg *OutputConfig, jobID string, return true } - body, err := json.Marshal(page) + body, err := json.Marshal(page, jsonx.V1()) if err != nil { return false } diff --git a/services/timestreaminfluxdb/docker_engine_ops_test.go b/services/timestreaminfluxdb/docker_engine_ops_test.go index 24bd69df7f..aacb8c7e38 100644 --- a/services/timestreaminfluxdb/docker_engine_ops_test.go +++ b/services/timestreaminfluxdb/docker_engine_ops_test.go @@ -1,7 +1,7 @@ package timestreaminfluxdb_test import ( - "encoding/json" + "encoding/json/jsontext" "os" "path/filepath" "testing" @@ -192,7 +192,7 @@ func TestParameterEnv(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - assert.Equal(t, tt.want, timestreaminfluxdb.ParameterEnv(json.RawMessage(tt.in))) + assert.Equal(t, tt.want, timestreaminfluxdb.ParameterEnv(jsontext.Value(tt.in))) }) } } diff --git a/services/timestreaminfluxdb/engine_ops_test.go b/services/timestreaminfluxdb/engine_ops_test.go index 8a83202322..448cf4187f 100644 --- a/services/timestreaminfluxdb/engine_ops_test.go +++ b/services/timestreaminfluxdb/engine_ops_test.go @@ -2,7 +2,7 @@ package timestreaminfluxdb_test import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "maps" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreaminfluxdb" ) @@ -273,7 +274,7 @@ func TestAuthParametersSecret(t *testing.T) { assert.Equal(t, arn, aws.ToString(got.InfluxAuthParametersSecretArn)) var secret map[string]string - require.NoError(t, json.Unmarshal([]byte(e.secrets.all()[arn]), &secret)) + require.NoError(t, json.Unmarshal([]byte(e.secrets.all()[arn]), &secret, jsonx.V1())) specs, _, _, _ := e.eng.snapshot() assert.Equal(t, map[string]string{ diff --git a/services/timestreaminfluxdb/engine_params.go b/services/timestreaminfluxdb/engine_params.go index 0ad52b6d50..d800823b49 100644 --- a/services/timestreaminfluxdb/engine_params.go +++ b/services/timestreaminfluxdb/engine_params.go @@ -1,13 +1,16 @@ package timestreaminfluxdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "slices" "strconv" "strings" "time" "unicode" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const hoursPerDay = 24 @@ -15,14 +18,14 @@ const hoursPerDay = 24 // ParameterEnv translates an InfluxDBv2 parameter group document into influxd environment settings. // Every InfluxDBv2 parameter has an influxd flag of the same name in kebab case (INFLUXD_); // groups for other engines are stored but not applied. -func ParameterEnv(raw json.RawMessage) []string { - var union map[string]json.RawMessage - if json.Unmarshal(raw, &union) != nil { +func ParameterEnv(raw jsontext.Value) []string { + var union map[string]jsontext.Value + if json.Unmarshal(raw, &union, jsonx.V1()) != nil { return nil } - var fields map[string]json.RawMessage - if json.Unmarshal(union["InfluxDBv2"], &fields) != nil { + var fields map[string]jsontext.Value + if json.Unmarshal(union["InfluxDBv2"], &fields, jsonx.V1()) != nil { return nil } @@ -54,13 +57,13 @@ func envName(param string) string { return sb.String() } -func paramValue(name string, raw json.RawMessage) (string, bool) { +func paramValue(name string, raw jsontext.Value) (string, bool) { if isDurationParam(name) { return durationValue(raw) } var v any - if json.Unmarshal(raw, &v) != nil { + if json.Unmarshal(raw, &v, jsonx.V1()) != nil { return "", false } @@ -76,12 +79,12 @@ func paramValue(name string, raw json.RawMessage) (string, bool) { return "", false } -func durationValue(raw json.RawMessage) (string, bool) { +func durationValue(raw jsontext.Value) (string, bool) { var d struct { DurationType string `json:"durationType"` Value int64 `json:"value"` } - if json.Unmarshal(raw, &d) != nil { + if json.Unmarshal(raw, &d, jsonx.V1()) != nil { return "", false } diff --git a/services/timestreaminfluxdb/handler.go b/services/timestreaminfluxdb/handler.go index 708974587a..277ee19cb6 100644 --- a/services/timestreaminfluxdb/handler.go +++ b/services/timestreaminfluxdb/handler.go @@ -2,7 +2,7 @@ package timestreaminfluxdb import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "slices" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -39,7 +40,7 @@ func bind[I, O any](fn func(*Handler, context.Context, *I) (*O, error)) opFunc { var in I if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, invalid("malformed request body: %v", err) } } diff --git a/services/timestreaminfluxdb/handler_other.go b/services/timestreaminfluxdb/handler_other.go index 334cbd731a..f5d6abce67 100644 --- a/services/timestreaminfluxdb/handler_other.go +++ b/services/timestreaminfluxdb/handler_other.go @@ -2,7 +2,7 @@ package timestreaminfluxdb import ( "context" - "encoding/json" + "encoding/json/jsontext" ) func registerGroupOps(ops map[string]opFunc) { @@ -15,7 +15,7 @@ type createGroupReq struct { Tags map[string]string `json:"tags"` Name string `json:"name"` Description string `json:"description"` - Parameters json.RawMessage `json:"parameters"` + Parameters jsontext.Value `json:"parameters"` } func (h *Handler) createGroup(ctx context.Context, in *createGroupReq) (*wParameterGroup, error) { diff --git a/services/timestreaminfluxdb/models.go b/services/timestreaminfluxdb/models.go index 8fcb08506b..7c70c494fa 100644 --- a/services/timestreaminfluxdb/models.go +++ b/services/timestreaminfluxdb/models.go @@ -1,7 +1,7 @@ package timestreaminfluxdb import ( - "encoding/json" + "encoding/json/jsontext" "time" ) @@ -149,7 +149,7 @@ type DBParameterGroup struct { Description string `json:"description,omitempty"` AccountID string `json:"accountId"` Region string `json:"region"` - Parameters json.RawMessage `json:"parameters,omitempty"` + Parameters jsontext.Value `json:"parameters,omitempty"` } // DBBackup is a backup of an instance or cluster. diff --git a/services/timestreaminfluxdb/parametergroups.go b/services/timestreaminfluxdb/parametergroups.go index 2929af5acc..c6a709cefc 100644 --- a/services/timestreaminfluxdb/parametergroups.go +++ b/services/timestreaminfluxdb/parametergroups.go @@ -3,7 +3,7 @@ package timestreaminfluxdb import ( "cmp" "context" - "encoding/json" + "encoding/json/jsontext" "slices" "github.com/blackbirdworks/gopherstack/pkgs/page" @@ -16,7 +16,7 @@ type GroupInput struct { Tags map[string]string Name string Description string - Parameters json.RawMessage + Parameters jsontext.Value } // CreateDBParameterGroup creates a parameter group. diff --git a/services/timestreaminfluxdb/persistence.go b/services/timestreaminfluxdb/persistence.go index 4b69fffcc7..3d8bb9960e 100644 --- a/services/timestreaminfluxdb/persistence.go +++ b/services/timestreaminfluxdb/persistence.go @@ -2,7 +2,7 @@ package timestreaminfluxdb import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -12,10 +12,10 @@ import ( const snapshotVersion = 1 type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serialises the backend state. diff --git a/services/timestreaminfluxdb/secrets.go b/services/timestreaminfluxdb/secrets.go index 36253ea804..dfcb154398 100644 --- a/services/timestreaminfluxdb/secrets.go +++ b/services/timestreaminfluxdb/secrets.go @@ -1,9 +1,10 @@ package timestreaminfluxdb import ( - "encoding/json" + "encoding/json/v2" "github.com/blackbirdworks/gopherstack/pkgs/awsid" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -39,7 +40,7 @@ func (b *InMemoryBackend) authSecretLocked(sc scope, id string, a authSecret) (s return "", nil } - body, err := json.Marshal(a) //nolint:gosec // the secret holds the credentials by design + body, err := json.Marshal(a, jsonx.V1()) if err != nil { return "", fail(ErrInternalServer, "encode auth secret") } diff --git a/services/timestreaminfluxdb/validate.go b/services/timestreaminfluxdb/validate.go index f05afcded1..1f70ff5abf 100644 --- a/services/timestreaminfluxdb/validate.go +++ b/services/timestreaminfluxdb/validate.go @@ -1,11 +1,14 @@ package timestreaminfluxdb import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "regexp" "slices" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -220,13 +223,13 @@ func validateListArgs(maxResults int32) error { } // validateParameters checks the union shape: one engine key holding a parameter object. -func validateParameters(raw json.RawMessage) error { +func validateParameters(raw jsontext.Value) error { if len(raw) == 0 { return nil } - var union map[string]json.RawMessage - if err := json.Unmarshal(raw, &union); err != nil || len(union) != 1 { + var union map[string]jsontext.Value + if err := json.Unmarshal(raw, &union, jsonx.V1()); err != nil || len(union) != 1 { return invalid("parameters must set exactly one of %s", strings.Join(paramGroups(), ", ")) } @@ -235,8 +238,8 @@ func validateParameters(raw json.RawMessage) error { return invalid("parameters must set exactly one of %s", strings.Join(paramGroups(), ", ")) } - var fields map[string]json.RawMessage - if err := json.Unmarshal(body, &fields); err != nil { + var fields map[string]jsontext.Value + if err := json.Unmarshal(body, &fields, jsonx.V1()); err != nil { return invalid("%s parameters must be an object", engine) } @@ -248,7 +251,7 @@ func validateParameters(raw json.RawMessage) error { return nil } -func validateParameterFields(engine string, fields map[string]json.RawMessage) error { +func validateParameterFields(engine string, fields map[string]jsontext.Value) error { for name, val := range fields { if !paramKeys[name] { return invalid("%s has no parameter named %q", engine, name) @@ -275,21 +278,21 @@ func validateParameterFields(engine string, fields map[string]json.RawMessage) e return nil } -func checkEnum(name string, raw json.RawMessage, allowed []string) error { +func checkEnum(name string, raw jsontext.Value, allowed []string) error { var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return invalid("%s must be a string", name) } return oneOf(name, s, allowed) } -func checkDuration(name string, raw json.RawMessage) error { +func checkDuration(name string, raw jsontext.Value) error { var d struct { Value *int64 `json:"value"` DurationType string `json:"durationType"` } - if err := json.Unmarshal(raw, &d); err != nil || d.Value == nil { + if err := json.Unmarshal(raw, &d, jsonx.V1()); err != nil || d.Value == nil { return invalid("%s must be a duration with durationType and value", name) } diff --git a/services/timestreaminfluxdb/wire.go b/services/timestreaminfluxdb/wire.go index 916d0d7a38..62e86e4d46 100644 --- a/services/timestreaminfluxdb/wire.go +++ b/services/timestreaminfluxdb/wire.go @@ -1,7 +1,7 @@ package timestreaminfluxdb import ( - "encoding/json" + "encoding/json/jsontext" "maps" "slices" "strings" @@ -129,11 +129,11 @@ type wClusterSummary struct { } type wParameterGroup struct { - Arn string `json:"arn"` - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Parameters json.RawMessage `json:"parameters,omitempty"` + Arn string `json:"arn"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parameters jsontext.Value `json:"parameters,omitempty"` } type wParameterGroupSummary struct { diff --git a/services/timestreamquery/column_info_marshal_test.go b/services/timestreamquery/column_info_marshal_test.go index 7f405f8d68..81ee388a6a 100644 --- a/services/timestreamquery/column_info_marshal_test.go +++ b/services/timestreamquery/column_info_marshal_test.go @@ -1,11 +1,13 @@ package timestreamquery //nolint:testpackage // needs unexported marshalColumnInfos for the wire-shape check. import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestMarshalColumnInfos_PreservesNestedUnion verifies that marshalColumnInfos @@ -95,12 +97,12 @@ func TestMarshalColumnInfos_PreservesNestedUnion(t *testing.T) { require.Len(t, marshalled, 1) // Round-trip through JSON, exactly as the handler's response does. - raw, err := json.Marshal(marshalled) + raw, err := json.Marshal(marshalled, jsonx.V1()) require.NoError(t, err) var decoded []map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) + require.NoError(t, json.Unmarshal(raw, &decoded, jsonx.V1())) require.Len(t, decoded, 1) typ, ok := decoded[0]["Type"].(map[string]any) diff --git a/services/timestreamquery/handler.go b/services/timestreamquery/handler.go index 2b64e96643..b7290cc2ba 100644 --- a/services/timestreamquery/handler.go +++ b/services/timestreamquery/handler.go @@ -2,7 +2,7 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -161,7 +162,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { Name string `json:"Name"` } - if unmarshalErr := json.Unmarshal(body, &req); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &req, jsonx.V1()); unmarshalErr != nil { return "" } @@ -272,7 +273,7 @@ func (h *Handler) handleDescribeEndpoints(host string) ([]byte, error) { "CachePeriodInMinutes": endpointCachePeriod, }, }, - }) + }, jsonx.V1()) } func (h *Handler) handleError(c *echo.Context, err error) error { @@ -296,7 +297,7 @@ func errorPayload(errType, msg string) []byte { b, _ := json.Marshal(map[string]string{ "__type": errType, "message": msg, - }) + }, jsonx.V1()) return b } diff --git a/services/timestreamquery/handler_account_settings.go b/services/timestreamquery/handler_account_settings.go index 4af40c7836..85f9270add 100644 --- a/services/timestreamquery/handler_account_settings.go +++ b/services/timestreamquery/handler_account_settings.go @@ -2,14 +2,16 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleDescribeAccountSettings(ctx context.Context) ([]byte, error) { settings := h.Backend.DescribeAccountSettings(ctx) - return json.Marshal(buildAccountSettingsResponse(settings)) + return json.Marshal(buildAccountSettingsResponse(settings), jsonx.V1()) } func buildAccountSettingsResponse(settings AccountSettings) map[string]any { @@ -32,7 +34,7 @@ func (h *Handler) handlePrepareQuery(ctx context.Context, body []byte) ([]byte, ValidateOnly bool `json:"ValidateOnly"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -49,7 +51,7 @@ func (h *Handler) handlePrepareQuery(ctx context.Context, body []byte) ([]byte, "QueryString": result.QueryString, "Columns": marshalSelectColumns(result.Selected), "Parameters": marshalColumnInfos(result.Parameters), - }) + }, jsonx.V1()) } // updateAccountSettingsRequest is the parsed request body for @@ -119,7 +121,7 @@ func (nc *accountSettingsNotificationConfigWire) toModel() *AccountSettingsNotif func (h *Handler) handleUpdateAccountSettings(ctx context.Context, body []byte) ([]byte, error) { var req updateAccountSettingsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -130,7 +132,7 @@ func (h *Handler) handleUpdateAccountSettings(ctx context.Context, body []byte) return nil, err } - return json.Marshal(buildAccountSettingsResponse(settings)) + return json.Marshal(buildAccountSettingsResponse(settings), jsonx.V1()) } func marshalSelectColumns(cols []SelectColumn) []map[string]any { diff --git a/services/timestreamquery/handler_query_execution.go b/services/timestreamquery/handler_query_execution.go index a146ae5e6a..ff8cfe58cc 100644 --- a/services/timestreamquery/handler_query_execution.go +++ b/services/timestreamquery/handler_query_execution.go @@ -2,8 +2,10 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleQuery(ctx context.Context, body []byte) ([]byte, error) { @@ -17,7 +19,7 @@ func (h *Handler) handleQuery(ctx context.Context, body []byte) ([]byte, error) MaxRows int32 `json:"MaxRows"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -58,7 +60,7 @@ func (h *Handler) handleQuery(ctx context.Context, body []byte) ([]byte, error) resp["QueryInsightsResponse"] = page.Insights } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // marshalRows converts []Row to JSON-serialisable form. @@ -99,7 +101,7 @@ func (h *Handler) handleCancelQuery(ctx context.Context, body []byte) ([]byte, e QueryID string `json:"QueryId"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -113,5 +115,5 @@ func (h *Handler) handleCancelQuery(ctx context.Context, body []byte) ([]byte, e return json.Marshal(map[string]any{ "CancellationMessage": "Query has been successfully cancelled.", - }) + }, jsonx.V1()) } diff --git a/services/timestreamquery/handler_scheduled_queries.go b/services/timestreamquery/handler_scheduled_queries.go index 123cb33990..569f31cf75 100644 --- a/services/timestreamquery/handler_scheduled_queries.go +++ b/services/timestreamquery/handler_scheduled_queries.go @@ -2,9 +2,11 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // multiMeasureAttributeMappingInput mirrors types.MultiMeasureAttributeMapping. @@ -137,7 +139,7 @@ func convertMultiMeasureAttributeMappings( func (h *Handler) handleCreateScheduledQuery(ctx context.Context, body []byte) ([]byte, error) { var req createScheduledQueryInput - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -211,7 +213,7 @@ func (h *Handler) handleCreateScheduledQuery(ctx context.Context, body []byte) ( return json.Marshal(map[string]any{ keyArn: sq.Arn, - }) + }, jsonx.V1()) } func (h *Handler) handleDeleteScheduledQuery(ctx context.Context, body []byte) ([]byte, error) { @@ -219,7 +221,7 @@ func (h *Handler) handleDeleteScheduledQuery(ctx context.Context, body []byte) ( ScheduledQueryArn string `json:"ScheduledQueryArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -239,7 +241,7 @@ func (h *Handler) handleDescribeScheduledQuery(ctx context.Context, body []byte) ScheduledQueryArn string `json:"ScheduledQueryArn"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -254,7 +256,7 @@ func (h *Handler) handleDescribeScheduledQuery(ctx context.Context, body []byte) return json.Marshal(map[string]any{ "ScheduledQuery": scheduledQueryToView(sq), - }) + }, jsonx.V1()) } func (h *Handler) handleExecuteScheduledQuery(ctx context.Context, body []byte) ([]byte, error) { @@ -263,7 +265,7 @@ func (h *Handler) handleExecuteScheduledQuery(ctx context.Context, body []byte) InvocationTime float64 `json:"InvocationTime"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -290,7 +292,7 @@ func (h *Handler) handleListScheduledQueries(ctx context.Context, body []byte) ( MaxResults int32 `json:"MaxResults"` } if len(body) > 0 { - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) } if err := validateScheduledQueryPaging(req.NextToken, req.MaxResults); err != nil { @@ -306,7 +308,7 @@ func (h *Handler) handleListScheduledQueries(ctx context.Context, body []byte) ( resp["NextToken"] = result.NextToken } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } func (h *Handler) handleUpdateScheduledQuery(ctx context.Context, body []byte) ([]byte, error) { @@ -315,7 +317,7 @@ func (h *Handler) handleUpdateScheduledQuery(ctx context.Context, body []byte) ( State string `json:"State"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } diff --git a/services/timestreamquery/handler_tags.go b/services/timestreamquery/handler_tags.go index 02ba40296b..78efcec040 100644 --- a/services/timestreamquery/handler_tags.go +++ b/services/timestreamquery/handler_tags.go @@ -2,8 +2,10 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleTagResource(ctx context.Context, body []byte) ([]byte, error) { @@ -15,7 +17,7 @@ func (h *Handler) handleTagResource(ctx context.Context, body []byte) ([]byte, e } `json:"Tags"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -33,7 +35,7 @@ func (h *Handler) handleTagResource(ctx context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, error) { @@ -42,7 +44,7 @@ func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, TagKeys []string `json:"TagKeys"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -54,7 +56,7 @@ func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([]byte, error) { @@ -62,7 +64,7 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([ ResourceARN string `json:"ResourceARN"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("invalid request: %w", err) } @@ -77,5 +79,5 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([ return json.Marshal(map[string]any{ "Tags": tags, - }) + }, jsonx.V1()) } diff --git a/services/timestreamquery/handler_test.go b/services/timestreamquery/handler_test.go index 3e20f852ef..89b4d276bf 100644 --- a/services/timestreamquery/handler_test.go +++ b/services/timestreamquery/handler_test.go @@ -2,7 +2,7 @@ package timestreamquery_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/config" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamquery" ) @@ -38,7 +39,7 @@ func doRequest( var bodyBytes []byte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -80,7 +81,7 @@ func parseResponse(t *testing.T, rec *httptest.ResponseRecorder) map[string]any t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -238,7 +239,7 @@ func TestTimestreamQueryHandler_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() h := newTestHandler() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(bodyBytes)) e := echo.New() diff --git a/services/timestreamquery/persistence.go b/services/timestreamquery/persistence.go index c5a70c57a9..b1e84282d2 100644 --- a/services/timestreamquery/persistence.go +++ b/services/timestreamquery/persistence.go @@ -2,9 +2,11 @@ package timestreamquery import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -30,7 +32,7 @@ const timestreamquerySnapshotVersion = 1 // guards against decoding a snapshot from an incompatible (older or newer) // build of this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` AccountSettings map[string]accountSettingsSnapshot `json:"account_settings"` Version int `json:"version"` } @@ -84,7 +86,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { Version: timestreamquerySnapshotVersion, Tables: tables, AccountSettings: settingsSnap, - }) + }, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "timestreamquery: failed to marshal snapshot", "error", err) diff --git a/services/timestreamquery/query_execution_test.go b/services/timestreamquery/query_execution_test.go index 4f4756eab0..8c84fd42b9 100644 --- a/services/timestreamquery/query_execution_test.go +++ b/services/timestreamquery/query_execution_test.go @@ -1,13 +1,14 @@ package timestreamquery_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamquery" ) @@ -295,7 +296,7 @@ func TestCancelQuery_UnknownIDReturnsValidationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } diff --git a/services/timestreamquery/scheduled_queries_test.go b/services/timestreamquery/scheduled_queries_test.go index 2fe517f4aa..ae3c84ae6d 100644 --- a/services/timestreamquery/scheduled_queries_test.go +++ b/services/timestreamquery/scheduled_queries_test.go @@ -1,7 +1,7 @@ package timestreamquery_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamquery" ) @@ -565,7 +566,7 @@ func TestCreateScheduledQuery_ConflictReturns409(t *testing.T) { assert.Equal(t, http.StatusConflict, rec2.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ConflictException", errBody["__type"]) } @@ -629,7 +630,7 @@ func TestCreateScheduledQuery_RequiresNotificationAndErrorReport(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"], "error response must include __type") } }) diff --git a/services/timestreamquery/ts_meta.go b/services/timestreamquery/ts_meta.go index 132699a2e4..6a4859b14d 100644 --- a/services/timestreamquery/ts_meta.go +++ b/services/timestreamquery/ts_meta.go @@ -1,10 +1,12 @@ package timestreamquery import ( - "encoding/json" + "encoding/json/v2" "regexp" "sort" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func varcharResult(names []string, rows [][]string) *resultSet { @@ -188,7 +190,7 @@ func (b *InMemoryBackend) showMeasures(region, q string, src RecordSource) (*res dims = append(dims, map[string]string{"dimension_name": d, "data_type": types[d]}) } - enc, _ := json.Marshal(dims) + enc, _ := json.Marshal(dims, jsonx.V1()) rows = append(rows, []string{m.name, m.typ, string(enc)}) } diff --git a/services/timestreamwrite/handler.go b/services/timestreamwrite/handler.go index 24e93b8b2a..71c56bd86e 100644 --- a/services/timestreamwrite/handler.go +++ b/services/timestreamwrite/handler.go @@ -2,7 +2,7 @@ package timestreamwrite import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/ctxval" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -284,12 +285,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError var rejectedErr *RejectedRecordsError switch { @@ -316,8 +315,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, awserr.ErrInvalidParameter) || errors.Is(err, errInvalidRequest) || errors.Is(err, errUnknownAction) || - errors.As(err, &syntaxErr) || - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err) || + jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyTypeField: "ValidationException", keyMessageField: awserr.StripCode("ValidationException", err.Error()), diff --git a/services/timestreamwrite/handler_batch_load_tasks_test.go b/services/timestreamwrite/handler_batch_load_tasks_test.go index f06f40e985..3d29547254 100644 --- a/services/timestreamwrite/handler_batch_load_tasks_test.go +++ b/services/timestreamwrite/handler_batch_load_tasks_test.go @@ -1,7 +1,7 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamwrite" ) @@ -72,7 +73,7 @@ func TestHandler_CreateBatchLoadTask(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["TaskId"]) } }) @@ -102,7 +103,7 @@ func TestHandler_DescribeBatchLoadTask(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskID, _ := createResp["TaskId"].(string) require.NotEmpty(t, taskID) @@ -137,7 +138,7 @@ func TestHandler_DescribeBatchLoadTask(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp, jsonx.V1())) desc, ok := resp["BatchLoadTaskDescription"].(map[string]any) assert.True(t, ok) assert.Equal(t, taskID, desc["TaskId"]) @@ -207,7 +208,7 @@ func TestHandler_ListBatchLoadTasks(t *testing.T) { assert.Equal(t, tt.wantStatus, result.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(result.Body.Bytes(), &resp, jsonx.V1())) tasks, ok := resp["BatchLoadTasks"].([]any) assert.True(t, ok) assert.Len(t, tasks, tt.wantLen) @@ -238,7 +239,7 @@ func TestHandler_ResumeBatchLoadTask(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) taskID, _ := createResp["TaskId"].(string) require.NotEmpty(t, taskID) @@ -349,7 +350,7 @@ func TestHandler_CreateBatchLoadTask_DataSourceConfigRequired(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var errBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ValidationException", errBody["__type"]) } }) @@ -380,7 +381,7 @@ func TestHandler_CreateBatchLoadTask_WithDataSourceConfig(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) require.NotEmpty(t, taskID) @@ -388,7 +389,7 @@ func TestHandler_CreateBatchLoadTask_WithDataSourceConfig(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) desc := descOut["BatchLoadTaskDescription"].(map[string]any) dsc := desc["DataSourceConfiguration"].(map[string]any) @@ -424,14 +425,14 @@ func TestHandler_CreateBatchLoadTask_WithReportConfig(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) descRec := doRequest(t, h, "DescribeBatchLoadTask", map[string]string{"TaskId": taskID}) require.Equal(t, http.StatusOK, descRec.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) desc := descOut["BatchLoadTaskDescription"].(map[string]any) rpt := desc["ReportConfiguration"].(map[string]any) @@ -504,7 +505,7 @@ func TestHandler_ListBatchLoadTasks_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) tasks1 := out1["BatchLoadTasks"].([]any) assert.Len(t, tasks1, 2) @@ -516,7 +517,7 @@ func TestHandler_ListBatchLoadTasks_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) tasks2 := out2["BatchLoadTasks"].([]any) assert.Len(t, tasks2, 2) @@ -555,7 +556,7 @@ func TestHandler_ListBatchLoadTasks_StatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tasks := out["BatchLoadTasks"].([]any) assert.Len(t, tasks, 1) @@ -594,7 +595,7 @@ func TestHandler_ListBatchLoadTasks_SummaryResumableUntil(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tasks := out["BatchLoadTasks"].([]any) require.Len(t, tasks, 1) @@ -633,7 +634,7 @@ func TestHandler_DescribeBatchLoadTask_ErrorMessage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) desc := out["BatchLoadTaskDescription"].(map[string]any) assert.Equal(t, "S3 object not found", desc["ErrorMessage"]) @@ -660,14 +661,14 @@ func TestHandler_BatchLoadTaskDescriptionView_MissingResumableUntilOmitted(t *te require.Equal(t, http.StatusOK, cr.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) dr := doRequest(t, h, "DescribeBatchLoadTask", map[string]string{"TaskId": taskID}) require.Equal(t, http.StatusOK, dr.Code) var descOut map[string]any - require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &descOut, jsonx.V1())) desc := descOut["BatchLoadTaskDescription"].(map[string]any) _, hasResumable := desc["ResumableUntil"] @@ -698,7 +699,7 @@ func TestHandler_DescribeBatchLoadTask_RecordVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) desc := out["BatchLoadTaskDescription"].(map[string]any) assert.InDelta(t, float64(42), desc["RecordVersion"], 0, @@ -726,14 +727,14 @@ func TestHandler_DescribeBatchLoadTask_RecordVersionZeroOmitted(t *testing.T) { require.Equal(t, http.StatusOK, cr.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) dr := doRequest(t, h, "DescribeBatchLoadTask", map[string]string{"TaskId": taskID}) require.Equal(t, http.StatusOK, dr.Code) var out map[string]any - require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &out, jsonx.V1())) desc := out["BatchLoadTaskDescription"].(map[string]any) _, hasRV := desc["RecordVersion"] @@ -771,7 +772,7 @@ func TestHandler_DescribeBatchLoadTask_ProgressReport(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) desc := out["BatchLoadTaskDescription"].(map[string]any) report := desc["ProgressReport"].(map[string]any) @@ -803,14 +804,14 @@ func TestHandler_DescribeBatchLoadTask_NoProgressReport(t *testing.T) { require.Equal(t, http.StatusOK, cr.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(cr.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) dr := doRequest(t, h, "DescribeBatchLoadTask", map[string]string{"TaskId": taskID}) require.Equal(t, http.StatusOK, dr.Code) var out map[string]any - require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(dr.Body.Bytes(), &out, jsonx.V1())) desc := out["BatchLoadTaskDescription"].(map[string]any) _, hasPR := desc["ProgressReport"] @@ -840,14 +841,14 @@ func TestHandler_DescribeBatchLoadTask_TimestampsAreFloats(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) taskID := createOut["TaskId"].(string) rec2 := doRequest(t, h, "DescribeBatchLoadTask", map[string]any{"TaskId": taskID}) require.Equal(t, http.StatusOK, rec2.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &raw, jsonx.V1())) desc := raw["BatchLoadTaskDescription"].(map[string]any) ct, ok := desc["CreationTime"].(float64) diff --git a/services/timestreamwrite/handler_databases_test.go b/services/timestreamwrite/handler_databases_test.go index 881d7aa0f6..05fa63c23c 100644 --- a/services/timestreamwrite/handler_databases_test.go +++ b/services/timestreamwrite/handler_databases_test.go @@ -1,7 +1,7 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateDatabase(t *testing.T) { @@ -41,7 +43,7 @@ func TestHandler_CreateDatabase(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) db, ok := resp["Database"].(map[string]any) assert.True(t, ok) assert.Equal(t, "my-db", db["DatabaseName"]) @@ -65,7 +67,7 @@ func TestHandler_CreateDatabase_Conflict(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ConflictException", errBody["__type"]) } @@ -122,7 +124,7 @@ func TestHandler_ListDatabases(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dbs, ok := resp["Databases"].([]any) assert.True(t, ok) assert.Len(t, dbs, 2) @@ -141,7 +143,7 @@ func TestHandler_ListDatabases_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) dbs := out["Databases"].([]any) require.Len(t, dbs, 3) @@ -165,7 +167,7 @@ func TestHandler_ListDatabases_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) dbs1 := out1["Databases"].([]any) assert.Len(t, dbs1, 2) @@ -178,7 +180,7 @@ func TestHandler_ListDatabases_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) dbs2 := out2["Databases"].([]any) assert.Len(t, dbs2, 2) @@ -189,7 +191,7 @@ func TestHandler_ListDatabases_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var out3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &out3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &out3, jsonx.V1())) dbs3 := out3["Databases"].([]any) assert.Len(t, dbs3, 1) @@ -209,7 +211,7 @@ func TestHandler_ListDatabases_NoNextTokenWhenFits(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Empty(t, out["NextToken"]) } @@ -302,7 +304,7 @@ func TestHandler_DeleteDatabase_RequiresEmpty(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -417,7 +419,7 @@ func TestHandler_CreateDatabase_MinimumNameLength(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -489,7 +491,7 @@ func TestHandler_CreateDatabase_NameFormatValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -510,7 +512,7 @@ func TestHandler_CreateDatabase_NameValidationErrorBody(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) assert.NotEmpty(t, body["message"]) } @@ -577,7 +579,7 @@ func TestHandler_CreateDatabase_TagKeyValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -599,7 +601,7 @@ func TestHandler_CreateDatabase_WithKmsKeyID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createOut map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createOut, jsonx.V1())) db := createOut["Database"].(map[string]any) assert.Equal(t, kmsKey, db["KmsKeyId"]) @@ -622,7 +624,7 @@ func TestHandler_CreateDatabase_KmsKeyRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) db := out["Database"].(map[string]any) assert.Equal(t, kmsKey, db["KmsKeyId"], "KmsKeyId should survive DescribeDatabase") @@ -645,7 +647,7 @@ func TestHandler_ListDatabases_IncludesKmsKeyID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) dbs := out["Databases"].([]any) require.Len(t, dbs, 1) @@ -684,7 +686,7 @@ func TestHandler_CreateDatabase_ConflictException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ConflictException", body["__type"]) assert.NotEmpty(t, body["message"]) } @@ -703,7 +705,7 @@ func TestHandler_DescribeDatabase_TableCountTracking(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) db := out["Database"].(map[string]any) @@ -735,7 +737,7 @@ func TestHandler_CreateDatabase_ARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) db := out["Database"].(map[string]any) arn := db["Arn"].(string) @@ -759,7 +761,7 @@ func TestHandler_CreateDatabase_TimestampsAreFloats(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) db := raw["Database"].(map[string]any) ct, ok := db["CreationTime"].(float64) @@ -786,7 +788,7 @@ func TestHandler_CreateDatabase_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Fetch the ARN then list tags db := out["Database"].(map[string]any) @@ -798,7 +800,7 @@ func TestHandler_CreateDatabase_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].([]any) assert.Len(t, tags, 2) diff --git a/services/timestreamwrite/handler_endpoints_test.go b/services/timestreamwrite/handler_endpoints_test.go index 6f6345aca0..d73383e251 100644 --- a/services/timestreamwrite/handler_endpoints_test.go +++ b/services/timestreamwrite/handler_endpoints_test.go @@ -2,7 +2,7 @@ package timestreamwrite_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DescribeEndpoints(t *testing.T) { @@ -20,7 +22,7 @@ func TestHandler_DescribeEndpoints(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints, ok := resp["Endpoints"].([]any) assert.True(t, ok) assert.NotEmpty(t, endpoints) @@ -40,7 +42,7 @@ func TestHandler_DescribeEndpoints_CachePeriodIs1440(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) endpoints := out["Endpoints"].([]any) require.NotEmpty(t, endpoints) @@ -60,7 +62,7 @@ func TestHandler_DescribeEndpoints_CachePeriodPositive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) endpoints, ok := out["Endpoints"].([]any) require.True(t, ok) @@ -95,7 +97,7 @@ func TestHandler_DescribeEndpoints_EchoesRequestHost(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) endpoints := resp["Endpoints"].([]any) require.NotEmpty(t, endpoints) diff --git a/services/timestreamwrite/handler_internal_error_test.go b/services/timestreamwrite/handler_internal_error_test.go index 67659813f0..5548926aab 100644 --- a/services/timestreamwrite/handler_internal_error_test.go +++ b/services/timestreamwrite/handler_internal_error_test.go @@ -1,7 +1,7 @@ package timestreamwrite //nolint:testpackage // needs access to the unexported handleError method. import ( - "encoding/json" + "encoding/json/v2" "errors" "net/http" "net/http/httptest" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errUnmatchedForTest = errors.New("boom: matches none of handleError's sentinel checks") @@ -46,6 +48,6 @@ func TestHandleError_DefaultBranchEmitsInternalServerException(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InternalServerException", body[keyTypeField]) } diff --git a/services/timestreamwrite/handler_records_test.go b/services/timestreamwrite/handler_records_test.go index f555fc289f..6402f0cca6 100644 --- a/services/timestreamwrite/handler_records_test.go +++ b/services/timestreamwrite/handler_records_test.go @@ -1,7 +1,7 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamwrite" ) @@ -149,7 +150,7 @@ func TestHandler_WriteRecords_RequiresMeasureName(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.NotEmpty(t, errResp["__type"], "error response must include __type") } @@ -369,7 +370,7 @@ func TestHandler_WriteRecords_InvalidMeasureValueType(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -450,7 +451,7 @@ func TestHandler_WriteRecords_InvalidTimeUnit(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -527,7 +528,7 @@ func TestHandler_WriteRecords_DimensionCountLimit(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -562,7 +563,7 @@ func TestHandler_WriteRecords_InvalidMeasureValueTypeViaCommonAttributes(t *test assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } @@ -594,7 +595,7 @@ func TestHandler_WriteRecords_InvalidTimeUnitViaCommonAttributes(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } @@ -683,7 +684,7 @@ func TestHandler_WriteRecords_MultiMeasure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 1, int(ingested["Total"].(float64))) @@ -722,7 +723,7 @@ func TestHandler_WriteRecords_CommonAttributes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 1, int(ingested["Total"].(float64))) @@ -770,7 +771,7 @@ func TestHandler_WriteRecords_CommonAttributesMerge(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 2, int(ingested["Total"].(float64))) @@ -814,7 +815,7 @@ func TestHandler_WriteRecords_CommonAttributesDimensionOverlapRejected(t *testin require.Equal(t, http.StatusBadRequest, rec.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "ValidationException", errBody["__type"]) } @@ -891,7 +892,7 @@ func TestHandler_WriteRecords_RejectedRecordsException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var errBody map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errBody, jsonx.V1())) assert.Equal(t, "RejectedRecordsException", errBody["__type"]) rejected, ok := errBody["RejectedRecords"].([]any) @@ -963,7 +964,7 @@ func TestHandler_WriteRecords_MaxRecordsLimit(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -988,7 +989,7 @@ func TestHandler_WriteRecords_EmptyBatch(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 0, int(ingested["Total"].(float64))) @@ -1035,7 +1036,7 @@ func TestHandler_WriteRecords_MagneticStoreInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 1, int(ingested["Total"].(float64))) @@ -1069,7 +1070,7 @@ func TestHandler_WriteRecords_ReturnsIngestedTotal(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, 2, int(ingested["Total"].(float64))) @@ -1097,7 +1098,7 @@ func TestHandler_WriteRecords_ReturnsMemoryStoreCount(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) ingested := out["RecordsIngested"].(map[string]any) assert.Equal(t, int(3), int(ingested["Total"].(float64))) diff --git a/services/timestreamwrite/handler_tables_test.go b/services/timestreamwrite/handler_tables_test.go index 769c5d3ac4..411b444d9c 100644 --- a/services/timestreamwrite/handler_tables_test.go +++ b/services/timestreamwrite/handler_tables_test.go @@ -1,7 +1,7 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamwrite" ) @@ -119,7 +120,7 @@ func TestHandler_ListTables(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tbls, ok := resp["Tables"].([]any) assert.True(t, ok) assert.Len(t, tbls, 2) @@ -150,7 +151,7 @@ func TestHandler_ListTables_DatabaseNameOptional(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tbls, ok := resp["Tables"].([]any) require.True(t, ok) assert.Len(t, tbls, 2) @@ -255,7 +256,7 @@ func TestHandler_ListTables_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbls := out["Tables"].([]any) require.Len(t, tbls, 2) @@ -278,7 +279,7 @@ func TestHandler_ListTables_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) tbls1 := out1["Tables"].([]any) assert.Len(t, tbls1, 2) @@ -294,7 +295,7 @@ func TestHandler_ListTables_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) tbls2 := out2["Tables"].([]any) assert.Len(t, tbls2, 1) @@ -323,7 +324,7 @@ func TestHandler_CreateTable_WithSchema(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) schema, ok := tbl["Schema"].(map[string]any) @@ -361,7 +362,7 @@ func TestHandler_DescribeTable_ReturnsSchema(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) schema, ok := tbl["Schema"].(map[string]any) @@ -394,7 +395,7 @@ func TestHandler_UpdateTable_WithSchema(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) schema := tbl["Schema"].(map[string]any) @@ -423,7 +424,7 @@ func TestHandler_ListTables_IncludesSchema(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tables := out["Tables"].([]any) require.Len(t, tables, 1) @@ -458,7 +459,7 @@ func TestHandler_CreateTable_MagneticStoreRejectedDataLocation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) mswp := tbl["MagneticStoreWriteProperties"].(map[string]any) @@ -501,7 +502,7 @@ func TestHandler_DescribeTable_MagneticStoreRejectedDataLocationRoundTrip(t *tes require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) mswp := tbl["MagneticStoreWriteProperties"].(map[string]any) @@ -578,7 +579,7 @@ func TestHandler_CreateTable_NameFormatValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -690,7 +691,7 @@ func TestHandler_CreateTable_RetentionPropertiesRangeValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -817,7 +818,7 @@ func TestHandler_CreateTable_PartitionKeyTypeValidation(t *testing.T) { if tt.wantStatus == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } }) @@ -846,7 +847,7 @@ func TestHandler_UpdateTable_PartitionKeyValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) // Valid DIMENSION with Name should succeed. @@ -879,7 +880,7 @@ func TestHandler_CreateTable_TagValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ValidationException", body["__type"]) } @@ -897,7 +898,7 @@ func TestHandler_CreateTable_ConflictException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ConflictException", body["__type"]) } @@ -916,7 +917,7 @@ func TestHandler_CreateTable_StatusActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) assert.Equal(t, "ACTIVE", tbl["TableStatus"]) @@ -941,7 +942,7 @@ func TestHandler_DescribeTable_StatusActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) assert.Equal(t, "ACTIVE", tbl["TableStatus"]) @@ -966,7 +967,7 @@ func TestHandler_ListTables_StatusActive(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tables := out["Tables"].([]any) require.Len(t, tables, 3) @@ -994,7 +995,7 @@ func TestHandler_CreateTable_ARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) arn := tbl["Arn"].(string) @@ -1018,7 +1019,7 @@ func TestHandler_CreateTable_TimestampsAreFloats(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) tbl := raw["Table"].(map[string]any) ct, ok := tbl["CreationTime"].(float64) @@ -1043,7 +1044,7 @@ func TestHandler_CreateTable_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) arn := tbl["Arn"].(string) @@ -1052,7 +1053,7 @@ func TestHandler_CreateTable_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].([]any) assert.Len(t, tags, 1) @@ -1081,7 +1082,7 @@ func TestHandler_CreateTable_RetentionProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) rp := tbl["RetentionProperties"].(map[string]any) @@ -1108,7 +1109,7 @@ func TestHandler_CreateTable_MagneticStoreWriteProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) mswp := tbl["MagneticStoreWriteProperties"].(map[string]any) @@ -1136,7 +1137,7 @@ func TestHandler_UpdateTable_RetentionProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) rp := tbl["RetentionProperties"].(map[string]any) @@ -1170,7 +1171,7 @@ func TestHandler_UpdateTable_MagneticStoreWriteProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) mswp := tbl["MagneticStoreWriteProperties"].(map[string]any) assert.Equal(t, true, mswp["EnableMagneticStoreWrites"]) @@ -1224,7 +1225,7 @@ func TestHandler_CreateTable_DefaultRetentionProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) rp := tbl["RetentionProperties"].(map[string]any) @@ -1250,7 +1251,7 @@ func TestHandler_CreateTable_NoPropertiesReturnsDefaults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) rp, hasRP := tbl["RetentionProperties"] @@ -1290,7 +1291,7 @@ func TestHandler_DescribeTable_RetentionAfterUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tbl := out["Table"].(map[string]any) rp := tbl["RetentionProperties"].(map[string]any) @@ -1318,7 +1319,7 @@ func TestHandler_ListTables_IncludesRetentionProperties(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) tables := out["Tables"].([]any) require.Len(t, tables, 1) diff --git a/services/timestreamwrite/handler_tags_test.go b/services/timestreamwrite/handler_tags_test.go index 3938c6e4f6..7bcf0b8c4c 100644 --- a/services/timestreamwrite/handler_tags_test.go +++ b/services/timestreamwrite/handler_tags_test.go @@ -1,13 +1,15 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_TagResource_UntagResource_ListTagsForResource(t *testing.T) { @@ -34,7 +36,7 @@ func TestHandler_TagResource_UntagResource_ListTagsForResource(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok := listResp["Tags"].([]any) assert.True(t, ok) assert.Len(t, tags, 2) @@ -48,7 +50,7 @@ func TestHandler_TagResource_UntagResource_ListTagsForResource(t *testing.T) { rec = doRequest(t, h, "ListTagsForResource", map[string]string{"ResourceARN": arn}) assert.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tags, ok = listResp["Tags"].([]any) assert.True(t, ok) assert.Len(t, tags, 1) @@ -96,7 +98,7 @@ func TestHandler_ListTagsForResource_Sorted(t *testing.T) { descRec := doRequest(t, h, "DescribeDatabase", map[string]any{"DatabaseName": "sort-db"}) var descOut map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) arn := descOut["Database"].(map[string]any)["Arn"].(string) doRequest(t, h, "TagResource", map[string]any{ @@ -112,7 +114,7 @@ func TestHandler_ListTagsForResource_Sorted(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var tagsOut map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &tagsOut, jsonx.V1())) tags := tagsOut["Tags"].([]any) require.Len(t, tags, 3) @@ -185,7 +187,7 @@ func TestHandler_TagResource_HTTP400OnUnknownARN(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -207,7 +209,7 @@ func TestHandler_TagResource_ScheduledQueryARN(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) require.Len(t, tags, 1) @@ -249,7 +251,7 @@ func TestHandler_ListTagsForResource_EmptyWhenNoTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Tags field should be present and empty (not absent). tags, ok := out["Tags"] @@ -287,7 +289,7 @@ func TestHandler_UntagResource_FromKnownARN(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) require.Len(t, tags, 1) @@ -327,7 +329,7 @@ func TestHandler_TagResource_CumulativeUpdates(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &out, jsonx.V1())) tags := out["Tags"].([]any) require.Len(t, tags, 2, "should have two distinct tags") diff --git a/services/timestreamwrite/handler_test.go b/services/timestreamwrite/handler_test.go index e732428fdd..7e47171be2 100644 --- a/services/timestreamwrite/handler_test.go +++ b/services/timestreamwrite/handler_test.go @@ -2,7 +2,7 @@ package timestreamwrite_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/timestreamwrite" ) @@ -78,7 +79,7 @@ func doRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/timestreamwrite/multi_region_test.go b/services/timestreamwrite/multi_region_test.go index 1c37796172..cb5a3a8398 100644 --- a/services/timestreamwrite/multi_region_test.go +++ b/services/timestreamwrite/multi_region_test.go @@ -2,7 +2,8 @@ package timestreamwrite_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/timestreamwrite" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/timestreamwrite/partition_key_enforcement_test.go b/services/timestreamwrite/partition_key_enforcement_test.go index cf091d750b..c0230b541b 100644 --- a/services/timestreamwrite/partition_key_enforcement_test.go +++ b/services/timestreamwrite/partition_key_enforcement_test.go @@ -1,12 +1,14 @@ package timestreamwrite_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_WriteRecords_RequiredPartitionKey(t *testing.T) { @@ -63,7 +65,7 @@ func TestHandler_WriteRecords_RequiredPartitionKey(t *testing.T) { if tt.wantRejected { var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Contains(t, out["RejectedRecords"].([]any)[0].(map[string]any)["Reason"], "region") } }) diff --git a/services/timestreamwrite/persistence.go b/services/timestreamwrite/persistence.go index 50cedf4c66..907fb25d82 100644 --- a/services/timestreamwrite/persistence.go +++ b/services/timestreamwrite/persistence.go @@ -2,10 +2,12 @@ package timestreamwrite import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" @@ -52,7 +54,7 @@ const timestreamwriteSnapshotVersion = 1 // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Records map[string]map[string][]Record `json:"records"` Tags map[string]map[string]string `json:"tags"` NextTaskID int `json:"next_task_id"` @@ -107,7 +109,7 @@ func (b *InMemoryBackend) Snapshot() ([]byte, error) { // Marshal failure is returned to the caller (the Persistable wrapper logs it // via the context-aware logger); do not log through slog.Default() here. - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { return nil, fmt.Errorf("timestreamwrite: marshal snapshot: %w", err) } diff --git a/services/transcribe/call_analytics_test.go b/services/transcribe/call_analytics_test.go index 98eb1e52dc..5f55131c5c 100644 --- a/services/transcribe/call_analytics_test.go +++ b/services/transcribe/call_analytics_test.go @@ -1,7 +1,8 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -515,7 +517,7 @@ func TestCallAnalyticsJobIncludesTranscriptURI(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) job, ok := resp["CallAnalyticsJob"].(map[string]any) require.True(t, ok, "CallAnalyticsJob must be present") @@ -557,7 +559,7 @@ func TestChannelId_JSONKey(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) job, _ := raw["CallAnalyticsJob"].(map[string]any) require.NotNil(t, job, "CallAnalyticsJob must be present") @@ -651,16 +653,21 @@ func TestCallAnalyticsCategory_CreateTimeAndLastUpdateTimeEchoed(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createRaw struct { - CategoryProperties map[string]json.RawMessage `json:"CategoryProperties"` + CategoryProperties map[string]jsontext.Value `json:"CategoryProperties"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw, jsonx.V1())) for _, field := range []string{"CreateTime", "LastUpdateTime"} { val, ok := createRaw.CategoryProperties[field] require.True(t, ok, "expected field %s in CreateCallAnalyticsCategory response", field) - var num json.Number - require.NoError(t, json.Unmarshal(val, &num), "field %s must be a JSON number (epoch seconds)", field) + var num jsonx.Number + require.NoError( + t, + json.Unmarshal(val, &num, jsonx.V1()), + "field %s must be a JSON number (epoch seconds)", + field, + ) } updateRec := doTranscribeRequest(t, h, "UpdateCallAnalyticsCategory", map[string]any{ @@ -673,9 +680,9 @@ func TestCallAnalyticsCategory_CreateTimeAndLastUpdateTimeEchoed(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateRaw struct { - CategoryProperties map[string]json.RawMessage `json:"CategoryProperties"` + CategoryProperties map[string]jsontext.Value `json:"CategoryProperties"` } - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw, jsonx.V1())) assert.Contains(t, updateRaw.CategoryProperties, "LastUpdateTime") } @@ -706,9 +713,9 @@ func TestListCallAnalyticsJobs_JobNameContainsAndStartTime(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var raw struct { - CallAnalyticsJobSummaries []map[string]json.RawMessage `json:"CallAnalyticsJobSummaries"` + CallAnalyticsJobSummaries []map[string]jsontext.Value `json:"CallAnalyticsJobSummaries"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.CallAnalyticsJobSummaries, 1) assert.Contains(t, raw.CallAnalyticsJobSummaries[0], "StartTime") } diff --git a/services/transcribe/handler.go b/services/transcribe/handler.go index b93734b9e0..92b599043c 100644 --- a/services/transcribe/handler.go +++ b/services/transcribe/handler.go @@ -2,7 +2,7 @@ package transcribe import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -106,7 +107,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var req extractResourceRequest - if jsonErr := json.Unmarshal(body, &req); jsonErr != nil { + if jsonErr := json.Unmarshal(body, &req, jsonx.V1()); jsonErr != nil { return "" } @@ -215,20 +216,17 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): return c.JSON(http.StatusNotFound, errorBody("NotFoundException", errMessage(err))) case errors.Is(err, awserr.ErrAlreadyExists): return c.JSON(http.StatusConflict, errorBody("ConflictException", errMessage(err))) case errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, errorBody("BadRequestException", errMessage(err))) case errors.Is(err, errUnknownAction): return c.JSON(http.StatusBadRequest, errorBody("UnknownOperationException", errMessage(err))) diff --git a/services/transcribe/handler_test.go b/services/transcribe/handler_test.go index 020306fe75..1da37a246e 100644 --- a/services/transcribe/handler_test.go +++ b/services/transcribe/handler_test.go @@ -2,7 +2,8 @@ package transcribe_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -35,7 +37,7 @@ func newHandlerWithBackend(t *testing.T) (*transcribe.Handler, *transcribe.InMem func doTranscribeRequest(t *testing.T, h *transcribe.Handler, action string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -502,7 +504,7 @@ func TestTranscribe_BadParams_NotInternalFailure(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"], "expected %s, not InternalFailureException", tt.wantType) }) @@ -671,13 +673,13 @@ func TestTranscribe_TimestampFields_AreJSONNumbers(t *testing.T) { rec := doTranscribeRequest(t, h, tt.action, tt.body) require.Equal(t, http.StatusOK, rec.Code) - var raw map[string]json.RawMessage - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + var raw map[string]jsontext.Value + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) // The response wraps the resource under a single top-level key; find it. - var inner map[string]json.RawMessage + var inner map[string]jsontext.Value for _, v := range raw { - _ = json.Unmarshal(v, &inner) + _ = json.Unmarshal(v, &inner, jsonx.V1()) if inner != nil { break @@ -689,8 +691,8 @@ func TestTranscribe_TimestampFields_AreJSONNumbers(t *testing.T) { val, ok := inner[field] require.True(t, ok, "expected field %s in response %s", field, rec.Body.String()) - var num json.Number - err := json.Unmarshal(val, &num) + var num jsonx.Number + err := json.Unmarshal(val, &num, jsonx.V1()) assert.NoError(t, err, "field %s must decode as a JSON number (epoch seconds), got %s", field, val) } }) diff --git a/services/transcribe/language_models_test.go b/services/transcribe/language_models_test.go index 8014cb228d..d4ec69f7b3 100644 --- a/services/transcribe/language_models_test.go +++ b/services/transcribe/language_models_test.go @@ -1,13 +1,15 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -289,7 +291,7 @@ func TestDescribeLanguageModel_TimeFields(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) lm, _ := raw["LanguageModel"].(map[string]any) require.NotNil(t, lm, "LanguageModel must be present") @@ -320,7 +322,7 @@ func TestDescribeLanguageModel_UpgradeAvailability(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) lm, _ := raw["LanguageModel"].(map[string]any) require.NotNil(t, lm) @@ -354,16 +356,16 @@ func TestDescribeLanguageModel_TimestampFieldsAreJSONNumbers(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var out struct { - LanguageModel map[string]json.RawMessage `json:"LanguageModel"` + LanguageModel map[string]jsontext.Value `json:"LanguageModel"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) for _, field := range []string{"CreateTime", "LastModifiedTime"} { val, ok := out.LanguageModel[field] require.True(t, ok, "expected field %s in response %s", field, rec.Body.String()) - var num json.Number - err := json.Unmarshal(val, &num) + var num jsonx.Number + err := json.Unmarshal(val, &num, jsonx.V1()) assert.NoError(t, err, "field %s must decode as a JSON number (epoch seconds), got %s", field, val) } } @@ -436,7 +438,7 @@ func TestCreateLanguageModel_EchoesInputDataConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) cfg, ok := raw["InputDataConfig"].(map[string]any) require.True(t, ok, "CreateLanguageModelOutput must echo InputDataConfig") assert.Equal(t, "s3://my-bucket/training/", cfg["S3Uri"]) diff --git a/services/transcribe/medical_scribe_test.go b/services/transcribe/medical_scribe_test.go index aa97e6bb99..02660a619d 100644 --- a/services/transcribe/medical_scribe_test.go +++ b/services/transcribe/medical_scribe_test.go @@ -1,13 +1,15 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -292,9 +294,9 @@ func TestListMedicalScribeJobs_JobNameContainsAndSummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var raw struct { - MedicalScribeJobSummaries []map[string]json.RawMessage `json:"MedicalScribeJobSummaries"` + MedicalScribeJobSummaries []map[string]jsontext.Value `json:"MedicalScribeJobSummaries"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.MedicalScribeJobSummaries, 1) summary := raw.MedicalScribeJobSummaries[0] @@ -334,7 +336,7 @@ func TestMedicalScribeJob_OutputURIsPresentWhenCompleted(t *testing.T) { } `json:"MedicalScribeOutput"` } `json:"MedicalScribeJob"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw, jsonx.V1())) assert.Contains(t, raw.MedicalScribeJob.MedicalScribeOutput.ClinicalDocumentURI, "scribe-output-bucket") assert.Contains(t, raw.MedicalScribeJob.MedicalScribeOutput.TranscriptFileURI, "scribe-output-bucket") } diff --git a/services/transcribe/medical_transcription_jobs_test.go b/services/transcribe/medical_transcription_jobs_test.go index ff4777b6d3..e552ccbf4d 100644 --- a/services/transcribe/medical_transcription_jobs_test.go +++ b/services/transcribe/medical_transcription_jobs_test.go @@ -1,7 +1,8 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -121,7 +123,7 @@ func TestHTTP_StartMedicalTranscriptionJob_ContentIdentificationType(t *testing. require.Equal(t, http.StatusOK, rec.Code) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) job := out["MedicalTranscriptionJob"].(map[string]any) assert.Equal(t, "PHI", job["ContentIdentificationType"]) } @@ -272,7 +274,7 @@ func TestMedicalTranscriptionJobTranscriptURI(t *testing.T) { } `json:"Transcript"` } `json:"MedicalTranscriptionJob"` } - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &out, jsonx.V1())) uri := out.MedicalTranscriptionJob.Transcript.TranscriptFileURI assert.NotEmpty(t, uri, "Transcript.TranscriptFileURI must not be empty") @@ -312,7 +314,7 @@ func TestStartMedicalTranscriptionJob_TranscriptURIInStartResponse(t *testing.T) } `json:"Transcript"` } `json:"MedicalTranscriptionJob"` } - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &out, jsonx.V1())) uri := out.MedicalTranscriptionJob.Transcript.TranscriptFileURI assert.NotEmpty(t, uri, "StartMedicalTranscriptionJob response must include TranscriptFileUri") @@ -369,9 +371,9 @@ func TestListMedicalTranscriptionJobs_JobNameContainsAndSummaryShape(t *testing. require.Equal(t, http.StatusOK, listRec.Code) var raw struct { - MedicalTranscriptionJobSummaries []map[string]json.RawMessage `json:"MedicalTranscriptionJobSummaries"` + MedicalTranscriptionJobSummaries []map[string]jsontext.Value `json:"MedicalTranscriptionJobSummaries"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &raw, jsonx.V1())) require.Len(t, raw.MedicalTranscriptionJobSummaries, 1) summary := raw.MedicalTranscriptionJobSummaries[0] @@ -380,6 +382,6 @@ func TestListMedicalTranscriptionJobs_JobNameContainsAndSummaryShape(t *testing. assert.NotContains(t, summary, "Tags", "MedicalTranscriptionJobSummary must not include Tags") var outputLocationType string - require.NoError(t, json.Unmarshal(summary["OutputLocationType"], &outputLocationType)) + require.NoError(t, json.Unmarshal(summary["OutputLocationType"], &outputLocationType, jsonx.V1())) assert.Equal(t, "CUSTOMER_BUCKET", outputLocationType) } diff --git a/services/transcribe/medical_vocabularies_test.go b/services/transcribe/medical_vocabularies_test.go index eb61bc2c16..645d396562 100644 --- a/services/transcribe/medical_vocabularies_test.go +++ b/services/transcribe/medical_vocabularies_test.go @@ -1,13 +1,14 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -160,7 +161,7 @@ func TestGetMedicalVocabulary_NotFound(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "BadRequestException", body["__type"]) }) } @@ -201,7 +202,7 @@ func TestMedicalVocabulary_LastModifiedTimeAndFailureReason(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createRaw map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw, jsonx.V1())) assert.Contains(t, createRaw, "LastModifiedTime") getRec := doTranscribeRequest(t, h, "GetMedicalVocabulary", map[string]any{ @@ -210,7 +211,7 @@ func TestMedicalVocabulary_LastModifiedTimeAndFailureReason(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getRaw map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw, jsonx.V1())) _, hasFailureReason := getRaw["FailureReason"] assert.False(t, hasFailureReason, "FailureReason must be omitted (empty) on success") @@ -221,6 +222,6 @@ func TestMedicalVocabulary_LastModifiedTimeAndFailureReason(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code) var updateRaw map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw, jsonx.V1())) assert.Contains(t, updateRaw, "LastModifiedTime") } diff --git a/services/transcribe/multi_region_test.go b/services/transcribe/multi_region_test.go index 45aa198007..c3410b63f6 100644 --- a/services/transcribe/multi_region_test.go +++ b/services/transcribe/multi_region_test.go @@ -2,7 +2,8 @@ package transcribe_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -182,8 +184,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/transcribe/persistence.go b/services/transcribe/persistence.go index 0ba1cd05cb..d647308592 100644 --- a/services/transcribe/persistence.go +++ b/services/transcribe/persistence.go @@ -2,9 +2,11 @@ package transcribe import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -34,7 +36,7 @@ const transcribeSnapshotVersion = 2 // decoding a snapshot from an incompatible (older or newer) build of this // backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourceTags map[string]map[string]string `json:"resourceTags"` Version int `json:"version"` } @@ -57,7 +59,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { b.mu.RLock("Snapshot") defer b.mu.RUnlock() - var tables map[string]json.RawMessage + var tables map[string]jsontext.Value tables, tblErr = b.registry.SnapshotAll() @@ -74,7 +76,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { return nil } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "transcribe: failed to marshal snapshot", "error", err) diff --git a/services/transcribe/realism_test.go b/services/transcribe/realism_test.go index d3cf01ac9c..6d14402b79 100644 --- a/services/transcribe/realism_test.go +++ b/services/transcribe/realism_test.go @@ -1,7 +1,7 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "testing" "testing/synctest" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -115,7 +116,7 @@ func TestRealism_RequestValidation(t *testing.T) { rec := doTranscribeRequest(t, newTestTranscribeHandler(t), tt.action, tt.body) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) assert.Contains(t, resp["message"], tt.wantMsg) assert.NotContains(t, resp["message"], "Exception: ") diff --git a/services/transcribe/s3_output_test.go b/services/transcribe/s3_output_test.go index f7cbea84f4..82dc9c203f 100644 --- a/services/transcribe/s3_output_test.go +++ b/services/transcribe/s3_output_test.go @@ -2,7 +2,8 @@ package transcribe_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "sync" "testing" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -84,10 +86,10 @@ func TestTranscriptionJobWritesOutput(t *testing.T) { Transcripts []struct { Transcript string `json:"transcript"` } `json:"transcripts"` - Items []json.RawMessage `json:"items"` + Items []jsontext.Value `json:"items"` } `json:"results"` } - require.NoError(t, json.Unmarshal(w.objects[tt.wantKey], &doc)) + require.NoError(t, json.Unmarshal(w.objects[tt.wantKey], &doc, jsonx.V1())) assert.Equal(t, "j", doc.JobName) assert.Equal(t, "COMPLETED", doc.Status) assert.NotEmpty(t, doc.Results.Transcripts) @@ -146,7 +148,7 @@ func TestCallAnalyticsJobWritesOutput(t *testing.T) { } var doc map[string]any - require.NoError(t, json.Unmarshal(w.objects[tt.wantKey], &doc)) + require.NoError(t, json.Unmarshal(w.objects[tt.wantKey], &doc, jsonx.V1())) assert.Equal(t, "COMPLETED", doc["JobStatus"]) assert.NotEmpty(t, doc["Transcript"]) assert.NotEmpty(t, doc["Participants"]) diff --git a/services/transcribe/tags_test.go b/services/transcribe/tags_test.go index f8f96d0225..b1f583a9b2 100644 --- a/services/transcribe/tags_test.go +++ b/services/transcribe/tags_test.go @@ -1,13 +1,14 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -307,7 +308,7 @@ func TestHTTP_ListTagsForResource_ReturnsStoredTags(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagMap := make(map[string]string, len(resp.Tags)) for _, tag := range resp.Tags { tagMap[tag.Key] = tag.Value @@ -344,7 +345,7 @@ func TestHTTP_UntagResource(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagMap := make(map[string]string, len(resp.Tags)) for _, tag := range resp.Tags { tagMap[tag.Key] = tag.Value diff --git a/services/transcribe/transcript_synthesis.go b/services/transcribe/transcript_synthesis.go index fc8a8bc5e6..9c91ea9f3d 100644 --- a/services/transcribe/transcript_synthesis.go +++ b/services/transcribe/transcript_synthesis.go @@ -1,9 +1,11 @@ package transcribe import ( - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // syntheticWordBaseDuration is the base duration in seconds for synthetic word timing. @@ -17,7 +19,7 @@ const syntheticWordGap = 0.05 // marshalJSON marshals v to JSON bytes, returning nil on error (only used for synthetic output). func marshalJSON(v any) ([]byte, error) { - return json.Marshal(v) + return json.Marshal(v, jsonx.V1()) } // deriveTranscriptText produces a transcript string from the job name and media URI. diff --git a/services/transcribe/transcription_jobs_test.go b/services/transcribe/transcription_jobs_test.go index de1ac43899..487737e88c 100644 --- a/services/transcribe/transcription_jobs_test.go +++ b/services/transcribe/transcription_jobs_test.go @@ -1,7 +1,7 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -898,7 +899,7 @@ func TestListTranscriptionJobsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, summariesOK := resp["TranscriptionJobSummaries"].([]any) require.True(t, summariesOK) @@ -919,7 +920,7 @@ func TestListTranscriptionJobsPagination(t *testing.T) { assert.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) summaries2, summaries2OK := resp2["TranscriptionJobSummaries"].([]any) require.True(t, summaries2OK) @@ -1069,7 +1070,7 @@ func TestMediaFileUri_JSONKey(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) job, _ := raw["TranscriptionJob"].(map[string]any) require.NotNil(t, job, "TranscriptionJob must be present") @@ -1102,7 +1103,7 @@ func TestTranscriptionJob_MediaInResponse(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &raw, jsonx.V1())) job, _ := raw["TranscriptionJob"].(map[string]any) require.NotNil(t, job) @@ -1131,7 +1132,7 @@ func TestTranscriptionJob_OutputBucketNotInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) job, _ := raw["TranscriptionJob"].(map[string]any) require.NotNil(t, job) @@ -1194,7 +1195,7 @@ func TestTranscriptionJob_LanguageIdSettings_Echoed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) job, _ := raw["TranscriptionJob"].(map[string]any) require.NotNil(t, job) diff --git a/services/transcribe/vocabularies_test.go b/services/transcribe/vocabularies_test.go index 2dcb0f5ece..26f810a331 100644 --- a/services/transcribe/vocabularies_test.go +++ b/services/transcribe/vocabularies_test.go @@ -1,7 +1,7 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -278,7 +279,7 @@ func TestGetVocabulary_DownloadUri(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw, jsonx.V1())) assert.NotEmpty(t, raw["DownloadUri"], "DownloadUri must be present in GetVocabulary response") } @@ -301,7 +302,7 @@ func TestGetVocabulary_LastModifiedTime(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var raw map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &raw, jsonx.V1())) assert.NotNil(t, raw["LastModifiedTime"], "LastModifiedTime must be present in GetVocabulary response") } @@ -464,7 +465,7 @@ func TestCreateVocabulary_LastModifiedTimeAndFailureReasonEchoed(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) _, hasLastModified := raw["LastModifiedTime"] assert.True(t, hasLastModified, "CreateVocabularyOutput must include LastModifiedTime") @@ -488,6 +489,6 @@ func TestListVocabularies_EchoesStatusFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var raw map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &raw, jsonx.V1())) assert.Equal(t, "READY", raw["Status"]) } diff --git a/services/transcribe/vocabulary_filters_test.go b/services/transcribe/vocabulary_filters_test.go index eb4b4ac4fb..dd099ab53b 100644 --- a/services/transcribe/vocabulary_filters_test.go +++ b/services/transcribe/vocabulary_filters_test.go @@ -1,13 +1,14 @@ package transcribe_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transcribe" ) @@ -340,7 +341,7 @@ func TestVocabularyFilter_LastModifiedTimeAndDownloadUri(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createRaw map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createRaw, jsonx.V1())) assert.Contains(t, createRaw, "LastModifiedTime", "CreateVocabularyFilterOutput must include LastModifiedTime") getRec := doTranscribeRequest(t, h, "GetVocabularyFilter", map[string]any{ @@ -349,7 +350,7 @@ func TestVocabularyFilter_LastModifiedTimeAndDownloadUri(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String()) var getRaw map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRaw, jsonx.V1())) assert.Contains(t, getRaw, "LastModifiedTime", "GetVocabularyFilterOutput must include LastModifiedTime") assert.Equal(t, "s3://bucket/filter.txt", getRaw["DownloadUri"]) @@ -360,6 +361,6 @@ func TestVocabularyFilter_LastModifiedTimeAndDownloadUri(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code, updateRec.Body.String()) var updateRaw map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateRaw, jsonx.V1())) assert.Contains(t, updateRaw, "LastModifiedTime", "UpdateVocabularyFilterOutput must include LastModifiedTime") } diff --git a/services/transfer/handler.go b/services/transfer/handler.go index 29e3f9c55c..e99c7501a9 100644 --- a/services/transfer/handler.go +++ b/services/transfer/handler.go @@ -2,7 +2,7 @@ package transfer import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/collections" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" @@ -221,7 +222,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { ServerID string `json:"ServerId"` UserName string `json:"UserName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.ServerID != "" && req.UserName != "" { return req.ServerID + "/" + req.UserName @@ -340,13 +341,10 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, errInvalidNextToken): return c.JSON(http.StatusBadRequest, map[string]string{ @@ -366,8 +364,8 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err case errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownAction), - errors.As(err, &syntaxErr), - errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), + jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyTypeField: "InvalidRequestException", keyMessageField: publicMessage(err), diff --git a/services/transfer/handler_access_test.go b/services/transfer/handler_access_test.go index 14f7df68f3..464d323a65 100644 --- a/services/transfer/handler_access_test.go +++ b/services/transfer/handler_access_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -26,7 +27,7 @@ func TestHandler_UpdateAccessFullFieldsRoundtrip(t *testing.T) { var srvOut struct { ServerID string `json:"ServerId"` } - require.NoError(t, json.Unmarshal(createSrvRec.Body.Bytes(), &srvOut)) + require.NoError(t, json.Unmarshal(createSrvRec.Body.Bytes(), &srvOut, jsonx.V1())) createAccessRec := doTransferRequest(t, h, "CreateAccess", map[string]any{ "ServerId": srvOut.ServerID, @@ -65,7 +66,7 @@ func TestHandler_UpdateAccessFullFieldsRoundtrip(t *testing.T) { Policy string `json:"Policy"` } `json:"Access"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "LOGICAL", descOut.Access.HomeDirectoryType) assert.JSONEq(t, `{"Version":"2012-10-17","Statement":[]}`, descOut.Access.Policy) @@ -118,7 +119,7 @@ func TestHandler_ListAccessesIncludesHomeDirectoryType(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) accesses := listResp["Accesses"].([]any) require.Len(t, accesses, 1) @@ -167,7 +168,7 @@ func TestHandler_CreateAccess(t *testing.T) { if tt.wantKey != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantKey]) } }) @@ -302,7 +303,7 @@ func TestHandler_DescribeAccess(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Access"]) } @@ -381,7 +382,7 @@ func TestHandler_CreateAccessPosixProfile(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) access := descResp["Access"].(map[string]any) posix := access["PosixProfile"].(map[string]any) diff --git a/services/transfer/handler_agreements_test.go b/services/transfer/handler_agreements_test.go index 8f7b8ea524..2a59af863e 100644 --- a/services/transfer/handler_agreements_test.go +++ b/services/transfer/handler_agreements_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -23,7 +24,7 @@ func TestHandler_DescribeAgreementIncludesArnAndTags(t *testing.T) { }) require.Equal(t, http.StatusOK, localRec.Code) var localResp map[string]any - require.NoError(t, json.Unmarshal(localRec.Body.Bytes(), &localResp)) + require.NoError(t, json.Unmarshal(localRec.Body.Bytes(), &localResp, jsonx.V1())) localProfileID := localResp["ProfileId"].(string) partnerRec := doTransferRequest(t, h, "CreateProfile", map[string]any{ @@ -31,13 +32,13 @@ func TestHandler_DescribeAgreementIncludesArnAndTags(t *testing.T) { }) require.Equal(t, http.StatusOK, partnerRec.Code) var partnerResp map[string]any - require.NoError(t, json.Unmarshal(partnerRec.Body.Bytes(), &partnerResp)) + require.NoError(t, json.Unmarshal(partnerRec.Body.Bytes(), &partnerResp, jsonx.V1())) partnerProfileID := partnerResp["ProfileId"].(string) serverRec := doTransferRequest(t, h, "CreateServer", map[string]any{}) require.Equal(t, http.StatusOK, serverRec.Code) var serverResp map[string]any - require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp)) + require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp, jsonx.V1())) serverID := serverResp["ServerId"].(string) agRec := doTransferRequest(t, h, "CreateAgreement", map[string]any{ @@ -50,7 +51,7 @@ func TestHandler_DescribeAgreementIncludesArnAndTags(t *testing.T) { }) require.Equal(t, http.StatusOK, agRec.Code) var agResp map[string]any - require.NoError(t, json.Unmarshal(agRec.Body.Bytes(), &agResp)) + require.NoError(t, json.Unmarshal(agRec.Body.Bytes(), &agResp, jsonx.V1())) agreementID := agResp["AgreementId"].(string) rec := doTransferRequest(t, h, "DescribeAgreement", map[string]any{ @@ -60,7 +61,7 @@ func TestHandler_DescribeAgreementIncludesArnAndTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ag := resp["Agreement"].(map[string]any) arn, hasArn := ag["Arn"].(string) @@ -116,7 +117,7 @@ func TestHandler_CreateAgreement(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["AgreementId"]) } }) @@ -248,7 +249,7 @@ func TestHandler_DescribeAgreement(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) agreementID := createResp["AgreementId"].(string) rec := doTransferRequest(t, h, "DescribeAgreement", map[string]any{ @@ -291,7 +292,7 @@ func TestHandler_UpdateAgreement(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) agreementID := createResp["AgreementId"].(string) rec := doTransferRequest(t, h, "UpdateAgreement", map[string]any{ @@ -328,7 +329,7 @@ func TestHandler_ListAgreementsEnrichedFields(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) agreements := listResp["Agreements"].([]any) require.Len(t, agreements, 1) @@ -376,7 +377,7 @@ func TestHandler_CreateAgreementInactiveStatus(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) agID := createResp["AgreementId"].(string) descRec := doTransferRequest(t, h, "DescribeAgreement", map[string]any{ @@ -386,7 +387,7 @@ func TestHandler_CreateAgreementInactiveStatus(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) ag := descResp["Agreement"].(map[string]any) assert.Equal(t, "INACTIVE", ag["Status"]) } diff --git a/services/transfer/handler_certificates_test.go b/services/transfer/handler_certificates_test.go index d6ad6485a9..c3f42d3086 100644 --- a/services/transfer/handler_certificates_test.go +++ b/services/transfer/handler_certificates_test.go @@ -1,13 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -79,7 +80,7 @@ func TestHandler_DescribeCertificateBodyReturnedWhenPresent(t *testing.T) { var importOut struct { CertificateID string `json:"CertificateId"` } - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importOut, jsonx.V1())) descRec := doTransferRequest(t, h, "DescribeCertificate", map[string]any{ "CertificateId": importOut.CertificateID, @@ -91,7 +92,7 @@ func TestHandler_DescribeCertificateBodyReturnedWhenPresent(t *testing.T) { Certificate string `json:"Certificate"` } `json:"Certificate"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, testCertPEM, descOut.Certificate.Certificate, "DescribeCertificate must return the certificate body when present") @@ -108,7 +109,7 @@ func TestHandler_DescribeCertificateIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["CertificateId"].(string) rec := doTransferRequest(t, h, "DescribeCertificate", map[string]any{ @@ -117,7 +118,7 @@ func TestHandler_DescribeCertificateIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cert := resp["Certificate"].(map[string]any) arn, hasArn := cert["Arn"].(string) @@ -139,7 +140,7 @@ func TestHandler_ListCertificatesIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) certs := resp["Certificates"].([]any) require.NotEmpty(t, certs) @@ -189,7 +190,7 @@ func TestHandler_CertificateARNContainsAccountAndRegion(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["CertificateId"].(string) rec := doTransferRequest(t, h, "DescribeCertificate", map[string]any{ @@ -198,7 +199,7 @@ func TestHandler_CertificateARNContainsAccountAndRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cert := resp["Certificate"].(map[string]any) // newTestHandler uses testAccountID = "123456789012" and testRegion = "us-east-1" @@ -284,7 +285,7 @@ func TestHandler_DescribeCertificate(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["CertificateId"].(string) rec := doTransferRequest(t, h, "DescribeCertificate", map[string]any{ @@ -314,7 +315,7 @@ func TestHandler_UpdateCertificate(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["CertificateId"].(string) rec := doTransferRequest(t, h, "UpdateCertificate", map[string]any{ @@ -357,7 +358,7 @@ func TestHandler_ImportCertificateMalformedPEM(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", resp["__type"]) } @@ -373,7 +374,7 @@ func TestHandler_ImportCertificateValidPEMExtractsDates(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) certID := createResp["CertificateId"].(string) descRec := doTransferRequest(t, h, "DescribeCertificate", map[string]any{ @@ -382,7 +383,7 @@ func TestHandler_ImportCertificateValidPEMExtractsDates(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) cert := descResp["Certificate"].(map[string]any) assert.NotEmpty(t, cert["NotBeforeDate"], "NotBeforeDate must be set from PEM") diff --git a/services/transfer/handler_connectors_test.go b/services/transfer/handler_connectors_test.go index a16bdfd297..68934b9352 100644 --- a/services/transfer/handler_connectors_test.go +++ b/services/transfer/handler_connectors_test.go @@ -1,13 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -34,7 +35,7 @@ func TestHandler_UpdateConnectorLoggingRoleAndSecurityPolicyName(t *testing.T) { var createOut struct { ConnectorID string `json:"ConnectorId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) updateRec := doTransferRequest(t, h, "UpdateConnector", map[string]any{ "ConnectorId": createOut.ConnectorID, @@ -54,7 +55,7 @@ func TestHandler_UpdateConnectorLoggingRoleAndSecurityPolicyName(t *testing.T) { SecurityPolicyName string `json:"SecurityPolicyName"` } `json:"Connector"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, "arn:aws:iam::123456789012:role/TransferLogging", descOut.Connector.LoggingRole) assert.Equal(t, "TransferSecurityPolicy-2024-01", descOut.Connector.SecurityPolicyName) @@ -107,7 +108,7 @@ func TestHandler_CreateConnector(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ConnectorId"]) } }) @@ -174,7 +175,7 @@ func TestHandler_DescribeConnector(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "DescribeConnector", map[string]any{ @@ -203,7 +204,7 @@ func TestHandler_UpdateConnector(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "UpdateConnector", map[string]any{ @@ -225,7 +226,7 @@ func TestHandler_TestConnection(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "TestConnection", map[string]any{ @@ -246,7 +247,7 @@ func TestHandler_StartFileTransfer(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "StartFileTransfer", map[string]any{ @@ -268,7 +269,7 @@ func TestHandler_StartDirectoryListing(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "StartDirectoryListing", map[string]any{ @@ -279,7 +280,7 @@ func TestHandler_StartDirectoryListing(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ListingId"], "ListingId is a required field on StartDirectoryListingOutput") assert.Equal(t, connectorID+"-"+resp["ListingId"].(string)+".json", resp["OutputFileName"]) } @@ -296,7 +297,7 @@ func TestHandler_ListFileTransferResults(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "ListFileTransferResults", map[string]any{ @@ -318,7 +319,7 @@ func TestHandler_StartRemoteDelete(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "StartRemoteDelete", map[string]any{ @@ -340,7 +341,7 @@ func TestHandler_StartRemoteMove(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) rec := doTransferRequest(t, h, "StartRemoteMove", map[string]any{ @@ -368,7 +369,7 @@ func TestHandler_StartFileTransferPersistsRecord(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) transferID := startResp["TransferId"].(string) assert.NotEmpty(t, transferID) @@ -379,7 +380,7 @@ func TestHandler_StartFileTransferPersistsRecord(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) results := listResp["FileTransferResults"].([]any) require.Len(t, results, 1) @@ -407,7 +408,7 @@ func TestHandler_DescribeConnectorLoggingRole(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) descRec := doTransferRequest(t, h, "DescribeConnector", map[string]any{ @@ -416,7 +417,7 @@ func TestHandler_DescribeConnectorLoggingRole(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) connector := descResp["Connector"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/logging", connector["LoggingRole"]) @@ -450,8 +451,8 @@ func TestHandler_StartDirectoryListingUniqueId(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var r1, r2 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1)) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &r1, jsonx.V1())) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &r2, jsonx.V1())) id1 := r1["ListingId"].(string) id2 := r2["ListingId"].(string) @@ -513,7 +514,7 @@ func TestHandler_StartRemoteDelete_ReturnsDeleteId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["DeleteId"]) assert.NotContains(t, resp, "TransferId", "StartRemoteDeleteOutput has no TransferId field in real AWS") @@ -540,7 +541,7 @@ func TestHandler_StartRemoteMove_ReturnsMoveId(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["MoveId"]) assert.NotContains(t, resp, "TransferId", "StartRemoteMoveOutput has no TransferId field in real AWS") @@ -577,7 +578,7 @@ func TestHandler_ConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) descRec := doTransferRequest(t, h, "DescribeConnector", map[string]any{ @@ -586,7 +587,7 @@ func TestHandler_ConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) connector := descResp["Connector"].(map[string]any) assert.Equal(t, tt.ipAddressType, connector["IpAddressType"]) assert.Contains(t, descRec.Body.String(), `"IpAddressType":"`+tt.ipAddressType+`"`) @@ -603,7 +604,7 @@ func TestHandler_ConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) descRec := doTransferRequest(t, h, "DescribeConnector", map[string]any{ @@ -612,7 +613,7 @@ func TestHandler_ConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) connector := descResp["Connector"].(map[string]any) _, hasIPAddressType := connector["IpAddressType"] assert.False(t, hasIPAddressType, "IpAddressType must be absent, not an empty string, when unset") @@ -634,7 +635,7 @@ func TestHandler_UpdateConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) connectorID := createResp["ConnectorId"].(string) updateRec := doTransferRequest(t, h, "UpdateConnector", map[string]any{ @@ -649,7 +650,7 @@ func TestHandler_UpdateConnectorIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) connector := descResp["Connector"].(map[string]any) assert.Equal(t, "DUALSTACK", connector["IpAddressType"]) } diff --git a/services/transfer/handler_host_keys_test.go b/services/transfer/handler_host_keys_test.go index 9eaeba8036..1ed9d2fcff 100644 --- a/services/transfer/handler_host_keys_test.go +++ b/services/transfer/handler_host_keys_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // testSSHHostKeyBody is a syntactically valid (but not cryptographically @@ -25,7 +27,7 @@ func TestHandler_DescribeHostKeyIncludesFingerprint(t *testing.T) { serverRec := doTransferRequest(t, h, "CreateServer", map[string]any{}) require.Equal(t, http.StatusOK, serverRec.Code) var serverResp map[string]any - require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp)) + require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp, jsonx.V1())) serverID := serverResp["ServerId"].(string) importRec := doTransferRequest(t, h, "ImportHostKey", map[string]any{ @@ -36,7 +38,7 @@ func TestHandler_DescribeHostKeyIncludesFingerprint(t *testing.T) { }) require.Equal(t, http.StatusOK, importRec.Code) var importResp map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp, jsonx.V1())) hostKeyID := importResp["HostKeyId"].(string) rec := doTransferRequest(t, h, "DescribeHostKey", map[string]any{ @@ -46,7 +48,7 @@ func TestHandler_DescribeHostKeyIncludesFingerprint(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hk := resp["HostKey"].(map[string]any) fp, hasFp := hk["HostKeyFingerprint"].(string) @@ -75,7 +77,7 @@ func TestHandler_ListHostKeysIncludesFingerprintAndArn(t *testing.T) { serverRec := doTransferRequest(t, h, "CreateServer", map[string]any{}) require.Equal(t, http.StatusOK, serverRec.Code) var serverResp map[string]any - require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp)) + require.NoError(t, json.Unmarshal(serverRec.Body.Bytes(), &serverResp, jsonx.V1())) serverID := serverResp["ServerId"].(string) doTransferRequest(t, h, "ImportHostKey", map[string]any{ @@ -90,7 +92,7 @@ func TestHandler_ListHostKeysIncludesFingerprintAndArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) hostKeys := resp["HostKeys"].([]any) require.NotEmpty(t, hostKeys) @@ -150,7 +152,7 @@ func TestHandler_DescribeHostKey(t *testing.T) { require.Equal(t, http.StatusOK, importRec.Code) var importResp map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp, jsonx.V1())) hostKeyID := importResp["HostKeyId"].(string) rec := doTransferRequest(t, h, "DescribeHostKey", map[string]any{ @@ -174,7 +176,7 @@ func TestHandler_DeleteHostKey(t *testing.T) { require.Equal(t, http.StatusOK, importRec.Code) var importResp map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp, jsonx.V1())) hostKeyID := importResp["HostKeyId"].(string) rec := doTransferRequest(t, h, "DeleteHostKey", map[string]any{ @@ -198,7 +200,7 @@ func TestHandler_UpdateHostKey(t *testing.T) { require.Equal(t, http.StatusOK, importRec.Code) var importResp map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp, jsonx.V1())) hostKeyID := importResp["HostKeyId"].(string) rec := doTransferRequest(t, h, "UpdateHostKey", map[string]any{ diff --git a/services/transfer/handler_profiles_test.go b/services/transfer/handler_profiles_test.go index 6c88bec2de..d1e943b2af 100644 --- a/services/transfer/handler_profiles_test.go +++ b/services/transfer/handler_profiles_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ProfileCertificateIDsRoundtrip verifies that CertificateIds set via UpdateProfile @@ -25,7 +27,7 @@ func TestHandler_ProfileCertificateIDsRoundtrip(t *testing.T) { var createOut struct { ProfileID string `json:"ProfileId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) certIDs := []string{"cert-aaa111", "cert-bbb222"} updateRec := doTransferRequest(t, h, "UpdateProfile", map[string]any{ @@ -44,7 +46,7 @@ func TestHandler_ProfileCertificateIDsRoundtrip(t *testing.T) { CertificateIDs []string `json:"CertificateIds"` } `json:"Profile"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descOut, jsonx.V1())) assert.Equal(t, certIDs, descOut.Profile.CertificateIDs) } @@ -62,7 +64,7 @@ func TestHandler_DescribeProfileIncludesArnAndTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) profileID := createResp["ProfileId"].(string) rec := doTransferRequest(t, h, "DescribeProfile", map[string]any{ @@ -71,7 +73,7 @@ func TestHandler_DescribeProfileIncludesArnAndTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) profile := resp["Profile"].(map[string]any) arn, hasArn := profile["Arn"].(string) @@ -98,7 +100,7 @@ func TestHandler_ListProfilesIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) profiles := resp["Profiles"].([]any) require.NotEmpty(t, profiles) @@ -119,7 +121,7 @@ func TestHandler_ProfileARNContainsAccountAndRegion(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) profileID := createResp["ProfileId"].(string) rec := doTransferRequest(t, h, "DescribeProfile", map[string]any{ @@ -128,7 +130,7 @@ func TestHandler_ProfileARNContainsAccountAndRegion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) profile := resp["Profile"].(map[string]any) // newTestHandler uses testAccountID = "123456789012" and testRegion = "us-east-1" @@ -192,7 +194,7 @@ func TestHandler_CreateProfileReturnsProfileID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ProfileId"]) } @@ -237,7 +239,7 @@ func TestHandler_CreateProfile(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["ProfileId"]) } }) @@ -256,7 +258,7 @@ func TestHandler_DescribeProfile(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) profileID := createResp["ProfileId"].(string) rec := doTransferRequest(t, h, "DescribeProfile", map[string]any{ @@ -285,7 +287,7 @@ func TestHandler_DeleteProfile(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) profileID := createResp["ProfileId"].(string) rec := doTransferRequest(t, h, "DeleteProfile", map[string]any{ @@ -306,7 +308,7 @@ func TestHandler_UpdateProfile(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) profileID := createResp["ProfileId"].(string) rec := doTransferRequest(t, h, "UpdateProfile", map[string]any{ diff --git a/services/transfer/handler_security_policies_test.go b/services/transfer/handler_security_policies_test.go index 3408cb257c..af63279761 100644 --- a/services/transfer/handler_security_policies_test.go +++ b/services/transfer/handler_security_policies_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ListSecurityPolicies(t *testing.T) { @@ -42,7 +44,7 @@ func TestHandler_ListSecurityPoliciesIncludesCurrentNames(t *testing.T) { var resp struct { SecurityPolicyNames []string `json:"SecurityPolicyNames"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) names := make(map[string]bool, len(resp.SecurityPolicyNames)) for _, n := range resp.SecurityPolicyNames { @@ -85,7 +87,7 @@ func TestHandler_DescribeSecurityPolicy_ServerShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pol := resp["SecurityPolicy"].(map[string]any) assert.Equal(t, "SERVER", pol["Type"]) @@ -109,7 +111,7 @@ func TestHandler_DescribeSecurityPolicy_ConnectorShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pol := resp["SecurityPolicy"].(map[string]any) assert.Equal(t, "CONNECTOR", pol["Type"]) @@ -129,6 +131,6 @@ func TestHandler_DescribeSecurityPolicy_UnknownName(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } diff --git a/services/transfer/handler_servers_fields_test.go b/services/transfer/handler_servers_fields_test.go index e43501f48e..2d2e6a3074 100644 --- a/services/transfer/handler_servers_fields_test.go +++ b/services/transfer/handler_servers_fields_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Test 1: CreateServer with IdentityProviderType=API_GATEWAY is echoed by DescribeServer. @@ -26,7 +28,7 @@ func TestHandler_CreateServerIdentityProviderTypeAPIGateway(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{ @@ -35,7 +37,7 @@ func TestHandler_CreateServerIdentityProviderTypeAPIGateway(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.Equal(t, "API_GATEWAY", server["IdentityProviderType"]) @@ -65,7 +67,7 @@ func TestHandler_CreateServerDomainEFS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{ @@ -74,7 +76,7 @@ func TestHandler_CreateServerDomainEFS(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.Equal(t, "EFS", server["Domain"]) @@ -89,7 +91,7 @@ func TestHandler_UpdateServerLoggingRoleAndBanners(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) updateRec := doTransferRequest(t, h, "UpdateServer", map[string]any{ @@ -106,7 +108,7 @@ func TestHandler_UpdateServerLoggingRoleAndBanners(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.Equal(t, "arn:aws:iam::000000000000:role/logging", server["LoggingRole"]) @@ -124,7 +126,7 @@ func TestHandler_StartServerStartingState(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // Stop the server first so we can start it. @@ -134,7 +136,7 @@ func TestHandler_StartServerStartingState(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var resp map[string]any - _ = json.Unmarshal(descRec.Body.Bytes(), &resp) + _ = json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1()) require.Equal(t, "OFFLINE", resp["Server"].(map[string]any)["State"].(string)) // Start the server: immediate state should be STARTING. @@ -144,14 +146,14 @@ func TestHandler_StartServerStartingState(t *testing.T) { // Check immediately — should be STARTING (async transition hasn't fired yet). descRec = doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) immediateState := descResp["Server"].(map[string]any)["State"].(string) assert.Equal(t, "STARTING", immediateState) // Wait past the async transition to ONLINE. time.Sleep(serverTransitionWait) descRec = doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) - _ = json.Unmarshal(descRec.Body.Bytes(), &descResp) + _ = json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1()) assert.Equal(t, "ONLINE", descResp["Server"].(map[string]any)["State"].(string)) }) } @@ -173,7 +175,7 @@ func TestHandler_TestIdentityProviderKnownUser(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.EqualValues(t, 200, resp["StatusCode"]) } @@ -192,7 +194,7 @@ func TestHandler_TestIdentityProviderUnknownUser(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.EqualValues(t, 401, resp["StatusCode"]) } @@ -205,7 +207,7 @@ func TestHandler_UpdateServerProtocolDetails(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) updateRec := doTransferRequest(t, h, "UpdateServer", map[string]any{ @@ -224,7 +226,7 @@ func TestHandler_UpdateServerProtocolDetails(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) pd := server["ProtocolDetails"].(map[string]any) @@ -249,14 +251,14 @@ func TestHandler_CreateServerEndpointTypeVPC(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.Equal(t, "VPC", server["EndpointType"]) @@ -279,14 +281,14 @@ func TestHandler_CreateServerWorkflowDetails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) wd := server["WorkflowDetails"].(map[string]any) onUpload := wd["OnUpload"].([]any) @@ -308,14 +310,14 @@ func TestHandler_CreateServerS3StorageOptions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) s3 := server["S3StorageOptions"].(map[string]any) assert.Equal(t, "ENABLED", s3["DirectoryListingOptimization"]) @@ -332,14 +334,14 @@ func TestHandler_CreateServerStructuredLogDestinations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) dests := server["StructuredLogDestinations"].([]any) require.Len(t, dests, 1) @@ -357,14 +359,14 @@ func TestHandler_CreateServerIpAddressType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.Equal(t, "DUALSTACK", server["IpAddressType"]) } diff --git a/services/transfer/handler_servers_test.go b/services/transfer/handler_servers_test.go index 7c6c32d6cc..b679da8f8e 100644 --- a/services/transfer/handler_servers_test.go +++ b/services/transfer/handler_servers_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "testing/synctest" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -23,7 +24,7 @@ func mustCreateServer(t *testing.T, h *transfer.Handler) string { var out struct { ServerID string `json:"ServerId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out.ServerID } @@ -40,7 +41,7 @@ func TestHandler_DeleteServerCascade(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // Create access and agreement on the server @@ -66,7 +67,7 @@ func TestHandler_DeleteServerCascade(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var resp map[string]any - _ = json.Unmarshal(descRec.Body.Bytes(), &resp) + _ = json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1()) require.Equal(t, "OFFLINE", resp["Server"].(map[string]any)["State"].(string)) deleteRec := doTransferRequest(t, h, "DeleteServer", map[string]any{"ServerId": serverID}) @@ -93,7 +94,7 @@ func TestHandler_DeleteServerOnlineSucceeds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // A real server self-starts after CreateServer; see TestHandler_CreateServer. @@ -101,7 +102,7 @@ func TestHandler_DeleteServerOnlineSucceeds(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var resp map[string]any - _ = json.Unmarshal(descRec.Body.Bytes(), &resp) + _ = json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1()) require.Equal(t, "ONLINE", resp["Server"].(map[string]any)["State"].(string)) delRec := doTransferRequest(t, h, "DeleteServer", map[string]any{"ServerId": serverID}) @@ -125,7 +126,7 @@ func TestHandler_ListServersIncludesIdentityProviderType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // Add a user to check UserCount. @@ -136,7 +137,7 @@ func TestHandler_ListServersIncludesIdentityProviderType(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) servers := listResp["Servers"].([]any) require.Len(t, servers, 1) @@ -161,7 +162,7 @@ func TestHandler_DescribeServerUserCount(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": s.ServerID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server := descResp["Server"].(map[string]any) assert.EqualValues(t, 0, server["UserCount"]) @@ -173,7 +174,7 @@ func TestHandler_DescribeServerUserCount(t *testing.T) { descRec = doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": s.ServerID}) require.Equal(t, http.StatusOK, descRec.Code) - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) server = descResp["Server"].(map[string]any) assert.EqualValues(t, 2, server["UserCount"]) } @@ -226,7 +227,7 @@ func TestHandler_CreateServer(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp[tt.wantKey]) }) } @@ -242,7 +243,7 @@ func TestHandler_DescribeServer(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) tests := []struct { @@ -286,7 +287,7 @@ func TestHandler_ListServers(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.IsType(t, []any{}, resp["Servers"]) } @@ -355,7 +356,7 @@ func TestHandler_DeleteServer(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // AWS requires server to be OFFLINE before deletion; stop it first. @@ -365,7 +366,7 @@ func TestHandler_DeleteServer(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var resp map[string]any - _ = json.Unmarshal(descRec.Body.Bytes(), &resp) + _ = json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1()) require.Equal(t, "OFFLINE", resp["Server"].(map[string]any)["State"].(string)) rec := doTransferRequest(t, h, "DeleteServer", map[string]any{"ServerId": serverID}) @@ -386,7 +387,7 @@ func TestHandler_UpdateServer(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) rec := doTransferRequest(t, h, "UpdateServer", map[string]any{ @@ -437,7 +438,7 @@ func TestHandler_ListServers_Pagination(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) servers := resp["Servers"].([]any) assert.GreaterOrEqual(t, len(servers), tt.wantMinCount) @@ -497,7 +498,7 @@ func TestHandler_StartStopDeleteServer(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) body := tt.body @@ -513,7 +514,7 @@ func TestHandler_StartStopDeleteServer(t *testing.T) { descRec := doTransferRequest(t, h, "DescribeServer", map[string]any{"ServerId": serverID}) var resp map[string]any - _ = json.Unmarshal(descRec.Body.Bytes(), &resp) + _ = json.Unmarshal(descRec.Body.Bytes(), &resp, jsonx.V1()) require.Equal(t, "OFFLINE", resp["Server"].(map[string]any)["State"].(string)) } diff --git a/services/transfer/handler_ssh_keys_test.go b/services/transfer/handler_ssh_keys_test.go index 167e1f2105..1ca9733cea 100644 --- a/services/transfer/handler_ssh_keys_test.go +++ b/services/transfer/handler_ssh_keys_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_ImportSSHPublicKey(t *testing.T) { @@ -53,7 +55,7 @@ func TestHandler_DeleteSSHPublicKey(t *testing.T) { require.Equal(t, http.StatusOK, importRec.Code) var importResp map[string]any - require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp)) + require.NoError(t, json.Unmarshal(importRec.Body.Bytes(), &importResp, jsonx.V1())) sshKeyID := importResp["SshPublicKeyId"].(string) rec := doTransferRequest(t, h, "DeleteSshPublicKey", map[string]any{ @@ -92,7 +94,7 @@ func TestHandler_ImportSshPublicKeyDuplicateBody(t *testing.T) { assert.Equal(t, http.StatusBadRequest, secondRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceExistsException", errResp["__type"]) } @@ -114,6 +116,6 @@ func TestHandler_ImportSshPublicKeyUnknownUserReturnsResourceNotFound(t *testing assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } diff --git a/services/transfer/handler_tags_test.go b/services/transfer/handler_tags_test.go index 746540b920..1a34c0f5a4 100644 --- a/services/transfer/handler_tags_test.go +++ b/services/transfer/handler_tags_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -31,7 +32,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisible(t *testing.T) { var createOut struct { ServerID string `json:"ServerId"` } - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createOut, jsonx.V1())) serverARN := "arn:aws:transfer:us-east-1:123456789012:server/" + createOut.ServerID listRec := doTransferRequest(t, h, "ListTagsForResource", map[string]any{ @@ -45,7 +46,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisible(t *testing.T) { Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tagMap := make(map[string]string, len(listOut.Tags)) @@ -88,7 +89,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin var out struct { AgreementID string `json:"AgreementId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return "arn:aws:transfer:us-east-1:123456789012:server/" + serverID + "/agreement/" + out.AgreementID }, @@ -107,7 +108,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin var out struct { ProfileID string `json:"ProfileId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return "arn:aws:transfer:us-east-1:123456789012:profile/" + out.ProfileID }, @@ -150,7 +151,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin var out struct { WebAppID string `json:"WebAppId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return "arn:aws:transfer:us-east-1:123456789012:webapp/" + out.WebAppID }, @@ -169,7 +170,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin var out struct { CertificateID string `json:"CertificateId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return "arn:aws:transfer:us-east-1:123456789012:certificate/" + out.CertificateID }, @@ -191,7 +192,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin var out struct { HostKeyID string `json:"HostKeyId"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return "arn:aws:transfer:us-east-1:123456789012:host-key/" + serverID + "/" + out.HostKeyID }, @@ -214,7 +215,7 @@ func TestHandler_ListTagsForResourceCreationTagsVisibleAcrossResources(t *testin Value string `json:"Value"` } `json:"Tags"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) tagMap := make(map[string]string, len(listOut.Tags)) for _, tag := range listOut.Tags { @@ -254,7 +255,7 @@ func TestHandler_TagResourceListTagsRoundTripServerARN(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) tagMap := make(map[string]string) @@ -293,7 +294,7 @@ func TestHandler_UntagResourceRoundTrip(t *testing.T) { listRec := doTransferRequest(t, h, "ListTagsForResource", map[string]any{"Arn": arnStr}) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags := listResp["Tags"].([]any) tagMap := make(map[string]string) @@ -342,7 +343,7 @@ func TestHandler_ListTagsForResource(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotNil(t, resp["Tags"]) } diff --git a/services/transfer/handler_test.go b/services/transfer/handler_test.go index 5790c69a53..7dd0ff3111 100644 --- a/services/transfer/handler_test.go +++ b/services/transfer/handler_test.go @@ -2,7 +2,7 @@ package transfer_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -147,7 +148,7 @@ func doTransferRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/services/transfer/handler_users_test.go b/services/transfer/handler_users_test.go index 59518d6f6b..2602eb7f1d 100644 --- a/services/transfer/handler_users_test.go +++ b/services/transfer/handler_users_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_ListUsersSshPublicKeyCountAndHomeDirectoryType verifies that ListUsers returns @@ -25,7 +27,7 @@ func TestHandler_ListUsersSshPublicKeyCountAndHomeDirectoryType(t *testing.T) { var srvOut struct { ServerID string `json:"ServerId"` } - require.NoError(t, json.Unmarshal(createSrvRec.Body.Bytes(), &srvOut)) + require.NoError(t, json.Unmarshal(createSrvRec.Body.Bytes(), &srvOut, jsonx.V1())) createUserRec := doTransferRequest(t, h, "CreateUser", map[string]any{ "ServerId": srvOut.ServerID, @@ -57,7 +59,7 @@ func TestHandler_ListUsersSshPublicKeyCountAndHomeDirectoryType(t *testing.T) { SSHPublicKeyCount int `json:"SshPublicKeyCount"` } `json:"Users"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listOut, jsonx.V1())) require.Len(t, listOut.Users, 1) assert.Equal(t, "LOGICAL", listOut.Users[0].HomeDirectoryType) @@ -95,7 +97,7 @@ func TestHandler_UserARNFormat(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) user := descResp["User"].(map[string]any) // newTestHandler uses testAccountID = "123456789012" and testRegion = "us-east-1" @@ -130,7 +132,7 @@ func TestHandler_CreateUserPosixProfile(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) user := descResp["User"].(map[string]any) posix := user["PosixProfile"].(map[string]any) @@ -164,7 +166,7 @@ func TestHandler_CreateUserHomeDirectoryLogical(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) user := descResp["User"].(map[string]any) assert.Equal(t, "LOGICAL", user["HomeDirectoryType"]) @@ -207,7 +209,7 @@ func TestHandler_DescribeUserIncludesSshPublicKeys(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) user := descResp["User"].(map[string]any) keys, ok := user["SshPublicKeys"].([]any) require.True(t, ok, "SshPublicKeys must be present") @@ -235,7 +237,7 @@ func TestHandler_ListUsersPagination(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) users := listResp["Users"].([]any) assert.Len(t, users, 2) @@ -269,7 +271,7 @@ func TestHandler_UpdateUserPosixProfile(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) user := descResp["User"].(map[string]any) posix := user["PosixProfile"].(map[string]any) assert.EqualValues(t, 9999, posix["Uid"]) @@ -285,7 +287,7 @@ func TestHandler_UserCRUD(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) // Create user @@ -298,7 +300,7 @@ func TestHandler_UserCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, createUserRec.Code) var userResp map[string]any - require.NoError(t, json.Unmarshal(createUserRec.Body.Bytes(), &userResp)) + require.NoError(t, json.Unmarshal(createUserRec.Body.Bytes(), &userResp, jsonx.V1())) assert.Equal(t, "alice", userResp["UserName"]) // Describe user @@ -315,7 +317,7 @@ func TestHandler_UserCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, listUsersRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listUsersRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listUsersRec.Body.Bytes(), &listResp, jsonx.V1())) users := listResp["Users"].([]any) assert.Len(t, users, 1) @@ -341,7 +343,7 @@ func TestHandler_UserCRUD(t *testing.T) { assert.Equal(t, http.StatusOK, listUsersRec2.Code) var listResp2 map[string]any - require.NoError(t, json.Unmarshal(listUsersRec2.Body.Bytes(), &listResp2)) + require.NoError(t, json.Unmarshal(listUsersRec2.Body.Bytes(), &listResp2, jsonx.V1())) assert.Empty(t, listResp2["Users"]) } @@ -354,7 +356,7 @@ func TestHandler_CreateUser_MissingFields(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) tests := []struct { @@ -395,7 +397,7 @@ func TestHandler_ListUsers_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) serverID := createResp["ServerId"].(string) for _, username := range []string{"alice", "bob", "charlie"} { @@ -434,7 +436,7 @@ func TestHandler_ListUsers_Pagination(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) users := resp["Users"].([]any) assert.GreaterOrEqual(t, len(users), tt.wantMinCount) @@ -503,7 +505,7 @@ func TestHandler_DescribeDeleteUpdateUser(t *testing.T) { require.Equal(t, http.StatusOK, createServerRec.Code) var serverResp map[string]any - require.NoError(t, json.Unmarshal(createServerRec.Body.Bytes(), &serverResp)) + require.NoError(t, json.Unmarshal(createServerRec.Body.Bytes(), &serverResp, jsonx.V1())) serverID := serverResp["ServerId"].(string) doTransferRequest(t, h, "CreateUser", map[string]any{ diff --git a/services/transfer/handler_web_apps_test.go b/services/transfer/handler_web_apps_test.go index 6e520b0602..a83bd5783c 100644 --- a/services/transfer/handler_web_apps_test.go +++ b/services/transfer/handler_web_apps_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // webAppCreateBody returns a minimal CreateWebApp request body satisfying the @@ -60,7 +62,7 @@ func TestHandler_WebAppCustomizationValidatesWebAppID(t *testing.T) { "%s on non-existent WebAppId must return 400 (ResourceNotFoundException)", tt.operation) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) errType, _ := errResp["__type"].(string) assert.Contains(t, errType, "ResourceNotFoundException", @@ -81,7 +83,7 @@ func TestHandler_WebAppCustomizationRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID, _ := createResp["WebAppId"].(string) require.NotEmpty(t, webAppID) @@ -99,7 +101,7 @@ func TestHandler_WebAppCustomizationRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code, "DescribeWebAppCustomization must succeed") var descResp map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &descResp, jsonx.V1())) customization, ok := descResp["WebAppCustomization"].(map[string]any) require.True(t, ok, "WebAppCustomization must be present in response") @@ -123,7 +125,7 @@ func TestHandler_CreateWebAppReturnsWebAppID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WebAppId"]) } @@ -138,7 +140,7 @@ func TestHandler_CreateWebApp_MissingIdentityProviderDetails(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "InvalidRequestException", errResp["__type"]) } @@ -158,7 +160,7 @@ func TestHandler_DescribeWebAppIncludesArnTagsAndIdentityProvider(t *testing.T) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) updateRec := doTransferRequest(t, h, "UpdateWebApp", map[string]any{ @@ -175,7 +177,7 @@ func TestHandler_DescribeWebAppIncludesArnTagsAndIdentityProvider(t *testing.T) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) webApp := descResp["WebApp"].(map[string]any) assert.Equal(t, @@ -212,14 +214,14 @@ func TestHandler_ListWebAppsIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) listRec := doTransferRequest(t, h, "ListWebApps", map[string]any{}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) webApps := listResp["WebApps"].([]any) require.Len(t, webApps, 1) @@ -261,7 +263,7 @@ func TestHandler_CreateWebApp(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WebAppId"]) }) } @@ -294,14 +296,14 @@ func TestHandler_CreateWebAppVpcEndpoint(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) descRec := doTransferRequest(t, h, "DescribeWebApp", map[string]any{"WebAppId": webAppID}) require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) webApp := descResp["WebApp"].(map[string]any) assert.Equal(t, "VPC", webApp["EndpointType"]) @@ -327,7 +329,7 @@ func TestHandler_DescribeWebApp(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) rec := doTransferRequest(t, h, "DescribeWebApp", map[string]any{ @@ -353,7 +355,7 @@ func TestHandler_DeleteWebApp(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) rec := doTransferRequest(t, h, "DeleteWebApp", map[string]any{ @@ -371,7 +373,7 @@ func TestHandler_UpdateWebApp(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) rec := doTransferRequest(t, h, "UpdateWebApp", map[string]any{ @@ -393,7 +395,7 @@ func TestHandler_UpdateWebAppVpcIPAddressType(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) updateRec := doTransferRequest(t, h, "UpdateWebApp", map[string]any{ @@ -423,7 +425,7 @@ func TestHandler_UpdateWebApp_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", errResp["__type"]) } @@ -436,7 +438,7 @@ func TestHandler_WebAppCustomization(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webAppID := createResp["WebAppId"].(string) // DescribeWebAppCustomization diff --git a/services/transfer/handler_workflows_test.go b/services/transfer/handler_workflows_test.go index c95e649dfb..faf2a3707c 100644 --- a/services/transfer/handler_workflows_test.go +++ b/services/transfer/handler_workflows_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestHandler_CreateWorkflowInvalidStepTypeRejected verifies that CreateWorkflow returns 400 @@ -61,7 +63,7 @@ func TestHandler_DescribeWorkflowIncludesArnAndTags(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) rec := doTransferRequest(t, h, "DescribeWorkflow", map[string]any{ @@ -70,7 +72,7 @@ func TestHandler_DescribeWorkflowIncludesArnAndTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wf := resp["Workflow"].(map[string]any) arn, hasArn := wf["Arn"].(string) @@ -96,7 +98,7 @@ func TestHandler_ListWorkflowsIncludesArn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) workflows := resp["Workflows"].([]any) require.NotEmpty(t, workflows) @@ -117,7 +119,7 @@ func TestHandler_CreateWorkflowReturnsWorkflowID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WorkflowId"]) } @@ -205,7 +207,7 @@ func TestHandler_CreateWorkflow(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["WorkflowId"]) }) } @@ -222,7 +224,7 @@ func TestHandler_DescribeWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) rec := doTransferRequest(t, h, "DescribeWorkflow", map[string]any{ @@ -250,7 +252,7 @@ func TestHandler_DeleteWorkflow(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) rec := doTransferRequest(t, h, "DeleteWorkflow", map[string]any{ @@ -270,7 +272,7 @@ func TestHandler_ListExecutions(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) rec := doTransferRequest(t, h, "ListExecutions", map[string]any{ @@ -321,7 +323,7 @@ func TestHandler_DescribeWorkflowTypedCopyStepDetails(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) descRec := doTransferRequest(t, h, "DescribeWorkflow", map[string]any{ @@ -330,7 +332,7 @@ func TestHandler_DescribeWorkflowTypedCopyStepDetails(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) wf := descResp["Workflow"].(map[string]any) steps := wf["Steps"].([]any) require.Len(t, steps, 1) diff --git a/services/transfer/multi_region_test.go b/services/transfer/multi_region_test.go index 21aeadc5fb..346fabcfa9 100644 --- a/services/transfer/multi_region_test.go +++ b/services/transfer/multi_region_test.go @@ -2,7 +2,7 @@ package transfer_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/transfer/paging.go b/services/transfer/paging.go index ffec79288c..b15d4f6ecc 100644 --- a/services/transfer/paging.go +++ b/services/transfer/paging.go @@ -1,9 +1,10 @@ package transfer import ( - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -36,7 +37,7 @@ func validateListPaging(body []byte) error { func decodeListPaging(body []byte) (listPagingInput, bool) { var in listPagingInput - err := json.Unmarshal(body, &in) + err := json.Unmarshal(body, &in, jsonx.V1()) return in, err == nil } diff --git a/services/transfer/persistence.go b/services/transfer/persistence.go index 3208b9db17..e847ac65d7 100644 --- a/services/transfer/persistence.go +++ b/services/transfer/persistence.go @@ -2,7 +2,7 @@ package transfer import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -31,7 +31,7 @@ const transferSnapshotVersion = 1 // (and TagsStore is handled separately below) -- see store_setup.go and the // field comments on InMemoryBackend for why. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` TagsStore map[string]map[string]string `json:"tags_store"` Version int `json:"version"` } diff --git a/services/transfer/realism_test.go b/services/transfer/realism_test.go index 9a00ae6c4d..ef604813aa 100644 --- a/services/transfer/realism_test.go +++ b/services/transfer/realism_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const realismRole = "arn:aws:iam::123456789012:role/TransferUserRole" @@ -18,7 +20,7 @@ func transferErr(t *testing.T, body []byte) (string, string) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out.Type, out.Message } diff --git a/services/transfer/resource_id_shape_test.go b/services/transfer/resource_id_shape_test.go index 4b514f4574..4886999b1f 100644 --- a/services/transfer/resource_id_shape_test.go +++ b/services/transfer/resource_id_shape_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // Patterns are the SDK-documented ID shapes; terraform-provider-aws validates workflow and server IDs against them. @@ -87,11 +89,11 @@ func TestResourceIDShapes(t *testing.T) { require.Equal(t, http.StatusOK, srv.Code) var srvOut, profOut map[string]any - require.NoError(t, json.Unmarshal(srv.Body.Bytes(), &srvOut)) + require.NoError(t, json.Unmarshal(srv.Body.Bytes(), &srvOut, jsonx.V1())) prof := doTransferRequest(t, h, "CreateProfile", map[string]any{"As2Id": "AS2", "ProfileType": "LOCAL"}) require.Equal(t, http.StatusOK, prof.Code) - require.NoError(t, json.Unmarshal(prof.Body.Bytes(), &profOut)) + require.NoError(t, json.Unmarshal(prof.Body.Bytes(), &profOut, jsonx.V1())) rec := doTransferRequest( t, @@ -102,7 +104,7 @@ func TestResourceIDShapes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Regexp(t, tt.pattern, out[tt.idField]) }) } @@ -116,7 +118,7 @@ func TestSSHKeyAndExecutionIDShapes(t *testing.T) { srv := doTransferRequest(t, h, "CreateServer", map[string]any{}) var srvOut map[string]any - require.NoError(t, json.Unmarshal(srv.Body.Bytes(), &srvOut)) + require.NoError(t, json.Unmarshal(srv.Body.Bytes(), &srvOut, jsonx.V1())) doTransferRequest(t, h, "CreateUser", map[string]any{ "ServerId": srvOut["ServerId"], "UserName": "alice", "Role": "arn:aws:iam::000000000000:role/r", @@ -128,6 +130,6 @@ func TestSSHKeyAndExecutionIDShapes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var out map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Regexp(t, `^key-[0-9a-f]{17}$`, out["SshPublicKeyId"]) } diff --git a/services/transfer/sftp_apigw.go b/services/transfer/sftp_apigw.go index 65e7b031ce..166a8178eb 100644 --- a/services/transfer/sftp_apigw.go +++ b/services/transfer/sftp_apigw.go @@ -2,7 +2,7 @@ package transfer import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "net/http" @@ -15,6 +15,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -205,7 +206,7 @@ func (s *sftpServer) apiGatewayLogin( } var resp idpAuthResponse - if jerr := json.Unmarshal(body, &resp); jerr != nil { + if jerr := json.Unmarshal(body, &resp, jsonx.V1()); jerr != nil { return nil, errSFTPDenied } diff --git a/services/transfer/sftp_server.go b/services/transfer/sftp_server.go index 317aa0bc9e..0c2602f86d 100644 --- a/services/transfer/sftp_server.go +++ b/services/transfer/sftp_server.go @@ -3,7 +3,7 @@ package transfer import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "errors" "io" "net" @@ -14,6 +14,7 @@ import ( "github.com/pkg/sftp" "golang.org/x/crypto/ssh" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -106,7 +107,7 @@ func (s *sftpServer) serve(conn net.Conn) { var sess sftpSession if raw := sc.Permissions.Extensions[sftpSessionExtension]; raw != "" { - if jerr := json.Unmarshal([]byte(raw), &sess); jerr != nil { + if jerr := json.Unmarshal([]byte(raw), &sess, jsonx.V1()); jerr != nil { return } } @@ -176,7 +177,7 @@ func (s *sftpServer) authenticate(c ssh.ConnMetadata, key ssh.PublicKey, passwor sess.User = c.User() - raw, err := json.Marshal(sess) + raw, err := json.Marshal(sess, jsonx.V1()) if err != nil { return nil, errSFTPDenied } @@ -288,7 +289,7 @@ func (s *sftpServer) lambdaLogin( event["password"] = password } - payload, err := json.Marshal(event) + payload, err := json.Marshal(event, jsonx.V1()) if err != nil { return nil, err } @@ -299,7 +300,7 @@ func (s *sftpServer) lambdaLogin( } var resp idpAuthResponse - if jerr := json.Unmarshal(out, &resp); jerr != nil { + if jerr := json.Unmarshal(out, &resp, jsonx.V1()); jerr != nil { return nil, errSFTPDenied } @@ -340,7 +341,7 @@ func parseLambdaMappings(details string) ([]HomeDirectoryMapEntry, error) { Entry string `json:"Entry"` Target string `json:"Target"` } - if err := json.Unmarshal([]byte(details), &raw); err != nil { + if err := json.Unmarshal([]byte(details), &raw, jsonx.V1()); err != nil { return nil, errSFTPDenied } diff --git a/services/transfer/undeclared_members_test.go b/services/transfer/undeclared_members_test.go index 0dac04035a..129d86df9c 100644 --- a/services/transfer/undeclared_members_test.go +++ b/services/transfer/undeclared_members_test.go @@ -1,12 +1,14 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_DescribeUser_SshKeyOmitsKeyType(t *testing.T) { @@ -45,7 +47,7 @@ func TestHandler_DescribeUser_SshKeyOmitsKeyType(t *testing.T) { SSHPublicKeys []map[string]any `json:"SshPublicKeys"` } `json:"User"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.User.SSHPublicKeys, 1) assert.NotContains(t, out.User.SSHPublicKeys[0], "KeyType") assert.Equal(t, tc.body, out.User.SSHPublicKeys[0]["SshPublicKeyBody"]) diff --git a/services/transfer/wire_field_fixes_test.go b/services/transfer/wire_field_fixes_test.go index fcf2838991..ae67e2faed 100644 --- a/services/transfer/wire_field_fixes_test.go +++ b/services/transfer/wire_field_fixes_test.go @@ -1,7 +1,7 @@ package transfer_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/transfer" ) @@ -78,7 +79,7 @@ func TestDescribeWorkflow_CustomStepTimeoutSecondsKey_RealClient(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) workflowID := createResp["WorkflowId"].(string) rec := doTransferRequest(t, h, "DescribeWorkflow", map[string]any{ @@ -230,7 +231,7 @@ func TestListExecutionsAndDescribeExecution_NoFabricatedWorkflowId(t *testing.T) var listResp struct { Executions []map[string]any `json:"Executions"` } - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) require.Len(t, listResp.Executions, 1, "must exercise a non-empty collection") require.Contains(t, listResp.Executions[0], "ExecutionId") @@ -247,7 +248,7 @@ func TestListExecutionsAndDescribeExecution_NoFabricatedWorkflowId(t *testing.T) var descResp struct { Execution map[string]any `json:"Execution"` } - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) require.Contains(t, descResp.Execution, "ExecutionId") _, descHasWorkflowID := descResp.Execution["WorkflowId"] diff --git a/services/translate/handler.go b/services/translate/handler.go index 1f5118d4d1..2deec69537 100644 --- a/services/translate/handler.go +++ b/services/translate/handler.go @@ -2,7 +2,7 @@ package translate import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -12,6 +12,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/httputils" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -106,7 +107,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { } var values map[string]any - if unmarshalErr := json.Unmarshal(body, &values); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(body, &values, jsonx.V1()); unmarshalErr != nil { return "" } @@ -147,7 +148,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt var input map[string]any if len(body) > 0 { - if err := json.Unmarshal(body, &input); err != nil { + if err := json.Unmarshal(body, &input, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid JSON: %w", ErrValidation, err) } } @@ -161,7 +162,7 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt return nil, err } - return json.Marshal(output) + return json.Marshal(output, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { diff --git a/services/translate/handler_languages_test.go b/services/translate/handler_languages_test.go index cc6355c5ab..95e0dde86e 100644 --- a/services/translate/handler_languages_test.go +++ b/services/translate/handler_languages_test.go @@ -1,12 +1,14 @@ package translate_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListLanguages(t *testing.T) { @@ -154,7 +156,7 @@ func TestListLanguages_UnsupportedDisplayLanguageCodeRejected(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "UnsupportedDisplayLanguageCodeException", body["__type"]) } }) diff --git a/services/translate/handler_parallel_data_test.go b/services/translate/handler_parallel_data_test.go index 822fd0cf01..82f9ccffce 100644 --- a/services/translate/handler_parallel_data_test.go +++ b/services/translate/handler_parallel_data_test.go @@ -1,13 +1,14 @@ package translate_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/translate" ) @@ -255,7 +256,7 @@ func TestUpdateParallelData_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -283,7 +284,7 @@ func TestCreateParallelData_DuplicateNameReturnsConflictException(t *testing.T) assert.Equal(t, http.StatusBadRequest, rec.Code) var respBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody, jsonx.V1())) assert.Equal(t, "ConflictException", respBody["__type"]) } @@ -435,7 +436,7 @@ func TestUpdateParallelData_ConcurrentModification(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ConcurrentModificationException", body["__type"]) }) } @@ -494,7 +495,7 @@ func TestCreateParallelData_TooManyTagsRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "TooManyTagsException", body["__type"]) } diff --git a/services/translate/handler_tags_test.go b/services/translate/handler_tags_test.go index 0003fdf509..744a6b2dd9 100644 --- a/services/translate/handler_tags_test.go +++ b/services/translate/handler_tags_test.go @@ -1,12 +1,14 @@ package translate_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestTagResource(t *testing.T) { @@ -85,7 +87,7 @@ func TestTagResource_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -103,7 +105,7 @@ func TestUntagResource_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -120,7 +122,7 @@ func TestListTagsForResource_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -180,7 +182,7 @@ func TestTagResource_TooManyTagsRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "TooManyTagsException", body["__type"]) } diff --git a/services/translate/handler_test.go b/services/translate/handler_test.go index df32822a18..ee41a8524a 100644 --- a/services/translate/handler_test.go +++ b/services/translate/handler_test.go @@ -3,7 +3,7 @@ package translate_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/translate" ) @@ -38,7 +39,7 @@ func doRequest(t *testing.T, h *translate.Handler, operation string, body any) * if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -63,7 +64,7 @@ func unmarshalJSON(t *testing.T, data []byte) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(data, &m)) + require.NoError(t, json.Unmarshal(data, &m, jsonx.V1())) return m } diff --git a/services/translate/handler_text_translation_jobs_test.go b/services/translate/handler_text_translation_jobs_test.go index ae9278a072..56fa152d58 100644 --- a/services/translate/handler_text_translation_jobs_test.go +++ b/services/translate/handler_text_translation_jobs_test.go @@ -1,7 +1,7 @@ package translate_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/translate" ) @@ -199,7 +200,7 @@ func TestStopTextTranslationJob_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -213,7 +214,7 @@ func TestDescribeTextTranslationJob_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", body["__type"]) } @@ -306,7 +307,7 @@ func TestStartTextTranslationJob_UnknownTerminologyRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var respBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", respBody["__type"]) }) } @@ -354,7 +355,7 @@ func TestStartTextTranslationJob_RequiredFieldsValidated(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var respBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody, jsonx.V1())) assert.Equal(t, "InvalidRequestException", respBody["__type"]) }) } @@ -409,7 +410,7 @@ func TestStartTextTranslationJob_UnsupportedLanguagePairRejected(t *testing.T) { if tt.wantCode == http.StatusBadRequest { var respBody map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &respBody, jsonx.V1())) assert.Equal(t, "UnsupportedLanguagePairException", respBody["__type"]) } }) @@ -544,7 +545,7 @@ func TestListTextTranslationJobs_InvalidFilterRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var body map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) assert.Equal(t, "InvalidFilterException", body["__type"]) } diff --git a/services/translate/multi_region_test.go b/services/translate/multi_region_test.go index 4dc043b372..83f9c4e69a 100644 --- a/services/translate/multi_region_test.go +++ b/services/translate/multi_region_test.go @@ -2,7 +2,8 @@ package translate_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/translate" ) @@ -183,8 +185,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/translate/persistence.go b/services/translate/persistence.go index 37de19b1ef..d103f65279 100644 --- a/services/translate/persistence.go +++ b/services/translate/persistence.go @@ -2,7 +2,7 @@ package translate import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" @@ -38,7 +38,7 @@ const translateSnapshotVersion = 1 // (see backend.go's InMemoryBackend field doc comment). It is persisted // directly here. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` AccountID string `json:"accountId"` Region string `json:"region"` diff --git a/services/verifiedpermissions/cedar_attributes.go b/services/verifiedpermissions/cedar_attributes.go index cdd9e948bc..0612b68c1c 100644 --- a/services/verifiedpermissions/cedar_attributes.go +++ b/services/verifiedpermissions/cedar_attributes.go @@ -1,11 +1,14 @@ package verifiedpermissions import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" cedar "github.com/cedar-policy/cedar-go" cedartypes "github.com/cedar-policy/cedar-go/types" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // entityIdentifierJSON already exists in handler_authorization.go (entityType/entityId). @@ -18,9 +21,9 @@ import ( // the same as cedar-go's own implicit JSON value format (types.UnmarshalJSON: // bare JSON literals plus __entity/__extn tags), so this needs its own converter // rather than reusing cedar-go's decoder. -func attributeValueToCedar(raw json.RawMessage) (cedar.Value, error) { - var m map[string]json.RawMessage - if err := json.Unmarshal(raw, &m); err != nil { +func attributeValueToCedar(raw jsontext.Value) (cedar.Value, error) { + var m map[string]jsontext.Value + if err := json.Unmarshal(raw, &m, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid attribute value: %w", errInvalidRequest, err) } @@ -38,25 +41,25 @@ func attributeValueToCedar(raw json.RawMessage) (cedar.Value, error) { // per-member would scatter the union's shape across more functions than it clarifies. // //nolint:cyclop // one discriminated-union switch over AWS's 9 AttributeValue member types; decomposing -func attributeValueMemberToCedar(key string, v json.RawMessage) (cedar.Value, error) { +func attributeValueMemberToCedar(key string, v jsontext.Value) (cedar.Value, error) { switch key { case "boolean": var b bool - if err := json.Unmarshal(v, &b); err != nil { + if err := json.Unmarshal(v, &b, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid boolean attribute: %w", errInvalidRequest, err) } return cedar.Boolean(b), nil case "string": var s string - if err := json.Unmarshal(v, &s); err != nil { + if err := json.Unmarshal(v, &s, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid string attribute: %w", errInvalidRequest, err) } return cedar.String(s), nil case "long": var n int64 - if err := json.Unmarshal(v, &n); err != nil { + if err := json.Unmarshal(v, &n, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid long attribute: %w", errInvalidRequest, err) } @@ -71,7 +74,7 @@ func attributeValueMemberToCedar(key string, v json.RawMessage) (cedar.Value, er return parseCedarStringAttr(v, cedartypes.ParseIPAddr) case "entityIdentifier": var eid entityIdentifierJSON - if err := json.Unmarshal(v, &eid); err != nil { + if err := json.Unmarshal(v, &eid, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid entityIdentifier attribute: %w", errInvalidRequest, err) } @@ -85,9 +88,9 @@ func attributeValueMemberToCedar(key string, v json.RawMessage) (cedar.Value, er } } -func parseCedarStringAttr[T cedar.Value](raw json.RawMessage, parse func(string) (T, error)) (cedar.Value, error) { +func parseCedarStringAttr[T cedar.Value](raw jsontext.Value, parse func(string) (T, error)) (cedar.Value, error) { var s string - if err := json.Unmarshal(raw, &s); err != nil { + if err := json.Unmarshal(raw, &s, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid string-encoded attribute: %w", errInvalidRequest, err) } @@ -99,9 +102,9 @@ func parseCedarStringAttr[T cedar.Value](raw json.RawMessage, parse func(string) return val, nil } -func recordAttributeToCedar(raw json.RawMessage) (cedar.Value, error) { - var fields map[string]json.RawMessage - if err := json.Unmarshal(raw, &fields); err != nil { +func recordAttributeToCedar(raw jsontext.Value) (cedar.Value, error) { + var fields map[string]jsontext.Value + if err := json.Unmarshal(raw, &fields, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid record attribute: %w", errInvalidRequest, err) } @@ -119,9 +122,9 @@ func recordAttributeToCedar(raw json.RawMessage) (cedar.Value, error) { return cedar.NewRecord(rec), nil } -func setAttributeToCedar(raw json.RawMessage) (cedar.Value, error) { - var items []json.RawMessage - if err := json.Unmarshal(raw, &items); err != nil { +func setAttributeToCedar(raw jsontext.Value) (cedar.Value, error) { + var items []jsontext.Value + if err := json.Unmarshal(raw, &items, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid set attribute: %w", errInvalidRequest, err) } @@ -141,7 +144,7 @@ func setAttributeToCedar(raw json.RawMessage) (cedar.Value, error) { // attributeMapToCedarRecord converts an AWS "attributes"/"contextMap" object // (map of name -> AttributeValue) into a cedar-go Record. -func attributeMapToCedarRecord(m map[string]json.RawMessage) (cedar.Record, error) { +func attributeMapToCedarRecord(m map[string]jsontext.Value) (cedar.Record, error) { rec := make(cedar.RecordMap, len(m)) for k, v := range m { @@ -161,10 +164,10 @@ func attributeMapToCedarRecord(m map[string]json.RawMessage) (cedar.Record, erro // awsAwsjson10_serializeDocumentEntityItem). CedarTagValue has the same members // as AttributeValue (serializers.go:2334), so tags reuse the attribute converter. type entityItemJSON struct { - Identifier *entityIdentifierJSON `json:"identifier,omitempty"` - Attributes map[string]json.RawMessage `json:"attributes,omitempty"` - Tags map[string]json.RawMessage `json:"tags,omitempty"` - Parents []entityIdentifierJSON `json:"parents,omitempty"` + Identifier *entityIdentifierJSON `json:"identifier,omitempty"` + Attributes map[string]jsontext.Value `json:"attributes,omitempty"` + Tags map[string]jsontext.Value `json:"tags,omitempty"` + Parents []entityIdentifierJSON `json:"parents,omitempty"` } // entitiesDefinitionJSON mirrors the real SDK's types.EntitiesDefinition union @@ -183,8 +186,8 @@ type entitiesDefinitionJSON struct { // (contextMap: typed AttributeValue objects: cedarJson: a literal Cedar JSON // object using cedar-go's own native record JSON shape). type contextDefinitionJSON struct { - ContextMap map[string]json.RawMessage `json:"contextMap,omitempty"` - CedarJSON string `json:"cedarJson,omitempty"` + ContextMap map[string]jsontext.Value `json:"contextMap,omitempty"` + CedarJSON string `json:"cedarJson,omitempty"` } // entitiesToCedar converts a parsed entitiesDefinitionJSON into a cedar-go @@ -198,7 +201,7 @@ func entitiesToCedar(def *entitiesDefinitionJSON) (cedar.EntityMap, error) { if def.CedarJSON != "" { var em cedar.EntityMap - if err := json.Unmarshal([]byte(def.CedarJSON), &em); err != nil { + if err := json.Unmarshal([]byte(def.CedarJSON), &em, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid entities cedarJson: %w", errInvalidRequest, err) } @@ -245,7 +248,7 @@ func contextToCedar(def *contextDefinitionJSON) (cedar.Record, error) { if def.CedarJSON != "" { var rec cedar.Record - if err := json.Unmarshal([]byte(def.CedarJSON), &rec); err != nil { + if err := json.Unmarshal([]byte(def.CedarJSON), &rec, jsonx.V1()); err != nil { return cedar.Record{}, fmt.Errorf("%w: invalid context cedarJson: %w", errInvalidRequest, err) } diff --git a/services/verifiedpermissions/handler.go b/services/verifiedpermissions/handler.go index 9644569b76..4687845e51 100644 --- a/services/verifiedpermissions/handler.go +++ b/services/verifiedpermissions/handler.go @@ -2,7 +2,7 @@ package verifiedpermissions import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -13,6 +13,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -147,7 +148,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { PolicyID string `json:"policyId"` PolicyTemplateID string `json:"policyTemplateId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.PolicyStoreID != "" && req.PolicyID != "" { return req.PolicyStoreID + "/" + req.PolicyID @@ -223,14 +224,11 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { message := stripCodePrefix(err.Error()) - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): return c.JSON(http.StatusBadRequest, map[string]string{ @@ -258,7 +256,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err keyMessageField: message, }) case errors.Is(err, awserr.ErrInvalidParameter), errors.Is(err, errInvalidRequest), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyTypeField: "ValidationException", keyMessageField: message, diff --git a/services/verifiedpermissions/handler_authorization.go b/services/verifiedpermissions/handler_authorization.go index 55f6ab88d4..934dafa251 100644 --- a/services/verifiedpermissions/handler_authorization.go +++ b/services/verifiedpermissions/handler_authorization.go @@ -3,7 +3,7 @@ package verifiedpermissions import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "maps" @@ -11,6 +11,8 @@ import ( "strings" cedar "github.com/cedar-policy/cedar-go" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type entityIdentifierJSON struct { @@ -414,7 +416,7 @@ func parseJWTClaims(token string) (map[string]any, error) { } var claims map[string]any - if unmarshalErr := json.Unmarshal(payload, &claims); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(payload, &claims, jsonx.V1()); unmarshalErr != nil { return nil, fmt.Errorf("%w: bad payload JSON: %w", errMalformedJWT, unmarshalErr) } diff --git a/services/verifiedpermissions/handler_authorization_test.go b/services/verifiedpermissions/handler_authorization_test.go index 93a9dd7f2a..819aa227fb 100644 --- a/services/verifiedpermissions/handler_authorization_test.go +++ b/services/verifiedpermissions/handler_authorization_test.go @@ -2,7 +2,8 @@ package verifiedpermissions_test import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,13 +11,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) // makeTestJWT builds a minimal unsigned JWT whose payload contains the given claims. func makeTestJWT(claims map[string]any) string { b64 := func(v any) string { - raw, _ := json.Marshal(v) + raw, _ := json.Marshal(v, jsonx.V1()) return base64.RawURLEncoding.EncodeToString(raw) } @@ -92,7 +94,7 @@ func TestVPHandler_BatchIsAuthorized(t *testing.T) { if tt.wantLen > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["results"].([]any) assert.Len(t, results, tt.wantLen) @@ -200,7 +202,7 @@ func TestVPHandler_IsAuthorized_DeterminingPolicies_ObjectShape(t *testing.T) { require.Equal(t, http.StatusOK, policyRec.Code) var policyResp map[string]any - require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp, jsonx.V1())) policyID := policyResp["policyId"].(string) rec := doVPRequest(t, h, "IsAuthorized", map[string]any{ @@ -212,7 +214,7 @@ func TestVPHandler_IsAuthorized_DeterminingPolicies_ObjectShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ALLOW", resp["decision"]) determining := resp["determiningPolicies"].([]any) @@ -248,7 +250,7 @@ func TestVPHandler_BatchIsAuthorized_RequestEcho_Shape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) results := resp["results"].([]any) require.Len(t, results, 1) @@ -283,7 +285,7 @@ func TestVPHandler_IsAuthorizedWithToken_PrincipalFromJWT(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) // Create a permissive policy so we can verify ALLOW. @@ -357,7 +359,7 @@ func TestVPHandler_IsAuthorizedWithToken_PrincipalFromJWT(t *testing.T) { if tt.wantDec != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantDec, resp["decision"]) } }) @@ -374,7 +376,7 @@ func TestVPHandler_IsAuthorizedWithToken_MissingToken(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) rec := doVPRequest(t, h, "IsAuthorizedWithToken", map[string]any{ @@ -393,7 +395,7 @@ func TestVPHandler_IsAuthorized_PolicySetCacheDirtyOnMutation(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) authReq := map[string]any{ @@ -416,7 +418,7 @@ func TestVPHandler_IsAuthorized_PolicySetCacheDirtyOnMutation(t *testing.T) { rec := doVPRequest(t, h, "IsAuthorized", authReq) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DENY", resp["decision"], "no policies → DENY") // Add a permissive policy. @@ -430,14 +432,14 @@ func TestVPHandler_IsAuthorized_PolicySetCacheDirtyOnMutation(t *testing.T) { }) require.Equal(t, http.StatusOK, policyRec.Code) var policyResp map[string]any - require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp, jsonx.V1())) policyID := policyResp["policyId"].(string) // After adding policy: cache must invalidate, now ALLOW. rec2 := doVPRequest(t, h, "IsAuthorized", authReq) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, "ALLOW", resp2["decision"], "after CreatePolicy → ALLOW (cache invalidated)") // Delete the policy: cache must invalidate again, back to DENY. @@ -450,7 +452,7 @@ func TestVPHandler_IsAuthorized_PolicySetCacheDirtyOnMutation(t *testing.T) { rec3 := doVPRequest(t, h, "IsAuthorized", authReq) require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) assert.Equal(t, "DENY", resp3["decision"], "after DeletePolicy → DENY (cache invalidated)") } @@ -503,7 +505,7 @@ func TestVPHandler_IsAuthorized_RequestValidation(t *testing.T) { require.Equal(t, http.StatusOK, ps.Code) var psResp map[string]any - _ = json.NewDecoder(ps.Body).Decode(&psResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(ps.Body), &psResp, jsonx.V1()) if v, ok := tt.body["policyStoreId"]; ok && v == "__REPLACE__" { tt.body["policyStoreId"] = psResp["policyStoreId"] @@ -514,7 +516,7 @@ func TestVPHandler_IsAuthorized_RequestValidation(t *testing.T) { if tt.wantField != "" { var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantField) } }) @@ -567,7 +569,7 @@ func TestVPHandler_IsAuthorizedWithToken_RequestValidation(t *testing.T) { require.Equal(t, http.StatusOK, ps.Code) var psResp map[string]any - _ = json.NewDecoder(ps.Body).Decode(&psResp) + _ = json.UnmarshalDecode(jsontext.NewDecoder(ps.Body), &psResp, jsonx.V1()) if v, ok := tt.body["policyStoreId"]; ok && v == "__REPLACE__" { tt.body["policyStoreId"] = psResp["policyStoreId"] @@ -645,7 +647,7 @@ func TestVPHandler_IsAuthorizedWithToken_MultipleIdentitySources_MatchByIssuer(t require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ALLOW", resp["decision"], "token from issuer B must resolve to the Customer identity source, not the first-created Employee one") } @@ -778,7 +780,7 @@ func TestVPHandler_IsAuthorizedWithToken_ClientIDValidation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantDec, resp["decision"]) }) } @@ -821,7 +823,7 @@ func TestVPHandler_IsAuthorizedWithToken_ResponseIncludesPrincipal(t *testing.T) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) principal, ok := resp["principal"].(map[string]any) require.True(t, ok, "response must echo the resolved principal, got: %v", resp) assert.Equal(t, "User", principal["entityType"]) @@ -849,7 +851,7 @@ func TestVPHandler_IsAuthorizedWithToken_ResponseIncludesPrincipal(t *testing.T) ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "DENY", resp["decision"]) assert.NotContains(t, resp, "principal") }) @@ -895,7 +897,7 @@ func TestVPHandler_BatchIsAuthorizedWithToken_RequestEcho_Shape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) principal, ok := resp["principal"].(map[string]any) require.True(t, ok, "top-level principal must be echoed, got: %v", resp) diff --git a/services/verifiedpermissions/handler_identity_sources_test.go b/services/verifiedpermissions/handler_identity_sources_test.go index 04de19e264..9d35bc2d34 100644 --- a/services/verifiedpermissions/handler_identity_sources_test.go +++ b/services/verifiedpermissions/handler_identity_sources_test.go @@ -1,13 +1,15 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -105,7 +107,7 @@ func TestVPHandler_IdentitySource(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -138,7 +140,7 @@ func TestVPHandler_GetIdentitySource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var isResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp, jsonx.V1())) return storeID, isResp["identitySourceId"].(string) }, @@ -206,7 +208,7 @@ func TestVPHandler_DeleteIdentitySource(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var isResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp, jsonx.V1())) return storeID, isResp["identitySourceId"].(string) }, @@ -285,7 +287,7 @@ func TestVPHandler_ListIdentitySources(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sources := resp["identitySources"].([]any) assert.Len(t, sources, tt.wantCount) }) @@ -348,7 +350,7 @@ func TestVPHandler_CreateIdentitySource_OIDCIdentityTokenClientIDs(t *testing.T) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotContains(t, createResp, "configuration", "CreateIdentitySourceOutput has no configuration field in the real SDK") @@ -361,7 +363,7 @@ func TestVPHandler_CreateIdentitySource_OIDCIdentityTokenClientIDs(t *testing.T) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) oidc := resp["configuration"].(map[string]any)["openIdConnectConfiguration"].(map[string]any) tokenSelection := oidc["tokenSelection"].(map[string]any) @@ -396,7 +398,7 @@ func TestVPHandler_CreateIdentitySource_CognitoIssuer(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotContains(t, createResp, "configuration", "CreateIdentitySourceOutput has no configuration field in the real SDK") @@ -409,7 +411,7 @@ func TestVPHandler_CreateIdentitySource_CognitoIssuer(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cognito := resp["configuration"].(map[string]any)["cognitoUserPoolConfiguration"].(map[string]any) assert.Equal(t, "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_test", cognito["issuer"]) @@ -446,7 +448,7 @@ func TestVPHandler_ListIdentitySources_FilterByPrincipalEntityType(t *testing.T) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sources := resp["identitySources"].([]any) require.Len(t, sources, 1) assert.Equal(t, "MyCorp::User", sources[0].(map[string]any)["principalEntityType"]) @@ -535,7 +537,7 @@ func TestVPHandler_UpdateIdentitySource(t *testing.T) { require.Equal(t, http.StatusOK, psRec.Code) var psResp map[string]any - require.NoError(t, json.NewDecoder(psRec.Body).Decode(&psResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(psRec.Body), &psResp, jsonx.V1())) psID := psResp["policyStoreId"].(string) @@ -551,7 +553,7 @@ func TestVPHandler_UpdateIdentitySource(t *testing.T) { require.Equal(t, http.StatusOK, isRec.Code) var isResp map[string]any - require.NoError(t, json.NewDecoder(isRec.Body).Decode(&isResp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(isRec.Body), &isResp, jsonx.V1())) isID := isResp["identitySourceId"].(string) @@ -588,7 +590,7 @@ func TestVPHandler_CreateUpdateIdentitySource_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code, "body: %s", createRec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) assert.NotContains(t, createResp, "principalEntityType") assert.NotContains(t, createResp, "configuration") assert.NotEmpty(t, createResp["identitySourceId"]) @@ -608,7 +610,7 @@ func TestVPHandler_CreateUpdateIdentitySource_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, updateRec.Code, "body: %s", updateRec.Body.String()) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotContains(t, updateResp, "principalEntityType") assert.NotContains(t, updateResp, "configuration") assert.Equal(t, createResp["identitySourceId"], updateResp["identitySourceId"]) diff --git a/services/verifiedpermissions/handler_policies_test.go b/services/verifiedpermissions/handler_policies_test.go index 39cdf2be70..6467d4e566 100644 --- a/services/verifiedpermissions/handler_policies_test.go +++ b/services/verifiedpermissions/handler_policies_test.go @@ -2,7 +2,7 @@ package verifiedpermissions_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -42,7 +43,7 @@ func TestVPHandler_PolicyCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) // Create policy @@ -57,7 +58,7 @@ func TestVPHandler_PolicyCRUD(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var policyResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp, jsonx.V1())) policyID := policyResp["policyId"].(string) assert.NotEmpty(t, policyID) @@ -253,7 +254,7 @@ func TestVPHandler_GetPolicy_NotFound(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) rec = doVPRequest(t, h, "GetPolicy", map[string]any{ @@ -280,7 +281,7 @@ func TestVPHandler_DeletePolicy_IdempotentOnMissingPolicy(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) rec = doVPRequest(t, h, "DeletePolicy", map[string]any{ @@ -318,7 +319,7 @@ func TestVPHandler_CreatePolicy_TemplateLinked(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) // Create a policy template first. @@ -329,7 +330,7 @@ func TestVPHandler_CreatePolicy_TemplateLinked(t *testing.T) { }) require.Equal(t, http.StatusOK, tmplRec.Code) var tmplResp map[string]any - require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp)) + require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp, jsonx.V1())) templateID := tmplResp["policyTemplateId"].(string) // Create template-linked policy @@ -346,7 +347,7 @@ func TestVPHandler_CreatePolicy_TemplateLinked(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var policyResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &policyResp, jsonx.V1())) assert.Equal(t, "TEMPLATE_LINKED", policyResp["policyType"]) } @@ -370,7 +371,7 @@ func TestVPHandler_UpdatePolicy_TemplateLinkedRejected(t *testing.T) { }) require.Equal(t, http.StatusOK, tmplRec.Code) var tmplResp map[string]any - require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp)) + require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp, jsonx.V1())) templateID := tmplResp["policyTemplateId"].(string) createRec := doVPRequest(t, h, "CreatePolicy", map[string]any{ @@ -385,7 +386,7 @@ func TestVPHandler_UpdatePolicy_TemplateLinkedRejected(t *testing.T) { }) require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["policyId"].(string) t.Run("templateLinked definition rejected outright", func(t *testing.T) { @@ -429,7 +430,7 @@ func TestVPHandler_UpdatePolicy_TemplateLinkedRejected(t *testing.T) { }) require.Equal(t, http.StatusOK, staticRec.Code) var staticResp map[string]any - require.NoError(t, json.Unmarshal(staticRec.Body.Bytes(), &staticResp)) + require.NoError(t, json.Unmarshal(staticRec.Body.Bytes(), &staticResp, jsonx.V1())) staticPolicyID := staticResp["policyId"].(string) rec := doVPRequest(t, h, "UpdatePolicy", map[string]any{ @@ -449,7 +450,7 @@ func TestVPHandler_UpdatePolicy_TemplateLinkedRejected(t *testing.T) { getRec := doVPRequest(t, h, "GetPolicy", map[string]any{"policyStoreId": storeID, "policyId": policyID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) def := getResp["definition"].(map[string]any)["templateLinked"].(map[string]any) assert.Equal(t, "alice", def["principal"].(map[string]any)["entityId"]) } @@ -479,7 +480,7 @@ func TestVPHandler_BatchGetPolicy(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var pResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &pResp, jsonx.V1())) policyID := pResp["policyId"].(string) return map[string]any{ @@ -544,7 +545,7 @@ func TestVPHandler_BatchGetPolicy(t *testing.T) { if tt.check != nil { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tt.check(t, resp) } }) @@ -562,7 +563,7 @@ func TestVPHandler_BatchGetPolicy_EmptyRequests(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp["results"]) assert.Empty(t, resp["errors"]) } @@ -580,7 +581,7 @@ func TestVPHandler_ListPolicies_FilterByPrincipalIdentifier(t *testing.T) { require.Equal(t, http.StatusOK, ptRec.Code) var ptResp map[string]any - require.NoError(t, json.Unmarshal(ptRec.Body.Bytes(), &ptResp)) + require.NoError(t, json.Unmarshal(ptRec.Body.Bytes(), &ptResp, jsonx.V1())) templateID := ptResp["policyTemplateId"].(string) linkedRec := doVPRequest(t, h, "CreatePolicy", map[string]any{ @@ -614,7 +615,7 @@ func TestVPHandler_ListPolicies_FilterByPrincipalIdentifier(t *testing.T) { require.Equal(t, http.StatusOK, filteredRec.Code, "body: %s", filteredRec.Body.String()) var filteredResp map[string]any - require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp)) + require.NoError(t, json.Unmarshal(filteredRec.Body.Bytes(), &filteredResp, jsonx.V1())) policies := filteredResp["policies"].([]any) require.Len(t, policies, 1) @@ -628,7 +629,7 @@ func TestVPHandler_ListPolicies_FilterByPrincipalIdentifier(t *testing.T) { require.Equal(t, http.StatusOK, unspecifiedRec.Code, "body: %s", unspecifiedRec.Body.String()) var unspecifiedResp map[string]any - require.NoError(t, json.Unmarshal(unspecifiedRec.Body.Bytes(), &unspecifiedResp)) + require.NoError(t, json.Unmarshal(unspecifiedRec.Body.Bytes(), &unspecifiedResp, jsonx.V1())) unspecifiedPolicies := unspecifiedResp["policies"].([]any) require.Len(t, unspecifiedPolicies, 1) } @@ -649,7 +650,7 @@ func TestVPHandler_BatchGetPolicy_IncludesDefinition(t *testing.T) { require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID, _ := storeResp["policyStoreId"].(string) require.NotEmpty(t, policyStoreID) @@ -667,7 +668,7 @@ func TestVPHandler_BatchGetPolicy_IncludesDefinition(t *testing.T) { require.Equal(t, http.StatusOK, staticRec.Code) var staticResp map[string]any - require.NoError(t, json.Unmarshal(staticRec.Body.Bytes(), &staticResp)) + require.NoError(t, json.Unmarshal(staticRec.Body.Bytes(), &staticResp, jsonx.V1())) staticPolicyID, _ := staticResp["policyId"].(string) require.NotEmpty(t, staticPolicyID) @@ -681,7 +682,7 @@ func TestVPHandler_BatchGetPolicy_IncludesDefinition(t *testing.T) { require.Equal(t, http.StatusOK, tmplRec.Code) var tmplResp map[string]any - require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp)) + require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp, jsonx.V1())) templateID, _ := tmplResp["policyTemplateId"].(string) require.NotEmpty(t, templateID) @@ -705,7 +706,7 @@ func TestVPHandler_BatchGetPolicy_IncludesDefinition(t *testing.T) { require.Equal(t, http.StatusOK, tlRec.Code) var tlResp map[string]any - require.NoError(t, json.Unmarshal(tlRec.Body.Bytes(), &tlResp)) + require.NoError(t, json.Unmarshal(tlRec.Body.Bytes(), &tlResp, jsonx.V1())) tlPolicyID, _ := tlResp["policyId"].(string) require.NotEmpty(t, tlPolicyID) @@ -748,7 +749,7 @@ func TestVPHandler_BatchGetPolicy_IncludesDefinition(t *testing.T) { require.Equal(t, http.StatusOK, batchRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &resp, jsonx.V1())) results, ok := resp["results"].([]any) require.True(t, ok, "results must be an array") @@ -779,7 +780,7 @@ func TestVPHandler_ListPolicies_PaginationOpaqueTokens(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) // Create 3 policies. @@ -803,7 +804,7 @@ func TestVPHandler_ListPolicies_PaginationOpaqueTokens(t *testing.T) { }) require.Equal(t, http.StatusOK, page1Rec.Code) var page1 map[string]any - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1, jsonx.V1())) nextToken, _ := page1["nextToken"].(string) require.NotEmpty(t, nextToken, "nextToken must be present when more results exist") @@ -821,7 +822,7 @@ func TestVPHandler_ListPolicies_PaginationOpaqueTokens(t *testing.T) { }) require.Equal(t, http.StatusOK, page2Rec.Code, "page2 body: %s", page2Rec.Body.String()) var page2 map[string]any - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2, jsonx.V1())) page2Items, _ := page2["policies"].([]any) assert.Len(t, page2Items, 1, "page2 should have the remaining 1 policy") @@ -851,7 +852,7 @@ func TestVPHandler_CreatePolicy_StaticScopeEcho(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Forbid", resp["effect"]) @@ -894,7 +895,7 @@ func TestVPHandler_CreatePolicy_UnconstrainedScopeOmitsEchoFields(t *testing.T) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Permit", resp["effect"]) assert.NotContains(t, resp, "principal") @@ -919,7 +920,7 @@ func TestVPHandler_GetPolicy_TemplateLinkedScopeEcho(t *testing.T) { }) require.Equal(t, http.StatusOK, tmplRec.Code) var tmplResp map[string]any - require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp)) + require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp, jsonx.V1())) templateID := tmplResp["policyTemplateId"].(string) rec := doVPRequest(t, h, "CreatePolicy", map[string]any{ @@ -934,7 +935,7 @@ func TestVPHandler_GetPolicy_TemplateLinkedScopeEcho(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) policyID := createResp["policyId"].(string) assert.Equal(t, "Forbid", createResp["effect"]) @@ -949,7 +950,7 @@ func TestVPHandler_GetPolicy_TemplateLinkedScopeEcho(t *testing.T) { }) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) principal, _ := getResp["principal"].(map[string]any) require.NotNil(t, principal) @@ -982,7 +983,7 @@ func TestVPHandler_ListPolicies_StaticItemOmitsStatement(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp["policies"].([]any) require.Len(t, items, 1) @@ -999,7 +1000,7 @@ func TestVPHandler_ListPolicies_StaticItemOmitsStatement(t *testing.T) { getRec := doVPRequest(t, h, "GetPolicy", map[string]any{"policyStoreId": storeID, "policyId": policyID}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) getStatic, _ := getResp["definition"].(map[string]any)["static"].(map[string]any) assert.Equal(t, `permit(principal, action, resource);`, getStatic["statement"]) } diff --git a/services/verifiedpermissions/handler_policy_stores_test.go b/services/verifiedpermissions/handler_policy_stores_test.go index ba5a183785..d275f8cf84 100644 --- a/services/verifiedpermissions/handler_policy_stores_test.go +++ b/services/verifiedpermissions/handler_policy_stores_test.go @@ -1,7 +1,7 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "maps" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -55,7 +56,7 @@ func TestVPHandler_CreatePolicyStore(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantKey) }) } @@ -81,7 +82,7 @@ func TestVPHandler_GetPolicyStore(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["policyStoreId"].(string) }, @@ -148,7 +149,7 @@ func TestVPHandler_ListPolicyStores(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores := resp["policyStores"].([]any) assert.Len(t, stores, tt.numStores) }) @@ -175,7 +176,7 @@ func TestVPHandler_DeletePolicyStore(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["policyStoreId"].(string) }, @@ -225,7 +226,7 @@ func TestVPHandler_UpdatePolicyStore(t *testing.T) { map[string]any{"description": "original", "validationSettings": map[string]any{"mode": "OFF"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["policyStoreId"].(string) }, @@ -302,7 +303,7 @@ func TestVPHandler_CreatePolicyStore_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotEmpty(t, resp["policyStoreId"]) assert.NotEmpty(t, resp["arn"]) } @@ -353,14 +354,14 @@ func TestVPHandler_DeletePolicyStore_DeletionProtection_InvalidStateException(t require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) storeID := createResp["policyStoreId"].(string) rec := doVPRequest(t, h, "DeletePolicyStore", map[string]any{"policyStoreId": storeID}) require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidStateException", resp["__type"]) } @@ -409,7 +410,7 @@ func TestVPHandler_CreatePolicyStore_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "validationSettings") assert.NotEmpty(t, resp["policyStoreId"]) @@ -432,7 +433,7 @@ func TestVPHandler_GetPolicyStore_CedarVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "CEDAR_4", resp["cedarVersion"]) } @@ -451,7 +452,7 @@ func TestVPHandler_ListPolicyStores_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["policyStores"].([]any) require.NotEmpty(t, items) @@ -479,7 +480,7 @@ func TestVPHandler_UpdatePolicyStore_WireShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "validationSettings") assert.NotEmpty(t, resp["createdDate"], "createdDate is a required UpdatePolicyStoreOutput field") @@ -554,7 +555,7 @@ func TestVPHandler_CreatePolicyStoreAlias(t *testing.T) { assert.Equal(t, tt.wantCode, rec.Code, "body: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantType != "" { assert.Equal(t, tt.wantType, resp["__type"]) @@ -591,13 +592,13 @@ func TestVPHandler_CreatePolicyStoreAlias_IdempotentAndConflict(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) rec2 := doVPRequest(t, h, "CreatePolicyStoreAlias", body) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, resp1["createdAt"], resp2["createdAt"], "same (aliasName, policyStoreId) replays, no duplicate") rec3 := doVPRequest(t, h, "CreatePolicyStoreAlias", map[string]any{ @@ -606,7 +607,7 @@ func TestVPHandler_CreatePolicyStoreAlias_IdempotentAndConflict(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec3.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) } @@ -657,7 +658,7 @@ func TestVPHandler_GetPolicyStoreAlias(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "Active", resp["state"]) assert.Equal(t, aliasName, resp["aliasName"]) }) @@ -700,7 +701,7 @@ func TestVPHandler_ListPolicyStoreAliases(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["policyStoreAliases"].([]any) assert.Len(t, items, tt.wantCount) }) @@ -756,7 +757,7 @@ func TestVPHandler_DeletePolicyStoreAlias(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantState, resp["state"]) }) } @@ -802,7 +803,7 @@ func TestVPHandler_DeletePolicyStore_CascadesAliases(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["policyStoreAliases"].([]any) assert.Empty(t, items, "dangling alias row survived policy store delete") @@ -872,7 +873,7 @@ func TestVPHandler_ResolvePolicyStoreID_AcceptsAlias(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, storeID, resp["policyStoreId"], "response echoes the real ID, not the alias") }) } @@ -923,13 +924,13 @@ func TestVPHandler_CreatePolicyStore_ClientTokenIdempotency(t *testing.T) { rec1 := doVPRequest(t, h, "CreatePolicyStore", body("first")) require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) // Same token, same parameters: replays the original policy store. rec2 := doVPRequest(t, h, "CreatePolicyStore", body("first")) require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, resp1["policyStoreId"], resp2["policyStoreId"]) assert.Equal(t, resp1["createdDate"], resp2["createdDate"]) @@ -938,7 +939,7 @@ func TestVPHandler_CreatePolicyStore_ClientTokenIdempotency(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec3.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) // A different token creates a genuinely new policy store. @@ -949,6 +950,6 @@ func TestVPHandler_CreatePolicyStore_ClientTokenIdempotency(t *testing.T) { }) require.Equal(t, http.StatusOK, rec4.Code) var resp4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4, jsonx.V1())) assert.NotEqual(t, resp1["policyStoreId"], resp4["policyStoreId"]) } diff --git a/services/verifiedpermissions/handler_policy_templates_test.go b/services/verifiedpermissions/handler_policy_templates_test.go index 946e024005..b5b412c861 100644 --- a/services/verifiedpermissions/handler_policy_templates_test.go +++ b/services/verifiedpermissions/handler_policy_templates_test.go @@ -1,12 +1,14 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestVPHandler_PolicyTemplateCRUD(t *testing.T) { @@ -38,7 +40,7 @@ func TestVPHandler_PolicyTemplateCRUD(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) // Create template @@ -50,7 +52,7 @@ func TestVPHandler_PolicyTemplateCRUD(t *testing.T) { require.Equal(t, tt.wantCode, rec.Code) var tplResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tplResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tplResp, jsonx.V1())) templateID := tplResp["policyTemplateId"].(string) assert.NotEmpty(t, templateID) @@ -195,7 +197,7 @@ func TestVPHandler_UpdatePolicyTemplate_NotFound(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) rec = doVPRequest(t, h, "UpdatePolicyTemplate", map[string]any{ @@ -219,7 +221,7 @@ func TestVPHandler_DeletePolicyTemplate_NotFound(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var storeResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &storeResp, jsonx.V1())) storeID := storeResp["policyStoreId"].(string) rec = doVPRequest(t, h, "DeletePolicyTemplate", map[string]any{ diff --git a/services/verifiedpermissions/handler_schema_test.go b/services/verifiedpermissions/handler_schema_test.go index 562b11631a..783fbabd34 100644 --- a/services/verifiedpermissions/handler_schema_test.go +++ b/services/verifiedpermissions/handler_schema_test.go @@ -1,13 +1,14 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -77,7 +78,7 @@ func TestVPHandler_PutSchema(t *testing.T) { if tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, storeID, resp["policyStoreId"]) assert.NotEmpty(t, resp["createdDate"]) assert.NotEmpty(t, resp["lastUpdatedDate"]) @@ -147,7 +148,7 @@ func TestVPHandler_GetSchema(t *testing.T) { if hasPut && tt.wantCode == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, storeID, resp["policyStoreId"]) assert.JSONEq(t, `{"namespace": "MyCorp"}`, resp["schema"].(string)) } @@ -180,6 +181,6 @@ func TestVPHandler_PutSchema_UpdateSchema(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, `{"v": 2}`, resp["schema"]) } diff --git a/services/verifiedpermissions/handler_tags_test.go b/services/verifiedpermissions/handler_tags_test.go index 55e5f0fd5f..80d4394d06 100644 --- a/services/verifiedpermissions/handler_tags_test.go +++ b/services/verifiedpermissions/handler_tags_test.go @@ -1,7 +1,7 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strconv" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -34,7 +35,7 @@ func TestVPHandler_TagResource(t *testing.T) { map[string]any{"description": "test", "validationSettings": map[string]any{"mode": "OFF"}}, ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["arn"].(string) }, @@ -95,7 +96,7 @@ func TestVPHandler_UntagResource(t *testing.T) { "validationSettings": map[string]any{"mode": "OFF"}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["arn"].(string) }, @@ -156,7 +157,7 @@ func TestVPHandler_ListTagsForResource(t *testing.T) { "validationSettings": map[string]any{"mode": "OFF"}, }) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["arn"].(string) }, @@ -193,7 +194,7 @@ func TestVPHandler_ListTagsForResource(t *testing.T) { if tt.wantTags { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "tags") } }) @@ -210,7 +211,7 @@ func TestVPHandler_TagResource_TooManyTags(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) resourceARN := getResp["arn"].(string) tags := make(map[string]any, 51) @@ -225,7 +226,7 @@ func TestVPHandler_TagResource_TooManyTags(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TooManyTagsException", resp["__type"]) } @@ -245,7 +246,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) // Get policy store ARN. @@ -254,7 +255,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { }) require.Equal(t, http.StatusOK, getStoreRec.Code) var getStoreResp map[string]any - require.NoError(t, json.Unmarshal(getStoreRec.Body.Bytes(), &getStoreResp)) + require.NoError(t, json.Unmarshal(getStoreRec.Body.Bytes(), &getStoreResp, jsonx.V1())) storeARN := getStoreResp["arn"].(string) _ = storeARN @@ -270,7 +271,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { }) require.Equal(t, http.StatusOK, policyRec.Code) var policyResp map[string]any - require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp)) + require.NoError(t, json.Unmarshal(policyRec.Body.Bytes(), &policyResp, jsonx.V1())) policyID := policyResp["policyId"].(string) // Build the policy ARN (format: arn:aws:verifiedpermissions:::policy//) @@ -289,7 +290,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { listRec1 := doVPRequest(t, h, "ListTagsForResource", map[string]any{"resourceArn": policyARN}) require.Equal(t, http.StatusOK, listRec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(listRec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(listRec1.Body.Bytes(), &resp1, jsonx.V1())) tags1, _ := resp1["tags"].(map[string]any) assert.Equal(t, "test", tags1["Env"]) @@ -303,7 +304,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { listRec2 := doVPRequest(t, h, "ListTagsForResource", map[string]any{"resourceArn": policyARN}) require.Equal(t, http.StatusOK, listRec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(listRec2.Body.Bytes(), &resp2, jsonx.V1())) tags2, _ := resp2["tags"].(map[string]any) assert.Equal(t, "prod", tags2["Env"]) @@ -319,7 +320,7 @@ func TestVPHandler_TagResource_PolicyResource(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) _, envPresent := tags["Env"] assert.False(t, envPresent, "Env tag should be removed after UntagResource") @@ -335,7 +336,7 @@ func TestVPHandler_TagResource_PolicyTemplateResource(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) tmplRec := doVPRequest(t, h, "CreatePolicyTemplate", map[string]any{ @@ -345,7 +346,7 @@ func TestVPHandler_TagResource_PolicyTemplateResource(t *testing.T) { }) require.Equal(t, http.StatusOK, tmplRec.Code) var tmplResp map[string]any - require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp)) + require.NoError(t, json.Unmarshal(tmplRec.Body.Bytes(), &tmplResp, jsonx.V1())) templateID := tmplResp["policyTemplateId"].(string) templateARN := fmt.Sprintf( @@ -364,7 +365,7 @@ func TestVPHandler_TagResource_PolicyTemplateResource(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) assert.Equal(t, "parity-test", tags["Purpose"]) } @@ -379,7 +380,7 @@ func TestVPHandler_TagResource_IdentitySourceResource(t *testing.T) { }) require.Equal(t, http.StatusOK, storeRec.Code) var storeResp map[string]any - require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp)) + require.NoError(t, json.Unmarshal(storeRec.Body.Bytes(), &storeResp, jsonx.V1())) policyStoreID := storeResp["policyStoreId"].(string) isRec := doVPRequest(t, h, "CreateIdentitySource", map[string]any{ @@ -394,7 +395,7 @@ func TestVPHandler_TagResource_IdentitySourceResource(t *testing.T) { }) require.Equal(t, http.StatusOK, isRec.Code, "CreateIdentitySource body: %s", isRec.Body.String()) var isResp map[string]any - require.NoError(t, json.Unmarshal(isRec.Body.Bytes(), &isResp)) + require.NoError(t, json.Unmarshal(isRec.Body.Bytes(), &isResp, jsonx.V1())) identitySourceID := isResp["identitySourceId"].(string) identitySourceARN := fmt.Sprintf( @@ -413,7 +414,7 @@ func TestVPHandler_TagResource_IdentitySourceResource(t *testing.T) { }) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) tags, _ := listResp["tags"].(map[string]any) assert.Equal(t, "parity", tags["Owner"]) } diff --git a/services/verifiedpermissions/handler_test.go b/services/verifiedpermissions/handler_test.go index 9a4bfee5b1..0eb2a67d03 100644 --- a/services/verifiedpermissions/handler_test.go +++ b/services/verifiedpermissions/handler_test.go @@ -2,7 +2,8 @@ package verifiedpermissions_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -33,7 +35,7 @@ func doVPRequest( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -75,7 +77,7 @@ func createTestPolicyStore(t *testing.T, h *verifiedpermissions.Handler) string require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp["policyStoreId"].(string) } @@ -235,7 +237,7 @@ func TestVPHandler_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -315,7 +317,7 @@ func TestVPHandler_Snapshot_Restore(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores := resp["policyStores"].([]any) assert.Len(t, stores, tt.numStores) }) @@ -384,7 +386,7 @@ func TestVPHandler_Snapshot_Restore_WithNewResources(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var isResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &isResp, jsonx.V1())) isID := isResp["identitySourceId"].(string) // Put schema @@ -414,7 +416,7 @@ func TestVPHandler_Snapshot_Restore_WithNewResources(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var schemaResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schemaResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &schemaResp, jsonx.V1())) assert.JSONEq(t, `{"ns": "test"}`, schemaResp["schema"].(string)) } @@ -475,7 +477,7 @@ func TestVPHandler_HandleError_ValidationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.NewDecoder(rec.Body).Decode(&resp)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(rec.Body), &resp, jsonx.V1())) assert.Equal(t, "ValidationException", resp["__type"]) } @@ -515,7 +517,7 @@ func TestVPHandler_UnknownOp_ReturnsUnknownOperationException(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "UnknownOperationException", resp["__type"], "body: %s", rec.Body.String()) }) } diff --git a/services/verifiedpermissions/idempotency_test.go b/services/verifiedpermissions/idempotency_test.go index ea1b1ddff8..9d6ed32bbf 100644 --- a/services/verifiedpermissions/idempotency_test.go +++ b/services/verifiedpermissions/idempotency_test.go @@ -1,12 +1,14 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestVPHandler_CreateOps_ClientTokenIdempotency table-drives the shared @@ -93,19 +95,19 @@ func TestVPHandler_CreateOps_ClientTokenIdempotency(t *testing.T) { rec1 := doVPRequest(t, h, tt.action, tt.body(storeID, "fixed-token", "v1")) require.Equal(t, http.StatusOK, rec1.Code, "body: %s", rec1.Body.String()) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) // Same token, same parameters: replays the original resource. rec2 := doVPRequest(t, h, tt.action, tt.body(storeID, "fixed-token", "v1")) require.Equal(t, http.StatusOK, rec2.Code, "body: %s", rec2.Body.String()) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) assert.Equal(t, resp1[tt.idField], resp2[tt.idField]) listRec := doVPRequest(t, h, tt.listAction, map[string]any{"policyStoreId": storeID}) require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) items, _ := listResp[tt.listField].([]any) assert.Len(t, items, 1, "a replayed ClientToken call must not create a duplicate resource") @@ -113,7 +115,7 @@ func TestVPHandler_CreateOps_ClientTokenIdempotency(t *testing.T) { rec3 := doVPRequest(t, h, tt.action, tt.body(storeID, "fixed-token", "v2-different")) assert.Equal(t, http.StatusBadRequest, rec3.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "ConflictException", errResp["__type"]) }) } diff --git a/services/verifiedpermissions/multi_region_test.go b/services/verifiedpermissions/multi_region_test.go index f790a3625f..81efc36fa2 100644 --- a/services/verifiedpermissions/multi_region_test.go +++ b/services/verifiedpermissions/multi_region_test.go @@ -2,7 +2,8 @@ package verifiedpermissions_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -19,6 +20,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/verifiedpermissions" ) @@ -180,8 +182,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/verifiedpermissions/oidc_jwks.go b/services/verifiedpermissions/oidc_jwks.go index e09957cda8..b584091b30 100644 --- a/services/verifiedpermissions/oidc_jwks.go +++ b/services/verifiedpermissions/oidc_jwks.go @@ -6,7 +6,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/base64" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "io" @@ -20,6 +20,7 @@ import ( awsarn "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -103,7 +104,7 @@ func (v *TokenVerifier) getJSON(rawURL string, out any) error { return fmt.Errorf("read %s: %w", rawURL, err) } - if err = json.Unmarshal(body, out); err != nil { + if err = json.Unmarshal(body, out, jsonx.V1()); err != nil { return fmt.Errorf("decode %s: %w", rawURL, err) } diff --git a/services/verifiedpermissions/omission_defaults_test.go b/services/verifiedpermissions/omission_defaults_test.go index a61e124fc7..c1e5a8194b 100644 --- a/services/verifiedpermissions/omission_defaults_test.go +++ b/services/verifiedpermissions/omission_defaults_test.go @@ -1,13 +1,15 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // These tests lock in the documented per-page default the real SDK states @@ -28,7 +30,7 @@ func TestVPHandler_ListPolicyStores_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stores, _ := resp["policyStores"].([]any) assert.Len(t, stores, 10, "ListPolicyStores omits maxResults => real SDK defaults to 10 per response") @@ -58,7 +60,7 @@ func TestVPHandler_ListPolicies_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, _ := resp["policies"].([]any) assert.Len(t, policies, 10, "ListPolicies omits maxResults => real SDK defaults to 10 per response") @@ -83,7 +85,7 @@ func TestVPHandler_ListPolicyTemplates_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) templates, _ := resp["policyTemplates"].([]any) assert.Len(t, templates, 10, "ListPolicyTemplates omits maxResults => real SDK defaults to 10 per response") @@ -114,7 +116,7 @@ func TestVPHandler_ListIdentitySources_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sources, _ := resp["identitySources"].([]any) assert.Len(t, sources, 10, "ListIdentitySources omits maxResults => real SDK defaults to 10 per response") @@ -139,7 +141,7 @@ func TestVPHandler_ListPolicyStoreAliases_DefaultPageSize(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) aliases, _ := resp["policyStoreAliases"].([]any) assert.Len(t, aliases, 5, "ListPolicyStoreAliases omits maxResults => real SDK defaults to 5 per response") @@ -169,7 +171,7 @@ func TestVPHandler_CreateIdentitySource_CognitoGroupEntityTypeDefault(t *testing require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String()) var created map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &created, jsonx.V1())) rec = doVPRequest(t, h, "GetIdentitySource", map[string]any{ "policyStoreId": storeID, @@ -178,7 +180,7 @@ func TestVPHandler_CreateIdentitySource_CognitoGroupEntityTypeDefault(t *testing require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) cfg, _ := resp["configuration"].(map[string]any) cognito, _ := cfg["cognitoUserPoolConfiguration"].(map[string]any) diff --git a/services/verifiedpermissions/persistence.go b/services/verifiedpermissions/persistence.go index 53cb195968..2ad772f1fa 100644 --- a/services/verifiedpermissions/persistence.go +++ b/services/verifiedpermissions/persistence.go @@ -2,7 +2,7 @@ package verifiedpermissions import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -57,7 +57,7 @@ func buildSchemaDTORegistry() (*store.Registry, *store.Table[policyStoreSchemaDT // InMemoryBackend's doc comment in backend.go) and is persisted here // directly, as before. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ResourceTags map[string]map[string]string `json:"resourceTags,omitempty"` AccountID string `json:"accountID"` Region string `json:"region"` @@ -163,7 +163,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // registerAllTables doc) from tables via the ephemeral DTO registry, factored // out of Restore to keep Restore's own cognitive complexity low. Callers must // hold b.mu.Lock. -func (b *InMemoryBackend) restoreSchemas(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreSchemas(tables map[string]jsontext.Value) error { dtoReg, dtoTable := buildSchemaDTORegistry() if err := dtoReg.RestoreAll(tables); err != nil { return fmt.Errorf("verifiedpermissions: restore snapshot schema DTO table: %w", err) diff --git a/services/verifiedpermissions/policy_scope.go b/services/verifiedpermissions/policy_scope.go index 9d6ccc13aa..56f067d4b7 100644 --- a/services/verifiedpermissions/policy_scope.go +++ b/services/verifiedpermissions/policy_scope.go @@ -1,10 +1,12 @@ package verifiedpermissions import ( - "encoding/json" + "encoding/json/v2" "regexp" cedar "github.com/cedar-policy/cedar-go" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // cedarScopeEntity mirrors the "entity" object nested inside a Cedar JSON @@ -80,7 +82,7 @@ func instantiateTemplate(templateStatement string, p *Policy) string { // cedarEntityLiteral renders entityType::"entityId" as a Cedar entity UID literal. func cedarEntityLiteral(entityType, entityID string) string { - b, _ := json.Marshal(entityID) // Cedar string-literal escaping matches JSON's for plain ASCII IDs. + b, _ := json.Marshal(entityID, jsonx.V1()) // Cedar string-literal escaping matches JSON's for plain ASCII IDs. return entityType + "::" + string(b) } @@ -105,7 +107,7 @@ func parseCedarScope(statement string) *PolicyScopeResult { } var pj cedarPolicyJSON - if unmarshalErr := json.Unmarshal(raw, &pj); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(raw, &pj, jsonx.V1()); unmarshalErr != nil { return nil } diff --git a/services/verifiedpermissions/policy_template_item_shape_test.go b/services/verifiedpermissions/policy_template_item_shape_test.go index dfeafdbb97..d4461e57d7 100644 --- a/services/verifiedpermissions/policy_template_item_shape_test.go +++ b/services/verifiedpermissions/policy_template_item_shape_test.go @@ -1,11 +1,13 @@ package verifiedpermissions_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListPolicyTemplates_ItemHasNoStatement proves ListPolicyTemplates' @@ -35,7 +37,7 @@ func TestListPolicyTemplates_ItemHasNoStatement(t *testing.T) { var body struct { PolicyTemplates []map[string]any `json:"policyTemplates"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body, jsonx.V1())) require.Len(t, body.PolicyTemplates, 1) item := body.PolicyTemplates[0] diff --git a/services/verifiedpermissions/policy_validation.go b/services/verifiedpermissions/policy_validation.go index 0b686b94bf..c5a3db341d 100644 --- a/services/verifiedpermissions/policy_validation.go +++ b/services/verifiedpermissions/policy_validation.go @@ -2,19 +2,22 @@ package verifiedpermissions import ( "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "strings" cedar "github.com/cedar-policy/cedar-go" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var errSchemaMismatch = errors.New("policy validation failed") type cedarSchemaNamespace struct { - EntityTypes map[string]json.RawMessage `json:"entityTypes"` - Actions map[string]json.RawMessage `json:"actions"` + EntityTypes map[string]jsontext.Value `json:"entityTypes"` + Actions map[string]jsontext.Value `json:"actions"` } // splitQualified splits "NS::Sub::Name" into its namespace and local name. @@ -34,7 +37,7 @@ func (b *InMemoryBackend) loadSchemaNamespacesLocked(policyStoreID string) (map[ } var ns map[string]cedarSchemaNamespace - if err := json.Unmarshal([]byte(s.Schema), &ns); err != nil { + if err := json.Unmarshal([]byte(s.Schema), &ns, jsonx.V1()); err != nil { return nil, true } @@ -73,7 +76,7 @@ func (b *InMemoryBackend) validateStaticPolicyLocked(policyStoreID, statement st } var pj cedarPolicyJSON - if json.Unmarshal(raw, &pj) != nil { + if json.Unmarshal(raw, &pj, jsonx.V1()) != nil { continue } diff --git a/services/verifiedpermissions/schema.go b/services/verifiedpermissions/schema.go index ce86f7c45b..5fb8f2d4b2 100644 --- a/services/verifiedpermissions/schema.go +++ b/services/verifiedpermissions/schema.go @@ -1,18 +1,20 @@ package verifiedpermissions import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "time" "github.com/blackbirdworks/gopherstack/pkgs/collections" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // extractSchemaNamespaces parses a Cedar JSON schema and returns its top-level namespace keys. func extractSchemaNamespaces(schemaJSON string) []string { - var top map[string]json.RawMessage + var top map[string]jsontext.Value - if err := json.Unmarshal([]byte(schemaJSON), &top); err != nil { + if err := json.Unmarshal([]byte(schemaJSON), &top, jsonx.V1()); err != nil { return []string{} } @@ -31,7 +33,7 @@ func (b *InMemoryBackend) PutSchema(policyStoreID, schema string) ([]string, err } // Validate JSON format. - if !json.Valid([]byte(schema)) { + if !jsontext.Value([]byte(schema)).IsValid(jsonx.V1()) { return nil, fmt.Errorf("%w: schema is not valid JSON", ErrValidation) } diff --git a/services/vpclattice/handler.go b/services/vpclattice/handler.go index e1fc76a20a..8fff26fd74 100644 --- a/services/vpclattice/handler.go +++ b/services/vpclattice/handler.go @@ -1,7 +1,8 @@ package vpclattice import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "net/http" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -593,7 +595,7 @@ func (h *Handler) handleREST(c *echo.Context) error { var body map[string]any if c.Request().ContentLength != 0 { - if err := json.NewDecoder(c.Request().Body).Decode(&body); err != nil && + if err := json.UnmarshalDecode(jsontext.NewDecoder(c.Request().Body), &body, jsonx.V1()); err != nil && err.Error() != "EOF" { return validationError(c, "invalid JSON body") } diff --git a/services/vpclattice/handler_test.go b/services/vpclattice/handler_test.go index d2224d5337..b7b8d1ac26 100644 --- a/services/vpclattice/handler_test.go +++ b/services/vpclattice/handler_test.go @@ -3,7 +3,7 @@ package vpclattice_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/vpclattice" ) @@ -33,7 +34,7 @@ func doRequestWithRegion( var buf *bytes.Reader if body != nil { - data, err := json.Marshal(body) + data, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) buf = bytes.NewReader(data) } else { @@ -71,7 +72,7 @@ func doRequest( var buf *bytes.Reader if body != nil { - data, err := json.Marshal(body) + data, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) buf = bytes.NewReader(data) } else { @@ -95,7 +96,7 @@ func doRequest( func parseBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/vpclattice/multi_region_test.go b/services/vpclattice/multi_region_test.go index b71532f154..5af29fae29 100644 --- a/services/vpclattice/multi_region_test.go +++ b/services/vpclattice/multi_region_test.go @@ -2,7 +2,7 @@ package vpclattice_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/vpclattice" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/vpclattice/persistence.go b/services/vpclattice/persistence.go index 602637d495..5932ddc433 100644 --- a/services/vpclattice/persistence.go +++ b/services/vpclattice/persistence.go @@ -2,7 +2,7 @@ package vpclattice import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -34,7 +34,7 @@ const vpclatticeSnapshotVersion = 1 // incompatible (older or newer) build of this backend as though it were the // current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Targets map[string][]*storedTarget `json:"targets"` AuthPolicies map[string]string `json:"authPolicies"` ResourcePolicies map[string]string `json:"resourcePolicies"` diff --git a/services/vpclattice/whitebox_test.go b/services/vpclattice/whitebox_test.go index 89799da154..da03922fbf 100644 --- a/services/vpclattice/whitebox_test.go +++ b/services/vpclattice/whitebox_test.go @@ -2,7 +2,7 @@ package vpclattice import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -10,6 +10,8 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func ruleCount(b *InMemoryBackend) int { @@ -24,7 +26,7 @@ func doWhiteboxRequest(t *testing.T, h *Handler, method, path string, body any) var buf *bytes.Reader if body != nil { - data, err := json.Marshal(body) + data, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) buf = bytes.NewReader(data) } else { @@ -49,7 +51,7 @@ func parseWhiteboxBody(t *testing.T, rec *httptest.ResponseRecorder) map[string] t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } diff --git a/services/waf/byte_match_sets_test.go b/services/waf/byte_match_sets_test.go index 04e8ba472d..060ec8b14a 100644 --- a/services/waf/byte_match_sets_test.go +++ b/services/waf/byte_match_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -23,7 +24,7 @@ func TestWAF_ByteMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) bmsMap := createResp["ByteMatchSet"].(map[string]any) id := bmsMap["ByteMatchSetId"].(string) require.NotEmpty(t, id) @@ -33,7 +34,7 @@ func TestWAF_ByteMatchSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "GetByteMatchSet", map[string]any{"ByteMatchSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bms := resp["ByteMatchSet"].(map[string]any) assert.Equal(t, "my-bms", bms["Name"]) @@ -57,7 +58,7 @@ func TestWAF_ByteMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetByteMatchSet", map[string]any{"ByteMatchSetId": id}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bms = resp["ByteMatchSet"].(map[string]any) tuples := bms["ByteMatchTuples"].([]any) require.Len(t, tuples, 1) @@ -69,7 +70,7 @@ func TestWAF_ByteMatchSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "ListByteMatchSets", map[string]any{"Limit": 100}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["ByteMatchSets"].([]any) assert.Len(t, sets, 1) @@ -118,7 +119,7 @@ func wafCreateByteMatchSet(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["ByteMatchSet"].(map[string]any) id := m["ByteMatchSetId"].(string) require.NotEmpty(t, id) diff --git a/services/waf/change_token_test.go b/services/waf/change_token_test.go index 96885f93a1..1a60e19932 100644 --- a/services/waf/change_token_test.go +++ b/services/waf/change_token_test.go @@ -13,13 +13,14 @@ package waf_test // itself consume any other token. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -73,7 +74,7 @@ func TestWAF_GetChangeTokenStatus_Fresh(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PROVISIONED", resp["ChangeTokenStatus"]) } @@ -97,7 +98,7 @@ func TestWAF_GetChangeTokenStatus_AfterMutation_INSYNC(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "INSYNC", resp["ChangeTokenStatus"]) } @@ -202,7 +203,7 @@ func TestChangeTokenStatus_Lifecycle(t *testing.T) { rec := wafDo(t, h, "GetChangeTokenStatus", map[string]any{"ChangeToken": token}) require.Equal(t, http.StatusOK, rec.Code) var before map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &before, jsonx.V1())) assert.Equal(t, tt.wantBefore, before["ChangeTokenStatus"], "status before mutation") if tt.mutate != nil { @@ -213,7 +214,7 @@ func TestChangeTokenStatus_Lifecycle(t *testing.T) { rec = wafDo(t, h, "GetChangeTokenStatus", map[string]any{"ChangeToken": token}) require.Equal(t, http.StatusOK, rec.Code) var after map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &after, jsonx.V1())) assert.Equal(t, tt.wantAfter, after["ChangeTokenStatus"], "status after mutation") }) } @@ -229,7 +230,7 @@ func TestChangeTokenStatus_UnknownReturnsINSYNC(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "INSYNC", resp["ChangeTokenStatus"], "unknown token should return INSYNC per AWS behavior") } @@ -299,7 +300,7 @@ func TestChangeToken_AlreadyConsumedTokenRejected(t *testing.T) { require.Equal(t, http.StatusOK, statusRec.Code) var statusResp map[string]any - require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp)) + require.NoError(t, json.Unmarshal(statusRec.Body.Bytes(), &statusResp, jsonx.V1())) require.Equal(t, "INSYNC", statusResp["ChangeTokenStatus"], "token must be consumed before reuse attempt") // Reusing the same, now-INSYNC token for a second mutation must fail. @@ -315,7 +316,7 @@ func TestChangeToken_AlreadyConsumedTokenRejected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.Len(t, listResp["Rules"], 1, "the rejected second CreateRule must not have mutated state") } diff --git a/services/waf/geo_match_sets_test.go b/services/waf/geo_match_sets_test.go index afce69e173..0b45020079 100644 --- a/services/waf/geo_match_sets_test.go +++ b/services/waf/geo_match_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -23,7 +24,7 @@ func TestWAF_GeoMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) gmsMap := createResp["GeoMatchSet"].(map[string]any) id := gmsMap["GeoMatchSetId"].(string) require.NotEmpty(t, id) @@ -48,7 +49,7 @@ func TestWAF_GeoMatchSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "GetGeoMatchSet", map[string]any{"GeoMatchSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) gms := resp["GeoMatchSet"].(map[string]any) constraints := gms["GeoMatchConstraints"].([]any) require.Len(t, constraints, 1) @@ -81,7 +82,7 @@ func TestWAF_GeoMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetGeoMatchSet", map[string]any{"GeoMatchSetId": id}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) gms = resp["GeoMatchSet"].(map[string]any) constraints = gms["GeoMatchConstraints"].([]any) require.Len(t, constraints, 1) @@ -90,7 +91,7 @@ func TestWAF_GeoMatchSet_CreateGetUpdateDeleteList(t *testing.T) { // List rec = wafDo(t, h, "ListGeoMatchSets", map[string]any{"Limit": 100}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["GeoMatchSets"].([]any) assert.Len(t, sets, 1) @@ -137,7 +138,7 @@ func wafCreateGeoMatchSet(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["GeoMatchSet"].(map[string]any) id := m["GeoMatchSetId"].(string) require.NotEmpty(t, id) diff --git a/services/waf/handler.go b/services/waf/handler.go index d850c0875f..32706bf544 100644 --- a/services/waf/handler.go +++ b/services/waf/handler.go @@ -2,7 +2,7 @@ package waf import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -10,6 +10,7 @@ import ( "github.com/labstack/echo/v5" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -112,12 +113,12 @@ func (h *Handler) dispatch(_ context.Context, action string, body []byte) ([]byt var tokenHolder struct { ChangeToken string `json:"ChangeToken"` } - if jerr := json.Unmarshal(body, &tokenHolder); jerr == nil && tokenHolder.ChangeToken != "" { + if jerr := json.Unmarshal(body, &tokenHolder, jsonx.V1()); jerr == nil && tokenHolder.ChangeToken != "" { h.Backend.MarkChangeTokenUsed(tokenHolder.ChangeToken) } } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } // paginate returns a window of items starting after nextMarker, limited to limit items, @@ -282,7 +283,7 @@ func unmarshal(body []byte, v any) error { return nil } - return json.Unmarshal(body, v) + return json.Unmarshal(body, v, jsonx.V1()) } func tagsToMap(tags []Tag) map[string]string { diff --git a/services/waf/handler_test.go b/services/waf/handler_test.go index e8302ad73d..9ceab9987b 100644 --- a/services/waf/handler_test.go +++ b/services/waf/handler_test.go @@ -7,7 +7,7 @@ package waf_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -33,7 +34,7 @@ func wafDo(t *testing.T, h *waf.Handler, action string, body any) *httptest.Resp var b []byte if body != nil { var err error - b, err = json.Marshal(body) + b, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -56,7 +57,7 @@ func wafGetToken(t *testing.T, h *waf.Handler) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) token, ok := resp["ChangeToken"].(string) require.True(t, ok) require.NotEmpty(t, token) @@ -68,7 +69,7 @@ func errType(t *testing.T, body []byte) string { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(body, &resp)) + require.NoError(t, json.Unmarshal(body, &resp, jsonx.V1())) typ, _ := resp["__type"].(string) diff --git a/services/waf/ip_sets_test.go b/services/waf/ip_sets_test.go index 283d23a7fc..85af15ad3a 100644 --- a/services/waf/ip_sets_test.go +++ b/services/waf/ip_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -22,7 +23,7 @@ func wafCreateIPSet(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["IPSet"].(map[string]any) id := m["IPSetId"].(string) require.NotEmpty(t, id) @@ -41,7 +42,7 @@ func TestWAF_IPSet_CreateGetUpdateDeleteList(t *testing.T) { rec := wafDo(t, h, "GetIPSet", map[string]any{"IPSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ipSet := resp["IPSet"].(map[string]any) assert.Equal(t, "my-ips", ipSet["Name"]) descs := ipSet["IPSetDescriptors"].([]any) @@ -66,7 +67,7 @@ func TestWAF_IPSet_CreateGetUpdateDeleteList(t *testing.T) { // Verify rec = wafDo(t, h, "GetIPSet", map[string]any{"IPSetId": id}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ipSet = resp["IPSet"].(map[string]any) descs = ipSet["IPSetDescriptors"].([]any) require.Len(t, descs, 1) @@ -92,7 +93,7 @@ func TestWAF_IPSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetIPSet", map[string]any{"IPSetId": id}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ipSet = resp["IPSet"].(map[string]any) descs = ipSet["IPSetDescriptors"].([]any) assert.Empty(t, descs) @@ -101,7 +102,7 @@ func TestWAF_IPSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "ListIPSets", map[string]any{"Limit": 100}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["IPSets"].([]any) assert.Len(t, sets, 1) @@ -139,7 +140,7 @@ func TestWAF_IPSet_IPv6Descriptor(t *testing.T) { rec = wafDo(t, h, "GetIPSet", map[string]any{"IPSetId": id}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ipSet := resp["IPSet"].(map[string]any) descs := ipSet["IPSetDescriptors"].([]any) require.Len(t, descs, 1) @@ -155,7 +156,7 @@ func TestWAF_MultipleIPSets_List(t *testing.T) { rec := wafDo(t, h, "ListIPSets", map[string]any{"Limit": 100}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sets := resp["IPSets"].([]any) assert.Len(t, sets, 2) } diff --git a/services/waf/logging_configuration_test.go b/services/waf/logging_configuration_test.go index 6c69cf2221..1f4b4a4f8d 100644 --- a/services/waf/logging_configuration_test.go +++ b/services/waf/logging_configuration_test.go @@ -1,12 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestLoggingConfigurationLifecycle(t *testing.T) { @@ -40,14 +42,14 @@ func TestLoggingConfigurationLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &putResp, jsonx.V1())) assert.NotNil(t, putResp["LoggingConfiguration"]) // Get rec = wafDo(t, h, "GetLoggingConfiguration", map[string]any{"ResourceArn": aclARN}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) cfgMap := getResp["LoggingConfiguration"].(map[string]any) assert.Equal(t, aclARN, cfgMap["ResourceArn"]) @@ -55,7 +57,7 @@ func TestLoggingConfigurationLifecycle(t *testing.T) { rec = wafDo(t, h, "ListLoggingConfigurations", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) cfgs := listResp["LoggingConfigurations"].([]any) assert.Len(t, cfgs, 1) @@ -70,7 +72,7 @@ func TestLoggingConfigurationLifecycle(t *testing.T) { // List after delete → empty rec = wafDo(t, h, "ListLoggingConfigurations", nil) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) cfgs = listResp["LoggingConfigurations"].([]any) assert.Empty(t, cfgs) }) diff --git a/services/waf/non_empty_entity_test.go b/services/waf/non_empty_entity_test.go index b763303508..b23cfa87fc 100644 --- a/services/waf/non_empty_entity_test.go +++ b/services/waf/non_empty_entity_test.go @@ -10,13 +10,14 @@ package waf_test // delete succeeds. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -505,7 +506,7 @@ func wafCreateNamedSet( require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m, ok := resp[respKey].(map[string]any) require.True(t, ok) id, ok := m[idKey].(string) diff --git a/services/waf/pagination_test.go b/services/waf/pagination_test.go index 2ea15bc145..196b925a97 100644 --- a/services/waf/pagination_test.go +++ b/services/waf/pagination_test.go @@ -4,13 +4,14 @@ package waf_test // every List* operation. import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -21,7 +22,7 @@ func wafListPage(t *testing.T, h *waf.Handler, op string, body map[string]any) m rec := wafDo(t, h, op, body) require.Equal(t, http.StatusOK, rec.Code, op+": "+rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) return resp } diff --git a/services/waf/permission_policy_test.go b/services/waf/permission_policy_test.go index 5f3886b52d..d4c6c5030e 100644 --- a/services/waf/permission_policy_test.go +++ b/services/waf/permission_policy_test.go @@ -1,12 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPermissionPolicyLifecycle(t *testing.T) { @@ -40,7 +42,7 @@ func TestPermissionPolicyLifecycle(t *testing.T) { rec = wafDo(t, h, "GetPermissionPolicy", map[string]any{"ResourceArn": rgARN}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) assert.Equal(t, policy, getResp["Policy"]) // Delete diff --git a/services/waf/persistence.go b/services/waf/persistence.go index f0cf3fb6b2..e164d896d8 100644 --- a/services/waf/persistence.go +++ b/services/waf/persistence.go @@ -2,7 +2,7 @@ package waf import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -33,7 +33,7 @@ const wafSnapshotVersion = 1 // plain maps -- see store_setup.go's file doc comment for why they do not fit // store.Table's keyed-by-identity-value shape. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` ChangeTokens map[string]string `json:"changeTokens"` RuleGroupRules map[string][]ActivatedRule `json:"ruleGroupRules"` PermissionPolicies map[string]string `json:"permissionPolicies"` diff --git a/services/waf/rate_based_rules_test.go b/services/waf/rate_based_rules_test.go index 9e690eefdb..98835f1be5 100644 --- a/services/waf/rate_based_rules_test.go +++ b/services/waf/rate_based_rules_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -26,7 +27,7 @@ func wafCreateRateBasedRule(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ruleMap, ok := resp["Rule"].(map[string]any) require.True(t, ok) id, ok := ruleMap["RuleId"].(string) @@ -60,7 +61,7 @@ func TestRateBasedRuleLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) ruleMap := getResp["Rule"].(map[string]any) assert.Equal(t, "BlockBots", ruleMap["Name"]) assert.Equal(t, "IP", ruleMap["RateKey"]) @@ -88,7 +89,7 @@ func TestRateBasedRuleLifecycle(t *testing.T) { // Get to verify updates rec = wafDo(t, h, "GetRateBasedRule", map[string]any{"RuleId": ruleID}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) ruleMap = getResp["Rule"].(map[string]any) assert.InEpsilon(t, float64(3000), ruleMap["RateLimit"], 0.001) preds := ruleMap["MatchPredicates"].([]any) @@ -117,7 +118,7 @@ func TestRateBasedRuleLifecycle(t *testing.T) { rec = wafDo(t, h, "ListRateBasedRules", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) rules := listResp["Rules"].([]any) assert.Len(t, rules, 1) @@ -125,7 +126,7 @@ func TestRateBasedRuleLifecycle(t *testing.T) { rec = wafDo(t, h, "GetRateBasedRuleManagedKeys", map[string]any{"RuleId": ruleID}) require.Equal(t, http.StatusOK, rec.Code) var mkResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mkResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &mkResp, jsonx.V1())) assert.NotNil(t, mkResp["ManagedKeys"]) // Delete diff --git a/services/waf/realclient_match_sets_and_rules_test.go b/services/waf/realclient_match_sets_and_rules_test.go index 7c5ff5c48a..100f1a94ee 100644 --- a/services/waf/realclient_match_sets_and_rules_test.go +++ b/services/waf/realclient_match_sets_and_rules_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -942,7 +943,7 @@ func testPermissionPolicyRealClient(t *testing.T) { }, } - policyBytes, err := json.Marshal(policyStatement) + policyBytes, err := json.Marshal(policyStatement, jsonx.V1()) require.NoError(t, err) policy := string(policyBytes) diff --git a/services/waf/regex_match_sets_test.go b/services/waf/regex_match_sets_test.go index c90aef1887..3f57ac03ba 100644 --- a/services/waf/regex_match_sets_test.go +++ b/services/waf/regex_match_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -22,7 +23,7 @@ func wafCreateRegexMatchSet(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) setMap, ok := resp["RegexMatchSet"].(map[string]any) require.True(t, ok) id, ok := setMap["RegexMatchSetId"].(string) @@ -57,7 +58,7 @@ func TestRegexMatchSetLifecycle(t *testing.T) { rec := wafDo(t, h, "GetRegexMatchSet", map[string]any{"RegexMatchSetId": setID}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) setMap := getResp["RegexMatchSet"].(map[string]any) assert.Equal(t, "SqlMatchSet", setMap["Name"]) @@ -80,7 +81,7 @@ func TestRegexMatchSetLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetRegexMatchSet", map[string]any{"RegexMatchSetId": setID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) setMap = getResp["RegexMatchSet"].(map[string]any) tuples := setMap["RegexMatchTuples"].([]any) assert.Len(t, tuples, 1) @@ -107,7 +108,7 @@ func TestRegexMatchSetLifecycle(t *testing.T) { rec = wafDo(t, h, "ListRegexMatchSets", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["RegexMatchSets"].([]any) assert.Len(t, sets, 1) diff --git a/services/waf/regex_pattern_sets_test.go b/services/waf/regex_pattern_sets_test.go index e9a4133096..330dec962d 100644 --- a/services/waf/regex_pattern_sets_test.go +++ b/services/waf/regex_pattern_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -22,7 +23,7 @@ func wafCreateRegexPatternSet(t *testing.T, h *waf.Handler, name string) string require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) setMap, ok := resp["RegexPatternSet"].(map[string]any) require.True(t, ok) id, ok := setMap["RegexPatternSetId"].(string) @@ -55,7 +56,7 @@ func TestRegexPatternSetLifecycle(t *testing.T) { rec := wafDo(t, h, "GetRegexPatternSet", map[string]any{"RegexPatternSetId": setID}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) setMap := getResp["RegexPatternSet"].(map[string]any) assert.Equal(t, "SqlPatterns", setMap["Name"]) assert.Empty(t, setMap["RegexPatternStrings"]) @@ -73,7 +74,7 @@ func TestRegexPatternSetLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetRegexPatternSet", map[string]any{"RegexPatternSetId": setID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) setMap = getResp["RegexPatternSet"].(map[string]any) assert.Len(t, setMap["RegexPatternStrings"], 2) @@ -89,7 +90,7 @@ func TestRegexPatternSetLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetRegexPatternSet", map[string]any{"RegexPatternSetId": setID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) setMap = getResp["RegexPatternSet"].(map[string]any) assert.Len(t, setMap["RegexPatternStrings"], 1) @@ -97,7 +98,7 @@ func TestRegexPatternSetLifecycle(t *testing.T) { rec = wafDo(t, h, "ListRegexPatternSets", nil) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["RegexPatternSets"].([]any) assert.Len(t, sets, 1) diff --git a/services/waf/request_validation_test.go b/services/waf/request_validation_test.go index edb9b1b6a8..64b0684734 100644 --- a/services/waf/request_validation_test.go +++ b/services/waf/request_validation_test.go @@ -1,13 +1,15 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_RequestValidation(t *testing.T) { @@ -128,7 +130,7 @@ func TestHandler_RequestValidation(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, tt.wantCode, out.Type) assert.Contains(t, out.Message, tt.wantMsg) assert.False(t, strings.HasPrefix(out.Message, tt.wantCode), "message repeats code: %q", out.Message) @@ -152,7 +154,7 @@ func TestHandler_StaleTokenMessage(t *testing.T) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(second.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(second.Body.Bytes(), &out, jsonx.V1())) assert.Equal(t, "WAFStaleDataException", out.Type) assert.Equal(t, "The input token is no longer current.", out.Message) } diff --git a/services/waf/rule_groups_test.go b/services/waf/rule_groups_test.go index ed96f059b1..d95b117381 100644 --- a/services/waf/rule_groups_test.go +++ b/services/waf/rule_groups_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -24,7 +25,7 @@ func wafCreateRuleGroup(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rgMap, ok := resp["RuleGroup"].(map[string]any) require.True(t, ok) id, ok := rgMap["RuleGroupId"].(string) @@ -60,7 +61,7 @@ func TestRuleGroupLifecycle(t *testing.T) { rec := wafDo(t, h, "GetRuleGroup", map[string]any{"RuleGroupId": rgID}) require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) rgMap := getResp["RuleGroup"].(map[string]any) assert.Equal(t, "MyGroup", rgMap["Name"]) @@ -89,7 +90,7 @@ func TestRuleGroupLifecycle(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) var listARResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listARResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listARResp, jsonx.V1())) activated := listARResp["ActivatedRules"].([]any) assert.Len(t, activated, 1) @@ -116,7 +117,7 @@ func TestRuleGroupLifecycle(t *testing.T) { rec = wafDo(t, h, "ListRuleGroups", nil) require.Equal(t, http.StatusOK, rec.Code) var listRGResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listRGResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listRGResp, jsonx.V1())) groups := listRGResp["RuleGroups"].([]any) assert.Len(t, groups, 1) @@ -216,7 +217,7 @@ func TestUpdateRuleGroup_RejectsDuplicateRuleId(t *testing.T) { rec = wafDo(t, h, "ListActivatedRulesInRuleGroup", map[string]any{"RuleGroupId": rgID}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) activated, _ := listResp["ActivatedRules"].([]any) assert.Len(t, activated, 1, "group must still contain only the one successfully-inserted rule") } @@ -291,7 +292,7 @@ func TestListSubscribedRuleGroups(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups := resp["RuleGroups"].([]any) assert.Empty(t, groups) }) diff --git a/services/waf/rules_test.go b/services/waf/rules_test.go index 24019ba617..e379b25e95 100644 --- a/services/waf/rules_test.go +++ b/services/waf/rules_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -24,7 +25,7 @@ func wafCreateRule(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ruleMap := resp["Rule"].(map[string]any) id := ruleMap["RuleId"].(string) require.NotEmpty(t, id) @@ -45,7 +46,7 @@ func TestWAF_Rule_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["Rule"].(map[string]any) assert.Equal(t, "my-rule", rule["Name"]) assert.Equal(t, id, rule["RuleId"]) @@ -55,7 +56,7 @@ func TestWAF_Rule_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) rules := listResp["Rules"].([]any) assert.Len(t, rules, 1) @@ -97,7 +98,7 @@ func TestWAF_Rule_UpdatePredicates_InsertDelete(t *testing.T) { // Verify predicate rec = wafDo(t, h, "GetRule", map[string]any{"RuleId": id}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule := resp["Rule"].(map[string]any) predicates := rule["Predicates"].([]any) require.Len(t, predicates, 1) @@ -124,7 +125,7 @@ func TestWAF_Rule_UpdatePredicates_InsertDelete(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetRule", map[string]any{"RuleId": id}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule = resp["Rule"].(map[string]any) predicates = rule["Predicates"].([]any) assert.Empty(t, predicates) diff --git a/services/waf/sampled_requests_test.go b/services/waf/sampled_requests_test.go index 6909be80ea..970159aea7 100644 --- a/services/waf/sampled_requests_test.go +++ b/services/waf/sampled_requests_test.go @@ -1,12 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestWAF_GetSampledRequests_EmptyStub(t *testing.T) { @@ -31,7 +33,7 @@ func TestWAF_GetSampledRequests_EmptyStub(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) samples := resp["SampledRequests"].([]any) assert.Empty(t, samples) assert.EqualValues(t, 0, resp["PopulationSize"]) @@ -105,7 +107,7 @@ func TestGetSampledRequests_ReturnsTimeWindow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tw, ok := resp["TimeWindow"].(map[string]any) require.True(t, ok, "TimeWindow must be present in response") diff --git a/services/waf/size_constraint_sets_test.go b/services/waf/size_constraint_sets_test.go index cee0604e52..acc2c4dd60 100644 --- a/services/waf/size_constraint_sets_test.go +++ b/services/waf/size_constraint_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -23,7 +24,7 @@ func TestWAF_SizeConstraintSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) scsMap := createResp["SizeConstraintSet"].(map[string]any) id := scsMap["SizeConstraintSetId"].(string) require.NotEmpty(t, id) @@ -50,7 +51,7 @@ func TestWAF_SizeConstraintSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "GetSizeConstraintSet", map[string]any{"SizeConstraintSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) scs := resp["SizeConstraintSet"].(map[string]any) constraints := scs["SizeConstraints"].([]any) require.Len(t, constraints, 1) @@ -61,7 +62,7 @@ func TestWAF_SizeConstraintSet_CreateGetUpdateDeleteList(t *testing.T) { // List rec = wafDo(t, h, "ListSizeConstraintSets", map[string]any{"Limit": 100}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["SizeConstraintSets"].([]any) assert.Len(t, sets, 1) @@ -110,7 +111,7 @@ func wafCreateSizeConstraintSet(t *testing.T, h *waf.Handler, name string) strin require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["SizeConstraintSet"].(map[string]any) id := m["SizeConstraintSetId"].(string) require.NotEmpty(t, id) diff --git a/services/waf/sql_injection_match_sets_test.go b/services/waf/sql_injection_match_sets_test.go index 8a0c876b3d..d4e7eaf1ba 100644 --- a/services/waf/sql_injection_match_sets_test.go +++ b/services/waf/sql_injection_match_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -23,7 +24,7 @@ func TestWAF_SqlInjectionMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) simsMap := createResp["SqlInjectionMatchSet"].(map[string]any) id := simsMap["SqlInjectionMatchSetId"].(string) require.NotEmpty(t, id) @@ -48,7 +49,7 @@ func TestWAF_SqlInjectionMatchSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "GetSqlInjectionMatchSet", map[string]any{"SqlInjectionMatchSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) sims := resp["SqlInjectionMatchSet"].(map[string]any) tuples := sims["SqlInjectionMatchTuples"].([]any) require.Len(t, tuples, 1) @@ -60,7 +61,7 @@ func TestWAF_SqlInjectionMatchSet_CreateGetUpdateDeleteList(t *testing.T) { // List rec = wafDo(t, h, "ListSqlInjectionMatchSets", map[string]any{"Limit": 100}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["SqlInjectionMatchSets"].([]any) assert.Len(t, sets, 1) @@ -107,7 +108,7 @@ func wafCreateSQLInjectionMatchSet(t *testing.T, h *waf.Handler, name string) st require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["SqlInjectionMatchSet"].(map[string]any) id := m["SqlInjectionMatchSetId"].(string) require.NotEmpty(t, id) diff --git a/services/waf/tag_leak_test.go b/services/waf/tag_leak_test.go index 4c19d76ccb..0de085f93e 100644 --- a/services/waf/tag_leak_test.go +++ b/services/waf/tag_leak_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -223,7 +224,7 @@ func TestWAF_Delete_ClearsTags(t *testing.T) { rec := wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resourceARN, ok := resp["WebACL"].(map[string]any)["WebACLArn"].(string) require.True(t, ok) @@ -257,7 +258,7 @@ func TestWAF_Delete_ClearsTags(t *testing.T) { rec := wafDo(t, h, "ListTagsForResource", map[string]any{"ResourceARN": resourceARN}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) require.NotEmpty(t, listResp["TagInfoForResource"].(map[string]any)["TagList"]) token := wafGetToken(t, h) @@ -277,7 +278,7 @@ func TestWAF_Delete_ClearsTags(t *testing.T) { // Deleting one resource must not disturb another's tags. rec = wafDo(t, h, "ListTagsForResource", map[string]any{"ResourceARN": otherARN}) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) assert.NotEmpty(t, listResp["TagInfoForResource"].(map[string]any)["TagList"]) }) } diff --git a/services/waf/tags_test.go b/services/waf/tags_test.go index a2179ba1c6..ecd07e29be 100644 --- a/services/waf/tags_test.go +++ b/services/waf/tags_test.go @@ -1,12 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestWAF_Tags_TagAndList(t *testing.T) { @@ -19,7 +21,7 @@ func TestWAF_Tags_TagAndList(t *testing.T) { rec := wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": aclID}) require.Equal(t, http.StatusOK, rec.Code) var aclResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp, jsonx.V1())) arn := aclResp["WebACL"].(map[string]any)["WebACLArn"].(string) require.NotEmpty(t, arn) @@ -37,7 +39,7 @@ func TestWAF_Tags_TagAndList(t *testing.T) { rec = wafDo(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) info := listResp["TagInfoForResource"].(map[string]any) assert.Equal(t, arn, info["ResourceARN"]) tagList := info["TagList"].([]any) @@ -62,7 +64,7 @@ func TestWAF_Tags_UntagResource(t *testing.T) { rec := wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": aclID}) var aclResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &aclResp, jsonx.V1())) arn := aclResp["WebACL"].(map[string]any)["WebACLArn"].(string) // Tag @@ -85,7 +87,7 @@ func TestWAF_Tags_UntagResource(t *testing.T) { // Verify only k2 remains rec = wafDo(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tagList := listResp["TagInfoForResource"].(map[string]any)["TagList"].([]any) require.Len(t, tagList, 1) assert.Equal(t, "k2", tagList[0].(map[string]any)["Key"]) @@ -108,12 +110,12 @@ func TestWAF_Tags_CreateWebACLWithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) arn := resp["WebACL"].(map[string]any)["WebACLArn"].(string) rec = wafDo(t, h, "ListTagsForResource", map[string]any{"ResourceARN": arn}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) tagList := listResp["TagInfoForResource"].(map[string]any)["TagList"].([]any) require.Len(t, tagList, 1) assert.Equal(t, "cost-center", tagList[0].(map[string]any)["Key"]) diff --git a/services/waf/web_acls_test.go b/services/waf/web_acls_test.go index 3e269f8dfc..b60f5ee818 100644 --- a/services/waf/web_acls_test.go +++ b/services/waf/web_acls_test.go @@ -1,7 +1,7 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strings" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -25,7 +26,7 @@ func wafCreateWebACL(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) aclMap, ok := resp["WebACL"].(map[string]any) require.True(t, ok) id, ok := aclMap["WebACLId"].(string) @@ -49,7 +50,7 @@ func TestWAF_WebACL_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) acl := getResp["WebACL"].(map[string]any) assert.Equal(t, "my-acl", acl["Name"]) assert.Equal(t, id, acl["WebACLId"]) @@ -64,7 +65,7 @@ func TestWAF_WebACL_CreateGetDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) acls := listResp["WebACLs"].([]any) assert.Len(t, acls, 1) @@ -92,7 +93,7 @@ func TestWAF_WebACL_DefaultAction_BLOCK(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) acl := resp["WebACL"].(map[string]any) da := acl["DefaultAction"].(map[string]any) assert.Equal(t, "BLOCK", da["Type"]) @@ -114,7 +115,7 @@ func TestWAF_WebACL_UpdateDefaultAction(t *testing.T) { rec = wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": id}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) acl := resp["WebACL"].(map[string]any) da := acl["DefaultAction"].(map[string]any) assert.Equal(t, "BLOCK", da["Type"]) @@ -148,7 +149,7 @@ func TestWAF_WebACL_UpdateRules_InsertAndDelete(t *testing.T) { // Verify rule present rec = wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": aclID}) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) acl := getResp["WebACL"].(map[string]any) rules := acl["Rules"].([]any) require.Len(t, rules, 1) @@ -175,7 +176,7 @@ func TestWAF_WebACL_UpdateRules_InsertAndDelete(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) rec = wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": aclID}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) acl = getResp["WebACL"].(map[string]any) rules = acl["Rules"].([]any) assert.Empty(t, rules) @@ -263,7 +264,7 @@ func TestWAF_WebACL_RulesPrioritySorted(t *testing.T) { rec = wafDo(t, h, "GetWebACL", map[string]any{"WebACLId": aclID}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules := resp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 2) assert.EqualValues(t, 1, rules[0].(map[string]any)["Priority"]) @@ -284,7 +285,7 @@ func TestWAF_WebACL_ChangeTokenRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, token, resp["ChangeToken"]) } @@ -299,7 +300,7 @@ func TestWAF_MultipleWebACLs_List(t *testing.T) { rec := wafDo(t, h, "ListWebACLs", map[string]any{"Limit": 100}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) acls := resp["WebACLs"].([]any) assert.Len(t, acls, 3) } @@ -369,7 +370,7 @@ func TestCreateWebACLMigrationStack(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) s3URL, ok := resp["S3ObjectUrl"].(string) require.True(t, ok) assert.Contains(t, s3URL, "my-migration-bucket") diff --git a/services/waf/xss_match_sets_test.go b/services/waf/xss_match_sets_test.go index 77e9177725..e5feca34f1 100644 --- a/services/waf/xss_match_sets_test.go +++ b/services/waf/xss_match_sets_test.go @@ -1,13 +1,14 @@ package waf_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/waf" ) @@ -23,7 +24,7 @@ func TestWAF_XssMatchSet_CreateGetUpdateDeleteList(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) xmsMap := createResp["XssMatchSet"].(map[string]any) id := xmsMap["XssMatchSetId"].(string) require.NotEmpty(t, id) @@ -48,7 +49,7 @@ func TestWAF_XssMatchSet_CreateGetUpdateDeleteList(t *testing.T) { rec = wafDo(t, h, "GetXssMatchSet", map[string]any{"XssMatchSetId": id}) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) xms := resp["XssMatchSet"].(map[string]any) tuples := xms["XssMatchTuples"].([]any) require.Len(t, tuples, 1) @@ -57,7 +58,7 @@ func TestWAF_XssMatchSet_CreateGetUpdateDeleteList(t *testing.T) { // List rec = wafDo(t, h, "ListXssMatchSets", map[string]any{"Limit": 100}) var listResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &listResp, jsonx.V1())) sets := listResp["XssMatchSets"].([]any) assert.Len(t, sets, 1) @@ -104,7 +105,7 @@ func wafCreateXSSMatchSet(t *testing.T, h *waf.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) m := resp["XssMatchSet"].(map[string]any) id := m["XssMatchSetId"].(string) require.NotEmpty(t, id) diff --git a/services/wafv2/capacity_test.go b/services/wafv2/capacity_test.go index cf932b4901..19896f15d0 100644 --- a/services/wafv2/capacity_test.go +++ b/services/wafv2/capacity_test.go @@ -1,13 +1,14 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -29,7 +30,7 @@ func checkCapacityFor(t *testing.T, h *wafv2.Handler, stmt map[string]any) int { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) consumed, ok := result["Capacity"].(float64) require.True(t, ok) @@ -358,7 +359,7 @@ func TestCheckCapacity_MultipleRulesSum(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) consumed, ok := result["Capacity"].(float64) require.True(t, ok) assert.InDelta(t, float64(1+40), consumed, 0) diff --git a/services/wafv2/export_test.go b/services/wafv2/export_test.go index 03a9a6478a..b857580317 100644 --- a/services/wafv2/export_test.go +++ b/services/wafv2/export_test.go @@ -2,7 +2,7 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" ) // CreateWebACLSimple is a test helper that creates a WebACL with minimal parameters, @@ -13,11 +13,11 @@ func CreateWebACLSimple( name, scope, description, defaultAction string, tags map[string]string, ) (*WebACL, error) { - var da json.RawMessage + var da jsontext.Value if defaultAction == "BLOCK" { - da = json.RawMessage(`{"Block":{}}`) + da = jsontext.Value(`{"Block":{}}`) } else { - da = json.RawMessage(`{"Allow":{}}`) + da = jsontext.Value(`{"Allow":{}}`) } return b.CreateWebACL( diff --git a/services/wafv2/handler.go b/services/wafv2/handler.go index 05cebab83f..91951b8eff 100644 --- a/services/wafv2/handler.go +++ b/services/wafv2/handler.go @@ -3,7 +3,8 @@ package wafv2 import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "maps" @@ -14,6 +15,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awserr" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -279,9 +281,6 @@ func (h *Handler) dispatch(ctx context.Context, op string, body []byte) ([]byte, return fn(ctx, body) } func (h *Handler) handleError(c *echo.Context, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - var errType string var statusCode int @@ -310,7 +309,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { case errors.Is(err, ErrConfigurationWarning): errType = "WAFConfigurationWarningException" statusCode = http.StatusBadRequest - case errors.Is(err, errInvalidRequest), errors.As(err, &syntaxErr), errors.As(err, &typeErr): + case errors.Is(err, errInvalidRequest), jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): errType = "WAFInvalidParameterException" statusCode = http.StatusBadRequest case errors.Is(err, errUnknownAction): @@ -324,7 +323,7 @@ func (h *Handler) handleError(c *echo.Context, err error) error { payload, _ := json.Marshal(map[string]string{ keyTypeField: errType, keyMessageField: publicMessage(err), - }) + }, jsonx.V1()) c.Response().Header().Set("X-Amzn-Errortype", errType) @@ -332,10 +331,10 @@ func (h *Handler) handleError(c *echo.Context, err error) error { } // parseVisibilityConfig parses a stored VisibilityConfig JSON string or returns a default. -func parseVisibilityConfig(stored json.RawMessage, metricName string) map[string]any { +func parseVisibilityConfig(stored jsontext.Value, metricName string) map[string]any { if len(stored) > 0 { var m map[string]any - if err := json.Unmarshal(stored, &m); err == nil { + if err := json.Unmarshal(stored, &m, jsonx.V1()); err == nil { return m } } @@ -514,7 +513,7 @@ func handleListResourceFamily[T any]( buildSummary func(T) map[string]string, ) ([]byte, error) { var req listFamilyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -531,5 +530,5 @@ func handleListResourceFamily[T any]( resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } diff --git a/services/wafv2/handler_api_keys.go b/services/wafv2/handler_api_keys.go index 7c474db3c5..55a506e7e7 100644 --- a/services/wafv2/handler_api_keys.go +++ b/services/wafv2/handler_api_keys.go @@ -3,9 +3,10 @@ package wafv2 import ( "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -17,7 +18,7 @@ type createAPIKeyRequest struct { func (h *Handler) handleCreateAPIKey(ctx context.Context, body []byte) ([]byte, error) { var req createAPIKeyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -46,7 +47,7 @@ func (h *Handler) handleCreateAPIKey(ctx context.Context, body []byte) ([]byte, return json.Marshal(map[string]any{ "APIKey": encodedKey, - }) + }, jsonx.V1()) } // deleteAPIKeyRequest is the request body for DeleteAPIKey. @@ -57,7 +58,7 @@ type deleteAPIKeyRequest struct { func (h *Handler) handleDeleteAPIKey(ctx context.Context, body []byte) ([]byte, error) { var req deleteAPIKeyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -94,7 +95,7 @@ type listAPIKeysRequest struct { func (h *Handler) handleListAPIKeys(ctx context.Context, body []byte) ([]byte, error) { var req listAPIKeysRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -137,7 +138,7 @@ func (h *Handler) handleListAPIKeys(ctx context.Context, body []byte) ([]byte, e resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // getDecryptedAPIKeyRequest is the request body for GetDecryptedAPIKey. @@ -148,7 +149,7 @@ type getDecryptedAPIKeyRequest struct { func (h *Handler) handleGetDecryptedAPIKey(ctx context.Context, body []byte) ([]byte, error) { var req getDecryptedAPIKeyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -176,7 +177,7 @@ func (h *Handler) handleGetDecryptedAPIKey(ctx context.Context, body []byte) ([] return json.Marshal(map[string]any{ "TokenDomains": a.TokenDomains, "CreationTimestamp": a.CreatedAt, - }) + }, jsonx.V1()) } // apiKeyDispatchOps returns the API-key-family operation dispatch entries. Each entry is a diff --git a/services/wafv2/handler_api_keys_test.go b/services/wafv2/handler_api_keys_test.go index aefcf246f9..e516f90cc7 100644 --- a/services/wafv2/handler_api_keys_test.go +++ b/services/wafv2/handler_api_keys_test.go @@ -2,7 +2,7 @@ package wafv2_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/wafv2/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_CreateAndDeleteAPIKey(t *testing.T) { @@ -79,7 +81,7 @@ func TestHandler_CreateAndDeleteAPIKey(t *testing.T) { } var createResult map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResult)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResult, jsonx.V1())) apiKey, _ := createResult["APIKey"].(string) deleteRec := doWafv2Request(t, h, "DeleteAPIKey", tt.deleteBody(apiKey)) @@ -100,7 +102,7 @@ func TestAPIKeyBase64Encoding(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apiKey, ok := resp["APIKey"].(string) require.True(t, ok) @@ -249,7 +251,7 @@ func TestHandler_GetDecryptedAPIKey(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) apiKey, _ = resp["APIKey"].(string) require.NotEmpty(t, apiKey) } @@ -267,7 +269,7 @@ func TestHandler_GetDecryptedAPIKey(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "Scope", "GetDecryptedAPIKeyOutput has no Scope member") assert.Contains(t, resp, "TokenDomains") } diff --git a/services/wafv2/handler_ip_sets.go b/services/wafv2/handler_ip_sets.go index aad5ce9f51..555628eacc 100644 --- a/services/wafv2/handler_ip_sets.go +++ b/services/wafv2/handler_ip_sets.go @@ -2,9 +2,10 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -21,7 +22,7 @@ type createIPSetRequest struct { func (h *Handler) handleCreateIPSet(ctx context.Context, body []byte) ([]byte, error) { var req createIPSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -88,7 +89,7 @@ func (h *Handler) handleCreateIPSet(ctx context.Context, body []byte) ([]byte, e keyLockToken: s.LockToken, keyDescription: s.Description, }, - }) + }, jsonx.V1()) } // getIPSetRequest is the request body for GetIPSet. @@ -100,7 +101,7 @@ type getIPSetRequest struct { func (h *Handler) handleGetIPSet(ctx context.Context, body []byte) ([]byte, error) { var req getIPSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -134,7 +135,7 @@ func (h *Handler) handleGetIPSet(ctx context.Context, body []byte) ([]byte, erro keyAddresses: s.Addresses, }, keyLockToken: s.LockToken, - }) + }, jsonx.V1()) } // updateIPSetRequest is the request body for UpdateIPSet. @@ -149,7 +150,7 @@ type updateIPSetRequest struct { func (h *Handler) handleUpdateIPSet(ctx context.Context, body []byte) ([]byte, error) { var req updateIPSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -191,7 +192,7 @@ func (h *Handler) handleUpdateIPSet(ctx context.Context, body []byte) ([]byte, e return json.Marshal(map[string]string{ keyNextLockToken: s.LockToken, - }) + }, jsonx.V1()) } // deleteIPSetRequest is the request body for DeleteIPSet. @@ -204,7 +205,7 @@ type deleteIPSetRequest struct { func (h *Handler) handleDeleteIPSet(ctx context.Context, body []byte) ([]byte, error) { var req deleteIPSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/wafv2/handler_ip_sets_test.go b/services/wafv2/handler_ip_sets_test.go index f2c4caee6f..a271d4aa62 100644 --- a/services/wafv2/handler_ip_sets_test.go +++ b/services/wafv2/handler_ip_sets_test.go @@ -2,7 +2,7 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -54,7 +55,7 @@ func TestHandler_CreateIPSet(t *testing.T) { if tt.wantID { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) summary, ok := result["Summary"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, summary["Id"]) @@ -164,7 +165,7 @@ func TestHandler_UpdateIPSet(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.NotEmpty(t, result["NextLockToken"]) } }) @@ -250,7 +251,7 @@ func TestHandler_ListIPSets(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) list, ok := result["IPSets"].([]any) require.True(t, ok) @@ -281,7 +282,7 @@ func TestHandler_CreateIPSet_Duplicate(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "WAFDuplicateItemException", result["__type"]) } @@ -364,7 +365,7 @@ func TestHandler_ListIPSets_Scope_Filter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) list, ok := result["IPSets"].([]any) require.True(t, ok) assert.Len(t, list, tt.wantCount) @@ -427,7 +428,7 @@ func createIPSetHelper2( require.Equal(t, http.StatusOK, rec.Code, "createIPSetHelper2: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary := resp["Summary"].(map[string]any) return summary["Id"].(string), summary["LockToken"].(string) @@ -500,7 +501,7 @@ func TestIPSetListPagination(t *testing.T) { require.Equal(t, http.StatusOK, page1Rec.Code) var page1Resp map[string]any - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp, jsonx.V1())) sets, _ := page1Resp["IPSets"].([]any) assert.Len(t, sets, 2) @@ -580,7 +581,7 @@ func TestIPSet_MaxEntriesRejected(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "10001 IPs should exceed limit") var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFLimitsExceededException", errResp["__type"]) } @@ -661,7 +662,7 @@ func TestUpdateIPSet_ClearAddresses(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "UpdateIPSet with empty addresses: %s", rec.Body.String()) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) newLockToken := updateResp["NextLockToken"].(string) // Verify addresses were cleared. @@ -669,7 +670,7 @@ func TestUpdateIPSet_ClearAddresses(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &getResp, jsonx.V1())) ipSet := getResp["IPSet"].(map[string]any) addresses, _ := ipSet["Addresses"].([]any) assert.Empty(t, addresses, "addresses should be empty after clearing") diff --git a/services/wafv2/handler_logging_config.go b/services/wafv2/handler_logging_config.go index 1e41925095..dd0a7103fd 100644 --- a/services/wafv2/handler_logging_config.go +++ b/services/wafv2/handler_logging_config.go @@ -2,11 +2,13 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "strings" arnpkg "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -63,7 +65,7 @@ type deleteLoggingConfigurationRequest struct { func (h *Handler) handleDeleteLoggingConfiguration(ctx context.Context, body []byte) ([]byte, error) { var req deleteLoggingConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -91,24 +93,24 @@ func (h *Handler) handleDeleteLoggingConfiguration(ctx context.Context, body []b // putLoggingConfigurationRequest is the request body for PutLoggingConfiguration. type putLoggingConfigurationRequest struct { - LoggingConfiguration json.RawMessage `json:"LoggingConfiguration"` + LoggingConfiguration jsontext.Value `json:"LoggingConfiguration"` } func (h *Handler) handlePutLoggingConfiguration(ctx context.Context, body []byte) ([]byte, error) { var req putLoggingConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } // Extract ResourceArn for validation. - var cfg map[string]json.RawMessage - if err := json.Unmarshal(req.LoggingConfiguration, &cfg); err != nil { + var cfg map[string]jsontext.Value + if err := json.Unmarshal(req.LoggingConfiguration, &cfg, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: invalid LoggingConfiguration JSON: %w", errInvalidRequest, err) } var resourceARN string if raw, ok := cfg["ResourceArn"]; ok { - if err := json.Unmarshal(raw, &resourceARN); err != nil || resourceARN == "" { + if err := json.Unmarshal(raw, &resourceARN, jsonx.V1()); err != nil || resourceARN == "" { return nil, fmt.Errorf("%w: LoggingConfiguration.ResourceArn is required", errInvalidRequest) } } else { @@ -118,7 +120,7 @@ func (h *Handler) handlePutLoggingConfiguration(ctx context.Context, body []byte // Validate destination ARN prefixes. if destRaw, ok := cfg["LogDestinationConfigs"]; ok { var destinations []string - if unmarshalErr := json.Unmarshal(destRaw, &destinations); unmarshalErr == nil { + if unmarshalErr := json.Unmarshal(destRaw, &destinations, jsonx.V1()); unmarshalErr == nil { for _, dest := range destinations { if destErr := validateLoggingDestination(dest); destErr != nil { return nil, destErr @@ -135,13 +137,13 @@ func (h *Handler) handlePutLoggingConfiguration(ctx context.Context, body []byte log.InfoContext(ctx, "wafv2: put logging configuration", "resourceArn", resourceARN) var respCfg any - if err := json.Unmarshal(req.LoggingConfiguration, &respCfg); err != nil { + if err := json.Unmarshal(req.LoggingConfiguration, &respCfg, jsonx.V1()); err != nil { respCfg = map[string]any{"ResourceArn": resourceARN} } return json.Marshal(map[string]any{ "LoggingConfiguration": respCfg, - }) + }, jsonx.V1()) } // validateLoggingDestination checks that a destination ARN has an accepted prefix. @@ -168,7 +170,7 @@ type getLoggingConfigurationRequest struct { func (h *Handler) handleGetLoggingConfiguration(ctx context.Context, body []byte) ([]byte, error) { var req getLoggingConfigurationRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -190,13 +192,13 @@ func (h *Handler) handleGetLoggingConfiguration(ctx context.Context, body []byte } var cfg any - if unmarshalErr := json.Unmarshal(cfgJSON, &cfg); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(cfgJSON, &cfg, jsonx.V1()); unmarshalErr != nil { cfg = map[string]any{"ResourceArn": req.ResourceArn} } return json.Marshal(map[string]any{ "LoggingConfiguration": cfg, - }) + }, jsonx.V1()) } // listLoggingConfigurationsRequest mirrors ListLoggingConfigurationsInput @@ -261,7 +263,7 @@ func skipToLoggingConfigMarker(entries []loggingConfigEntry, nextMarker string) // and LogScope, paginated by Limit/NextMarker. func (h *Handler) handleListLoggingConfigurations(ctx context.Context, body []byte) ([]byte, error) { var req listLoggingConfigurationsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -271,7 +273,7 @@ func (h *Handler) handleListLoggingConfigurations(ctx context.Context, body []by for _, cfg := range configs { var v map[string]any - if err := json.Unmarshal(cfg, &v); err != nil { + if err := json.Unmarshal(cfg, &v, jsonx.V1()); err != nil { continue } @@ -298,7 +300,7 @@ func (h *Handler) handleListLoggingConfigurations(ctx context.Context, body []by resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // loggingConfigDispatchOps returns the logging-configuration-family operation dispatch diff --git a/services/wafv2/handler_logging_config_test.go b/services/wafv2/handler_logging_config_test.go index 2e18078365..9390a75ed3 100644 --- a/services/wafv2/handler_logging_config_test.go +++ b/services/wafv2/handler_logging_config_test.go @@ -2,13 +2,15 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -29,7 +31,7 @@ func TestHandler_DeleteLoggingConfiguration(t *testing.T) { require.NoError(t, h.Backend.PutLoggingConfiguration( context.Background(), arnStr, - json.RawMessage(`{"ResourceArn":"`+arnStr+`"}`), + jsontext.Value(`{"ResourceArn":"`+arnStr+`"}`), )) return arnStr @@ -88,7 +90,7 @@ func TestLoggingConfigurationFullRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) webACLARN := createResp["Summary"].(map[string]any)["ARN"].(string) // Put a full logging configuration. @@ -111,7 +113,7 @@ func TestLoggingConfigurationFullRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) cfg, ok := getResp["LoggingConfiguration"].(map[string]any) require.True(t, ok) @@ -126,7 +128,7 @@ func TestLoggingConfigurationFullRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) configs, ok := listResp["LoggingConfigurations"].([]any) require.True(t, ok) assert.Len(t, configs, 1) @@ -155,7 +157,7 @@ func TestLoggingConfig_FirehoseDestination(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["LoggingConfiguration"].(map[string]any) dests := cfg["LogDestinationConfigs"].([]any) require.Len(t, dests, 1) @@ -182,7 +184,7 @@ func TestLoggingConfig_S3Destination(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["LoggingConfiguration"].(map[string]any) dests := cfg["LogDestinationConfigs"].([]any) require.Len(t, dests, 1) @@ -209,7 +211,7 @@ func TestLoggingConfig_CloudWatchDestination(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["LoggingConfiguration"].(map[string]any) dests := cfg["LogDestinationConfigs"].([]any) require.Len(t, dests, 1) @@ -275,7 +277,7 @@ func TestLoggingConfig_DeleteAndGetNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, getRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -290,7 +292,7 @@ func TestLoggingConfig_DeleteNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -335,7 +337,7 @@ func TestLoggingConfig_FullFieldRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) cfg := resp["LoggingConfiguration"].(map[string]any) // RedactedFields should round-trip. @@ -443,7 +445,7 @@ func TestListLoggingConfigurations_ReturnsStoredConfigs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) configs, _ := resp["LoggingConfigurations"].([]any) assert.Len(t, configs, 2, "ListLoggingConfigurations should return 2 stored configs") } diff --git a/services/wafv2/handler_managed_rule_catalog.go b/services/wafv2/handler_managed_rule_catalog.go index 7d77b7dff5..eaffe087c2 100644 --- a/services/wafv2/handler_managed_rule_catalog.go +++ b/services/wafv2/handler_managed_rule_catalog.go @@ -2,9 +2,11 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // defaultManagedRuleGroupVersion is this catalog's single hardcoded version @@ -17,7 +19,7 @@ func (h *Handler) handleDescribeAllManagedProducts(body []byte) ([]byte, error) var req struct { Scope string `json:"Scope"` } - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -36,7 +38,7 @@ func (h *Handler) handleDescribeAllManagedProducts(body []byte) ([]byte, error) }) } - return json.Marshal(map[string]any{"ManagedProducts": products}) + return json.Marshal(map[string]any{"ManagedProducts": products}, jsonx.V1()) } // describeManagedProductsByVendorRequest is the request body for DescribeManagedProductsByVendor. @@ -48,7 +50,7 @@ type describeManagedProductsByVendorRequest struct { // handleDescribeManagedProductsByVendor returns catalog entries filtered by vendor. func (h *Handler) handleDescribeManagedProductsByVendor(body []byte) ([]byte, error) { var req describeManagedProductsByVendorRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -75,7 +77,7 @@ func (h *Handler) handleDescribeManagedProductsByVendor(body []byte) ([]byte, er }) } - return json.Marshal(map[string]any{"ManagedProducts": products}) + return json.Marshal(map[string]any{"ManagedProducts": products}, jsonx.V1()) } // describeManagedRuleGroupRequest is the request body for DescribeManagedRuleGroup. @@ -89,7 +91,7 @@ type describeManagedRuleGroupRequest struct { // handleDescribeManagedRuleGroup returns catalog data for the requested managed rule group. func (h *Handler) handleDescribeManagedRuleGroup(body []byte) ([]byte, error) { var req describeManagedRuleGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -133,7 +135,7 @@ func (h *Handler) handleDescribeManagedRuleGroup(body []byte) ([]byte, error) { resp["VersionName"] = defaultManagedRuleGroupVersion } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } } @@ -205,7 +207,7 @@ type generateMobileSdkReleaseURLRequest struct { // handleGenerateMobileSdkReleaseURL returns a presigned-style URL for the requested mobile SDK release. func (h *Handler) handleGenerateMobileSdkReleaseURL(body []byte) ([]byte, error) { var req generateMobileSdkReleaseURLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -231,7 +233,7 @@ func (h *Handler) handleGenerateMobileSdkReleaseURL(body []byte) ([]byte, error) return json.Marshal(map[string]any{ "Url": url, - }) + }, jsonx.V1()) } // getMobileSdkReleaseRequest is the request body for GetMobileSdkRelease. @@ -243,7 +245,7 @@ type getMobileSdkReleaseRequest struct { // handleGetMobileSdkRelease returns the mobile SDK release from the catalog. func (h *Handler) handleGetMobileSdkRelease(body []byte) ([]byte, error) { var req getMobileSdkReleaseRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -272,7 +274,7 @@ func (h *Handler) handleGetMobileSdkRelease(body []byte) ([]byte, error) { "ReleaseNotes": release.ReleaseNotes, "Tags": []any{}, }, - }) + }, jsonx.V1()) } // listAvailableManagedRuleGroupVersionsRequest is the request body for ListAvailableManagedRuleGroupVersions. @@ -287,7 +289,7 @@ type listAvailableManagedRuleGroupVersionsRequest struct { // handleListAvailableManagedRuleGroupVersions returns versions for managed rule groups that support versioning. func (h *Handler) handleListAvailableManagedRuleGroupVersions(body []byte) ([]byte, error) { var req listAvailableManagedRuleGroupVersionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -299,11 +301,11 @@ func (h *Handler) handleListAvailableManagedRuleGroupVersions(body []byte) ([]by {"Name": defaultManagedRuleGroupVersion, "LastUpdateTimestamp": nil}, }, "CurrentDefaultVersion": defaultManagedRuleGroupVersion, - }) + }, jsonx.V1()) } } - return json.Marshal(map[string]any{"Versions": []any{}, "CurrentDefaultVersion": ""}) + return json.Marshal(map[string]any{"Versions": []any{}, "CurrentDefaultVersion": ""}, jsonx.V1()) } // listAvailableManagedRuleGroupsRequest is the request body for ListAvailableManagedRuleGroups. @@ -316,7 +318,7 @@ type listAvailableManagedRuleGroupsRequest struct { // handleListAvailableManagedRuleGroups returns the catalog of managed rule groups. func (h *Handler) handleListAvailableManagedRuleGroups(body []byte) ([]byte, error) { var req listAvailableManagedRuleGroupsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -350,7 +352,7 @@ func (h *Handler) handleListAvailableManagedRuleGroups(body []byte) ([]byte, err resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // listMobileSdkReleasesRequest is the request body for ListMobileSdkReleases. @@ -366,7 +368,7 @@ type listMobileSdkReleasesRequest struct { // handleListMobileSdkReleases lists mobile SDK releases from the catalog. func (h *Handler) handleListMobileSdkReleases(body []byte) ([]byte, error) { var req listMobileSdkReleasesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -398,7 +400,7 @@ func (h *Handler) handleListMobileSdkReleases(body []byte) ([]byte, error) { resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // managedRuleCatalogDispatchOps returns the managed-rule-group and mobile-SDK catalog diff --git a/services/wafv2/handler_managed_rule_catalog_test.go b/services/wafv2/handler_managed_rule_catalog_test.go index 59772f49c1..efd4365edb 100644 --- a/services/wafv2/handler_managed_rule_catalog_test.go +++ b/services/wafv2/handler_managed_rule_catalog_test.go @@ -1,12 +1,14 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestListAvailableManagedRuleGroups_ReturnsCatalog(t *testing.T) { @@ -19,7 +21,7 @@ func TestListAvailableManagedRuleGroups_ReturnsCatalog(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["ManagedRuleGroups"].([]any) require.True(t, ok) @@ -53,7 +55,7 @@ func TestDescribeManagedRuleGroup_ReturnsCatalogData(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) capacity, _ := resp["Capacity"].(float64) assert.InDelta(t, float64(700), capacity, 0, "AWSManagedRulesCommonRuleSet should have capacity 700") @@ -73,7 +75,7 @@ func TestGetMobileSdkRelease_KnownVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "GetMobileSdkRelease Android/3.1.0: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) release := resp["MobileSdkRelease"].(map[string]any) assert.Equal(t, "3.1.0", release["ReleaseVersion"]) @@ -95,7 +97,7 @@ func TestGetMobileSdkRelease_IOSVersion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) release := resp["MobileSdkRelease"].(map[string]any) assert.Equal(t, "3.0.0", release["ReleaseVersion"]) } @@ -112,7 +114,7 @@ func TestGetMobileSdkRelease_UnknownVersion(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -139,7 +141,7 @@ func TestGetMobileSdkRelease_MissingPlatform(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", errResp["__type"]) } @@ -155,7 +157,7 @@ func TestListMobileSdkReleases_Android(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) releases, ok := resp["ReleaseSummaries"].([]any) require.True(t, ok) @@ -180,7 +182,7 @@ func TestListMobileSdkReleases_iOS(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) releases, _ := resp["ReleaseSummaries"].([]any) assert.NotEmpty(t, releases, "should return at least one iOS release") @@ -198,7 +200,7 @@ func TestListMobileSdkReleases_UnknownPlatform(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) releases, _ := resp["ReleaseSummaries"].([]any) assert.Empty(t, releases, "unknown platform should return empty list") } @@ -215,7 +217,7 @@ func TestGenerateMobileSdkReleaseUrl_Shape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) url, ok := resp["Url"].(string) require.True(t, ok, "Url field should be present") @@ -259,14 +261,14 @@ func TestWebACL_ManagedRuleGroupStatement_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "ManagedRuleGroupStatement: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 1) @@ -333,7 +335,7 @@ func TestDescribeManagedRuleGroup(t *testing.T) { require.Equal(t, tc.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantType != "" { assert.Equal(t, tc.wantType, resp["__type"]) @@ -414,7 +416,7 @@ func TestGenerateMobileSdkReleaseUrl(t *testing.T) { require.Equal(t, tc.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tc.wantType != "" { assert.Equal(t, tc.wantType, resp["__type"]) @@ -510,7 +512,7 @@ func TestDescribeManagedRuleGroupRules(t *testing.T) { } `json:"AvailableLabels"` Capacity float64 `json:"Capacity"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) // Rules and AvailableLabels must never be null (always an array). assert.NotNil(t, out.Rules, "Rules must not be null") diff --git a/services/wafv2/handler_managed_rule_sets.go b/services/wafv2/handler_managed_rule_sets.go index ef1c37c45a..7584e60c57 100644 --- a/services/wafv2/handler_managed_rule_sets.go +++ b/services/wafv2/handler_managed_rule_sets.go @@ -2,9 +2,10 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -33,7 +34,7 @@ type getManagedRuleSetRequest struct { // handleGetManagedRuleSet returns the stored managed rule set. func (h *Handler) handleGetManagedRuleSet(ctx context.Context, body []byte) ([]byte, error) { var req getManagedRuleSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -64,7 +65,7 @@ func (h *Handler) handleGetManagedRuleSet(ctx context.Context, body []byte) ([]b "RecommendedVersion": ms.RecommendedVersion, }, keyLockToken: ms.LockToken, - }) + }, jsonx.V1()) } // listManagedRuleSetsRequest is the request body for ListManagedRuleSets. @@ -77,7 +78,7 @@ type listManagedRuleSetsRequest struct { // handleListManagedRuleSets lists all stored managed rule sets, filtered by scope. func (h *Handler) handleListManagedRuleSets(ctx context.Context, body []byte) ([]byte, error) { var req listManagedRuleSetsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -115,7 +116,7 @@ func (h *Handler) handleListManagedRuleSets(ctx context.Context, body []byte) ([ resp["NextMarker"] = nextMarker } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // putManagedRuleSetVersionsRequest is the request body for PutManagedRuleSetVersions. @@ -130,7 +131,7 @@ type putManagedRuleSetVersionsRequest struct { func (h *Handler) handlePutManagedRuleSetVersions(ctx context.Context, body []byte) ([]byte, error) { var req putManagedRuleSetVersionsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -165,7 +166,7 @@ func (h *Handler) handlePutManagedRuleSetVersions(ctx context.Context, body []by log := logger.Load(ctx) log.InfoContext(ctx, "wafv2: put managed rule set versions", "id", req.ID) - return json.Marshal(map[string]string{keyNextLockToken: ms.LockToken}) + return json.Marshal(map[string]string{keyNextLockToken: ms.LockToken}, jsonx.V1()) } // updateManagedRuleSetVersionExpiryDateRequest is the request body for @@ -187,7 +188,7 @@ type updateManagedRuleSetVersionExpiryDateRequest struct { func (h *Handler) handleUpdateManagedRuleSetVersionExpiryDate(ctx context.Context, body []byte) ([]byte, error) { var req updateManagedRuleSetVersionExpiryDateRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -235,7 +236,7 @@ func (h *Handler) handleUpdateManagedRuleSetVersionExpiryDate(ctx context.Contex keyNextLockToken: ms.LockToken, "ExpiringVersion": req.VersionToExpire, "ExpiryTimestamp": req.ExpiryTimestamp, - }) + }, jsonx.V1()) } // managedRuleSetDispatchOps returns the ManagedRuleSet-family operation dispatch entries. diff --git a/services/wafv2/handler_managed_rule_sets_test.go b/services/wafv2/handler_managed_rule_sets_test.go index 387f961a6d..bda216f6fc 100644 --- a/services/wafv2/handler_managed_rule_sets_test.go +++ b/services/wafv2/handler_managed_rule_sets_test.go @@ -1,12 +1,14 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestManagedRuleSet_PutAndGet(t *testing.T) { @@ -33,7 +35,7 @@ func TestManagedRuleSet_PutAndGet(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code, "PutManagedRuleSetVersions: %s", putRec.Body.String()) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) lockToken1, _ := putResp["NextLockToken"].(string) assert.NotEmpty(t, lockToken1, "PutManagedRuleSetVersions should return NextLockToken") @@ -46,7 +48,7 @@ func TestManagedRuleSet_PutAndGet(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "GetManagedRuleSet: %s", getRec.Body.String()) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ms := getResp["ManagedRuleSet"].(map[string]any) assert.Equal(t, msID, ms["Id"]) @@ -78,7 +80,7 @@ func TestManagedRuleSet_GetNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -93,7 +95,7 @@ func TestManagedRuleSet_GetMissingID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", errResp["__type"]) } @@ -119,7 +121,7 @@ func TestManagedRuleSet_UpdateExpiryDate(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) lockToken := putResp["NextLockToken"].(string) expiry := int64(9999999999) @@ -142,7 +144,7 @@ func TestManagedRuleSet_UpdateExpiryDate(t *testing.T) { ) var updateResp map[string]any - require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(updateRec.Body.Bytes(), &updateResp, jsonx.V1())) assert.NotEmpty(t, updateResp["NextLockToken"]) assert.Equal(t, "Version_1.0", updateResp["ExpiringVersion"]) assert.InDelta(t, float64(expiry), updateResp["ExpiryTimestamp"], 0) @@ -156,7 +158,7 @@ func TestManagedRuleSet_UpdateExpiryDate(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) ms := getResp["ManagedRuleSet"].(map[string]any) versions := ms["PublishedVersions"].(map[string]any) v10 := versions["Version_1.0"].(map[string]any) @@ -180,7 +182,7 @@ func TestManagedRuleSet_UpdateExpiryNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -199,7 +201,7 @@ func TestManagedRuleSet_UpdateExpiryVersionNotFound(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) lockToken := putResp["NextLockToken"].(string) // Attempt to expire a version that was never published. @@ -214,7 +216,7 @@ func TestManagedRuleSet_UpdateExpiryVersionNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -242,7 +244,7 @@ func TestManagedRuleSet_PutLockTokenEnforcement(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFOptimisticLockException", errResp["__type"]) } @@ -275,7 +277,7 @@ func TestListManagedRuleSets_ScopeFilter(t *testing.T) { require.Equal(t, http.StatusOK, filtRec.Code) var filtResp map[string]any - require.NoError(t, json.Unmarshal(filtRec.Body.Bytes(), &filtResp)) + require.NoError(t, json.Unmarshal(filtRec.Body.Bytes(), &filtResp, jsonx.V1())) filt, _ := filtResp["ManagedRuleSets"].([]any) assert.Len(t, filt, 1) assert.Equal(t, "regional-ms", filt[0].(map[string]any)["Name"]) @@ -284,7 +286,7 @@ func TestListManagedRuleSets_ScopeFilter(t *testing.T) { require.Equal(t, http.StatusOK, cfRec.Code) var cfResp map[string]any - require.NoError(t, json.Unmarshal(cfRec.Body.Bytes(), &cfResp)) + require.NoError(t, json.Unmarshal(cfRec.Body.Bytes(), &cfResp, jsonx.V1())) cf, _ := cfResp["ManagedRuleSets"].([]any) assert.Len(t, cf, 1) assert.Equal(t, "cloudfront-ms", cf[0].(map[string]any)["Name"]) @@ -388,7 +390,7 @@ func TestManagedRuleSet_RequiredFieldValidation(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String()) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", errResp["__type"]) }) } @@ -439,7 +441,7 @@ func TestListManagedRuleSets_DuplicateNamePagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["ManagedRuleSets"].([]any) for _, item := range items { diff --git a/services/wafv2/handler_permission_policies.go b/services/wafv2/handler_permission_policies.go index b97e1839e8..74d9945c56 100644 --- a/services/wafv2/handler_permission_policies.go +++ b/services/wafv2/handler_permission_policies.go @@ -2,9 +2,10 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" ) @@ -15,7 +16,7 @@ type deletePermissionPolicyRequest struct { func (h *Handler) handleDeletePermissionPolicy(ctx context.Context, body []byte) ([]byte, error) { var req deletePermissionPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -41,7 +42,7 @@ type putPermissionPolicyRequest struct { func (h *Handler) handlePutPermissionPolicy(ctx context.Context, body []byte) ([]byte, error) { var req putPermissionPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -66,7 +67,7 @@ type getPermissionPolicyRequest struct { func (h *Handler) handleGetPermissionPolicy(ctx context.Context, body []byte) ([]byte, error) { var req getPermissionPolicyRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -79,7 +80,7 @@ func (h *Handler) handleGetPermissionPolicy(ctx context.Context, body []byte) ([ return nil, err } - return json.Marshal(map[string]string{"Policy": policy}) + return json.Marshal(map[string]string{"Policy": policy}, jsonx.V1()) } // permissionPolicyDispatchOps returns the permission-policy-family operation dispatch diff --git a/services/wafv2/handler_permission_policies_test.go b/services/wafv2/handler_permission_policies_test.go index 49803740fb..3b6c2fcd9f 100644 --- a/services/wafv2/handler_permission_policies_test.go +++ b/services/wafv2/handler_permission_policies_test.go @@ -2,13 +2,14 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -107,7 +108,7 @@ func TestPermissionPolicy_PutGetDelete(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code, "GetPermissionPolicy: %s", getRec.Body.String()) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) storedPolicy, ok := getResp["Policy"].(string) require.True(t, ok, "Policy field should be present") assert.Equal(t, policy, storedPolicy) @@ -125,7 +126,7 @@ func TestPermissionPolicy_PutGetDelete(t *testing.T) { assert.Equal(t, http.StatusBadRequest, getRec2.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(getRec2.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -140,7 +141,7 @@ func TestPermissionPolicy_DeleteNotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } @@ -184,7 +185,7 @@ func TestPermissionPolicy_UpdateReplace(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, policy2, resp["Policy"]) } @@ -259,7 +260,7 @@ func TestHandler_PutAndGetPermissionPolicy(t *testing.T) { if tt.wantGetStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Policy") } } diff --git a/services/wafv2/handler_rate_based_rules.go b/services/wafv2/handler_rate_based_rules.go index 2d32d18d0f..8c3a3679ef 100644 --- a/services/wafv2/handler_rate_based_rules.go +++ b/services/wafv2/handler_rate_based_rules.go @@ -2,9 +2,11 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // getRateBasedStatementManagedKeysRequest is the request body for GetRateBasedStatementManagedKeys. @@ -19,7 +21,7 @@ type getRateBasedStatementManagedKeysRequest struct { // handleGetRateBasedStatementManagedKeys returns empty rate-based managed keys. func (h *Handler) handleGetRateBasedStatementManagedKeys(ctx context.Context, body []byte) ([]byte, error) { var req getRateBasedStatementManagedKeysRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -46,7 +48,7 @@ func (h *Handler) handleGetRateBasedStatementManagedKeys(ctx context.Context, bo return json.Marshal(map[string]any{ "ManagedKeysIPV4": map[string]any{keyIPAddressVersion: "IPV4", keyAddresses: []any{}}, "ManagedKeysIPV6": map[string]any{keyIPAddressVersion: "IPV6", keyAddresses: []any{}}, - }) + }, jsonx.V1()) } // getSampledRequestsRequest is the request body for GetSampledRequests. @@ -61,7 +63,7 @@ type getSampledRequestsRequest struct { // handleGetSampledRequests returns an empty sampled requests response. func (h *Handler) handleGetSampledRequests(ctx context.Context, body []byte) ([]byte, error) { var req getSampledRequestsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -97,7 +99,7 @@ func (h *Handler) handleGetSampledRequests(ctx context.Context, body []byte) ([] "SampledRequests": []any{}, "PopulationSize": int64(0), "TimeWindow": req.TimeWindow, - }) + }, jsonx.V1()) } // getTopPathStatisticsByTrafficRequest is the request body for @@ -128,7 +130,7 @@ type getTopPathStatisticsByTrafficRequest struct { // this same structural gap. func (h *Handler) handleGetTopPathStatisticsByTraffic(ctx context.Context, body []byte) ([]byte, error) { var req getTopPathStatisticsByTrafficRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -166,7 +168,7 @@ func (h *Handler) handleGetTopPathStatisticsByTraffic(ctx context.Context, body return json.Marshal(map[string]any{ "PathStatistics": []any{}, "TotalRequestCount": int64(0), - }) + }, jsonx.V1()) } // rateBasedRuleDispatchOps returns the rate-based-rule and traffic-monitoring operation diff --git a/services/wafv2/handler_rate_based_rules_test.go b/services/wafv2/handler_rate_based_rules_test.go index 56570b789d..a1cea6a6a4 100644 --- a/services/wafv2/handler_rate_based_rules_test.go +++ b/services/wafv2/handler_rate_based_rules_test.go @@ -1,7 +1,7 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -105,7 +106,7 @@ func TestRateBasedStatement_WithScopeDown(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "RateBasedStatement with ScopeDown: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) // Retrieve and verify the rule is stored with ScopeDownStatement. @@ -113,7 +114,7 @@ func TestRateBasedStatement_WithScopeDown(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 1) @@ -275,7 +276,7 @@ func TestGetRateBasedStatementManagedKeys_Valid(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "GetRateBasedStatementManagedKeys: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) ipv4, ok := resp["ManagedKeysIPV4"].(map[string]any) require.True(t, ok, "ManagedKeysIPV4 should be present") @@ -349,7 +350,7 @@ func TestValidation_RuleNameUniqueness(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "duplicate rule names should be rejected") var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", errResp["__type"]) } @@ -541,7 +542,7 @@ func TestGetTopPathStatisticsByTraffic(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "PathStatistics", "PathStatistics is a required response member") assert.Empty(t, resp["PathStatistics"], "no real traffic in emulator") assert.Contains(t, resp, "TotalRequestCount", "TotalRequestCount is a required response member") @@ -675,7 +676,7 @@ func TestGetRateBasedStatementManagedKeys(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "ManagedKeysIPV4") assert.Contains(t, resp, "ManagedKeysIPV6") } @@ -809,7 +810,7 @@ func TestGetSampledRequests(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "SampledRequests") assert.Empty(t, resp["SampledRequests"], "no real traffic in emulator") assert.Contains(t, resp, "TimeWindow") @@ -1053,7 +1054,7 @@ func TestGetRevenueStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // No-fabrication assertion: whichever statistic list is populated for this // StatisticType must be empty -- this emulator has no real AI-bot traffic to @@ -1135,7 +1136,7 @@ func TestGetRevenueStatisticsSummary(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) breakdown, ok := resp["RevenueBreakdown"].(map[string]any) require.True(t, ok, "RevenueBreakdown must be present") @@ -1269,7 +1270,7 @@ func TestGetRevenueStatisticsTimeSeries(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "DataPoints") assert.Empty(t, resp["DataPoints"], "no revenue events exist in this emulator to bucket into a time series") }) @@ -1364,7 +1365,7 @@ func TestListSettlementRecords(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Settlements") // No-fabrication assertion: no real payment/settlement pipeline exists in // this emulator, so there must never be an invented PayerAddress, diff --git a/services/wafv2/handler_regex_pattern_sets.go b/services/wafv2/handler_regex_pattern_sets.go index 2ea94bc74c..11bc8e5b9d 100644 --- a/services/wafv2/handler_regex_pattern_sets.go +++ b/services/wafv2/handler_regex_pattern_sets.go @@ -2,9 +2,11 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -12,16 +14,16 @@ import ( // createRegexPatternSetRequest is the request body for CreateRegexPatternSet. // RegularExpressionList accepts both []string and []{RegexString:...} shapes. type createRegexPatternSetRequest struct { - Name string `json:"Name"` - Scope string `json:"Scope"` - Description string `json:"Description"` - RegularExpressionList json.RawMessage `json:"RegularExpressionList"` - Tags []tags.KV `json:"Tags"` + Name string `json:"Name"` + Scope string `json:"Scope"` + Description string `json:"Description"` + RegularExpressionList jsontext.Value `json:"RegularExpressionList"` + Tags []tags.KV `json:"Tags"` } func (h *Handler) handleCreateRegexPatternSet(ctx context.Context, body []byte) ([]byte, error) { var req createRegexPatternSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -84,24 +86,24 @@ func (h *Handler) handleCreateRegexPatternSet(ctx context.Context, body []byte) keyLockToken: rps.LockToken, keyDescription: rps.Description, }, - }) + }, jsonx.V1()) } // parseRegexEntries accepts either []RegexEntry or []string and normalises to []RegexEntry. -func parseRegexEntries(raw json.RawMessage) ([]RegexEntry, error) { +func parseRegexEntries(raw jsontext.Value) ([]RegexEntry, error) { if len(raw) == 0 { return []RegexEntry{}, nil } // Try object form first: [{"RegexString": "..."}] var entries []RegexEntry - if err := json.Unmarshal(raw, &entries); err == nil { + if err := json.Unmarshal(raw, &entries, jsonx.V1()); err == nil { return entries, nil } // Fall back to plain string form: ["...", "..."] var strs []string - if err := json.Unmarshal(raw, &strs); err != nil { + if err := json.Unmarshal(raw, &strs, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: RegularExpressionList must be an array of objects or strings", errInvalidRequest) } @@ -123,7 +125,7 @@ type deleteRegexPatternSetRequest struct { func (h *Handler) handleDeleteRegexPatternSet(ctx context.Context, body []byte) ([]byte, error) { var req deleteRegexPatternSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -150,7 +152,7 @@ type getRegexPatternSetRequest struct { func (h *Handler) handleGetRegexPatternSet(ctx context.Context, body []byte) ([]byte, error) { var req getRegexPatternSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -188,7 +190,7 @@ func (h *Handler) handleGetRegexPatternSet(ctx context.Context, body []byte) ([] "RegularExpressionList": r.RegularExpressionList, }, keyLockToken: r.LockToken, - }) + }, jsonx.V1()) } func (h *Handler) handleListRegexPatternSets(ctx context.Context, body []byte) ([]byte, error) { @@ -212,17 +214,17 @@ func (h *Handler) handleListRegexPatternSets(ctx context.Context, body []byte) ( // updateRegexPatternSetRequest is the request body for UpdateRegexPatternSet. type updateRegexPatternSetRequest struct { - ID string `json:"Id"` - Name string `json:"Name"` - Scope string `json:"Scope"` - LockToken string `json:"LockToken"` - Description string `json:"Description"` - RegularExpressionList json.RawMessage `json:"RegularExpressionList"` + ID string `json:"Id"` + Name string `json:"Name"` + Scope string `json:"Scope"` + LockToken string `json:"LockToken"` + Description string `json:"Description"` + RegularExpressionList jsontext.Value `json:"RegularExpressionList"` } func (h *Handler) handleUpdateRegexPatternSet(ctx context.Context, body []byte) ([]byte, error) { var req updateRegexPatternSetRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -259,7 +261,7 @@ func (h *Handler) handleUpdateRegexPatternSet(ctx context.Context, body []byte) log := logger.Load(ctx) log.InfoContext(ctx, "wafv2: updated regex pattern set", "id", req.ID) - return json.Marshal(map[string]string{keyNextLockToken: r.LockToken}) + return json.Marshal(map[string]string{keyNextLockToken: r.LockToken}, jsonx.V1()) } // regexPatternSetDispatchOps returns the RegexPatternSet-family operation dispatch diff --git a/services/wafv2/handler_regex_pattern_sets_test.go b/services/wafv2/handler_regex_pattern_sets_test.go index 28aad8b1bb..ac3cb40ac2 100644 --- a/services/wafv2/handler_regex_pattern_sets_test.go +++ b/services/wafv2/handler_regex_pattern_sets_test.go @@ -2,13 +2,14 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -74,7 +75,7 @@ func TestHandler_CreateRegexPatternSet(t *testing.T) { if tt.wantID { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) summary, ok := result["Summary"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, summary["Id"]) @@ -180,7 +181,7 @@ func TestRegexPatternSetObjectShape(t *testing.T) { require.Equal(t, http.StatusOK, recObj.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recObj.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recObj.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) // Get and verify the entries are returned as objects. @@ -190,7 +191,7 @@ func TestRegexPatternSetObjectShape(t *testing.T) { require.Equal(t, http.StatusOK, recGet.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp, jsonx.V1())) rps := getResp["RegexPatternSet"].(map[string]any) entries, ok := rps["RegularExpressionList"].([]any) @@ -232,7 +233,7 @@ func TestRegexPatternSet_MaxEntries(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "11 regex entries should exceed limit") var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFLimitsExceededException", errResp["__type"]) } @@ -268,7 +269,7 @@ func createRegexPatternSetHelper(t *testing.T, h *wafv2.Handler, name string) st require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary, ok := resp["Summary"].(map[string]any) require.True(t, ok) @@ -331,7 +332,7 @@ func TestHandler_GetRegexPatternSet(t *testing.T) { if tt.wantField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantField) } }) @@ -389,7 +390,7 @@ func TestHandler_ListRegexPatternSets(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["RegexPatternSets"].([]any) require.True(t, ok) @@ -459,7 +460,7 @@ func TestHandler_UpdateRegexPatternSet(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "NextLockToken") } }) diff --git a/services/wafv2/handler_resource_associations.go b/services/wafv2/handler_resource_associations.go index fe1092bab6..d2afbc12de 100644 --- a/services/wafv2/handler_resource_associations.go +++ b/services/wafv2/handler_resource_associations.go @@ -2,9 +2,11 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // associateWebACLRequest is the request body for AssociateWebACL. @@ -15,7 +17,7 @@ type associateWebACLRequest struct { func (h *Handler) handleAssociateWebACL(ctx context.Context, body []byte) ([]byte, error) { var req associateWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -84,7 +86,7 @@ type disassociateWebACLRequest struct { func (h *Handler) handleDisassociateWebACL(ctx context.Context, body []byte) ([]byte, error) { var req disassociateWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -106,7 +108,7 @@ type getWebACLForResourceRequest struct { func (h *Handler) handleGetWebACLForResource(ctx context.Context, body []byte) ([]byte, error) { var req getWebACLForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -130,7 +132,7 @@ type listResourcesForWebACLRequest struct { func (h *Handler) handleListResourcesForWebACL(ctx context.Context, body []byte) ([]byte, error) { var req listResourcesForWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -156,7 +158,7 @@ func (h *Handler) handleListResourcesForWebACL(ctx context.Context, body []byte) } } - return json.Marshal(map[string]any{"ResourceArns": filtered}) + return json.Marshal(map[string]any{"ResourceArns": filtered}, jsonx.V1()) } const ( diff --git a/services/wafv2/handler_resource_associations_test.go b/services/wafv2/handler_resource_associations_test.go index 71c7d95b79..2d9d0da0bf 100644 --- a/services/wafv2/handler_resource_associations_test.go +++ b/services/wafv2/handler_resource_associations_test.go @@ -2,7 +2,7 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -270,7 +271,7 @@ func TestHandler_GetWebACLForResource(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) webACL, ok := result["WebACL"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, webACL["Id"]) @@ -297,7 +298,7 @@ func TestHandler_GetWebACLForResource_WithVisibilityConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) webACL, ok := result["WebACL"].(map[string]any) require.True(t, ok) assert.Equal(t, "my-acl", webACL["Name"]) @@ -317,7 +318,7 @@ func TestGetWebACLForResourceReturnsRules(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getRespW map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRespW)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getRespW, jsonx.V1())) webACLARN := getRespW["WebACL"].(map[string]any)["ARN"].(string) // Associate with a resource. @@ -335,7 +336,7 @@ func TestGetWebACLForResourceReturnsRules(t *testing.T) { require.Equal(t, http.StatusOK, forResourceRec.Code) var forResourceResp map[string]any - require.NoError(t, json.Unmarshal(forResourceRec.Body.Bytes(), &forResourceResp)) + require.NoError(t, json.Unmarshal(forResourceRec.Body.Bytes(), &forResourceResp, jsonx.V1())) webACL := forResourceResp["WebACL"].(map[string]any) rules, ok := webACL["Rules"].([]any) @@ -371,7 +372,7 @@ func TestListResourcesForWebACL_MultipleResources(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) arns, _ := resp["ResourceArns"].([]any) assert.Len(t, arns, 3, "should list 3 associated resources") @@ -451,7 +452,7 @@ func TestHandler_ListResourcesForWebACL(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) resources, ok := resp["ResourceArns"].([]any) require.True(t, ok) assert.Len(t, resources, tt.wantResources) @@ -539,7 +540,7 @@ func TestAssociateWebACL_ReplaceExisting(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) webACL := resp["WebACL"].(map[string]any) assert.Equal(t, "acl-two", webACL["Name"]) } @@ -557,7 +558,7 @@ func TestGetWebACLForResource_NotFound(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) } diff --git a/services/wafv2/handler_revenue_statistics.go b/services/wafv2/handler_revenue_statistics.go index 531b69d117..afba8c4e8f 100644 --- a/services/wafv2/handler_revenue_statistics.go +++ b/services/wafv2/handler_revenue_statistics.go @@ -2,8 +2,10 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // This file implements the AI-bot pay-per-crawl monetization reporting family added to @@ -333,7 +335,7 @@ func validateGetRevenueStatisticsRequest(req getRevenueStatisticsRequest) error // dollar amount. func (h *Handler) handleGetRevenueStatistics(body []byte) ([]byte, error) { var req getRevenueStatisticsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -366,7 +368,7 @@ func (h *Handler) handleGetRevenueStatistics(body []byte) ([]byte, error) { resp["RevenuePathStatistics"] = []any{} } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // ---- GetRevenueStatisticsSummary --------------------------------------------------- @@ -385,7 +387,7 @@ type getRevenueStatisticsSummaryRequest struct { // ("zero monetized traffic observed") rather than a fabricated dollar figure. func (h *Handler) handleGetRevenueStatisticsSummary(body []byte) ([]byte, error) { var req getRevenueStatisticsSummaryRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -414,7 +416,7 @@ func (h *Handler) handleGetRevenueStatisticsSummary(body []byte) ([]byte, error) "UnverifiedAmount": "0", "VerifiedAmount": "0", }, - }) + }, jsonx.V1()) } // ---- GetRevenueStatisticsTimeSeries ------------------------------------------------- @@ -487,7 +489,7 @@ func validateGetRevenueStatisticsTimeSeriesRequest(req getRevenueStatisticsTimeS // time-series intervals, so there is nothing genuine to aggregate. func (h *Handler) handleGetRevenueStatisticsTimeSeries(body []byte) ([]byte, error) { var req getRevenueStatisticsTimeSeriesRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -511,7 +513,7 @@ func (h *Handler) handleGetRevenueStatisticsTimeSeries(body []byte) ([]byte, err return nil, err } - return json.Marshal(map[string]any{"DataPoints": []any{}}) + return json.Marshal(map[string]any{"DataPoints": []any{}}, jsonx.V1()) } // ---- ListSettlementRecords ---------------------------------------------------------- @@ -565,7 +567,7 @@ func validateListSettlementRecordsRequest(req listSettlementRecordsRequest) erro // TransactionId, or Amount. func (h *Handler) handleListSettlementRecords(body []byte) ([]byte, error) { var req listSettlementRecordsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -589,7 +591,7 @@ func (h *Handler) handleListSettlementRecords(body []byte) ([]byte, error) { return nil, err } - return json.Marshal(map[string]any{"Settlements": []any{}}) + return json.Marshal(map[string]any{"Settlements": []any{}}, jsonx.V1()) } // revenueStatisticsDispatchOps returns the AI-bot monetization reporting operation diff --git a/services/wafv2/handler_rule_groups.go b/services/wafv2/handler_rule_groups.go index f8da2e8bea..1f00f19c80 100644 --- a/services/wafv2/handler_rule_groups.go +++ b/services/wafv2/handler_rule_groups.go @@ -2,9 +2,11 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -17,7 +19,7 @@ type checkCapacityRequest struct { func (h *Handler) handleCheckCapacity(ctx context.Context, body []byte) ([]byte, error) { var req checkCapacityRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -32,7 +34,7 @@ func (h *Handler) handleCheckCapacity(ctx context.Context, body []byte) ([]byte, return json.Marshal(map[string]any{ "Capacity": capacity, - }) + }, jsonx.V1()) } // createRuleGroupRequest is the request body for CreateRuleGroup. @@ -40,9 +42,9 @@ type createRuleGroupRequest struct { Name string `json:"Name"` Scope string `json:"Scope"` Description string `json:"Description"` - VisibilityConfig json.RawMessage `json:"VisibilityConfig"` - CustomResponseBodies json.RawMessage `json:"CustomResponseBodies"` - MonetizationConfig json.RawMessage `json:"MonetizationConfig"` + VisibilityConfig jsontext.Value `json:"VisibilityConfig"` + CustomResponseBodies jsontext.Value `json:"CustomResponseBodies"` + MonetizationConfig jsontext.Value `json:"MonetizationConfig"` Rules []map[string]any `json:"Rules"` Tags []tags.KV `json:"Tags"` Capacity int64 `json:"Capacity"` @@ -50,7 +52,7 @@ type createRuleGroupRequest struct { func (h *Handler) handleCreateRuleGroup(ctx context.Context, body []byte) ([]byte, error) { var req createRuleGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -121,7 +123,7 @@ func (h *Handler) handleCreateRuleGroup(ctx context.Context, body []byte) ([]byt keyLockToken: rg.LockToken, keyDescription: rg.Description, }, - }) + }, jsonx.V1()) } // getRuleGroupRequest is the request body for GetRuleGroup. @@ -134,7 +136,7 @@ type getRuleGroupRequest struct { func (h *Handler) handleGetRuleGroup(ctx context.Context, body []byte) ([]byte, error) { var req getRuleGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -171,7 +173,7 @@ func (h *Handler) handleGetRuleGroup(ctx context.Context, body []byte) ([]byte, } arnStr := rg.ARN - visConfig := parseVisibilityConfig(json.RawMessage(rg.VisibilityConfig), rg.Name) + visConfig := parseVisibilityConfig(jsontext.Value(rg.VisibilityConfig), rg.Name) // LabelNamespace grammar ("awswaf::rulegroup::") confirmed via @@ -194,14 +196,14 @@ func (h *Handler) handleGetRuleGroup(ctx context.Context, body []byte) ([]byte, if len(rg.CustomResponseBodies) > 0 { var crb any - if json.Unmarshal(rg.CustomResponseBodies, &crb) == nil { + if json.Unmarshal(rg.CustomResponseBodies, &crb, jsonx.V1()) == nil { ruleGroupMap["CustomResponseBodies"] = crb } } if len(rg.MonetizationConfig) > 0 { var mc any - if json.Unmarshal(rg.MonetizationConfig, &mc) == nil { + if json.Unmarshal(rg.MonetizationConfig, &mc, jsonx.V1()) == nil { ruleGroupMap["MonetizationConfig"] = mc } } @@ -209,7 +211,7 @@ func (h *Handler) handleGetRuleGroup(ctx context.Context, body []byte) ([]byte, return json.Marshal(map[string]any{ "RuleGroup": ruleGroupMap, keyLockToken: rg.LockToken, - }) + }, jsonx.V1()) } func (h *Handler) handleListRuleGroups(ctx context.Context, body []byte) ([]byte, error) { @@ -238,15 +240,15 @@ type updateRuleGroupRequest struct { Scope string `json:"Scope"` LockToken string `json:"LockToken"` Description string `json:"Description"` - VisibilityConfig json.RawMessage `json:"VisibilityConfig"` - CustomResponseBodies json.RawMessage `json:"CustomResponseBodies"` - MonetizationConfig json.RawMessage `json:"MonetizationConfig"` + VisibilityConfig jsontext.Value `json:"VisibilityConfig"` + CustomResponseBodies jsontext.Value `json:"CustomResponseBodies"` + MonetizationConfig jsontext.Value `json:"MonetizationConfig"` Rules []map[string]any `json:"Rules"` } func (h *Handler) handleUpdateRuleGroup(ctx context.Context, body []byte) ([]byte, error) { var req updateRuleGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -287,7 +289,7 @@ func (h *Handler) handleUpdateRuleGroup(ctx context.Context, body []byte) ([]byt log := logger.Load(ctx) log.InfoContext(ctx, "wafv2: updated rule group", "id", req.ID) - return json.Marshal(map[string]string{keyNextLockToken: rg.LockToken}) + return json.Marshal(map[string]string{keyNextLockToken: rg.LockToken}, jsonx.V1()) } // deleteRuleGroupRequest is the request body for DeleteRuleGroup. @@ -300,7 +302,7 @@ type deleteRuleGroupRequest struct { func (h *Handler) handleDeleteRuleGroup(ctx context.Context, body []byte) ([]byte, error) { var req deleteRuleGroupRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/wafv2/handler_rule_groups_test.go b/services/wafv2/handler_rule_groups_test.go index 5fb68a5aa3..7caf3fc0ae 100644 --- a/services/wafv2/handler_rule_groups_test.go +++ b/services/wafv2/handler_rule_groups_test.go @@ -2,13 +2,14 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -56,7 +57,7 @@ func TestHandler_CheckCapacity(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) consumed, ok := result["Capacity"].(float64) require.True(t, ok) assert.GreaterOrEqual(t, int(consumed), tt.wantMinCap) @@ -106,7 +107,7 @@ func TestHandler_CreateRuleGroup(t *testing.T) { if tt.wantID { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) summary, ok := result["Summary"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, summary["Id"]) @@ -136,7 +137,7 @@ func TestHandler_CreateRuleGroup_Duplicate(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "WAFDuplicateItemException", result["__type"]) } @@ -190,7 +191,7 @@ func TestHandler_DeleteFirewallManagerRuleGroups(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.NotEmpty(t, result["NextWebACLLockToken"]) } }) @@ -266,7 +267,7 @@ func TestDeleteRuleGroupReferencedByWebACL(t *testing.T) { require.Equal(t, http.StatusOK, rgRec.Code) var rgResp map[string]any - require.NoError(t, json.Unmarshal(rgRec.Body.Bytes(), &rgResp)) + require.NoError(t, json.Unmarshal(rgRec.Body.Bytes(), &rgResp, jsonx.V1())) summary := rgResp["Summary"].(map[string]any) rgID := summary["Id"].(string) rgARN := summary["ARN"].(string) @@ -304,7 +305,7 @@ func TestDeleteRuleGroupReferencedByWebACL(t *testing.T) { assert.Equal(t, http.StatusBadRequest, delRec.Code) var delResp map[string]any - require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp)) + require.NoError(t, json.Unmarshal(delRec.Body.Bytes(), &delResp, jsonx.V1())) assert.Equal(t, "WAFAssociatedItemException", delResp["__type"]) } @@ -323,7 +324,7 @@ func TestRuleGroupScopeValidationOnGet(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) // Get with wrong scope should fail. @@ -369,7 +370,7 @@ func TestRuleGroup_UpdateRules(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) rgID := createResp["Summary"].(map[string]any)["Id"].(string) rgLock := createResp["Summary"].(map[string]any)["LockToken"].(string) @@ -411,7 +412,7 @@ func TestRuleGroup_UpdateRules(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["RuleGroup"].(map[string]any)["Rules"].([]any) assert.Len(t, rules, 2) } @@ -437,7 +438,7 @@ func TestListRuleGroups_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, page1Rec.Code) var page1Resp map[string]any - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp, jsonx.V1())) groups1, _ := page1Resp["RuleGroups"].([]any) assert.Len(t, groups1, 3) @@ -453,7 +454,7 @@ func TestListRuleGroups_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, page2Rec.Code) var page2Resp map[string]any - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2Resp)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2Resp, jsonx.V1())) groups2, _ := page2Resp["RuleGroups"].([]any) assert.Len(t, groups2, 2) @@ -476,7 +477,7 @@ func createRuleGroupHelper(t *testing.T, h *wafv2.Handler, name string) (string, require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary, ok := resp["Summary"].(map[string]any) require.True(t, ok) @@ -541,7 +542,7 @@ func TestHandler_GetRuleGroup(t *testing.T) { if tt.wantField != "" { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, tt.wantField) } }) @@ -599,7 +600,7 @@ func TestHandler_ListRuleGroups(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, ok := resp["RuleGroups"].([]any) require.True(t, ok) @@ -666,7 +667,7 @@ func TestHandler_UpdateRuleGroup(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "NextLockToken") } }) @@ -764,7 +765,7 @@ func TestCheckCapacity_EdgeCases(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) consumed, ok := result["Capacity"].(float64) require.True(t, ok) @@ -787,7 +788,7 @@ func TestCheckCapacity_NilRules(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) capacity, _ := resp["Capacity"].(float64) assert.InDelta(t, float64(0), capacity, 0, "nil rules capacity should be 0") @@ -798,7 +799,7 @@ func TestCheckCapacity_NilRules(t *testing.T) { }) require.Equal(t, http.StatusOK, rec.Code) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) capacity, _ = resp["Capacity"].(float64) assert.InDelta(t, float64(0), capacity, 0, "empty rules capacity should be 0") } diff --git a/services/wafv2/handler_tags.go b/services/wafv2/handler_tags.go index 1a49b25505..efd9a72ece 100644 --- a/services/wafv2/handler_tags.go +++ b/services/wafv2/handler_tags.go @@ -2,9 +2,10 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) @@ -22,7 +23,7 @@ type tagResourceRequest struct { func (h *Handler) handleTagResource(ctx context.Context, body []byte) ([]byte, error) { var req tagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -51,7 +52,7 @@ type listTagsForResourceRequest struct { func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([]byte, error) { var req listTagsForResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -78,7 +79,7 @@ func (h *Handler) handleListTagsForResource(ctx context.Context, body []byte) ([ resp["NextMarker"] = pg.Next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // untagResourceRequest is the request body for UntagResource. @@ -89,7 +90,7 @@ type untagResourceRequest struct { func (h *Handler) handleUntagResource(ctx context.Context, body []byte) ([]byte, error) { var req untagResourceRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } diff --git a/services/wafv2/handler_tags_test.go b/services/wafv2/handler_tags_test.go index 135ada616d..88eb7bfa67 100644 --- a/services/wafv2/handler_tags_test.go +++ b/services/wafv2/handler_tags_test.go @@ -2,7 +2,7 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -55,7 +56,7 @@ func TestHandler_TagResource_and_ListTags(t *testing.T) { assert.Equal(t, http.StatusOK, listRec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &result, jsonx.V1())) info, ok := result["TagInfoForResource"].(map[string]any) require.True(t, ok) @@ -188,7 +189,7 @@ func TestHandler_TagResource_Errors(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, tt.wantType, result["__type"]) }) } @@ -228,7 +229,7 @@ func TestHandler_ListTagsForResource_Errors(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, tt.wantType, result["__type"]) }) } @@ -264,7 +265,7 @@ func TestTagValidation(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) arnStr := createResp["Summary"].(map[string]any)["ARN"].(string) // More than 50 tags should fail. @@ -280,7 +281,7 @@ func TestTagValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, tooManyRec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(tooManyRec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(tooManyRec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFTagOperationException", errResp["__type"]) // Reserved prefix "aws:" should fail. @@ -291,7 +292,7 @@ func TestTagValidation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, reservedRec.Code) var reservedErr map[string]any - require.NoError(t, json.Unmarshal(reservedRec.Body.Bytes(), &reservedErr)) + require.NoError(t, json.Unmarshal(reservedRec.Body.Bytes(), &reservedErr, jsonx.V1())) assert.Equal(t, "WAFTagOperationException", reservedErr["__type"]) } @@ -324,7 +325,7 @@ func TestHandler_TagResource_RegexPatternSet(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagInfo, ok := resp["TagInfoForResource"].(map[string]any) require.True(t, ok) tagList, ok := tagInfo["TagList"].([]any) @@ -390,7 +391,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, firstRec.Code, firstRec.Body.String()) var first map[string]any - require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &first)) + require.NoError(t, json.Unmarshal(firstRec.Body.Bytes(), &first, jsonx.V1())) firstInfo, ok := first["TagInfoForResource"].(map[string]any) require.True(t, ok) @@ -411,7 +412,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, secondRec.Code, secondRec.Body.String()) var second map[string]any - require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &second)) + require.NoError(t, json.Unmarshal(secondRec.Body.Bytes(), &second, jsonx.V1())) secondInfo, ok := second["TagInfoForResource"].(map[string]any) require.True(t, ok) @@ -428,7 +429,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, allRec.Code) var all map[string]any - require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &all)) + require.NoError(t, json.Unmarshal(allRec.Body.Bytes(), &all, jsonx.V1())) allInfo, ok := all["TagInfoForResource"].(map[string]any) require.True(t, ok) allList, ok := allInfo["TagList"].([]any) diff --git a/services/wafv2/handler_test.go b/services/wafv2/handler_test.go index d8c576b6d8..d4132c7673 100644 --- a/services/wafv2/handler_test.go +++ b/services/wafv2/handler_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -37,7 +38,7 @@ func doWafv2Request( if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -133,7 +134,7 @@ func TestHandler_UnknownOperation(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "WAFInvalidOperationException", result["__type"]) } @@ -261,7 +262,7 @@ func TestLockTokenEnforcement(t *testing.T) { assert.Equal(t, http.StatusBadRequest, recBad.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(recBad.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(recBad.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFOptimisticLockException", errResp["__type"]) // Update with correct lock token should succeed. @@ -388,7 +389,7 @@ func TestLockTokenRequired_MissingTokenRejected(t *testing.T) { ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", resp["__type"]) }) } @@ -413,7 +414,7 @@ func TestVisibilityConfigMissingMetricName(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", resp["__type"]) } @@ -445,7 +446,7 @@ func TestListPagination(t *testing.T) { require.Equal(t, http.StatusOK, page1Rec.Code) var page1Resp map[string]any - require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp)) + require.NoError(t, json.Unmarshal(page1Rec.Body.Bytes(), &page1Resp, jsonx.V1())) acls1, ok := page1Resp["WebACLs"].([]any) require.True(t, ok) @@ -469,7 +470,7 @@ func TestListPagination(t *testing.T) { require.Equal(t, http.StatusOK, page2Rec.Code) var page2Resp map[string]any - require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2Resp)) + require.NoError(t, json.Unmarshal(page2Rec.Body.Bytes(), &page2Resp, jsonx.V1())) acls2, ok := page2Resp["WebACLs"].([]any) require.True(t, ok) @@ -496,7 +497,7 @@ func TestScopeValidationOnGet(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) // Get with wrong scope should fail. @@ -571,7 +572,7 @@ func TestHandler_Reset(t *testing.T) { // Verify it exists. rec := doWafv2Request(t, h, "ListWebACLs", map[string]any{"Scope": "REGIONAL"}) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ := resp["WebACLs"].([]any) assert.Len(t, items, 1) @@ -580,7 +581,7 @@ func TestHandler_Reset(t *testing.T) { // Verify it's gone. rec = doWafv2Request(t, h, "ListWebACLs", map[string]any{"Scope": "REGIONAL"}) - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) items, _ = resp["WebACLs"].([]any) assert.Empty(t, items) } @@ -727,7 +728,7 @@ func TestValidation_ScopeMismatch(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code, "scope mismatch should return 400") var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFNonexistentItemException", errResp["__type"]) }) diff --git a/services/wafv2/handler_web_acls.go b/services/wafv2/handler_web_acls.go index b181635a55..4b98d6d4e0 100644 --- a/services/wafv2/handler_web_acls.go +++ b/services/wafv2/handler_web_acls.go @@ -2,25 +2,27 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/tags" ) // createWebACLRequest is the request body for CreateWebACL. type createWebACLRequest struct { - DefaultAction json.RawMessage `json:"DefaultAction"` - VisibilityConfig json.RawMessage `json:"VisibilityConfig"` - CustomResponseBodies json.RawMessage `json:"CustomResponseBodies"` - AssociationConfig json.RawMessage `json:"AssociationConfig"` - CaptchaConfig json.RawMessage `json:"CaptchaConfig"` - ChallengeConfig json.RawMessage `json:"ChallengeConfig"` - MonetizationConfig json.RawMessage `json:"MonetizationConfig"` - DataProtectionConfig json.RawMessage `json:"DataProtectionConfig"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig"` - OnSourceDDoSProtectionConfig json.RawMessage `json:"OnSourceDDoSProtectionConfig"` + DefaultAction jsontext.Value `json:"DefaultAction"` + VisibilityConfig jsontext.Value `json:"VisibilityConfig"` + CustomResponseBodies jsontext.Value `json:"CustomResponseBodies"` + AssociationConfig jsontext.Value `json:"AssociationConfig"` + CaptchaConfig jsontext.Value `json:"CaptchaConfig"` + ChallengeConfig jsontext.Value `json:"ChallengeConfig"` + MonetizationConfig jsontext.Value `json:"MonetizationConfig"` + DataProtectionConfig jsontext.Value `json:"DataProtectionConfig"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig"` + OnSourceDDoSProtectionConfig jsontext.Value `json:"OnSourceDDoSProtectionConfig"` Name string `json:"Name"` Scope string `json:"Scope"` Description string `json:"Description"` @@ -31,7 +33,7 @@ type createWebACLRequest struct { func (h *Handler) handleCreateWebACL(ctx context.Context, body []byte) ([]byte, error) { var req createWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -108,17 +110,17 @@ func (h *Handler) handleCreateWebACL(ctx context.Context, body []byte) ([]byte, keyLockToken: w.LockToken, keyDescription: w.Description, }, - }) + }, jsonx.V1()) } // validateDefaultAction validates that exactly one of Allow/Block is set in DefaultAction. -func validateDefaultAction(raw json.RawMessage) error { +func validateDefaultAction(raw jsontext.Value) error { if len(raw) == 0 { return nil } - var m map[string]json.RawMessage - if err := json.Unmarshal(raw, &m); err != nil { + var m map[string]jsontext.Value + if err := json.Unmarshal(raw, &m, jsonx.V1()); err != nil { return fmt.Errorf("%w: invalid DefaultAction JSON: %w", errInvalidRequest, err) } @@ -142,7 +144,7 @@ type getWebACLRequest struct { func (h *Handler) handleGetWebACL(ctx context.Context, body []byte) ([]byte, error) { var req getWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -196,11 +198,11 @@ func (h *Handler) marshalWebACL(ctx context.Context, w *WebACL) ([]byte, error) defaultActionJSON := w.DefaultAction if len(defaultActionJSON) == 0 { - defaultActionJSON = json.RawMessage(`{"Allow":{}}`) + defaultActionJSON = jsontext.Value(`{"Allow":{}}`) } var defaultActionMap any - if err := json.Unmarshal(defaultActionJSON, &defaultActionMap); err != nil { + if err := json.Unmarshal(defaultActionJSON, &defaultActionMap, jsonx.V1()); err != nil { defaultActionMap = map[string]any{"Allow": map[string]any{}} } @@ -231,7 +233,7 @@ func (h *Handler) marshalWebACL(ctx context.Context, w *WebACL) ([]byte, error) return json.Marshal(map[string]any{ "WebACL": webACLMap, keyLockToken: w.LockToken, - }) + }, jsonx.V1()) } // addOptionalWebACLConfigs unmarshals each opaque config field on w that is @@ -240,7 +242,7 @@ func (h *Handler) marshalWebACL(ctx context.Context, w *WebACL) ([]byte, error) func addOptionalWebACLConfigs(webACLMap map[string]any, w *WebACL) { configs := []struct { key string - raw json.RawMessage + raw jsontext.Value }{ {"CustomResponseBodies", w.CustomResponseBodies}, {"AssociationConfig", w.AssociationConfig}, @@ -258,7 +260,7 @@ func addOptionalWebACLConfigs(webACLMap map[string]any, w *WebACL) { } var v any - if json.Unmarshal(c.raw, &v) == nil { + if json.Unmarshal(c.raw, &v, jsonx.V1()) == nil { webACLMap[c.key] = v } } @@ -266,16 +268,16 @@ func addOptionalWebACLConfigs(webACLMap map[string]any, w *WebACL) { // updateWebACLRequest is the request body for UpdateWebACL. type updateWebACLRequest struct { - DefaultAction json.RawMessage `json:"DefaultAction"` - VisibilityConfig json.RawMessage `json:"VisibilityConfig"` - CustomResponseBodies json.RawMessage `json:"CustomResponseBodies"` - AssociationConfig json.RawMessage `json:"AssociationConfig"` - CaptchaConfig json.RawMessage `json:"CaptchaConfig"` - ChallengeConfig json.RawMessage `json:"ChallengeConfig"` - MonetizationConfig json.RawMessage `json:"MonetizationConfig"` - DataProtectionConfig json.RawMessage `json:"DataProtectionConfig"` - ApplicationConfig json.RawMessage `json:"ApplicationConfig"` - OnSourceDDoSProtectionConfig json.RawMessage `json:"OnSourceDDoSProtectionConfig"` + DefaultAction jsontext.Value `json:"DefaultAction"` + VisibilityConfig jsontext.Value `json:"VisibilityConfig"` + CustomResponseBodies jsontext.Value `json:"CustomResponseBodies"` + AssociationConfig jsontext.Value `json:"AssociationConfig"` + CaptchaConfig jsontext.Value `json:"CaptchaConfig"` + ChallengeConfig jsontext.Value `json:"ChallengeConfig"` + MonetizationConfig jsontext.Value `json:"MonetizationConfig"` + DataProtectionConfig jsontext.Value `json:"DataProtectionConfig"` + ApplicationConfig jsontext.Value `json:"ApplicationConfig"` + OnSourceDDoSProtectionConfig jsontext.Value `json:"OnSourceDDoSProtectionConfig"` ID string `json:"Id"` Name string `json:"Name"` Scope string `json:"Scope"` @@ -287,7 +289,7 @@ type updateWebACLRequest struct { func (h *Handler) handleUpdateWebACL(ctx context.Context, body []byte) ([]byte, error) { var req updateWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -346,7 +348,7 @@ func (h *Handler) handleUpdateWebACL(ctx context.Context, body []byte) ([]byte, return json.Marshal(map[string]string{ keyNextLockToken: w.LockToken, - }) + }, jsonx.V1()) } // deleteWebACLRequest is the request body for DeleteWebACL. @@ -359,7 +361,7 @@ type deleteWebACLRequest struct { func (h *Handler) handleDeleteWebACL(ctx context.Context, body []byte) ([]byte, error) { var req deleteWebACLRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -405,7 +407,7 @@ type deleteFirewallManagerRuleGroupsRequest struct { func (h *Handler) handleDeleteFirewallManagerRuleGroups(ctx context.Context, body []byte) ([]byte, error) { var req deleteFirewallManagerRuleGroupsRequest - if err := json.Unmarshal(body, &req); err != nil { + if err := json.Unmarshal(body, &req, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: %w", errInvalidRequest, err) } @@ -427,7 +429,7 @@ func (h *Handler) handleDeleteFirewallManagerRuleGroups(ctx context.Context, bod return json.Marshal(map[string]string{ "NextWebACLLockToken": w.LockToken, - }) + }, jsonx.V1()) } // webACLDispatchOps returns the WebACL-family operation dispatch entries. Each entry is a diff --git a/services/wafv2/handler_web_acls_test.go b/services/wafv2/handler_web_acls_test.go index b4492609ea..50828f0b09 100644 --- a/services/wafv2/handler_web_acls_test.go +++ b/services/wafv2/handler_web_acls_test.go @@ -2,13 +2,14 @@ package wafv2_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -57,7 +58,7 @@ func TestHandler_CreateWebACL(t *testing.T) { if tt.wantID { var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) summary, ok := result["Summary"].(map[string]any) require.True(t, ok) assert.NotEmpty(t, summary["Id"]) @@ -162,7 +163,7 @@ func TestHandler_UpdateWebACL(t *testing.T) { if tt.wantStatus == http.StatusOK { var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.NotEmpty(t, result["NextLockToken"]) } }) @@ -248,7 +249,7 @@ func TestHandler_ListWebACLs(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) list, ok := result["WebACLs"].([]any) require.True(t, ok) @@ -275,7 +276,7 @@ func TestHandler_CreateWebACL_Duplicate(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec2.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "WAFDuplicateItemException", result["__type"]) } @@ -287,7 +288,7 @@ func TestHandler_GetWebACL_MissingID(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var result map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) assert.Equal(t, "WAFInvalidParameterException", result["__type"]) } @@ -362,7 +363,7 @@ func TestHandler_ListWebACLs_Scope_Filter(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) list, ok := result["WebACLs"].([]any) require.True(t, ok) assert.Len(t, list, tt.wantCount) @@ -408,7 +409,7 @@ func TestHandler_GetWebACL_BlockDefaultAction(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) webACL, ok := result["WebACL"].(map[string]any) require.True(t, ok) defaultAction, ok := webACL["DefaultAction"].(map[string]any) @@ -443,7 +444,7 @@ func TestHandler_GetWebACL_WithVisibilityConfig(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResult map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResult)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResult, jsonx.V1())) summary := createResult["Summary"].(map[string]any) id := summary["Id"].(string) @@ -455,7 +456,7 @@ func TestHandler_GetWebACL_WithVisibilityConfig(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var result map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &result, jsonx.V1())) webACL, ok := result["WebACL"].(map[string]any) require.True(t, ok) vis, ok := webACL["VisibilityConfig"].(map[string]any) @@ -497,7 +498,7 @@ func createWebACLWithRules(t *testing.T, h *wafv2.Handler, name, scope string) ( require.Equal(t, http.StatusOK, rec.Code, "createWebACLWithRules: %s", rec.Body.String()) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary := resp["Summary"].(map[string]any) return summary["Id"].(string), summary["LockToken"].(string) @@ -517,7 +518,7 @@ func TestWebACLRulesRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) webACL := resp["WebACL"].(map[string]any) rules, ok := webACL["Rules"].([]any) @@ -548,14 +549,14 @@ func TestDefaultActionRawJSONRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recAllow.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(recAllow.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(recAllow.Body.Bytes(), &createResp, jsonx.V1())) idAllow := createResp["Summary"].(map[string]any)["Id"].(string) recGet := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": idAllow}) require.Equal(t, http.StatusOK, recGet.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &getResp, jsonx.V1())) da := getResp["WebACL"].(map[string]any)["DefaultAction"].(map[string]any) _, hasAllow := da["Allow"] assert.True(t, hasAllow, "DefaultAction should contain Allow key") @@ -572,14 +573,14 @@ func TestDefaultActionRawJSONRoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, recBlock.Code) var createRespBlock map[string]any - require.NoError(t, json.Unmarshal(recBlock.Body.Bytes(), &createRespBlock)) + require.NoError(t, json.Unmarshal(recBlock.Body.Bytes(), &createRespBlock, jsonx.V1())) idBlock := createRespBlock["Summary"].(map[string]any)["Id"].(string) recGetBlock := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": idBlock}) require.Equal(t, http.StatusOK, recGetBlock.Code) var getRespBlock map[string]any - require.NoError(t, json.Unmarshal(recGetBlock.Body.Bytes(), &getRespBlock)) + require.NoError(t, json.Unmarshal(recGetBlock.Body.Bytes(), &getRespBlock, jsonx.V1())) daBlock := getRespBlock["WebACL"].(map[string]any)["DefaultAction"].(map[string]any) _, hasBlock := daBlock["Block"] assert.True(t, hasBlock, "DefaultAction should contain Block key") @@ -616,14 +617,14 @@ func TestWebACLExtendedFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) webACL := getResp["WebACL"].(map[string]any) // TokenDomains should be round-tripped. @@ -676,7 +677,7 @@ func TestCaptchaAction_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "Captcha action: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) // Verify round-trip. @@ -684,7 +685,7 @@ func TestCaptchaAction_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 1) @@ -724,14 +725,14 @@ func TestChallengeAction_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "Challenge action: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 1) @@ -761,14 +762,14 @@ func TestChallengeConfig_WebACLLevel(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) webACL := getResp["WebACL"].(map[string]any) challengeCfg, ok := webACL["ChallengeConfig"].(map[string]any) @@ -809,14 +810,14 @@ func TestLabelMatchStatement_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "LabelMatchStatement: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) require.Len(t, rules, 1) @@ -887,14 +888,14 @@ func TestAssociationConfig_RequestBodyDecryption(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "AssociationConfig: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) webACL := getResp["WebACL"].(map[string]any) assocCfg, ok := webACL["AssociationConfig"].(map[string]any) @@ -1020,14 +1021,14 @@ func TestWebACL_CustomResponseBodies_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "CustomResponseBodies: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) webACL := getResp["WebACL"].(map[string]any) crb, ok := webACL["CustomResponseBodies"].(map[string]any) @@ -1120,14 +1121,14 @@ func TestWebACL_AllActionTypes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "all action types: %s", rec.Body.String()) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) id := createResp["Summary"].(map[string]any)["Id"].(string) getRec := doWafv2Request(t, h, "GetWebACL", map[string]any{"Id": id}) require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules := getResp["WebACL"].(map[string]any)["Rules"].([]any) assert.Len(t, rules, 6, "all 6 rules should round-trip") } @@ -1155,7 +1156,7 @@ func TestWebACL_Update_ClearsOldRules(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) rules := resp["WebACL"].(map[string]any)["Rules"].([]any) assert.Empty(t, rules, "rules should be empty after clearing") } @@ -1171,7 +1172,7 @@ func createWebACLHelper(t *testing.T, h *wafv2.Handler, name, scope string) (str require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summary, ok := resp["Summary"].(map[string]any) require.True(t, ok) @@ -1230,7 +1231,7 @@ func TestHandler_DeleteWebACL_CascadeLogging(t *testing.T) { // Delete the WebACL. var createResp map[string]any rec2 := doWafv2Request(t, h, "ListWebACLs", map[string]any{"Scope": "REGIONAL"}) - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &createResp, jsonx.V1())) webACLs, _ := createResp["WebACLs"].([]any) require.Len(t, webACLs, 1) webACL := webACLs[0].(map[string]any) @@ -1274,7 +1275,7 @@ func TestDeleteWebACL_FailsWhenAssociated(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFAssociatedItemException", errResp["__type"]) // After disassociation, delete should succeed. @@ -1325,7 +1326,7 @@ func TestCreateWebACL_DuplicateNameScope(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rec.Code) var errResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &errResp, jsonx.V1())) assert.Equal(t, "WAFDuplicateItemException", errResp["__type"]) } @@ -1348,7 +1349,7 @@ func TestLockToken_RotatesOnUpdate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "update with correct token: %s", rec.Body.String()) var updateResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &updateResp, jsonx.V1())) token2 := updateResp["NextLockToken"].(string) assert.NotEmpty(t, token2) diff --git a/services/wafv2/interfaces.go b/services/wafv2/interfaces.go index ad6001944e..c68778a745 100644 --- a/services/wafv2/interfaces.go +++ b/services/wafv2/interfaces.go @@ -2,7 +2,7 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" ) // StorageBackend is the interface for WAFv2 storage operations. @@ -16,11 +16,11 @@ type StorageBackend interface { CreateWebACL( ctx context.Context, name, scope, description string, - defaultAction, visibilityConfig json.RawMessage, + defaultAction, visibilityConfig jsontext.Value, rules []map[string]any, tokenDomains []string, - customResponseBodies, associationConfig, captchaConfig, challengeConfig json.RawMessage, - monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig json.RawMessage, + customResponseBodies, associationConfig, captchaConfig, challengeConfig jsontext.Value, + monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig jsontext.Value, tags map[string]string, ) (*WebACL, error) GetWebACL(ctx context.Context, id string) (*WebACL, error) @@ -28,11 +28,11 @@ type StorageBackend interface { UpdateWebACL( ctx context.Context, id, description, lockToken string, - defaultAction, visibilityConfig json.RawMessage, + defaultAction, visibilityConfig jsontext.Value, rules []map[string]any, tokenDomains []string, - customResponseBodies, associationConfig, captchaConfig, challengeConfig json.RawMessage, - monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig json.RawMessage, + customResponseBodies, associationConfig, captchaConfig, challengeConfig jsontext.Value, + monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig jsontext.Value, ) (*WebACL, error) DeleteWebACL(ctx context.Context, id, lockToken string) error ListWebACLs(ctx context.Context) []*WebACL @@ -73,7 +73,7 @@ type StorageBackend interface { name, scope, description, visibilityConfig string, capacity int64, rules []map[string]any, - customResponseBodies, monetizationConfig json.RawMessage, + customResponseBodies, monetizationConfig jsontext.Value, tags map[string]string, ) (*RuleGroup, error) GetRuleGroup(ctx context.Context, id string) (*RuleGroup, error) @@ -83,15 +83,15 @@ type StorageBackend interface { ctx context.Context, id, description, visibilityConfig, lockToken string, rules []map[string]any, - customResponseBodies, monetizationConfig json.RawMessage, + customResponseBodies, monetizationConfig jsontext.Value, ) (*RuleGroup, error) DeleteRuleGroup(ctx context.Context, id, lockToken string) error DeleteAPIKey(ctx context.Context, scope, apiKey string) error DeleteFirewallManagerRuleGroups(ctx context.Context, webACLARN, lockToken string) (*WebACL, error) - PutLoggingConfiguration(ctx context.Context, resourceARN string, configJSON json.RawMessage) error + PutLoggingConfiguration(ctx context.Context, resourceARN string, configJSON jsontext.Value) error DeleteLoggingConfiguration(ctx context.Context, resourceARN, logScope string) error - GetLoggingConfiguration(ctx context.Context, resourceARN, logScope string) (json.RawMessage, error) - ListLoggingConfigurations(ctx context.Context, scope string) []json.RawMessage + GetLoggingConfiguration(ctx context.Context, resourceARN, logScope string) (jsontext.Value, error) + ListLoggingConfigurations(ctx context.Context, scope string) []jsontext.Value DeletePermissionPolicy(ctx context.Context, resourceARN string) error DeleteRegexPatternSet(ctx context.Context, id, lockToken string) error ListAPIKeys(ctx context.Context, scope string) []*APIKey diff --git a/services/wafv2/list_summary_shapes_test.go b/services/wafv2/list_summary_shapes_test.go index 04619e9336..e14f30cdc0 100644 --- a/services/wafv2/list_summary_shapes_test.go +++ b/services/wafv2/list_summary_shapes_test.go @@ -1,7 +1,7 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -9,6 +9,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/wafv2/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestListAPIKeys_SummaryShape locks that ListAPIKeys' APIKeySummary items @@ -32,7 +34,7 @@ func TestListAPIKeys_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["APIKeySummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 1) @@ -58,7 +60,7 @@ func TestGetDecryptedAPIKey_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) apiKey, ok := createResp["APIKey"].(string) require.True(t, ok) @@ -69,7 +71,7 @@ func TestGetDecryptedAPIKey_SummaryShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp, "Scope", "GetDecryptedAPIKeyOutput has no Scope member") assert.Contains(t, resp, "TokenDomains") assert.Contains(t, resp, "CreationTimestamp") diff --git a/services/wafv2/logging_config.go b/services/wafv2/logging_config.go index a90af8a895..9d7494247d 100644 --- a/services/wafv2/logging_config.go +++ b/services/wafv2/logging_config.go @@ -2,22 +2,25 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutLoggingConfiguration stores a full logging configuration JSON for the given resource ARN. func (b *InMemoryBackend) PutLoggingConfiguration( ctx context.Context, resourceARN string, - configJSON json.RawMessage, + configJSON jsontext.Value, ) error { b.mu.Lock("PutLoggingConfiguration") defer b.mu.Unlock() region := getRegion(ctx, b.region) - stored := make(json.RawMessage, len(configJSON)) + stored := make(jsontext.Value, len(configJSON)) copy(stored, configJSON) b.loggingConfigsStore(region)[resourceARN] = stored @@ -50,7 +53,7 @@ func (b *InMemoryBackend) DeleteLoggingConfiguration(ctx context.Context, resour func (b *InMemoryBackend) GetLoggingConfiguration( ctx context.Context, resourceARN, logScope string, -) (json.RawMessage, error) { +) (jsontext.Value, error) { b.mu.RLock("GetLoggingConfiguration") defer b.mu.RUnlock() @@ -65,7 +68,7 @@ func (b *InMemoryBackend) GetLoggingConfiguration( ) } - out := make(json.RawMessage, len(cfg)) + out := make(jsontext.Value, len(cfg)) copy(out, cfg) return out, nil @@ -73,7 +76,7 @@ func (b *InMemoryBackend) GetLoggingConfiguration( // loggingConfigInScope reports whether cfg's LogScope (default CUSTOMER when the // document omits it) matches wantScope ("" matches the default, CUSTOMER). -func loggingConfigInScope(cfg json.RawMessage, wantScope string) bool { +func loggingConfigInScope(cfg jsontext.Value, wantScope string) bool { if wantScope == "" { wantScope = defaultLogScope } @@ -82,7 +85,7 @@ func loggingConfigInScope(cfg json.RawMessage, wantScope string) bool { LogScope string `json:"LogScope"` } - if err := json.Unmarshal(cfg, &doc); err != nil { + if err := json.Unmarshal(cfg, &doc, jsonx.V1()); err != nil { return false } @@ -97,7 +100,7 @@ func loggingConfigInScope(cfg json.RawMessage, wantScope string) bool { // ListLoggingConfigurations returns all stored logging configuration JSONs for the // given scope (REGIONAL configs live under the request's actual region; CLOUDFRONT // configs are global and stored under the "" region key -- see arnRegionForScope). -func (b *InMemoryBackend) ListLoggingConfigurations(ctx context.Context, scope string) []json.RawMessage { +func (b *InMemoryBackend) ListLoggingConfigurations(ctx context.Context, scope string) []jsontext.Value { b.mu.RLock("ListLoggingConfigurations") defer b.mu.RUnlock() @@ -111,11 +114,11 @@ func (b *InMemoryBackend) ListLoggingConfigurations(ctx context.Context, scope s sort.Strings(arns) - result := make([]json.RawMessage, 0, len(arns)) + result := make([]jsontext.Value, 0, len(arns)) for _, arn := range arns { cfg := regionMap[arn] - out := make(json.RawMessage, len(cfg)) + out := make(jsontext.Value, len(cfg)) copy(out, cfg) result = append(result, out) } diff --git a/services/wafv2/models.go b/services/wafv2/models.go index 6566ae452a..3f6ef3f80d 100644 --- a/services/wafv2/models.go +++ b/services/wafv2/models.go @@ -1,6 +1,8 @@ package wafv2 -import "encoding/json" +import ( + "encoding/json/jsontext" +) const ( // ScopeRegional is the REGIONAL WAFv2 scope. @@ -53,16 +55,16 @@ type VisibilityConfig struct { type WebACL struct { Tags map[string]string `json:"tags,omitempty"` ARN string `json:"arn,omitempty"` - DefaultAction json.RawMessage `json:"defaultAction,omitempty"` - VisibilityConfig json.RawMessage `json:"visibilityConfig,omitempty"` - CustomResponseBodies json.RawMessage `json:"customResponseBodies,omitempty"` - AssociationConfig json.RawMessage `json:"associationConfig,omitempty"` - CaptchaConfig json.RawMessage `json:"captchaConfig,omitempty"` - ChallengeConfig json.RawMessage `json:"challengeConfig,omitempty"` - MonetizationConfig json.RawMessage `json:"monetizationConfig,omitempty"` - DataProtectionConfig json.RawMessage `json:"dataProtectionConfig,omitempty"` - ApplicationConfig json.RawMessage `json:"applicationConfig,omitempty"` - OnSourceDDoSProtectionConfig json.RawMessage `json:"onSourceDDoSProtectionConfig,omitempty"` + DefaultAction jsontext.Value `json:"defaultAction,omitempty"` + VisibilityConfig jsontext.Value `json:"visibilityConfig,omitempty"` + CustomResponseBodies jsontext.Value `json:"customResponseBodies,omitempty"` + AssociationConfig jsontext.Value `json:"associationConfig,omitempty"` + CaptchaConfig jsontext.Value `json:"captchaConfig,omitempty"` + ChallengeConfig jsontext.Value `json:"challengeConfig,omitempty"` + MonetizationConfig jsontext.Value `json:"monetizationConfig,omitempty"` + DataProtectionConfig jsontext.Value `json:"dataProtectionConfig,omitempty"` + ApplicationConfig jsontext.Value `json:"applicationConfig,omitempty"` + OnSourceDDoSProtectionConfig jsontext.Value `json:"onSourceDDoSProtectionConfig,omitempty"` ID string `json:"id"` Name string `json:"name"` Scope string `json:"scope"` @@ -106,8 +108,8 @@ type RuleGroup struct { Scope string `json:"scope"` Description string `json:"description"` VisibilityConfig string `json:"visibilityConfig"` - CustomResponseBodies json.RawMessage `json:"customResponseBodies,omitempty"` - MonetizationConfig json.RawMessage `json:"monetizationConfig,omitempty"` + CustomResponseBodies jsontext.Value `json:"customResponseBodies,omitempty"` + MonetizationConfig jsontext.Value `json:"monetizationConfig,omitempty"` LockToken string `json:"lockToken"` Rules []map[string]any `json:"rules,omitempty"` Capacity int64 `json:"capacity"` diff --git a/services/wafv2/multi_region_test.go b/services/wafv2/multi_region_test.go index 689da195d5..e7675c80de 100644 --- a/services/wafv2/multi_region_test.go +++ b/services/wafv2/multi_region_test.go @@ -1,7 +1,7 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/wafv2" ) @@ -30,7 +31,7 @@ func regionCall(t *testing.T, h *wafv2.Handler, region, target, body string) map require.Equal(t, http.StatusOK, rec.Code, rec.Body.String()) out := map[string]any{} - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) return out } diff --git a/services/wafv2/persistence.go b/services/wafv2/persistence.go index a2b1199870..37f067a9de 100644 --- a/services/wafv2/persistence.go +++ b/services/wafv2/persistence.go @@ -2,7 +2,7 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -74,13 +74,13 @@ func newDirtyDTORegistry() ( // they do not fit store.Table's keyed-by-identity-value shape and are // unaffected by this conversion. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` - LoggingConfigs map[string]map[string]json.RawMessage `json:"loggingConfigs,omitempty"` - PermissionPolicies map[string]map[string]string `json:"permissionPolicies,omitempty"` - Associations map[string]map[string]string `json:"associations,omitempty"` - AccountID string `json:"accountID"` - Region string `json:"region"` - Version int `json:"version"` + Tables map[string]jsontext.Value `json:"tables"` + LoggingConfigs map[string]map[string]jsontext.Value `json:"loggingConfigs,omitempty"` + PermissionPolicies map[string]map[string]string `json:"permissionPolicies,omitempty"` + Associations map[string]map[string]string `json:"associations,omitempty"` + AccountID string `json:"accountID"` + Region string `json:"region"` + Version int `json:"version"` } // Snapshot serializes the backend state to JSON. @@ -150,7 +150,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { "gotVersion", snap.Version, "wantVersion", wafv2SnapshotVersion) b.resetTablesLocked() - b.loggingConfigs = make(map[string]map[string]json.RawMessage) + b.loggingConfigs = make(map[string]map[string]jsontext.Value) b.permissionPolicies = make(map[string]map[string]string) b.associations = make(map[string]map[string]string) @@ -189,7 +189,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { if snap.LoggingConfigs != nil { b.loggingConfigs = snap.LoggingConfigs } else { - b.loggingConfigs = make(map[string]map[string]json.RawMessage) + b.loggingConfigs = make(map[string]map[string]jsontext.Value) } if snap.PermissionPolicies != nil { diff --git a/services/wafv2/realism_test.go b/services/wafv2/realism_test.go index 9b66992da9..166a9548ac 100644 --- a/services/wafv2/realism_test.go +++ b/services/wafv2/realism_test.go @@ -1,7 +1,7 @@ package wafv2_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func decodeError(t *testing.T, body []byte) (string, string) { @@ -18,7 +20,7 @@ func decodeError(t *testing.T, body []byte) (string, string) { Type string `json:"__type"` Message string `json:"message"` } - require.NoError(t, json.Unmarshal(body, &out)) + require.NoError(t, json.Unmarshal(body, &out, jsonx.V1())) return out.Type, out.Message } @@ -115,7 +117,7 @@ func TestLoggingDestinationNaming(t *testing.T) { ARN string `json:"ARN"` } `json:"Summary"` } - require.NoError(t, json.Unmarshal(acl.Body.Bytes(), &created)) + require.NoError(t, json.Unmarshal(acl.Body.Bytes(), &created, jsonx.V1())) rec := doWafv2Request(t, h, "PutLoggingConfiguration", map[string]any{ "LoggingConfiguration": map[string]any{ diff --git a/services/wafv2/rule_groups.go b/services/wafv2/rule_groups.go index 9b160a6869..f1d9cc766c 100644 --- a/services/wafv2/rule_groups.go +++ b/services/wafv2/rule_groups.go @@ -2,7 +2,8 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "sort" @@ -10,6 +11,7 @@ import ( "github.com/google/uuid" "github.com/blackbirdworks/gopherstack/pkgs/arn" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (b *InMemoryBackend) buildRuleGroupARN(name, id, scope, region string) string { @@ -46,7 +48,7 @@ func (b *InMemoryBackend) CreateRuleGroup( name, scope, description, visibilityConfig string, capacity int64, rules []map[string]any, - customResponseBodies, monetizationConfig json.RawMessage, + customResponseBodies, monetizationConfig jsontext.Value, tags map[string]string, ) (*RuleGroup, error) { b.mu.Lock("CreateRuleGroup") @@ -197,7 +199,7 @@ func (b *InMemoryBackend) UpdateRuleGroup( ctx context.Context, id, description, visibilityConfig, lockToken string, rules []map[string]any, - customResponseBodies, monetizationConfig json.RawMessage, + customResponseBodies, monetizationConfig jsontext.Value, ) (*RuleGroup, error) { b.mu.Lock("UpdateRuleGroup") defer b.mu.Unlock() @@ -242,13 +244,13 @@ func cloneRuleGroup(rg *RuleGroup) *RuleGroup { cp.Rules = cloneRules(rg.Rules) if rg.CustomResponseBodies != nil { - crb := make(json.RawMessage, len(rg.CustomResponseBodies)) + crb := make(jsontext.Value, len(rg.CustomResponseBodies)) copy(crb, rg.CustomResponseBodies) cp.CustomResponseBodies = crb } if rg.MonetizationConfig != nil { - mc := make(json.RawMessage, len(rg.MonetizationConfig)) + mc := make(jsontext.Value, len(rg.MonetizationConfig)) copy(mc, rg.MonetizationConfig) cp.MonetizationConfig = mc } @@ -278,13 +280,13 @@ func cloneRules(rules []map[string]any) []map[string]any { return []map[string]any{} } - data, marshalErr := json.Marshal(rules) + data, marshalErr := json.Marshal(rules, jsonx.V1()) if marshalErr != nil { return shallowCopyRules(rules) } var out []map[string]any - if unmarshalErr := json.Unmarshal(data, &out); unmarshalErr != nil { + if unmarshalErr := json.Unmarshal(data, &out, jsonx.V1()); unmarshalErr != nil { return shallowCopyRules(rules) } diff --git a/services/wafv2/store.go b/services/wafv2/store.go index 2610c92402..514a861a0f 100644 --- a/services/wafv2/store.go +++ b/services/wafv2/store.go @@ -2,13 +2,15 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "maps" "regexp" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/lockmetrics" "github.com/blackbirdworks/gopherstack/pkgs/store" ) @@ -128,7 +130,7 @@ type InMemoryBackend struct { managedRuleSetsByRegion *store.Index[ManagedRuleSet] apiKeys *store.Table[APIKey] apiKeysByRegion *store.Index[APIKey] - loggingConfigs map[string]map[string]json.RawMessage + loggingConfigs map[string]map[string]jsontext.Value permissionPolicies map[string]map[string]string associations map[string]map[string]string mu *lockmetrics.RWMutex @@ -161,7 +163,7 @@ func initRegion[V any](m map[string]map[string]V, region string) map[string]V { func NewInMemoryBackend(accountID, region string) *InMemoryBackend { b := &InMemoryBackend{ registry: store.NewRegistry(), - loggingConfigs: make(map[string]map[string]json.RawMessage), + loggingConfigs: make(map[string]map[string]jsontext.Value), permissionPolicies: make(map[string]map[string]string), associations: make(map[string]map[string]string), accountID: accountID, @@ -173,7 +175,7 @@ func NewInMemoryBackend(accountID, region string) *InMemoryBackend { return b } -func (b *InMemoryBackend) loggingConfigsStore(region string) map[string]json.RawMessage { +func (b *InMemoryBackend) loggingConfigsStore(region string) map[string]jsontext.Value { return initRegion(b.loggingConfigs, region) } func (b *InMemoryBackend) permissionPoliciesStore(region string) map[string]string { @@ -219,13 +221,13 @@ func nameScope(name, scope string) string { } // validateVisibilityConfig parses and validates a VisibilityConfig JSON blob. -func validateVisibilityConfig(raw json.RawMessage) error { +func validateVisibilityConfig(raw jsontext.Value) error { if len(raw) == 0 { return nil } var vc VisibilityConfig - if err := json.Unmarshal(raw, &vc); err != nil { + if err := json.Unmarshal(raw, &vc, jsonx.V1()); err != nil { return fmt.Errorf("%w: invalid VisibilityConfig: %w", errInvalidRequest, err) } @@ -243,7 +245,7 @@ func toInt64(v any) (int64, bool) { return int64(n), true case float64: return int64(n), true - case json.Number: + case jsonx.Number: i, err := n.Int64() return i, err == nil @@ -275,7 +277,7 @@ func (b *InMemoryBackend) Reset() { defer b.mu.Unlock() b.resetTablesLocked() - b.loggingConfigs = make(map[string]map[string]json.RawMessage) + b.loggingConfigs = make(map[string]map[string]jsontext.Value) b.permissionPolicies = make(map[string]map[string]string) b.associations = make(map[string]map[string]string) } diff --git a/services/wafv2/web_acls.go b/services/wafv2/web_acls.go index c95468ebfa..1250d0f7fc 100644 --- a/services/wafv2/web_acls.go +++ b/services/wafv2/web_acls.go @@ -2,7 +2,7 @@ package wafv2 import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" "sort" @@ -29,11 +29,11 @@ func (b *InMemoryBackend) WebACLARN(name, id, scope string) string { func (b *InMemoryBackend) CreateWebACL( ctx context.Context, name, scope, description string, - defaultAction, visibilityConfig json.RawMessage, + defaultAction, visibilityConfig jsontext.Value, rules []map[string]any, tokenDomains []string, - customResponseBodies, associationConfig, captchaConfig, challengeConfig json.RawMessage, - monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig json.RawMessage, + customResponseBodies, associationConfig, captchaConfig, challengeConfig jsontext.Value, + monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig jsontext.Value, tags map[string]string, ) (*WebACL, error) { b.mu.Lock("CreateWebACL") @@ -134,11 +134,11 @@ func (b *InMemoryBackend) GetWebACLByARN(ctx context.Context, webACLARN string) func (b *InMemoryBackend) UpdateWebACL( ctx context.Context, id, description, lockToken string, - defaultAction, visibilityConfig json.RawMessage, + defaultAction, visibilityConfig jsontext.Value, rules []map[string]any, tokenDomains []string, - customResponseBodies, associationConfig, captchaConfig, challengeConfig json.RawMessage, - monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig json.RawMessage, + customResponseBodies, associationConfig, captchaConfig, challengeConfig jsontext.Value, + monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig jsontext.Value, ) (*WebACL, error) { b.mu.Lock("UpdateWebACL") defer b.mu.Unlock() @@ -195,8 +195,8 @@ func (b *InMemoryBackend) UpdateWebACL( // Extracted from UpdateWebACL to keep its cyclomatic complexity down. func applyOptionalWebACLConfigs( w *WebACL, - customResponseBodies, associationConfig, captchaConfig, challengeConfig json.RawMessage, - monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig json.RawMessage, + customResponseBodies, associationConfig, captchaConfig, challengeConfig jsontext.Value, + monetizationConfig, dataProtectionConfig, applicationConfig, onSourceDDoSProtectionConfig jsontext.Value, ) { if len(customResponseBodies) > 0 { w.CustomResponseBodies = customResponseBodies @@ -302,61 +302,61 @@ func cloneWebACL(w *WebACL) *WebACL { // Clone RawMessage fields (byte slices). if w.DefaultAction != nil { - da := make(json.RawMessage, len(w.DefaultAction)) + da := make(jsontext.Value, len(w.DefaultAction)) copy(da, w.DefaultAction) cp.DefaultAction = da } if w.VisibilityConfig != nil { - vc := make(json.RawMessage, len(w.VisibilityConfig)) + vc := make(jsontext.Value, len(w.VisibilityConfig)) copy(vc, w.VisibilityConfig) cp.VisibilityConfig = vc } if w.CustomResponseBodies != nil { - crb := make(json.RawMessage, len(w.CustomResponseBodies)) + crb := make(jsontext.Value, len(w.CustomResponseBodies)) copy(crb, w.CustomResponseBodies) cp.CustomResponseBodies = crb } if w.AssociationConfig != nil { - ac := make(json.RawMessage, len(w.AssociationConfig)) + ac := make(jsontext.Value, len(w.AssociationConfig)) copy(ac, w.AssociationConfig) cp.AssociationConfig = ac } if w.CaptchaConfig != nil { - cc := make(json.RawMessage, len(w.CaptchaConfig)) + cc := make(jsontext.Value, len(w.CaptchaConfig)) copy(cc, w.CaptchaConfig) cp.CaptchaConfig = cc } if w.ChallengeConfig != nil { - chc := make(json.RawMessage, len(w.ChallengeConfig)) + chc := make(jsontext.Value, len(w.ChallengeConfig)) copy(chc, w.ChallengeConfig) cp.ChallengeConfig = chc } if w.MonetizationConfig != nil { - mc := make(json.RawMessage, len(w.MonetizationConfig)) + mc := make(jsontext.Value, len(w.MonetizationConfig)) copy(mc, w.MonetizationConfig) cp.MonetizationConfig = mc } if w.DataProtectionConfig != nil { - dpc := make(json.RawMessage, len(w.DataProtectionConfig)) + dpc := make(jsontext.Value, len(w.DataProtectionConfig)) copy(dpc, w.DataProtectionConfig) cp.DataProtectionConfig = dpc } if w.ApplicationConfig != nil { - ac := make(json.RawMessage, len(w.ApplicationConfig)) + ac := make(jsontext.Value, len(w.ApplicationConfig)) copy(ac, w.ApplicationConfig) cp.ApplicationConfig = ac } if w.OnSourceDDoSProtectionConfig != nil { - osdc := make(json.RawMessage, len(w.OnSourceDDoSProtectionConfig)) + osdc := make(jsontext.Value, len(w.OnSourceDDoSProtectionConfig)) copy(osdc, w.OnSourceDDoSProtectionConfig) cp.OnSourceDDoSProtectionConfig = osdc } diff --git a/services/workmail/handler.go b/services/workmail/handler.go index 50bea99fc4..fb9a5d10b9 100644 --- a/services/workmail/handler.go +++ b/services/workmail/handler.go @@ -2,7 +2,7 @@ package workmail import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/idempotency" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" ) @@ -116,7 +117,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) precheck(action string, body []byte) error { @@ -178,7 +179,7 @@ func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err } payload, marshalErr := json.Marshal( - service.JSONErrorResponse{Type: code, Message: strings.TrimPrefix(err.Error(), code+": ")}, + service.JSONErrorResponse{Type: code, Message: strings.TrimPrefix(err.Error(), code+": ")}, jsonx.V1(), ) if marshalErr != nil { return c.String(http.StatusInternalServerError, "internal server error") diff --git a/services/workmail/handler_access_control_test.go b/services/workmail/handler_access_control_test.go index d92d35a184..4736eea1a9 100644 --- a/services/workmail/handler_access_control_test.go +++ b/services/workmail/handler_access_control_test.go @@ -1,7 +1,7 @@ package workmail_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workmail" ) @@ -124,7 +125,7 @@ func TestGetAccessControlEffect_RuleEvaluation(t *testing.T) { body["Name"] = name body["Effect"] = effect - jsonBody, err := json.Marshal(body) + jsonBody, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) rec := doOp(t, h, "PutAccessControlRule", string(jsonBody)) diff --git a/services/workmail/handler_availability_config.go b/services/workmail/handler_availability_config.go index acec865643..050445a5af 100644 --- a/services/workmail/handler_availability_config.go +++ b/services/workmail/handler_availability_config.go @@ -2,8 +2,11 @@ package workmail import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Availability Configurations ---- @@ -117,12 +120,12 @@ type listAvailabilityConfigsReq struct { } type availabilityConfigJSON struct { - EwsProvider *json.RawMessage `json:"EwsProvider,omitempty"` - LambdaProvider *json.RawMessage `json:"LambdaProvider,omitempty"` - DomainName string `json:"DomainName"` - ProviderType string `json:"ProviderType"` - DateCreated int64 `json:"DateCreated"` - DateModified int64 `json:"DateModified"` + EwsProvider *jsontext.Value `json:"EwsProvider,omitempty"` + LambdaProvider *jsontext.Value `json:"LambdaProvider,omitempty"` + DomainName string `json:"DomainName"` + ProviderType string `json:"ProviderType"` + DateCreated int64 `json:"DateCreated"` + DateModified int64 `json:"DateModified"` } type listAvailabilityConfigsResp struct { @@ -153,12 +156,12 @@ func (h *Handler) handleListAvailabilityConfigurations( raw, _ := json.Marshal(map[string]string{ "EwsEndpoint": c.EwsEndpoint, "EwsUsername": c.EwsUsername, - }) - rm := json.RawMessage(raw) + }, jsonx.V1()) + rm := jsontext.Value(raw) item.EwsProvider = &rm } else { - raw, _ := json.Marshal(map[string]string{"LambdaArn": c.LambdaARN}) - rm := json.RawMessage(raw) + raw, _ := json.Marshal(map[string]string{"LambdaArn": c.LambdaARN}, jsonx.V1()) + rm := jsontext.Value(raw) item.LambdaProvider = &rm } result = append(result, item) diff --git a/services/workmail/handler_impersonation_test.go b/services/workmail/handler_impersonation_test.go index 7e98058a5c..362d852300 100644 --- a/services/workmail/handler_impersonation_test.go +++ b/services/workmail/handler_impersonation_test.go @@ -1,7 +1,7 @@ package workmail_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workmail" ) @@ -167,7 +168,7 @@ func TestGetImpersonationRoleEffect(t *testing.T) { h := newTestHandler(t) orgID := createTestOrg(t, h, "imp-effect-org") - targetJSON, _ := json.Marshal(tc.targetUsers) + targetJSON, _ := json.Marshal(tc.targetUsers, jsonx.V1()) roleBody := fmt.Sprintf( `{"OrganizationId":%q,"Name":"test-role","Type":"FULL_ACCESS","Rules":[{"ImpersonationRuleId":"rule-1","Name":"rule-one","Effect":%q,"TargetUsers":%s}]}`, //nolint:lll // existing issue. orgID, diff --git a/services/workmail/handler_mobile_device_access_test.go b/services/workmail/handler_mobile_device_access_test.go index 4b5805cdf3..b149f4ba37 100644 --- a/services/workmail/handler_mobile_device_access_test.go +++ b/services/workmail/handler_mobile_device_access_test.go @@ -1,13 +1,15 @@ package workmail_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ---- Mobile Device Access Rules ---- @@ -35,11 +37,11 @@ func TestMobileDeviceAccessRuleLifecycle(t *testing.T) { body := fmt.Sprintf(`{"OrganizationId":%q,"Name":%q,"Effect":%q,"Description":%q`, orgID, tc.name, tc.effect, tc.description) if len(tc.deviceModels) > 0 { - modelsJSON, _ := json.Marshal(tc.deviceModels) + modelsJSON, _ := json.Marshal(tc.deviceModels, jsonx.V1()) body += fmt.Sprintf(`,"DeviceModels":%s`, string(modelsJSON)) } if len(tc.deviceTypes) > 0 { - typesJSON, _ := json.Marshal(tc.deviceTypes) + typesJSON, _ := json.Marshal(tc.deviceTypes, jsonx.V1()) body += fmt.Sprintf(`,"DeviceTypes":%s`, string(typesJSON)) } body += "}" @@ -163,7 +165,7 @@ func TestGetMobileDeviceAccessEffect(t *testing.T) { h := newTestHandler(t) orgID := createTestOrg(t, h, "effect-org-"+tc.name) - modelsJSON, _ := json.Marshal(tc.deviceModels) + modelsJSON, _ := json.Marshal(tc.deviceModels, jsonx.V1()) doOp(t, h, "CreateMobileDeviceAccessRule", fmt.Sprintf( `{"OrganizationId":%q,"Name":"test-rule","Effect":%q,"DeviceModels":%s}`, orgID, tc.ruleEffect, string(modelsJSON), diff --git a/services/workmail/handler_test.go b/services/workmail/handler_test.go index ee212344e8..c549f819c9 100644 --- a/services/workmail/handler_test.go +++ b/services/workmail/handler_test.go @@ -1,7 +1,7 @@ package workmail_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workmail" ) @@ -41,7 +42,7 @@ func decodeJSON(t *testing.T, rec *httptest.ResponseRecorder) map[string]any { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &m, jsonx.V1())) return m } @@ -190,7 +191,7 @@ func TestWorkMail_Protocol_GetRoot_Returns_OpList(t *testing.T) { require.NoError(t, h.Handler()(c)) assert.Equal(t, http.StatusOK, rec.Code) var ops []string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &ops, jsonx.V1())) assert.NotEmpty(t, ops) } diff --git a/services/workmail/lifecycle.go b/services/workmail/lifecycle.go index 8b8b71a48f..15865fb44f 100644 --- a/services/workmail/lifecycle.go +++ b/services/workmail/lifecycle.go @@ -2,12 +2,14 @@ package workmail import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "regexp" "strconv" "strings" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -95,7 +97,7 @@ func validatePageParams(body []byte) error { NextToken *string `json:"NextToken"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.MaxResults != nil && (*req.MaxResults < minPageSize || *req.MaxResults > defaultPageSize) { return fmt.Errorf("%w: MaxResults must be between %d and %d", ErrValidation, minPageSize, defaultPageSize) @@ -188,7 +190,7 @@ func organizationIDOf(body []byte) string { OrganizationID string `json:"OrganizationId"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) return req.OrganizationID } diff --git a/services/workmail/persistence.go b/services/workmail/persistence.go index 4b595a9b45..84b1247c79 100644 --- a/services/workmail/persistence.go +++ b/services/workmail/persistence.go @@ -2,7 +2,7 @@ package workmail import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "maps" @@ -149,7 +149,7 @@ func buildPersistenceDTORegistry() persistenceDTOTables { // snapshot from an incompatible (older or newer) build of this backend as // though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` OrgsByAlias map[string]string `json:"orgsByAlias"` UsersByEmail map[string]map[string]string `json:"usersByEmail"` GroupsByEmail map[string]map[string]string `json:"groupsByEmail"` @@ -321,7 +321,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { // restoreResourceTables rebuilds every store.Table on b from snap's // per-table JSON, factored out of Restore to keep Restore's own cognitive // complexity low. Callers must hold b.mu.Lock. -func (b *InMemoryBackend) restoreResourceTables(tables map[string]json.RawMessage) error { +func (b *InMemoryBackend) restoreResourceTables(tables map[string]jsontext.Value) error { if err := b.registry.RestoreAll(tables); err != nil { return fmt.Errorf("workmail: restore clean tables: %w", err) } diff --git a/services/workspaces/bundles_test.go b/services/workspaces/bundles_test.go index 5a2dbcaeee..6e72f19a38 100644 --- a/services/workspaces/bundles_test.go +++ b/services/workspaces/bundles_test.go @@ -1,7 +1,7 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -27,7 +28,7 @@ func TestDescribeWorkspaceBundles_PaginatesResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bundles, _ := resp["Bundles"].([]any) assert.NotEmpty(t, bundles, "DescribeWorkspaceBundles must return at least one bundle") @@ -79,7 +80,7 @@ func TestDescribeWorkspaceBundles_FiltersByBundleID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bundles, _ := resp["Bundles"].([]any) assert.Len(t, bundles, tc.wantCount) @@ -95,7 +96,7 @@ func TestDescribeWorkspaceBundles_ComputeTypeAndStorage(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bundles := resp["Bundles"].([]any) require.NotEmpty(t, bundles) @@ -169,7 +170,7 @@ func TestDescribeWorkspaceBundles_ByOwnerAmazon(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) bundles := resp["Bundles"].([]any) for _, b := range bundles { @@ -199,7 +200,7 @@ func TestCreateWorkspaceBundle_StoresStorageCapacity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bun := resp["WorkspaceBundle"].(map[string]any) assert.Equal(t, "50", bun["UserStorage"].(map[string]any)["Capacity"]) @@ -222,7 +223,7 @@ func TestDescribeWorkspaceBundles_IncludesCustomBundle(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) customBundleID := createResp["WorkspaceBundle"].(map[string]any)["BundleId"].(string) // Without owner: only the account's own (custom) bundles. @@ -230,7 +231,7 @@ func TestDescribeWorkspaceBundles_IncludesCustomBundle(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) bundles := resp["Bundles"].([]any) found := false @@ -253,7 +254,7 @@ func TestDescribeWorkspaceBundles_FilterByID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) bundles := resp["Bundles"].([]any) require.Len(t, bundles, 1) assert.Equal(t, "wsb-bh8rsxt14", bundles[0].(map[string]any)["BundleId"]) @@ -475,7 +476,7 @@ func TestCreateWorkspaceBundle_UnknownImage_ConsumesNoState(t *testing.T) { require.Equal(t, http.StatusOK, describeRec.Code) var before map[string]any - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before, jsonx.V1())) countBefore := len(before["Bundles"].([]any)) rejectedRec := doTargetRequest( @@ -487,7 +488,7 @@ func TestCreateWorkspaceBundle_UnknownImage_ConsumesNoState(t *testing.T) { require.Equal(t, http.StatusOK, describeRec2.Code) var after map[string]any - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after, jsonx.V1())) countAfter := len(after["Bundles"].([]any)) assert.Equal(t, countBefore, countAfter, "rejected create must not add a bundle") diff --git a/services/workspaces/directories_test.go b/services/workspaces/directories_test.go index f23186bd39..81ec2ebf42 100644 --- a/services/workspaces/directories_test.go +++ b/services/workspaces/directories_test.go @@ -2,13 +2,14 @@ package workspaces_test import ( "encoding/base64" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -29,7 +30,7 @@ func TestDescribeWorkspaceDirectories_PaginatesResults(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs, _ := resp["Directories"].([]any) assert.Len(t, dirs, 2, "both registered directories must be returned") @@ -42,7 +43,7 @@ func TestDescribeWorkspaceDirectories_PaginatesResults(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) dirs2, _ := resp2["Directories"].([]any) require.Len(t, dirs2, 1, "cursor should skip d-aaa and return only d-bbb") @@ -61,7 +62,7 @@ func TestDescribeWorkspaceDirectories_AfterRegister(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs := resp["Directories"].([]any) assert.Empty(t, dirs, "DescribeWorkspaceDirectories must be empty before registration") @@ -77,7 +78,7 @@ func TestDescribeWorkspaceDirectories_AfterRegister(t *testing.T) { require.Equal(t, http.StatusOK, rec3.Code) var resp3 map[string]any - require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3)) + require.NoError(t, json.Unmarshal(rec3.Body.Bytes(), &resp3, jsonx.V1())) dirs3 := resp3["Directories"].([]any) require.Len(t, dirs3, 1) @@ -108,7 +109,7 @@ func TestDescribeWorkspaceDirectories_FilterByID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs := resp["Directories"].([]any) require.Len(t, dirs, 1) assert.Equal(t, "d-aaaa", dirs[0].(map[string]any)["DirectoryId"]) @@ -131,7 +132,7 @@ func TestDescribeWorkspaceDirectories_AfterDeregister(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) dirs := resp["Directories"].([]any) assert.Empty(t, dirs, "deregistered directory must not appear") } @@ -156,7 +157,7 @@ func TestRegisterWorkspaceDirectory_DuplicateRejected(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec2.Code, "body: %s", rec2.Body) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "ResourceAlreadyExistsException", errOut["__type"]) } @@ -195,7 +196,7 @@ func TestDeregisterWorkspaceDirectory_BlockedByWorkspaces(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec2.Code, "body: %s", rec2.Body) var errOut map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidResourceStateException", errOut["__type"]) // The directory must still be registered -- deregistration was rejected, diff --git a/services/workspaces/directory_registration.go b/services/workspaces/directory_registration.go index 1afea6788f..8df52b9e15 100644 --- a/services/workspaces/directory_registration.go +++ b/services/workspaces/directory_registration.go @@ -1,12 +1,13 @@ package workspaces import ( - "encoding/json" + "encoding/json/v2" "fmt" "slices" "strings" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -239,7 +240,7 @@ func streamingPropertiesFromDS(ds *storedDirSettings) *StreamingProperties { } var sp StreamingProperties - if err := json.Unmarshal([]byte(raw), &sp); err != nil { + if err := json.Unmarshal([]byte(raw), &sp, jsonx.V1()); err != nil { return nil } diff --git a/services/workspaces/handler.go b/services/workspaces/handler.go index df8a713bdb..ed5f74d649 100644 --- a/services/workspaces/handler.go +++ b/services/workspaces/handler.go @@ -2,7 +2,7 @@ package workspaces import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/labstack/echo/v5" "github.com/blackbirdworks/gopherstack/pkgs/awserr" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -94,7 +95,7 @@ func (h *Handler) dispatch(ctx context.Context, action string, body []byte) ([]b return nil, err } - return json.Marshal(result) + return json.Marshal(result, jsonx.V1()) } func (h *Handler) handleError(_ context.Context, c *echo.Context, _ string, err error) error { diff --git a/services/workspaces/handler_test.go b/services/workspaces/handler_test.go index f0ef791f73..28b054a9f4 100644 --- a/services/workspaces/handler_test.go +++ b/services/workspaces/handler_test.go @@ -3,7 +3,7 @@ package workspaces_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strconv" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -59,7 +60,7 @@ func doTargetRequest( if body != nil { var marshalErr error - bodyBytes, marshalErr = json.Marshal(body) + bodyBytes, marshalErr = json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) } @@ -79,7 +80,7 @@ func doTargetRequest( func decodeJSON(t *testing.T, body []byte, dst any) { t.Helper() - if err := json.Unmarshal(body, dst); err != nil { + if err := json.Unmarshal(body, dst, jsonx.V1()); err != nil { t.Fatalf("unmarshal response: %v (body=%s)", err, body) } } @@ -122,7 +123,7 @@ func createWorkspace(t *testing.T, h *workspaces.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pending, ok := resp["PendingRequests"].([]any) require.True(t, ok, "PendingRequests missing from response") @@ -173,7 +174,7 @@ func createWorkspaceWithRunningMode(t *testing.T, h *workspaces.Handler, running require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pending, ok := resp["PendingRequests"].([]any) require.True(t, ok, "PendingRequests missing from response") @@ -204,7 +205,7 @@ func createImage(t *testing.T, h *workspaces.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) id, ok := resp["ImageId"].(string) require.True(t, ok) @@ -267,7 +268,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) pending, ok := resp["PendingRequests"].([]any) require.True(t, ok) assert.Len(t, pending, 1) @@ -291,7 +292,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) wsList, ok := resp["Workspaces"].([]any) require.True(t, ok) assert.Len(t, wsList, 1) @@ -308,7 +309,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) wsList, ok := resp["Workspaces"].([]any) require.True(t, ok) assert.Len(t, wsList, 1) @@ -329,7 +330,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) statuses, ok := resp["WorkspacesConnectionStatus"].([]any) require.True(t, ok) assert.Len(t, statuses, 1) @@ -418,7 +419,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Empty(t, failures) @@ -438,7 +439,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Len(t, failures, 1) @@ -461,7 +462,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Empty(t, failures) @@ -478,7 +479,7 @@ func TestWorkSpaces_Operations(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) stopFailures, _ := stopResp["FailedRequests"].([]any) require.Empty(t, stopFailures, "setup: StopWorkspaces must succeed to reach STOPPED") @@ -495,7 +496,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Empty(t, failures) @@ -518,7 +519,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Empty(t, failures) @@ -541,7 +542,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Empty(t, failures) @@ -561,7 +562,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) failures, ok := resp["FailedRequests"].([]any) require.True(t, ok) assert.Len(t, failures, 1) @@ -624,7 +625,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) tagList, ok := resp["TagList"].([]any) require.True(t, ok) assert.Len(t, tagList, 1) @@ -641,7 +642,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) bundles, ok := resp["Bundles"].([]any) require.True(t, ok) assert.NotEmpty(t, bundles) @@ -655,7 +656,7 @@ func TestWorkSpaces_Operations(t *testing.T) { check: func(t *testing.T, respBody []byte) { t.Helper() var resp map[string]any - require.NoError(t, json.Unmarshal(respBody, &resp)) + require.NoError(t, json.Unmarshal(respBody, &resp, jsonx.V1())) dirs, ok := resp["Directories"].([]any) require.True(t, ok) assert.Empty(t, dirs) diff --git a/services/workspaces/images_test.go b/services/workspaces/images_test.go index 2d9b279b1c..988a093e85 100644 --- a/services/workspaces/images_test.go +++ b/services/workspaces/images_test.go @@ -1,7 +1,7 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -309,7 +310,7 @@ func TestCreateWorkspaceImage_UnknownWorkspace_ConsumesNoState(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code, rec1.Body.String()) var out1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &out1, jsonx.V1())) firstID, _ := out1["ImageId"].(string) require.NotEmpty(t, firstID) @@ -319,7 +320,7 @@ func TestCreateWorkspaceImage_UnknownWorkspace_ConsumesNoState(t *testing.T) { var before struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before, jsonx.V1())) rejectedRec := doTargetRequest( t, h, "CreateWorkspaceImage", createWorkspaceImageReq("ws-doesnotexist"), @@ -332,7 +333,7 @@ func TestCreateWorkspaceImage_UnknownWorkspace_ConsumesNoState(t *testing.T) { var after struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after, jsonx.V1())) assert.Len(t, after.Images, len(before.Images), "rejected create must not add an image") @@ -340,7 +341,7 @@ func TestCreateWorkspaceImage_UnknownWorkspace_ConsumesNoState(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code, rec2.Body.String()) var out2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &out2, jsonx.V1())) secondID, _ := out2["ImageId"].(string) require.NotEmpty(t, secondID) @@ -462,7 +463,7 @@ func TestCopyWorkspaceImage_UnknownSourceImage_ConsumesNoState(t *testing.T) { var before struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before, jsonx.V1())) rejectedRec := doTargetRequest( t, h, "CopyWorkspaceImage", createCopyImageReq("wsi-doesnotexist", "us-east-1"), @@ -475,7 +476,7 @@ func TestCopyWorkspaceImage_UnknownSourceImage_ConsumesNoState(t *testing.T) { var after struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after, jsonx.V1())) assert.Len(t, after.Images, len(before.Images), "rejected copy must not add an image") @@ -577,7 +578,7 @@ func TestCreateUpdatedWorkspaceImage_UnknownSourceImage_ConsumesNoState(t *testi var before struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before)) + require.NoError(t, json.Unmarshal(describeRec.Body.Bytes(), &before, jsonx.V1())) rejectedRec := doTargetRequest( t, h, "CreateUpdatedWorkspaceImage", createUpdatedImageReq("wsi-doesnotexist"), @@ -590,7 +591,7 @@ func TestCreateUpdatedWorkspaceImage_UnknownSourceImage_ConsumesNoState(t *testi var after struct { Images []map[string]any `json:"Images"` } - require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after)) + require.NoError(t, json.Unmarshal(describeRec2.Body.Bytes(), &after, jsonx.V1())) assert.Len(t, after.Images, len(before.Images), "rejected create must not add an image") diff --git a/services/workspaces/multi_region_test.go b/services/workspaces/multi_region_test.go index ee611ec970..db10b976b3 100644 --- a/services/workspaces/multi_region_test.go +++ b/services/workspaces/multi_region_test.go @@ -2,7 +2,8 @@ package workspaces_test import ( "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net" "net/http" "net/http/httptest" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/service" svc "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -173,8 +175,8 @@ func TestHandler_MultiRegionPersistence(t *testing.T) { snap := src.Snapshot(context.Background()) require.NotNil(t, snap) - var doc map[string]json.RawMessage - require.NoError(t, json.Unmarshal(snap, &doc)) + var doc map[string]jsontext.Value + require.NoError(t, json.Unmarshal(snap, &doc, jsonx.V1())) if _, ok := doc["regions"]; ok != tc.remote { t.Fatalf("regions key present = %v, want %v", ok, tc.remote) diff --git a/services/workspaces/persistence.go b/services/workspaces/persistence.go index 5d797d95a8..939b48eb49 100644 --- a/services/workspaces/persistence.go +++ b/services/workspaces/persistence.go @@ -2,7 +2,7 @@ package workspaces import ( "context" - "encoding/json" + "encoding/json/jsontext" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -43,7 +43,7 @@ const workspacesSnapshotVersion = 1 // against decoding a snapshot from an incompatible (older or newer) build of // this backend as though it were the current shape; see Restore. type backendSnapshot struct { - Tables map[string]json.RawMessage `json:"tables"` + Tables map[string]jsontext.Value `json:"tables"` Tags map[string]map[string]string `json:"tags"` ClientProperties map[string]storedClientProps `json:"clientProperties,omitempty"` ImagePermissions map[string]map[string]bool `json:"imagePermissions,omitempty"` diff --git a/services/workspaces/pools_test.go b/services/workspaces/pools_test.go index 23a429f902..1d8f95558d 100644 --- a/services/workspaces/pools_test.go +++ b/services/workspaces/pools_test.go @@ -1,7 +1,7 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" @@ -10,6 +10,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/workspaces/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestWorkspacesPool_CapacityStatusAndRunningMode verifies fields that were @@ -196,7 +198,7 @@ func TestWorkspacesPool_UpdateRunningModeRequiresStopped(t *testing.T) { if tc.wantStatus == http.StatusBadRequest { var errOut map[string]string - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &errOut, jsonx.V1())) assert.Equal(t, "InvalidResourceStateException", errOut["__type"]) } diff --git a/services/workspaces/tags_test.go b/services/workspaces/tags_test.go index 9c5d5b2537..5eb99df325 100644 --- a/services/workspaces/tags_test.go +++ b/services/workspaces/tags_test.go @@ -1,13 +1,16 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -126,7 +129,7 @@ func TestCreateTags_VisibleInDescribeTags(t *testing.T) { Value string `json:"Value"` } `json:"TagList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := make(map[string]string, len(resp.TagList)) for _, tag := range resp.TagList { @@ -172,7 +175,7 @@ func TestDeleteTags_RemovedFromDescribeTags(t *testing.T) { Value string `json:"Value"` } `json:"TagList"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags := make(map[string]string, len(resp.TagList)) for _, tag := range resp.TagList { @@ -206,9 +209,9 @@ func TestDescribeWorkspaces_NoTagsField(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp struct { - Workspaces []map[string]json.RawMessage `json:"Workspaces"` + Workspaces []map[string]jsontext.Value `json:"Workspaces"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.Workspaces, 1) assert.NotContains(t, resp.Workspaces[0], "Tags") } @@ -238,7 +241,7 @@ func TestCreateWorkspaces_Tags_VisibleInDescribeTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending, _ := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) wsID := pending[0].(map[string]any)["WorkspaceId"].(string) @@ -249,7 +252,7 @@ func TestCreateWorkspaces_Tags_VisibleInDescribeTags(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var tagResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &tagResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &tagResp, jsonx.V1())) tagList, _ := tagResp["TagList"].([]any) found := false diff --git a/services/workspaces/workspace_properties.go b/services/workspaces/workspace_properties.go index 61db4c1579..76c58c118f 100644 --- a/services/workspaces/workspace_properties.go +++ b/services/workspaces/workspace_properties.go @@ -1,8 +1,10 @@ package workspaces import ( - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // ModifyEndpointEncryptionMode stores the endpoint encryption mode for a @@ -151,7 +153,7 @@ func (b *InMemoryBackend) ModifyStreamingProperties(directoryID string, props St merged.GlobalAccelerator = props.GlobalAccelerator } - raw, err := json.Marshal(merged) + raw, err := json.Marshal(merged, jsonx.V1()) if err != nil { return fmt.Errorf("encode streaming properties: %w", err) } diff --git a/services/workspaces/workspace_properties_test.go b/services/workspaces/workspace_properties_test.go index 6f32d30f09..7fac31bfef 100644 --- a/services/workspaces/workspace_properties_test.go +++ b/services/workspaces/workspace_properties_test.go @@ -1,12 +1,14 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestDirectoryModifyOps exercises the directory-scoped property-modify @@ -193,7 +195,7 @@ func TestDirectoryModifyOps_RejectsLegacyDirectoryIdKey(t *testing.T) { "DirectoryId": tc.body["DirectoryId"], }) - rawBody, marshalErr := json.Marshal(tc.body) + rawBody, marshalErr := json.Marshal(tc.body, jsonx.V1()) require.NoError(t, marshalErr) require.NotContains( t, string(rawBody), `"ResourceId"`, diff --git a/services/workspaces/workspaces_lifecycle_test.go b/services/workspaces/workspaces_lifecycle_test.go index ced7d83a60..183482bf49 100644 --- a/services/workspaces/workspaces_lifecycle_test.go +++ b/services/workspaces/workspaces_lifecycle_test.go @@ -1,7 +1,7 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "net" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -29,7 +30,7 @@ func TestConnectionStatus_Available_IsDisconnected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) statuses := resp["WorkspacesConnectionStatus"].([]any) require.Len(t, statuses, 1) assert.Equal(t, "DISCONNECTED", statuses[0].(map[string]any)["ConnectionState"], @@ -48,7 +49,7 @@ func TestConnectionStatus_Stopped_IsNotConnected(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) stopFailures, _ := stopResp["FailedRequests"].([]any) require.Empty(t, stopFailures, "StopWorkspaces must succeed to reach STOPPED") @@ -58,7 +59,7 @@ func TestConnectionStatus_Stopped_IsNotConnected(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) statuses := resp["WorkspacesConnectionStatus"].([]any) require.Len(t, statuses, 1) assert.Equal(t, "NOT_CONNECTED", statuses[0].(map[string]any)["ConnectionState"], @@ -80,7 +81,7 @@ func TestConnectionStatus_AllWorkspaces_NoFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) statuses := resp["WorkspacesConnectionStatus"].([]any) assert.Len(t, statuses, 2, "empty WorkspaceIds must return status for all workspaces") @@ -103,7 +104,7 @@ func TestConnectionStatus_UnknownID_Excluded(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) statuses := resp["WorkspacesConnectionStatus"].([]any) assert.Empty(t, statuses, "unknown workspace ID must be silently excluded from status results") } @@ -205,7 +206,7 @@ func TestRebootRebuildWorkspaces_StatePrecondition(t *testing.T) { ErrorMessage string `json:"ErrorMessage"` } `json:"FailedRequests"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if !tc.wantFailed { assert.Empty(t, out.FailedRequests) @@ -247,7 +248,7 @@ func TestRebootWorkspaces_AllowsAvailableAndStopped(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) stopFailures, _ := stopResp["FailedRequests"].([]any) require.Empty(t, stopFailures, "StopWorkspaces must succeed to reach STOPPED") } @@ -262,7 +263,7 @@ func TestRebootWorkspaces_AllowsAvailableAndStopped(t *testing.T) { ErrorCode string `json:"ErrorCode"` } `json:"FailedRequests"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) if tc.stop { require.Len(t, out.FailedRequests, 1) @@ -292,7 +293,7 @@ func TestMigrateWorkspace_SourceRemovedTargetCreated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Original workspace must no longer exist. assert.Empty(t, workspaces.WorkspaceState(backend, srcID), @@ -375,7 +376,7 @@ func TestDescribeWorkspaces_EmptyList_WhenNoWorkspaces(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList := resp["Workspaces"].([]any) assert.Empty(t, wsList) @@ -396,7 +397,7 @@ func TestDescribeWorkspaces_MultipleIDs_AllReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList := resp["Workspaces"].([]any) assert.Len(t, wsList, 2) } @@ -416,7 +417,7 @@ func TestDescribeWorkspaces_FilterByUserName(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList := resp["Workspaces"].([]any) assert.Len(t, wsList, 2, "UserName filter must return only Alice's workspaces") @@ -446,7 +447,7 @@ func TestCreateWorkspaces_VolumeEncryption_Propagated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) @@ -460,7 +461,7 @@ func TestCreateWorkspaces_VolumeEncryption_Propagated(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) wsList := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) descWs := wsList[0].(map[string]any) @@ -512,7 +513,7 @@ func TestErrorMessages_AreNotGarbledByExtraFormatArgs(t *testing.T) { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.NotContains(t, resp["message"], "%!(EXTRA", "error message must not contain a garbled fmt.Sprintf EXTRA marker") assert.NotEmpty(t, resp["message"]) @@ -565,7 +566,7 @@ func TestCreateWorkspaces_PartialFailure_DoesNotAbortWholeBatch(t *testing.T) { } `json:"PendingRequests"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) require.Len(t, resp.PendingRequests, 1, "the valid spec must succeed") assert.Equal(t, "gooduser", resp.PendingRequests[0].UserName) diff --git a/services/workspaces/workspaces_test.go b/services/workspaces/workspaces_test.go index e88575ff7b..e6c74bd471 100644 --- a/services/workspaces/workspaces_test.go +++ b/services/workspaces/workspaces_test.go @@ -2,7 +2,7 @@ package workspaces_test import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "sort" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/workspaces" ) @@ -39,7 +40,7 @@ func createWorkspaceWithSpec(t *testing.T, h *workspaces.Handler, userID, dirID require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pending, _ := resp["PendingRequests"].([]any) require.Len(t, pending, 1) @@ -64,7 +65,7 @@ func describeWorkspacesPage( require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, _ := resp["Workspaces"].([]any) ids := make([]string, 0, len(wsList)) @@ -236,7 +237,7 @@ func TestStopWorkspaces_TransitionsToStopped(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) assert.Empty(t, failures) @@ -258,7 +259,7 @@ func TestStopWorkspaces_StateVisibleInDescribe(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) stopFailures, _ := stopResp["FailedRequests"].([]any) require.Empty(t, stopFailures, "StopWorkspaces must succeed to reach STOPPED") @@ -268,7 +269,7 @@ func TestStopWorkspaces_StateVisibleInDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, _ := resp["Workspaces"].([]any) require.Len(t, wsList, 1) ws := wsList[0].(map[string]any) @@ -299,7 +300,7 @@ func TestStartWorkspaces_ResumesFromStopped(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) assert.Empty(t, failures) @@ -328,7 +329,7 @@ func TestStopWorkspaces_AlreadyStopped_Fails(t *testing.T) { require.Equal(t, http.StatusOK, firstStopRec.Code) var firstStopResp map[string]any - require.NoError(t, json.Unmarshal(firstStopRec.Body.Bytes(), &firstStopResp)) + require.NoError(t, json.Unmarshal(firstStopRec.Body.Bytes(), &firstStopResp, jsonx.V1())) firstStopFailures, _ := firstStopResp["FailedRequests"].([]any) require.Empty(t, firstStopFailures, "first StopWorkspaces call must succeed to reach STOPPED") @@ -338,7 +339,7 @@ func TestStopWorkspaces_AlreadyStopped_Fails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) require.Len(t, failures, 1, "stopping an already-STOPPED workspace must fail") failed := failures[0].(map[string]any) @@ -362,7 +363,7 @@ func TestStartWorkspaces_AlreadyAvailable_Fails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) require.Len(t, failures, 1, "starting an already-AVAILABLE workspace must fail") failed := failures[0].(map[string]any) @@ -393,7 +394,7 @@ func TestStopWorkspaces_AlwaysOnRunningMode_Fails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) require.Len(t, failures, 1, "stopping an ALWAYS_ON workspace must fail even though its state is AVAILABLE") @@ -430,7 +431,7 @@ func TestStartWorkspaces_AlwaysOnRunningMode_Fails(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) require.Len(t, failures, 1, "starting an ALWAYS_ON workspace must fail even though its state is STOPPED") @@ -458,7 +459,7 @@ func TestStopStartWorkspaces_AutoStopRunningMode_Succeeds(t *testing.T) { require.Equal(t, http.StatusOK, stopRec.Code) var stopResp map[string]any - require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp)) + require.NoError(t, json.Unmarshal(stopRec.Body.Bytes(), &stopResp, jsonx.V1())) stopFailures, _ := stopResp["FailedRequests"].([]any) assert.Empty(t, stopFailures, "AUTO_STOP workspace must be stoppable") require.Equal(t, "STOPPED", workspaces.WorkspaceState(backend, wsID)) @@ -469,7 +470,7 @@ func TestStopStartWorkspaces_AutoStopRunningMode_Succeeds(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) startFailures, _ := startResp["FailedRequests"].([]any) assert.Empty(t, startFailures, "AUTO_STOP workspace must be startable") assert.Equal(t, "AVAILABLE", workspaces.WorkspaceState(backend, wsID)) @@ -492,7 +493,7 @@ func TestStopWorkspaces_UnknownID_ReturnsFailure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) assert.Len(t, failures, 1, "unknown workspace must produce one FailedRequest") @@ -514,7 +515,7 @@ func TestStartWorkspaces_UnknownID_ReturnsFailure(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) failures, _ := resp["FailedRequests"].([]any) assert.Len(t, failures, 1, "unknown workspace must produce one FailedRequest") } @@ -576,7 +577,7 @@ func TestModifyWorkspaceProperties_VisibleInDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, _ := resp["Workspaces"].([]any) require.Len(t, wsList, 1) @@ -606,7 +607,7 @@ func TestWorkspaceProperties_DefaultRunningModeBeforeModify(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, _ := resp["Workspaces"].([]any) require.Len(t, wsList, 1) @@ -639,7 +640,7 @@ func TestTerminateWorkspaces_RemovedFromDescribe(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, _ := resp["Workspaces"].([]any) assert.Empty(t, wsList, "terminated workspace must not appear in DescribeWorkspaces") } @@ -771,7 +772,7 @@ func TestPagination_FilteredByDirectoryID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList := resp["Workspaces"].([]any) assert.Len(t, wsList, 2, "filter by DirectoryId must return only matching workspaces") } @@ -809,7 +810,7 @@ func TestWorkspace_RealMembersPopulated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) wsList, ok := resp["Workspaces"].([]any) require.True(t, ok) require.Len(t, wsList, 1) @@ -853,7 +854,7 @@ func TestWorkspace_WorkspaceNameThreadedThrough(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending, _ := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) pendingItem := pending[0].(map[string]any) @@ -867,7 +868,7 @@ func TestWorkspace_WorkspaceNameThreadedThrough(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) wsList, _ := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) ws := wsList[0].(map[string]any) @@ -901,7 +902,7 @@ func describeOneWorkspace(t *testing.T, h *workspaces.Handler, workspaceID strin var out struct { Workspaces []describedWorkspace `json:"Workspaces"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Len(t, out.Workspaces, 1) return out.Workspaces[0] @@ -956,7 +957,7 @@ func TestCreateStandbyWorkspaces_DataReplicationAndRelated(t *testing.T) { WorkspaceID string `json:"WorkspaceId"` } `json:"PendingStandbyRequests"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Empty(t, out.FailedStandbyRequests) require.Len(t, out.PendingStandbyRequests, 1) @@ -1001,7 +1002,7 @@ func TestCreateStandbyWorkspaces_UnknownPrimaryFails(t *testing.T) { } `json:"FailedStandbyRequests"` PendingStandbyRequests []any `json:"PendingStandbyRequests"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) require.Empty(t, out.PendingStandbyRequests) require.Len(t, out.FailedStandbyRequests, 1) diff --git a/services/workspaces/workspaces_validation_test.go b/services/workspaces/workspaces_validation_test.go index 3408a7f01c..ec2e473e70 100644 --- a/services/workspaces/workspaces_validation_test.go +++ b/services/workspaces/workspaces_validation_test.go @@ -1,7 +1,7 @@ package workspaces_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,8 @@ import ( sdktypes "github.com/aws/aws-sdk-go-v2/service/workspaces/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // --------------------------------------------------------------------------- @@ -113,7 +115,7 @@ func TestCreateWorkspaces_MaxAllowed_Returns200(t *testing.T) { ) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pending := resp["PendingRequests"].([]any) assert.Len(t, pending, 25) } @@ -145,7 +147,7 @@ func TestCreateWorkspaces_WithProperties_Stored(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) @@ -172,7 +174,7 @@ func TestCreateWorkspaces_WithProperties_Stored(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) wsList := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) @@ -210,7 +212,7 @@ func TestCreateWorkspaces_DefaultsRunningModeToAlwaysOn(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) ws := pending[0].(map[string]any) @@ -226,7 +228,7 @@ func TestCreateWorkspaces_DefaultsRunningModeToAlwaysOn(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) wsList := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) @@ -259,7 +261,7 @@ func TestCreateWorkspaces_ExplicitRunningMode_NotOverwritten(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) ws := pending[0].(map[string]any) @@ -273,7 +275,7 @@ func TestCreateWorkspaces_ExplicitRunningMode_NotOverwritten(t *testing.T) { require.Equal(t, http.StatusOK, descRec.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(descRec.Body.Bytes(), &descResp, jsonx.V1())) wsList := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) assert.Equal(t, "AUTO_STOP", wsList[0].(map[string]any)["WorkspaceProperties"].(map[string]any)["RunningMode"], @@ -320,7 +322,7 @@ func TestCreateWorkspaces_InvalidRunningMode_FailsPerItem(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "batch op must return 200 even with a failed item") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) pending, _ := resp["PendingRequests"].([]any) assert.Empty(t, pending, "the invalid item must not be created") @@ -358,7 +360,7 @@ func TestCreateWorkspaces_SubnetId_Propagated(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &createResp, jsonx.V1())) pending := createResp["PendingRequests"].([]any) require.Len(t, pending, 1) @@ -372,7 +374,7 @@ func TestCreateWorkspaces_SubnetId_Propagated(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var descResp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &descResp, jsonx.V1())) wsList := descResp["Workspaces"].([]any) require.Len(t, wsList, 1) assert.Equal(t, "subnet-12345678", wsList[0].(map[string]any)["SubnetId"]) @@ -528,7 +530,7 @@ func TestModifyWorkspaceProperties_InvalidComputeType_MessageIsDescriptive(t *te var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp["message"], "NOT_A_REAL_TYPE", "message should describe the actual invalid value, not just the error code") assert.NotContains(t, resp["message"], "%!(EXTRA") diff --git a/services/xray/handler.go b/services/xray/handler.go index 9889c101b8..6cb3a16e84 100644 --- a/services/xray/handler.go +++ b/services/xray/handler.go @@ -2,7 +2,7 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "errors" "fmt" "net/http" @@ -14,6 +14,7 @@ import ( "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" "github.com/blackbirdworks/gopherstack/pkgs/config" "github.com/blackbirdworks/gopherstack/pkgs/httputils" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/regionpeers" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -336,7 +337,7 @@ func (h *Handler) ExtractResource(c *echo.Context) string { RuleName string `json:"RuleName"` } - _ = json.Unmarshal(body, &req) + _ = json.Unmarshal(body, &req, jsonx.V1()) if req.GroupName != "" { return req.GroupName @@ -498,9 +499,6 @@ func invalidParameterExceptionName(err error) string { } func (h *Handler) handleError(c *echo.Context, _ string, err error) error { - var syntaxErr *json.SyntaxError - var typeErr *json.UnmarshalTypeError - switch { case errors.Is(err, awserr.ErrNotFound): return c.JSON(http.StatusBadRequest, map[string]string{ @@ -518,7 +516,7 @@ func (h *Handler) handleError(c *echo.Context, _ string, err error) error { keyMessageField: err.Error(), }) case errors.Is(err, errInvalidRequest), errors.Is(err, errUnknownPath), - errors.As(err, &syntaxErr), errors.As(err, &typeErr): + jsonx.IsSyntaxError(err), jsonx.IsTypeError(err): return c.JSON(http.StatusBadRequest, map[string]string{ keyTypeField: errInvalidRequestException, keyMessageField: err.Error(), diff --git a/services/xray/handler_encryption_config.go b/services/xray/handler_encryption_config.go index 64fb6f079d..cb92649375 100644 --- a/services/xray/handler_encryption_config.go +++ b/services/xray/handler_encryption_config.go @@ -2,11 +2,13 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "github.com/labstack/echo/v5" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putEncryptionConfigInput struct { @@ -30,13 +32,13 @@ func (h *Handler) handleGetEncryptionConfigBody(_ context.Context, _ []byte) ([] return json.Marshal(map[string]any{ keyEncryptionConfig: cfg, - }) + }, jsonx.V1()) } func (h *Handler) handlePutEncryptionConfig(_ context.Context, body []byte) ([]byte, error) { var in putEncryptionConfigInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -65,5 +67,5 @@ func (h *Handler) handlePutEncryptionConfig(_ context.Context, body []byte) ([]b return json.Marshal(map[string]any{ keyEncryptionConfig: cfg, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_encryption_config_test.go b/services/xray/handler_encryption_config_test.go index 8f17a085d2..a925b599d1 100644 --- a/services/xray/handler_encryption_config_test.go +++ b/services/xray/handler_encryption_config_test.go @@ -1,12 +1,14 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetEncryptionConfig(t *testing.T) { @@ -143,7 +145,7 @@ func TestEncryption_UpdatingStatusAfterPut(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) enc, ok := putResp["EncryptionConfig"].(map[string]any) require.True(t, ok) @@ -154,7 +156,7 @@ func TestEncryption_UpdatingStatusAfterPut(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) enc2, ok := getResp["EncryptionConfig"].(map[string]any) require.True(t, ok) @@ -200,7 +202,7 @@ func TestGetEncryptionConfig_DefaultIsNONE(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) enc, ok := resp["EncryptionConfig"].(map[string]any) require.True(t, ok, "EncryptionConfig must be present in response") @@ -294,7 +296,7 @@ func TestHandler_EncryptionConfig_KMSRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp, jsonx.V1())) cfg, ok := resp["EncryptionConfig"].(map[string]any) require.True(t, ok) @@ -309,7 +311,7 @@ func TestHandler_EncryptionConfig_KMSRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec4.Code) var resp4 map[string]any - require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4)) + require.NoError(t, json.Unmarshal(rec4.Body.Bytes(), &resp4, jsonx.V1())) cfg4, _ := resp4["EncryptionConfig"].(map[string]any) assert.Equal(t, "NONE", cfg4["Type"]) } @@ -358,7 +360,7 @@ func TestEncryptionConfig_UpdatingThenActiveStatus(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) enc, ok := putResp["EncryptionConfig"].(map[string]any) require.True(t, ok) @@ -368,7 +370,7 @@ func TestEncryptionConfig_UpdatingThenActiveStatus(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) enc2, ok := getResp["EncryptionConfig"].(map[string]any) require.True(t, ok) @@ -384,7 +386,7 @@ func TestEncryptionConfig_NoneTypeAccepted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) enc, ok := resp["EncryptionConfig"].(map[string]any) require.True(t, ok) diff --git a/services/xray/handler_groups.go b/services/xray/handler_groups.go index 24619dcf00..fd6da66310 100644 --- a/services/xray/handler_groups.go +++ b/services/xray/handler_groups.go @@ -2,9 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -42,7 +43,7 @@ type createGroupInput struct { func (h *Handler) handleCreateGroup(_ context.Context, body []byte) ([]byte, error) { var in createGroupInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -76,7 +77,7 @@ func (h *Handler) handleCreateGroup(_ context.Context, body []byte) ([]byte, err return json.Marshal(map[string]any{ keyGroup: toGroupView(g), - }) + }, jsonx.V1()) } type getGroupInput struct { @@ -87,7 +88,7 @@ type getGroupInput struct { func (h *Handler) handleGetGroup(_ context.Context, body []byte) ([]byte, error) { var in getGroupInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -113,7 +114,7 @@ func (h *Handler) handleGetGroup(_ context.Context, body []byte) ([]byte, error) return json.Marshal(map[string]any{ keyGroup: toGroupView(g), - }) + }, jsonx.V1()) } // GetGroupsInput (xray@v1.39.4 api_op_GetGroups.go) has no page-size member, @@ -125,7 +126,7 @@ type getGroupsInput struct { func (h *Handler) handleGetGroups(_ context.Context, body []byte) ([]byte, error) { var in getGroupsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -147,7 +148,7 @@ func (h *Handler) handleGetGroups(_ context.Context, body []byte) ([]byte, error keyNextToken: pg.Next, } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } // updateGroupInput uses pointer fields for FilterExpression and InsightsConfiguration @@ -165,7 +166,7 @@ type updateGroupInput struct { func (h *Handler) handleUpdateGroup(_ context.Context, body []byte) ([]byte, error) { var in updateGroupInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -194,7 +195,7 @@ func (h *Handler) handleUpdateGroup(_ context.Context, body []byte) ([]byte, err return json.Marshal(map[string]any{ keyGroup: toGroupView(g), - }) + }, jsonx.V1()) } type deleteGroupInput struct { @@ -205,7 +206,7 @@ type deleteGroupInput struct { func (h *Handler) handleDeleteGroup(_ context.Context, body []byte) ([]byte, error) { var in deleteGroupInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -218,5 +219,5 @@ func (h *Handler) handleDeleteGroup(_ context.Context, body []byte) ([]byte, err return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/xray/handler_groups_test.go b/services/xray/handler_groups_test.go index 5d99d35ea7..f1ccd8d43a 100644 --- a/services/xray/handler_groups_test.go +++ b/services/xray/handler_groups_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -94,7 +95,7 @@ func TestHandler_GetGroups(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["Groups"].([]any) require.True(t, ok) @@ -259,7 +260,7 @@ func TestHandler_UpdateGroup_AppliesInsightsConfiguration(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) group, ok := resp["Group"].(map[string]any) require.True(t, ok) @@ -299,7 +300,7 @@ func TestHandler_UpdateGroup_OmittedFieldsAreNotWiped(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &resp, jsonx.V1())) group, ok := resp["Group"].(map[string]any) require.True(t, ok) assert.Equal(t, `service("original")`, group["FilterExpression"], @@ -313,7 +314,7 @@ func TestHandler_UpdateGroup_OmittedFieldsAreNotWiped(t *testing.T) { require.Equal(t, http.StatusOK, updRec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(updRec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(updRec2.Body.Bytes(), &resp2, jsonx.V1())) group2, ok := resp2["Group"].(map[string]any) require.True(t, ok) ic, ok := group2["InsightsConfiguration"].(map[string]any) @@ -353,7 +354,7 @@ func TestGroup_ARNBasedLookup(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) group, ok := createResp["Group"].(map[string]any) require.True(t, ok) @@ -369,7 +370,7 @@ func TestGroup_ARNBasedLookup(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) g2, ok := getResp["Group"].(map[string]any) require.True(t, ok) @@ -428,7 +429,7 @@ func TestHandler_GetGroups_Pagination(t *testing.T) { require.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["Groups"].([]any) require.True(t, ok, "Groups field must be present and array") @@ -462,7 +463,7 @@ func TestHandler_GetGroups_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) groups1 := resp1["Groups"].([]any) assert.Len(t, groups1, defaultPageSize) @@ -474,7 +475,7 @@ func TestHandler_GetGroups_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) groups2 := resp2["Groups"].([]any) assert.Len(t, groups2, total-defaultPageSize) @@ -490,7 +491,7 @@ func TestHandler_GetGroups_EmptyBodyAccepted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "Groups") assert.Contains(t, resp, "NextToken") } @@ -504,7 +505,7 @@ func TestGroups_ARNBasedLookup(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) group, ok := createResp["Group"].(map[string]any) require.True(t, ok) @@ -517,7 +518,7 @@ func TestGroups_ARNBasedLookup(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) g2, ok := getResp["Group"].(map[string]any) require.True(t, ok) @@ -533,7 +534,7 @@ func TestGroups_UpdateByARN(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) group, _ := createResp["Group"].(map[string]any) groupARN, _ := group["GroupARN"].(string) @@ -545,7 +546,7 @@ func TestGroups_UpdateByARN(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updResp map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) g2, ok := updResp["Group"].(map[string]any) require.True(t, ok) @@ -561,7 +562,7 @@ func TestGroups_DeleteByARN(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) group, _ := createResp["Group"].(map[string]any) groupARN, _ := group["GroupARN"].(string) @@ -603,7 +604,7 @@ func TestGroups_FilterExpressionStoredAndReturned(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) g, ok := resp["Group"].(map[string]any) require.True(t, ok) @@ -621,7 +622,7 @@ func TestGroups_ARNPresentInCreateResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) group, ok := resp["Group"].(map[string]any) require.True(t, ok) @@ -647,7 +648,7 @@ func TestGroups_ListGroups(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) groups, ok := resp["Groups"].([]any) require.True(t, ok) diff --git a/services/xray/handler_indexing_rules.go b/services/xray/handler_indexing_rules.go index d42eae5934..68a614344c 100644 --- a/services/xray/handler_indexing_rules.go +++ b/services/xray/handler_indexing_rules.go @@ -2,9 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -32,7 +33,7 @@ type updateIndexingRuleInput struct { func (h *Handler) handleUpdateIndexingRule(_ context.Context, body []byte) ([]byte, error) { var in updateIndexingRuleInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -54,7 +55,7 @@ func (h *Handler) handleUpdateIndexingRule(_ context.Context, body []byte) ([]by return json.Marshal(map[string]any{ "IndexingRule": toIndexingRuleView(rule), - }) + }, jsonx.V1()) } type indexingRuleView struct { @@ -90,7 +91,7 @@ type getIndexingRulesInput struct { func (h *Handler) handleGetIndexingRules(_ context.Context, body []byte) ([]byte, error) { var in getIndexingRulesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -111,5 +112,5 @@ func (h *Handler) handleGetIndexingRules(_ context.Context, body []byte) ([]byte return json.Marshal(map[string]any{ "IndexingRules": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_indexing_rules_test.go b/services/xray/handler_indexing_rules_test.go index 71fee3c341..8c41189302 100644 --- a/services/xray/handler_indexing_rules_test.go +++ b/services/xray/handler_indexing_rules_test.go @@ -1,12 +1,14 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_GetIndexingRules(t *testing.T) { @@ -33,7 +35,7 @@ func TestHandler_GetIndexingRules(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, ok := resp["IndexingRules"].([]any) require.True(t, ok) @@ -80,7 +82,7 @@ func TestHandler_GetIndexingRules_Pagination(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, _ := resp["IndexingRules"].([]any) assert.GreaterOrEqual(t, len(rules), tt.wantMin) @@ -98,7 +100,7 @@ func TestIndexingRules_GetDefault(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rules, ok := resp["IndexingRules"].([]any) require.True(t, ok) @@ -114,7 +116,7 @@ func TestIndexingRules_UpdateModifiesTimestamp(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule, ok := resp["IndexingRule"].(map[string]any) require.True(t, ok) @@ -147,7 +149,7 @@ func TestIndexingRules_UpdateAppliesProbabilisticRule(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) rule, ok := resp["IndexingRule"].(map[string]any) require.True(t, ok) @@ -165,7 +167,7 @@ func TestIndexingRules_UpdateAppliesProbabilisticRule(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) rules, ok := getResp["IndexingRules"].([]any) require.True(t, ok) @@ -197,7 +199,7 @@ func TestHandler_UpdateIndexingRule_NotFoundReturnsResourceNotFoundException(t * require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // UpdateIndexingRule's modeled error set uses ResourceNotFoundException, unlike // most other X-Ray not-found cases (which use InvalidRequestException). assert.Equal(t, "ResourceNotFoundException", resp["__type"]) diff --git a/services/xray/handler_insights.go b/services/xray/handler_insights.go index e25ab7968e..7ddbdcbd42 100644 --- a/services/xray/handler_insights.go +++ b/services/xray/handler_insights.go @@ -2,10 +2,11 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -64,7 +65,7 @@ func toInsightView(i *Insight) insightView { func (h *Handler) handleGetInsight(_ context.Context, body []byte) ([]byte, error) { var in getInsightInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -80,7 +81,7 @@ func (h *Handler) handleGetInsight(_ context.Context, body []byte) ([]byte, erro return json.Marshal(map[string]any{ "Insight": toInsightView(i), - }) + }, jsonx.V1()) } type getInsightEventsInput struct { @@ -97,7 +98,7 @@ type insightEventView struct { func (h *Handler) handleGetInsightEvents(_ context.Context, body []byte) ([]byte, error) { var in getInsightEventsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -128,7 +129,7 @@ func (h *Handler) handleGetInsightEvents(_ context.Context, body []byte) ([]byte return json.Marshal(map[string]any{ "InsightEvents": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } type getInsightImpactGraphInput struct { @@ -141,7 +142,7 @@ type getInsightImpactGraphInput struct { func (h *Handler) handleGetInsightImpactGraph(_ context.Context, body []byte) ([]byte, error) { var in getInsightImpactGraphInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -163,7 +164,7 @@ func (h *Handler) handleGetInsightImpactGraph(_ context.Context, body []byte) ([ "ServiceGraphStartTime": in.StartTime, "ServiceGraphEndTime": in.EndTime, keyNextToken: "", - }) + }, jsonx.V1()) } type getInsightSummariesInput struct { @@ -215,7 +216,7 @@ func toInsightSummaryView(i *Insight) insightSummaryView { func (h *Handler) handleGetInsightSummaries(_ context.Context, body []byte) ([]byte, error) { var in getInsightSummariesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -266,5 +267,5 @@ func (h *Handler) handleGetInsightSummaries(_ context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "InsightSummaries": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_insights_test.go b/services/xray/handler_insights_test.go index 5ec29b498c..e0193feae5 100644 --- a/services/xray/handler_insights_test.go +++ b/services/xray/handler_insights_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "maps" "net/http" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -45,7 +46,7 @@ func TestGetInsightSummaries_ALLState(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) @@ -117,7 +118,7 @@ func TestGetInsightSummariesStateFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) @@ -145,7 +146,7 @@ func TestGroupInsightsConfigurationRoundtrip(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) group, ok := resp["Group"].(map[string]any) require.True(t, ok) @@ -237,7 +238,7 @@ func TestHandler_GetInsight_ResponseFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insight, ok := resp["Insight"].(map[string]any) require.True(t, ok) @@ -296,7 +297,7 @@ func TestHandler_GetInsightEvents(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, ok := resp["InsightEvents"].([]any) require.True(t, ok) @@ -362,7 +363,7 @@ func TestHandler_GetInsightImpactGraph(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "insight-xyz", resp["InsightId"]) @@ -426,7 +427,7 @@ func TestHandler_GetInsightSummaries(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) @@ -491,7 +492,7 @@ func TestHandler_GetInsightEvents_Pagination(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) nextToken, _ := resp["NextToken"].(string) if tt.wantHasNext { @@ -524,7 +525,7 @@ func TestHandler_GetInsightEvents_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) events1, _ := resp1["InsightEvents"].([]any) assert.Len(t, events1, 4) @@ -540,7 +541,7 @@ func TestHandler_GetInsightEvents_NextTokenContinuation(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) events2, _ := resp2["InsightEvents"].([]any) assert.Len(t, events2, 2) @@ -604,7 +605,7 @@ func TestHandler_GetInsightSummaries_Pagination(t *testing.T) { } var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["InsightSummaries"].([]any) assert.Len(t, summaries, tt.wantCount) @@ -630,7 +631,7 @@ func TestHandler_GetInsight_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insightData, ok := resp["Insight"].(map[string]any) require.True(t, ok) @@ -657,7 +658,7 @@ func TestHandler_GetInsightSummaries_ResponseShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) @@ -699,7 +700,7 @@ func TestGetInsightSummaries_LastUpdateTimeReflectsActivity(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) require.Len(t, summaries, 1) @@ -717,7 +718,7 @@ func TestGetInsightSummaries_LastUpdateTimeReflectsActivity(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) insight, ok := getResp["Insight"].(map[string]any) require.True(t, ok) assert.NotContains(t, insight, "LastUpdateTime") @@ -770,7 +771,7 @@ func TestGetInsight_FieldsReturned(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) insight, ok := resp["Insight"].(map[string]any) require.True(t, ok) @@ -813,7 +814,7 @@ func TestGetInsightEvents_ReturnsEventsForInsight(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) events, ok := resp["InsightEvents"].([]any) require.True(t, ok) @@ -868,7 +869,7 @@ func TestGetInsightImpactGraph_ReturnsInsightId(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "i-impact-001", resp["InsightId"]) } }) @@ -919,7 +920,7 @@ func TestGetInsightSummaries_StatesValidation(t *testing.T) { if tt.wantStatus == http.StatusOK && tt.wantCount > 0 { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["InsightSummaries"].([]any) require.True(t, ok) diff --git a/services/xray/handler_resource_policies.go b/services/xray/handler_resource_policies.go index a62dde5c9d..0e6a6c7aaf 100644 --- a/services/xray/handler_resource_policies.go +++ b/services/xray/handler_resource_policies.go @@ -2,10 +2,11 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -17,7 +18,7 @@ type deleteResourcePolicyInput struct { func (h *Handler) handleDeleteResourcePolicy(_ context.Context, body []byte) ([]byte, error) { var in deleteResourcePolicyInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -30,7 +31,7 @@ func (h *Handler) handleDeleteResourcePolicy(_ context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type resourcePolicyView struct { @@ -55,7 +56,7 @@ func (h *Handler) handleListResourcePolicies(_ context.Context, body []byte) ([] MaxResults int `json:"MaxResults"` } if len(body) > 0 { - _ = json.Unmarshal(body, &in) + _ = json.Unmarshal(body, &in, jsonx.V1()) } policies := h.Backend.ListResourcePolicies() @@ -71,7 +72,7 @@ func (h *Handler) handleListResourcePolicies(_ context.Context, body []byte) ([] pg := page.New(views, in.NextToken, in.MaxResults, defaultResourcePoliciesPageSize) resp := map[string]any{"ResourcePolicies": pg.Data, keyNextToken: pg.Next} - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type putResourcePolicyInput struct { @@ -84,7 +85,7 @@ type putResourcePolicyInput struct { func (h *Handler) handlePutResourcePolicy(ctx context.Context, body []byte) ([]byte, error) { var in putResourcePolicyInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -107,5 +108,5 @@ func (h *Handler) handlePutResourcePolicy(ctx context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "ResourcePolicy": toResourcePolicyView(p), - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_resource_policies_test.go b/services/xray/handler_resource_policies_test.go index 843e19caea..ac469ff0fb 100644 --- a/services/xray/handler_resource_policies_test.go +++ b/services/xray/handler_resource_policies_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -67,7 +68,7 @@ func TestPutAndListResourcePolicies(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ResourcePolicies"].([]any) require.True(t, ok) @@ -82,7 +83,7 @@ func TestPutAndListResourcePolicies(t *testing.T) { require.Equal(t, http.StatusOK, putRec.Code) var putResp map[string]any - require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp)) + require.NoError(t, json.Unmarshal(putRec.Body.Bytes(), &putResp, jsonx.V1())) policyObj, ok := putResp["ResourcePolicy"].(map[string]any) require.True(t, ok) @@ -94,7 +95,7 @@ func TestPutAndListResourcePolicies(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) policies, ok = listResp["ResourcePolicies"].([]any) require.True(t, ok) @@ -148,7 +149,7 @@ func TestListResourcePolicies_NextTokenInResponse(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["NextToken"] assert.True(t, ok, "NextToken must be present in ListResourcePolicies response") @@ -314,7 +315,7 @@ func TestResourcePolicy_ListAfterPut(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) policies, ok := resp["ResourcePolicies"].([]any) require.True(t, ok) @@ -339,7 +340,7 @@ func TestResourcePolicy_DeleteExistingPolicy(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) policies, _ := resp["ResourcePolicies"].([]any) assert.Empty(t, policies, "policy must be removed after delete") @@ -372,7 +373,7 @@ func TestResourcePolicy_MaxCountUsesCorrectErrorCode(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PolicyCountLimitExceededException", resp["__type"]) } @@ -393,7 +394,7 @@ func TestResourcePolicy_SizeLimitExceeded(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "PolicySizeLimitExceededException", resp["__type"]) } @@ -413,7 +414,7 @@ func TestResourcePolicy_LastUpdatedTimeIsEpochSeconds(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) policy, ok := resp["ResourcePolicy"].(map[string]any) require.True(t, ok) @@ -447,7 +448,7 @@ func TestHandler_DeleteResourcePolicy_RevisionIDEnforced(t *testing.T) { require.Equal(t, http.StatusBadRequest, wrongRec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(wrongRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(wrongRec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "InvalidPolicyRevisionIdException", resp["__type"]) // The policy must still exist after the rejected delete. @@ -455,7 +456,7 @@ func TestHandler_DeleteResourcePolicy_RevisionIDEnforced(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) policies, _ := listResp["ResourcePolicies"].([]any) assert.Len(t, policies, 1, "policy must survive a delete with the wrong PolicyRevisionId") } diff --git a/services/xray/handler_sampling_rules.go b/services/xray/handler_sampling_rules.go index 15ea6b120a..1f6fa8b716 100644 --- a/services/xray/handler_sampling_rules.go +++ b/services/xray/handler_sampling_rules.go @@ -2,9 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -94,7 +95,7 @@ type createSamplingRuleInput struct { func (h *Handler) handleCreateSamplingRule(_ context.Context, body []byte) ([]byte, error) { var in createSamplingRuleInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -143,7 +144,7 @@ func (h *Handler) handleCreateSamplingRule(_ context.Context, body []byte) ([]by return json.Marshal(map[string]any{ keySamplingRuleRecord: toSamplingRuleRecord(r), - }) + }, jsonx.V1()) } type getSamplingRulesInput struct { @@ -153,7 +154,7 @@ type getSamplingRulesInput struct { func (h *Handler) handleGetSamplingRules(_ context.Context, body []byte) ([]byte, error) { var in getSamplingRulesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -174,7 +175,7 @@ func (h *Handler) handleGetSamplingRules(_ context.Context, body []byte) ([]byte return json.Marshal(map[string]any{ "SamplingRuleRecords": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } // samplingRuleUpdateInput uses json.RawMessage so we can detect which fields @@ -204,7 +205,7 @@ type updateSamplingRuleInput struct { func (h *Handler) handleUpdateSamplingRule(_ context.Context, body []byte) ([]byte, error) { var in updateSamplingRuleInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -246,7 +247,7 @@ func (h *Handler) handleUpdateSamplingRule(_ context.Context, body []byte) ([]by return json.Marshal(map[string]any{ keySamplingRuleRecord: toSamplingRuleRecord(r), - }) + }, jsonx.V1()) } type deleteSamplingRuleInput struct { @@ -257,7 +258,7 @@ type deleteSamplingRuleInput struct { func (h *Handler) handleDeleteSamplingRule(_ context.Context, body []byte) ([]byte, error) { var in deleteSamplingRuleInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -273,5 +274,5 @@ func (h *Handler) handleDeleteSamplingRule(_ context.Context, body []byte) ([]by return json.Marshal(map[string]any{ keySamplingRuleRecord: toSamplingRuleRecord(r), - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_sampling_rules_test.go b/services/xray/handler_sampling_rules_test.go index 6eed4ea626..8b2b885149 100644 --- a/services/xray/handler_sampling_rules_test.go +++ b/services/xray/handler_sampling_rules_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -84,7 +85,7 @@ func TestHandler_GetSamplingRules(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["SamplingRuleRecords"].([]any) require.True(t, ok) @@ -172,7 +173,7 @@ func TestHandler_UpdateSamplingRule_Attributes(t *testing.T) { } `json:"SamplingRule"` } `json:"SamplingRuleRecord"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, map[string]string{"env": "prod"}, resp.SamplingRuleRecord.SamplingRule.Attributes) } @@ -238,7 +239,7 @@ func TestUpdateSamplingRule_ZeroFixedRate(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) record, ok := resp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) @@ -277,7 +278,7 @@ func TestSamplingRuleAttributes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) record, ok := resp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) @@ -310,7 +311,7 @@ func TestSamplingRuleModifiedAtInRecord(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["SamplingRuleRecords"].([]any) require.True(t, ok) @@ -425,7 +426,7 @@ func TestHandler_GetSamplingRules_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["SamplingRuleRecords"].([]any) require.True(t, ok) @@ -545,7 +546,7 @@ func TestSamplingRule_DefaultRulePresentAtStart(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) records, ok := resp["SamplingRuleRecords"].([]any) require.True(t, ok) @@ -630,7 +631,7 @@ func TestUpdateSamplingRule_ZeroValues(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) record, ok := resp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) @@ -762,7 +763,7 @@ func TestSamplingRule_SamplingRateBoostRoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) record, ok := createResp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) rule, ok := record["SamplingRule"].(map[string]any) @@ -785,7 +786,7 @@ func TestSamplingRule_SamplingRateBoostRoundTrips(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updResp map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) updRecord, ok := updResp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) updRule, ok := updRecord["SamplingRule"].(map[string]any) @@ -811,7 +812,7 @@ func TestSamplingRule_UpdateAndDeleteByARN(t *testing.T) { require.Equal(t, http.StatusOK, createRec.Code) var createResp map[string]any - require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp)) + require.NoError(t, json.Unmarshal(createRec.Body.Bytes(), &createResp, jsonx.V1())) record, ok := createResp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) rule, ok := record["SamplingRule"].(map[string]any) @@ -826,7 +827,7 @@ func TestSamplingRule_UpdateAndDeleteByARN(t *testing.T) { require.Equal(t, http.StatusOK, updRec.Code) var updResp map[string]any - require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp)) + require.NoError(t, json.Unmarshal(updRec.Body.Bytes(), &updResp, jsonx.V1())) updRecord, ok := updResp["SamplingRuleRecord"].(map[string]any) require.True(t, ok) updRule, ok := updRecord["SamplingRule"].(map[string]any) @@ -840,7 +841,7 @@ func TestSamplingRule_UpdateAndDeleteByARN(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var getResp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &getResp, jsonx.V1())) records, ok := getResp["SamplingRuleRecords"].([]any) require.True(t, ok) for _, r := range records { diff --git a/services/xray/handler_sampling_statistics.go b/services/xray/handler_sampling_statistics.go index cd8e7ec60e..099ad14c43 100644 --- a/services/xray/handler_sampling_statistics.go +++ b/services/xray/handler_sampling_statistics.go @@ -2,9 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -23,7 +24,7 @@ type getSamplingStatisticSummariesInput struct { func (h *Handler) handleGetSamplingStatisticSummaries(_ context.Context, body []byte) ([]byte, error) { var in getSamplingStatisticSummariesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -50,7 +51,7 @@ func (h *Handler) handleGetSamplingStatisticSummaries(_ context.Context, body [] return json.Marshal(map[string]any{ "SamplingStatisticSummaries": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } type samplingStatisticsDocumentInput struct { @@ -104,7 +105,7 @@ type unprocessedStatisticsView struct { func (h *Handler) handleGetSamplingTargets(_ context.Context, body []byte) ([]byte, error) { var in getSamplingTargetsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -156,5 +157,5 @@ func (h *Handler) handleGetSamplingTargets(_ context.Context, body []byte) ([]by "UnprocessedStatistics": unprocessedViews, "UnprocessedBoostStatistics": unprocessedBoostViews, "LastRuleModification": lastModTS, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_sampling_statistics_test.go b/services/xray/handler_sampling_statistics_test.go index 589434ba70..34e6e40af8 100644 --- a/services/xray/handler_sampling_statistics_test.go +++ b/services/xray/handler_sampling_statistics_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -26,7 +27,7 @@ func TestGetSamplingTargets_LastRuleModAndTTL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // LastRuleModification should be a timestamp. lastMod, ok := resp["LastRuleModification"].(float64) @@ -65,7 +66,7 @@ func TestGetSamplingStatisticSummaries_AfterPut(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["SamplingStatisticSummaries"].([]any) require.True(t, ok) @@ -92,7 +93,7 @@ func TestGetSamplingTargets_UnprocessedWithErrorCode(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedStatistics"].([]any) require.True(t, ok) @@ -126,7 +127,7 @@ func TestGetSamplingTargets_EmptyClientID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedStatistics"].([]any) require.True(t, ok) @@ -159,7 +160,7 @@ func TestHandler_GetSamplingStatisticSummaries(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["SamplingStatisticSummaries"].([]any) require.True(t, ok) @@ -233,7 +234,7 @@ func TestHandler_GetSamplingTargets(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["SamplingTargetDocuments"].([]any) require.True(t, ok) @@ -260,7 +261,7 @@ func TestHandler_GetSamplingTargets_TargetFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["SamplingTargetDocuments"].([]any) require.True(t, ok) @@ -305,7 +306,7 @@ func TestHandler_GetSamplingStatisticSummaries_Pagination(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "SamplingStatisticSummaries") assert.Contains(t, resp, "NextToken") }) @@ -326,7 +327,7 @@ func TestGetSamplingTargets_LastRuleModification(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) lastMod, ok := resp["LastRuleModification"].(float64) require.True(t, ok, "LastRuleModification must be present") @@ -347,7 +348,7 @@ func TestGetSamplingTargets_ReservoirQuotaTTL(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, ok := resp["SamplingTargetDocuments"].([]any) require.True(t, ok) @@ -417,7 +418,7 @@ func TestGetSamplingTargets_UnprocessedForUnknownRules(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) targets, _ := resp["SamplingTargetDocuments"].([]any) assert.Len(t, targets, tt.wantTargets) @@ -490,7 +491,7 @@ func TestGetSamplingTargets_BoostStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessedBoost, _ := resp["UnprocessedBoostStatistics"].([]any) assert.Len(t, unprocessedBoost, tt.wantUnprocessed) @@ -524,7 +525,7 @@ func TestGetSamplingStatisticSummaries_AccumulationFromTargets(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["SamplingStatisticSummaries"].([]any) require.True(t, ok) @@ -547,7 +548,7 @@ func TestGetSamplingStatisticSummaries_EmptyWithoutStats(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["SamplingStatisticSummaries"].([]any) require.True(t, ok) @@ -573,7 +574,7 @@ func TestGetSamplingStatisticSummaries_AccumulatesAcrossCalls(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["SamplingStatisticSummaries"].([]any) require.Len(t, summaries, 1) diff --git a/services/xray/handler_service_graph.go b/services/xray/handler_service_graph.go index a3f4713d32..ce134da179 100644 --- a/services/xray/handler_service_graph.go +++ b/services/xray/handler_service_graph.go @@ -2,10 +2,11 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -20,7 +21,7 @@ type getServiceGraphInput struct { func (h *Handler) handleGetServiceGraph(_ context.Context, body []byte) ([]byte, error) { var in getServiceGraphInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -49,7 +50,7 @@ func (h *Handler) handleGetServiceGraph(_ context.Context, body []byte) ([]byte, "ContainsOldGroupVersions": false, keyStartTime: in.StartTime, keyEndTime: in.EndTime, - }) + }, jsonx.V1()) } type getTimeSeriesServiceStatisticsInput struct { @@ -66,7 +67,7 @@ type getTimeSeriesServiceStatisticsInput struct { func (h *Handler) handleGetTimeSeriesServiceStatistics(_ context.Context, body []byte) ([]byte, error) { var in getTimeSeriesServiceStatisticsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -104,7 +105,7 @@ func (h *Handler) handleGetTimeSeriesServiceStatistics(_ context.Context, body [ "TimeSeriesServiceStatistics": pg.Data, "ContainsOldGroupVersions": false, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } type getTraceGraphInput struct { @@ -115,7 +116,7 @@ type getTraceGraphInput struct { func (h *Handler) handleGetTraceGraph(_ context.Context, body []byte) ([]byte, error) { var in getTraceGraphInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -135,7 +136,7 @@ func (h *Handler) handleGetTraceGraph(_ context.Context, body []byte) ([]byte, e return json.Marshal(map[string]any{ keyServices: pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } // resolveGroupFilterExpression resolves an optional GroupName/GroupARN (both diff --git a/services/xray/handler_service_graph_test.go b/services/xray/handler_service_graph_test.go index c573e3909a..83827b2486 100644 --- a/services/xray/handler_service_graph_test.go +++ b/services/xray/handler_service_graph_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGetServiceGraph_NonEmpty(t *testing.T) { @@ -33,7 +35,7 @@ func TestGetServiceGraph_NonEmpty(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -63,7 +65,7 @@ func TestGetTimeSeriesServiceStatistics_BucketedData(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stats, ok := resp["TimeSeriesServiceStatistics"].([]any) require.True(t, ok) @@ -99,7 +101,7 @@ func TestGetTraceGraph_ReturnsNodes(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -117,7 +119,7 @@ func TestGetTraceGraph_EmptyForUnknownTraces(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -177,7 +179,7 @@ func TestGetServiceGraph_ContainsOldGroupVersions(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["ContainsOldGroupVersions"] assert.True(t, ok, "ContainsOldGroupVersions must be present in GetServiceGraph response") @@ -231,7 +233,7 @@ func TestHandler_GetServiceGraph_Pagination(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "NextToken") assert.Contains(t, resp, "Services") assert.Contains(t, resp, "StartTime") @@ -279,7 +281,7 @@ func TestHandler_GetTraceGraph_Pagination(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "NextToken") assert.Contains(t, resp, "Services") } @@ -339,7 +341,7 @@ func TestHandler_GetTimeSeriesServiceStatistics_Pagination(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Contains(t, resp, "NextToken") assert.Contains(t, resp, "TimeSeriesServiceStatistics") } @@ -369,7 +371,7 @@ func TestGetServiceGraph_NodesAfterSegments(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -394,7 +396,7 @@ func TestGetServiceGraph_NodeFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["Services"].([]any) require.NotEmpty(t, services) @@ -459,7 +461,7 @@ func TestGetServiceGraph_EmptyOutsideTimeWindow(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["Services"].([]any) assert.Empty(t, services, "service graph must be empty when no segments fall in the time window") @@ -510,7 +512,7 @@ func TestGetTimeSeriesServiceStatistics_ReturnsContainsOldGroupVersions(t *testi require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, ok := resp["ContainsOldGroupVersions"] assert.True(t, ok, "ContainsOldGroupVersions must be present in response") @@ -542,7 +544,7 @@ func TestGetTimeSeriesServiceStatistics_BucketedStatsAfterSegments(t *testing.T) require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) stats, ok := resp["TimeSeriesServiceStatistics"].([]any) require.True(t, ok) @@ -575,7 +577,7 @@ func TestGetTraceGraph_ReturnsNodesForTraceIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -601,7 +603,7 @@ func TestGetTraceGraph_EmptyForUnknownTrace(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, _ := resp["Services"].([]any) assert.Empty(t, services) @@ -634,7 +636,7 @@ func TestGetServiceGraph_EdgeStatisticsAndDirection(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) diff --git a/services/xray/handler_tags.go b/services/xray/handler_tags.go index 03d73f34c6..ab79f19809 100644 --- a/services/xray/handler_tags.go +++ b/services/xray/handler_tags.go @@ -2,11 +2,12 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "strings" "unicode/utf8" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -18,7 +19,7 @@ type listTagsForResourceInput struct { func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]byte, error) { var in listTagsForResourceInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -41,7 +42,7 @@ func (h *Handler) handleListTagsForResource(_ context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "Tags": pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } // tagWire mirrors types.Tag (xray@v1.39.4): a {Key, Value} object, one per @@ -61,7 +62,7 @@ type tagResourceInput struct { func (h *Handler) handleTagResource(_ context.Context, body []byte) ([]byte, error) { var in tagResourceInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -78,7 +79,7 @@ func (h *Handler) handleTagResource(_ context.Context, body []byte) ([]byte, err return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type untagResourceInput struct { @@ -89,7 +90,7 @@ type untagResourceInput struct { func (h *Handler) handleUntagResource(_ context.Context, body []byte) ([]byte, error) { var in untagResourceInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -102,7 +103,7 @@ func (h *Handler) handleUntagResource(_ context.Context, body []byte) ([]byte, e return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } const ( diff --git a/services/xray/handler_tags_test.go b/services/xray/handler_tags_test.go index 03e7ed2e15..23b0a1b12e 100644 --- a/services/xray/handler_tags_test.go +++ b/services/xray/handler_tags_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "strconv" "testing" @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -23,7 +24,7 @@ func createTaggableGroup(t *testing.T, h *xray.Handler, name string) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) group, ok := resp["Group"].(map[string]any) require.True(t, ok) @@ -83,7 +84,7 @@ func TestHandler_ListTagsForResource_Pagination(t *testing.T) { if tt.wantStatus == http.StatusOK { var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["Tags"].([]any) assert.Len(t, tags, tt.wantTags) assert.Contains(t, resp, "NextToken") @@ -111,7 +112,7 @@ func TestHandler_ListTagsForResource_WithTags(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) tagsList, _ := resp["Tags"].([]any) assert.Len(t, tagsList, len(tags)) @@ -173,7 +174,7 @@ func TestTags_RoundTrip(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags, ok := resp["Tags"].([]any) require.True(t, ok) @@ -204,7 +205,7 @@ func TestTags_UntagResource(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &resp, jsonx.V1())) tags, _ := resp["Tags"].([]any) assert.Len(t, tags, 1, "only key2 should remain after untagging key1") @@ -222,7 +223,7 @@ func TestTags_TagResource_UnknownResourceReturns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -238,7 +239,7 @@ func TestTags_UntagResource_UnknownResourceReturns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) } @@ -260,6 +261,6 @@ func TestTags_TagResource_ExceedsMaxTagsReturns400(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "TooManyTagsException", resp["__type"]) } diff --git a/services/xray/handler_telemetry.go b/services/xray/handler_telemetry.go index 2f95403c4c..82469192f4 100644 --- a/services/xray/handler_telemetry.go +++ b/services/xray/handler_telemetry.go @@ -2,8 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type telemetryRecordInput struct { @@ -21,7 +23,7 @@ type putTelemetryRecordsInput struct { func (h *Handler) handlePutTelemetryRecords(_ context.Context, body []byte) ([]byte, error) { var in putTelemetryRecordsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -47,5 +49,5 @@ func (h *Handler) handlePutTelemetryRecords(_ context.Context, body []byte) ([]b h.Backend.PutTelemetryRecords(records) } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } diff --git a/services/xray/handler_telemetry_test.go b/services/xray/handler_telemetry_test.go index 6e069697bb..d3380b9ab2 100644 --- a/services/xray/handler_telemetry_test.go +++ b/services/xray/handler_telemetry_test.go @@ -1,13 +1,15 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutTelemetryRecords(t *testing.T) { @@ -37,7 +39,7 @@ func TestPutTelemetryRecords_Accepted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Response should be an empty JSON object. assert.Empty(t, resp) } @@ -93,7 +95,7 @@ func TestPutTelemetryRecords_AllFieldsAccepted(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Empty(t, resp, "response must be an empty JSON object") }) } diff --git a/services/xray/handler_test.go b/services/xray/handler_test.go index 8f56b43bec..d6616a08cc 100644 --- a/services/xray/handler_test.go +++ b/services/xray/handler_test.go @@ -2,7 +2,7 @@ package xray_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -218,7 +219,7 @@ func TestHandler_ExtractResource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyBytes, err := json.Marshal(tt.body) + bodyBytes, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/CreateGroup", bytes.NewReader(bodyBytes)) @@ -275,7 +276,7 @@ func TestXRay_Handler_Reset(t *testing.T) { var out struct { Groups []any `json:"Groups"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &out, jsonx.V1())) assert.Len(t, out.Groups, tt.wantAfter) }) } diff --git a/services/xray/handler_trace_retrieval.go b/services/xray/handler_trace_retrieval.go index 513b1f6cdc..f622287d6a 100644 --- a/services/xray/handler_trace_retrieval.go +++ b/services/xray/handler_trace_retrieval.go @@ -2,10 +2,11 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -35,7 +36,7 @@ func buildTraceView(t *Trace) map[string]any { for _, rawSeg := range t.Segments { var doc segmentDoc - if err := json.Unmarshal([]byte(rawSeg), &doc); err == nil { + if err := json.Unmarshal([]byte(rawSeg), &doc, jsonx.V1()); err == nil { spans = append(spans, map[string]any{ "Document": rawSeg, "Id": doc.ID, @@ -66,7 +67,7 @@ func buildTraceView(t *Trace) map[string]any { func (h *Handler) handleListRetrievedTraces(_ context.Context, body []byte) ([]byte, error) { var in listRetrievedTracesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -101,7 +102,7 @@ func (h *Handler) handleListRetrievedTraces(_ context.Context, body []byte) ([]b resp[keyNextToken] = pg.Next } - return json.Marshal(resp) + return json.Marshal(resp, jsonx.V1()) } type startTraceRetrievalInput struct { @@ -113,7 +114,7 @@ type startTraceRetrievalInput struct { func (h *Handler) handleStartTraceRetrieval(_ context.Context, body []byte) ([]byte, error) { var in startTraceRetrievalInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -134,7 +135,7 @@ func (h *Handler) handleStartTraceRetrieval(_ context.Context, body []byte) ([]b return json.Marshal(map[string]any{ "RetrievalToken": token, - }) + }, jsonx.V1()) } const ( @@ -148,7 +149,7 @@ type cancelTraceRetrievalInput struct { func (h *Handler) handleCancelTraceRetrieval(_ context.Context, body []byte) ([]byte, error) { var in cancelTraceRetrievalInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -161,7 +162,7 @@ func (h *Handler) handleCancelTraceRetrieval(_ context.Context, body []byte) ([] return nil, err } - return json.Marshal(map[string]any{}) + return json.Marshal(map[string]any{}, jsonx.V1()) } type getRetrievedTracesGraphInput struct { @@ -172,7 +173,7 @@ type getRetrievedTracesGraphInput struct { func (h *Handler) handleGetRetrievedTracesGraph(_ context.Context, body []byte) ([]byte, error) { var in getRetrievedTracesGraphInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -196,5 +197,5 @@ func (h *Handler) handleGetRetrievedTracesGraph(_ context.Context, body []byte) "RetrievalStatus": status, keyServices: pg.Data, keyNextToken: pg.Next, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_trace_retrieval_test.go b/services/xray/handler_trace_retrieval_test.go index b3e2101538..7d2ccc364a 100644 --- a/services/xray/handler_trace_retrieval_test.go +++ b/services/xray/handler_trace_retrieval_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -31,7 +32,7 @@ func TestListRetrievedTraces_IncludesSegments(t *testing.T) { "start_time": 1700000000.0, "end_time": 1700000001.5, } - segJSON, err := json.Marshal(seg) + segJSON, err := json.Marshal(seg, jsonx.V1()) require.NoError(t, err) unprocessed := b.PutTraceSegments([]string{string(segJSON)}) @@ -46,7 +47,7 @@ func TestListRetrievedTraces_IncludesSegments(t *testing.T) { require.Equal(t, 200, startResp.Code) var startResult map[string]any - require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startResult)) + require.NoError(t, json.Unmarshal(startResp.Body.Bytes(), &startResult, jsonx.V1())) token, ok := startResult["RetrievalToken"].(string) require.True(t, ok, "expected RetrievalToken in response") @@ -55,7 +56,7 @@ func TestListRetrievedTraces_IncludesSegments(t *testing.T) { require.Equal(t, 200, listResp.Code) var listResult map[string]any - require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listResult)) + require.NoError(t, json.Unmarshal(listResp.Body.Bytes(), &listResult, jsonx.V1())) traces, ok := listResult["Traces"].([]any) require.True(t, ok, "expected Traces array in response") @@ -90,7 +91,7 @@ func startTestRetrieval(t *testing.T, h *xray.Handler) string { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) token, ok := resp["RetrievalToken"].(string) require.True(t, ok) @@ -130,7 +131,7 @@ func TestHandler_CancelTraceRetrieval(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }) } @@ -148,7 +149,7 @@ func TestHandler_GetRetrievedTracesGraph(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "COMPLETE", resp["RetrievalStatus"]) @@ -173,7 +174,7 @@ func TestHandler_GetRetrievedTracesGraph(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "ResourceNotFoundException", resp["__type"]) }) } @@ -204,7 +205,7 @@ func TestHandler_GetRetrievedTracesGraph_ReflectsRetrievedTraces(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) token, _ := startResp["RetrievalToken"].(string) require.NotEmpty(t, token) @@ -212,7 +213,7 @@ func TestHandler_GetRetrievedTracesGraph_ReflectsRetrievedTraces(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) services, ok := resp["Services"].([]any) require.True(t, ok) @@ -241,7 +242,7 @@ func TestTraceRetrieval_StartAndList(t *testing.T) { require.Equal(t, http.StatusOK, startRec.Code) var startResp map[string]any - require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp)) + require.NoError(t, json.Unmarshal(startRec.Body.Bytes(), &startResp, jsonx.V1())) token, ok := startResp["RetrievalToken"].(string) require.True(t, ok) @@ -253,7 +254,7 @@ func TestTraceRetrieval_StartAndList(t *testing.T) { require.Equal(t, http.StatusOK, listRec.Code) var listResp map[string]any - require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp)) + require.NoError(t, json.Unmarshal(listRec.Body.Bytes(), &listResp, jsonx.V1())) assert.NotEmpty(t, listResp["RetrievalStatus"]) } diff --git a/services/xray/handler_trace_segment_destination.go b/services/xray/handler_trace_segment_destination.go index c54a3456cd..41d4befae4 100644 --- a/services/xray/handler_trace_segment_destination.go +++ b/services/xray/handler_trace_segment_destination.go @@ -2,8 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func (h *Handler) handleGetTraceSegmentDestination(_ context.Context, _ []byte) ([]byte, error) { @@ -12,7 +14,7 @@ func (h *Handler) handleGetTraceSegmentDestination(_ context.Context, _ []byte) return json.Marshal(map[string]any{ "Destination": dest, "Status": statusActive, - }) + }, jsonx.V1()) } type updateTraceSegmentDestinationInput struct { @@ -22,7 +24,7 @@ type updateTraceSegmentDestinationInput struct { func (h *Handler) handleUpdateTraceSegmentDestination(_ context.Context, body []byte) ([]byte, error) { var in updateTraceSegmentDestinationInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -44,5 +46,5 @@ func (h *Handler) handleUpdateTraceSegmentDestination(_ context.Context, body [] return json.Marshal(map[string]any{ "Destination": dest, "Status": statusActive, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_trace_segments.go b/services/xray/handler_trace_segments.go index 946c9500ad..16188b9310 100644 --- a/services/xray/handler_trace_segments.go +++ b/services/xray/handler_trace_segments.go @@ -2,8 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type putTraceSegmentsInput struct { @@ -13,7 +15,7 @@ type putTraceSegmentsInput struct { func (h *Handler) handlePutTraceSegments(_ context.Context, body []byte) ([]byte, error) { var in putTraceSegmentsInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -45,5 +47,5 @@ func (h *Handler) handlePutTraceSegments(_ context.Context, body []byte) ([]byte return json.Marshal(map[string]any{ "UnprocessedTraceSegments": out, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_trace_segments_test.go b/services/xray/handler_trace_segments_test.go index 5ded66f0be..ef153ad517 100644 --- a/services/xray/handler_trace_segments_test.go +++ b/services/xray/handler_trace_segments_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "strings" @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestHandler_PutTraceSegments(t *testing.T) { @@ -55,7 +57,7 @@ func TestHandler_PutTraceSegments_Unprocessed(t *testing.T) { assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedTraceSegments"].([]any) require.True(t, ok) @@ -82,7 +84,7 @@ func TestPutAndGetTraceSummaries_Populated(t *testing.T) { require.Equal(t, http.StatusOK, getRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -124,7 +126,7 @@ func TestPutTraceSegments_MalformedJSON(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code, "malformed segments should not crash the handler") var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedTraceSegments"].([]any) require.True(t, ok) @@ -145,7 +147,7 @@ func TestPutTraceSegments_MissingTraceID(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedTraceSegments"].([]any) require.True(t, ok) @@ -292,7 +294,7 @@ func TestHandler_PutTraceSegments_ExactLimitRespondsWithUnprocessedField(t *test assert.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) _, hasField := resp["UnprocessedTraceSegments"] assert.True(t, hasField) } @@ -361,7 +363,7 @@ func TestPutTraceSegments_ParsesSegmentFields(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, _ := resp["UnprocessedTraceSegments"].([]any) if tt.wantParsed { @@ -400,7 +402,7 @@ func TestPutTraceSegments_IndexesSegmentsForDownstreamAPIs(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var sumResp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &sumResp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &sumResp, jsonx.V1())) summaries, ok := sumResp["TraceSummaries"].([]any) require.True(t, ok) @@ -411,7 +413,7 @@ func TestPutTraceSegments_IndexesSegmentsForDownstreamAPIs(t *testing.T) { require.Equal(t, http.StatusOK, batchRec.Code) var batchResp map[string]any - require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp)) + require.NoError(t, json.Unmarshal(batchRec.Body.Bytes(), &batchResp, jsonx.V1())) traces, ok := batchResp["Traces"].([]any) require.True(t, ok) @@ -437,7 +439,7 @@ func TestPutTraceSegments_MultipleTracesIndexedSeparately(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var sumResp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &sumResp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &sumResp, jsonx.V1())) summaries, _ := sumResp["TraceSummaries"].([]any) assert.Len(t, summaries, 3, "three separate traces must be indexed separately") diff --git a/services/xray/handler_traces.go b/services/xray/handler_traces.go index 5306a88cf0..e1dde65996 100644 --- a/services/xray/handler_traces.go +++ b/services/xray/handler_traces.go @@ -2,10 +2,11 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "fmt" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/page" ) @@ -222,7 +223,7 @@ func validateSamplingStrategy(ss *samplingStrategyInput) error { func (h *Handler) handleGetTraceSummaries(_ context.Context, body []byte) ([]byte, error) { var in getTraceSummariesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -280,7 +281,7 @@ func (h *Handler) handleGetTraceSummaries(_ context.Context, body []byte) ([]byt // per-TraceSummary field. "ApproximateTime": float64(time.Now().Unix()), keyNextToken: pg.Next, - }) + }, jsonx.V1()) } type batchGetTracesInput struct { @@ -302,7 +303,7 @@ type traceOutput struct { func (h *Handler) handleBatchGetTraces(_ context.Context, body []byte) ([]byte, error) { var in batchGetTracesInput if len(body) > 0 { - if err := json.Unmarshal(body, &in); err != nil { + if err := json.Unmarshal(body, &in, jsonx.V1()); err != nil { return nil, err } } @@ -341,7 +342,7 @@ func (h *Handler) handleBatchGetTraces(_ context.Context, body []byte) ([]byte, ID string `json:"id"` } - if err := json.Unmarshal([]byte(rawDoc), &hdr); err == nil { + if err := json.Unmarshal([]byte(rawDoc), &hdr, jsonx.V1()); err == nil { segViews = append(segViews, batchSegmentOutput{ ID: hdr.ID, Document: rawDoc, @@ -360,5 +361,5 @@ func (h *Handler) handleBatchGetTraces(_ context.Context, body []byte) ([]byte, return json.Marshal(map[string]any{ "Traces": traces, "UnprocessedTraceIds": unprocessed, - }) + }, jsonx.V1()) } diff --git a/services/xray/handler_traces_test.go b/services/xray/handler_traces_test.go index f4524ad238..ad4f66d9a0 100644 --- a/services/xray/handler_traces_test.go +++ b/services/xray/handler_traces_test.go @@ -1,7 +1,7 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -73,7 +74,7 @@ func TestHandler_BatchGetTraces(t *testing.T) { assert.Equal(t, tt.wantStatus, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) if tt.wantTraces > 0 { traces, ok := resp["Traces"].([]any) @@ -110,7 +111,7 @@ func TestBatchGetTraces_SegmentShape(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) traces, ok := resp["Traces"].([]any) require.True(t, ok) @@ -163,7 +164,7 @@ func TestGetTraceSummaries_FaultFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -197,7 +198,7 @@ func TestGetTraceSummaries_TimeRangeType(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) // Should still return the trace — TimeRangeType is accepted without error. summaries, ok := resp["TraceSummaries"].([]any) @@ -225,7 +226,7 @@ func TestTraceSummary_FaultAndErrorPropagation(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -270,7 +271,7 @@ func TestGetTraceSummaries_ThrottleFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -337,7 +338,7 @@ func TestGetTraceSummariesTimeFilter(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -366,7 +367,7 @@ func TestGetTraceSummaries_TimeWindowExclusion(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -393,7 +394,7 @@ func TestTraceSummary_UsersFromAnnotations(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -429,7 +430,7 @@ func TestTraceSummary_OmitsForecastStatistics(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) summaries, ok := resp["TraceSummaries"].([]any) require.True(t, ok) @@ -453,7 +454,7 @@ func TestBatchGetTraces_UnprocessedForMissingIDs(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) unprocessed, ok := resp["UnprocessedTraceIds"].([]any) require.True(t, ok) @@ -545,7 +546,7 @@ func TestHandler_GetTraceSummaries_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec1.Code) var resp1 map[string]any - require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1)) + require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &resp1, jsonx.V1())) summaries1, _ := resp1["TraceSummaries"].([]any) assert.Len(t, summaries1, defaultPageSize) @@ -556,7 +557,7 @@ func TestHandler_GetTraceSummaries_Pagination(t *testing.T) { require.Equal(t, http.StatusOK, rec2.Code) var resp2 map[string]any - require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2)) + require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &resp2, jsonx.V1())) summaries2, _ := resp2["TraceSummaries"].([]any) assert.Len(t, summaries2, total-defaultPageSize) @@ -581,7 +582,7 @@ func TestGetTraceSummaries_DurationFromSegmentTimes(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -644,7 +645,7 @@ func TestGetTraceSummaries_BooleanFlagFields(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -710,7 +711,7 @@ func TestGetTraceSummaries_HTTPFieldsPopulated(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -749,7 +750,7 @@ func TestGetTraceSummaries_ServiceIDsFromSegments(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -781,7 +782,7 @@ func TestGetTraceSummaries_EntryPointFromRootSegment(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -812,7 +813,7 @@ func TestGetTraceSummaries_IsPartialWithoutRootSegment(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -839,7 +840,7 @@ func TestGetTraceSummaries_TracesProcessedCount(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) count, ok := resp["TracesProcessedCount"].(float64) require.True(t, ok) @@ -928,7 +929,7 @@ func TestGetTraceSummaries_FilterExpressions(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) assert.Len(t, summaries, tt.wantCount, "filter=%q wantCount=%d", tt.filter, tt.wantCount) @@ -961,7 +962,7 @@ func TestGetTraceSummaries_OmitsForecastStatistics(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -996,7 +997,7 @@ func TestGetTraceSummaries_AnnotationFilter(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -1026,7 +1027,7 @@ func TestGetTraceSummaries_ResponseTimeFilter(t *testing.T) { require.Equal(t, http.StatusOK, sumRec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(sumRec.Body.Bytes(), &resp, jsonx.V1())) summaries, _ := resp["TraceSummaries"].([]any) require.Len(t, summaries, 1) @@ -1076,7 +1077,7 @@ func TestBatchGetTraces_UnprocessedForMissingTraces(t *testing.T) { require.Equal(t, http.StatusOK, rec.Code) var resp map[string]any - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) traces, _ := resp["Traces"].([]any) assert.Empty(t, traces, "missing traces produce no Traces entries") diff --git a/services/xray/helpers_test.go b/services/xray/helpers_test.go index 7305063dfa..632e5faf76 100644 --- a/services/xray/helpers_test.go +++ b/services/xray/helpers_test.go @@ -2,7 +2,7 @@ package xray_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -12,6 +12,7 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -34,7 +35,7 @@ func buildFaultSegmentJSON(t *testing.T, traceID, name string, fault bool) strin "fault": fault, } - data, err := json.Marshal(seg) + data, err := json.Marshal(seg, jsonx.V1()) require.NoError(t, err) return string(data) @@ -61,7 +62,7 @@ func doXrayRequest(t *testing.T, h *xray.Handler, path string, body any) *httpte if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -126,7 +127,7 @@ func segJSONWithHTTP(traceID, segID, name string, startTime, endTime float64, st // segFmt formats a float64 timestamp as a JSON value string (no quotes). func segFmt(ts float64) string { - out, _ := json.Marshal(ts) + out, _ := json.Marshal(ts, jsonx.V1()) return string(out) } diff --git a/services/xray/multi_region_test.go b/services/xray/multi_region_test.go index 60643394d9..46b552876b 100644 --- a/services/xray/multi_region_test.go +++ b/services/xray/multi_region_test.go @@ -2,7 +2,7 @@ package xray_test import ( "context" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "strings" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/services/xray" ) @@ -41,7 +42,7 @@ func decode(t *testing.T, body string) map[string]any { out := map[string]any{} if body != "" { - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) } return out diff --git a/services/xray/persistence.go b/services/xray/persistence.go index efc499678c..72053117d9 100644 --- a/services/xray/persistence.go +++ b/services/xray/persistence.go @@ -2,9 +2,10 @@ package xray import ( "context" - "encoding/json" + "encoding/json/v2" "time" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/persistence" ) @@ -85,7 +86,7 @@ func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte { ResourceTags: b.resourceTags, } - data, err := json.Marshal(snap) + data, err := json.Marshal(snap, jsonx.V1()) if err != nil { logger.Load(ctx).WarnContext(ctx, "xray: failed to marshal snapshot", "error", err) @@ -182,7 +183,7 @@ func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error { for _, t := range b.traces.All() { for _, rawSeg := range t.Segments { var seg Segment - if err := json.Unmarshal([]byte(rawSeg), &seg); err == nil { + if err := json.Unmarshal([]byte(rawSeg), &seg, jsonx.V1()); err == nil { seg.Document = rawSeg b.parsedSegments.Put(&seg) } diff --git a/services/xray/policy_lockout.go b/services/xray/policy_lockout.go index 8df55a7ffc..1b0bb9f231 100644 --- a/services/xray/policy_lockout.go +++ b/services/xray/policy_lockout.go @@ -1,18 +1,21 @@ package xray import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "path" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) type lockoutStatement struct { - Effect string `json:"Effect"` - Principal json.RawMessage `json:"Principal"` - Action json.RawMessage `json:"Action"` - Condition json.RawMessage `json:"Condition"` - NotPrincipal json.RawMessage `json:"NotPrincipal"` - NotAction json.RawMessage `json:"NotAction"` + Effect string `json:"Effect"` + Principal jsontext.Value `json:"Principal"` + Action jsontext.Value `json:"Action"` + Condition jsontext.Value `json:"Condition"` + NotPrincipal jsontext.Value `json:"NotPrincipal"` + NotAction jsontext.Value `json:"NotAction"` } // policyLocksOutCaller reports whether policyDocument has an unconditional Deny of xray:PutResourcePolicy that @@ -23,16 +26,16 @@ func policyLocksOutCaller(policyDocument, callerARN string) bool { } var doc struct { - Statement json.RawMessage `json:"Statement"` + Statement jsontext.Value `json:"Statement"` } - if json.Unmarshal([]byte(policyDocument), &doc) != nil { + if json.Unmarshal([]byte(policyDocument), &doc, jsonx.V1()) != nil { return false } var stmts []lockoutStatement - if json.Unmarshal(doc.Statement, &stmts) != nil { + if json.Unmarshal(doc.Statement, &stmts, jsonx.V1()) != nil { var one lockoutStatement - if json.Unmarshal(doc.Statement, &one) != nil { + if json.Unmarshal(doc.Statement, &one, jsonx.V1()) != nil { return false } @@ -53,19 +56,19 @@ func policyLocksOutCaller(policyDocument, callerARN string) bool { return false } -func stringList(raw json.RawMessage) []string { +func stringList(raw jsontext.Value) []string { var one string - if json.Unmarshal(raw, &one) == nil { + if json.Unmarshal(raw, &one, jsonx.V1()) == nil { return []string{one} } var many []string - _ = json.Unmarshal(raw, &many) + _ = json.Unmarshal(raw, &many, jsonx.V1()) return many } -func matchesAny(raw json.RawMessage, target string, fold bool) bool { +func matchesAny(raw jsontext.Value, target string, fold bool) bool { for _, p := range stringList(raw) { if fold { p = strings.ToLower(p) @@ -79,12 +82,12 @@ func matchesAny(raw json.RawMessage, target string, fold bool) bool { return false } -func principalMatches(raw json.RawMessage, callerARN string) bool { +func principalMatches(raw jsontext.Value, callerARN string) bool { var wrapper struct { - AWS json.RawMessage `json:"AWS"` + AWS jsontext.Value `json:"AWS"` } - if json.Unmarshal(raw, &wrapper) == nil && len(wrapper.AWS) > 0 { + if json.Unmarshal(raw, &wrapper, jsonx.V1()) == nil && len(wrapper.AWS) > 0 { raw = wrapper.AWS } diff --git a/services/xray/policy_lockout_test.go b/services/xray/policy_lockout_test.go index 96e9847274..fb5c7e4552 100644 --- a/services/xray/policy_lockout_test.go +++ b/services/xray/policy_lockout_test.go @@ -2,7 +2,7 @@ package xray_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "net/http/httptest" "testing" @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/awsmeta" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestPutResourcePolicy_LockoutPrevention(t *testing.T) { @@ -70,7 +71,7 @@ func TestPutResourcePolicy_LockoutPrevention(t *testing.T) { h := newTestHandler(t) payload, err := json.Marshal(map[string]any{ "PolicyName": "p", "PolicyDocument": tt.doc, "BypassPolicyLockoutCheck": tt.bypass, - }) + }, jsonx.V1()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/PutResourcePolicy", bytes.NewReader(payload)) @@ -89,7 +90,7 @@ func TestPutResourcePolicy_LockoutPrevention(t *testing.T) { if tt.wantType != "" { var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, tt.wantType, resp["__type"]) } }) diff --git a/services/xray/resource_policies.go b/services/xray/resource_policies.go index 232aa42e6d..7d48b795a4 100644 --- a/services/xray/resource_policies.go +++ b/services/xray/resource_policies.go @@ -1,11 +1,14 @@ package xray import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "sort" "strconv" "time" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func cloneResourcePolicy(p *ResourcePolicy) *ResourcePolicy { @@ -21,8 +24,8 @@ func cloneResourcePolicy(p *ResourcePolicy) *ResourcePolicy { // Returns ErrMalformedPolicyDocument if policyDocument is not valid JSON. func (b *InMemoryBackend) PutResourcePolicy(policyName, policyDocument, revisionID string) (*ResourcePolicy, error) { // Validate JSON. - var js json.RawMessage - if err := json.Unmarshal([]byte(policyDocument), &js); err != nil { + var js jsontext.Value + if err := json.Unmarshal([]byte(policyDocument), &js, jsonx.V1()); err != nil { return nil, fmt.Errorf("%w: policy document is not valid JSON: %w", ErrMalformedPolicyDocument, err) } diff --git a/services/xray/service_graph_histogram_test.go b/services/xray/service_graph_histogram_test.go index ae416b14f4..90c231b82e 100644 --- a/services/xray/service_graph_histogram_test.go +++ b/services/xray/service_graph_histogram_test.go @@ -1,13 +1,15 @@ package xray_test import ( - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestGetServiceGraph_Histograms(t *testing.T) { @@ -39,7 +41,7 @@ func TestGetServiceGraph_Histograms(t *testing.T) { } `json:"Edges"` } `json:"Services"` } - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) byName := map[string]int{} for i, s := range resp.Services { diff --git a/services/xray/trace_segments.go b/services/xray/trace_segments.go index cd8f4f229a..9fa5ffa087 100644 --- a/services/xray/trace_segments.go +++ b/services/xray/trace_segments.go @@ -1,11 +1,13 @@ package xray import ( - "encoding/json" + "encoding/json/v2" "math" "time" "github.com/google/uuid" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // PutTraceSegments stores raw segment JSON strings, parses them into typed Segment structs, @@ -19,7 +21,7 @@ func (b *InMemoryBackend) PutTraceSegments(segments []string) []string { for _, seg := range segments { var hdr segmentHeader - if err := json.Unmarshal([]byte(seg), &hdr); err != nil || hdr.TraceID == "" { + if err := json.Unmarshal([]byte(seg), &hdr, jsonx.V1()); err != nil || hdr.TraceID == "" { unprocessed = append(unprocessed, uuid.NewString()) continue @@ -46,7 +48,7 @@ func (b *InMemoryBackend) PutTraceSegments(segments []string) []string { // Parse into typed Segment struct and index it. var parsed Segment - if err := json.Unmarshal([]byte(seg), &parsed); err == nil { + if err := json.Unmarshal([]byte(seg), &parsed, jsonx.V1()); err == nil { parsed.Document = seg b.parsedSegments.Put(&parsed) diff --git a/services/xray/whitebox_test.go b/services/xray/whitebox_test.go index 468ab4c42c..7c2b89f564 100644 --- a/services/xray/whitebox_test.go +++ b/services/xray/whitebox_test.go @@ -2,7 +2,7 @@ package xray import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "net/http" "net/http/httptest" @@ -11,12 +11,14 @@ import ( "github.com/labstack/echo/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func doWhiteboxRequest(t *testing.T, h *Handler, path string, body any) *httptest.ResponseRecorder { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) e := echo.New() @@ -53,6 +55,6 @@ func TestCreateSamplingRule_RuleLimitExceeded(t *testing.T) { require.Equal(t, http.StatusBadRequest, rec.Code) var resp map[string]string - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp, jsonx.V1())) assert.Equal(t, "RuleLimitExceededException", resp["__type"]) } diff --git a/test/guards/hardcoded_arn_partition_guard_test.go b/test/guards/hardcoded_arn_partition_guard_test.go index acde1927e4..f3e23c5995 100644 --- a/test/guards/hardcoded_arn_partition_guard_test.go +++ b/test/guards/hardcoded_arn_partition_guard_test.go @@ -1,7 +1,7 @@ package guards_test import ( - "encoding/json" + "encoding/json/v2" "os" "path/filepath" "sort" @@ -9,6 +9,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const hardcodedARNAllowlistPath = "test/guards/testdata/hardcoded_arn_allowlist.json" @@ -23,7 +25,7 @@ func TestNoNewHardcodedAWSPartitionARNs(t *testing.T) { require.NoError(t, err) var allow map[string]int - require.NoError(t, json.Unmarshal(raw, &allow)) + require.NoError(t, json.Unmarshal(raw, &allow, jsonx.V1())) got := countHardcodedARNs(t) diff --git a/test/guards/jsonv1_import_guard_test.go b/test/guards/jsonv1_import_guard_test.go new file mode 100644 index 0000000000..59b01b3ddb --- /dev/null +++ b/test/guards/jsonv1_import_guard_test.go @@ -0,0 +1,110 @@ +package guards_test + +import ( + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const jsonV1Path = "encoding/json" + +// importsJSONV1 reports whether src imports encoding/json (v1); string +// literals containing the path do not count. +func importsJSONV1(name string, src any) (bool, error) { + f, err := parser.ParseFile(token.NewFileSet(), name, src, parser.ImportsOnly) + if err != nil { + return false, err + } + + for _, imp := range f.Imports { + if p, uerr := strconv.Unquote(imp.Path.Value); uerr == nil && p == jsonV1Path { + return true, nil + } + } + + return false, nil +} + +func TestImportsJSONV1(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + src string + want bool + }{ + {name: "single", src: "package p\nimport \"encoding/json\"\n", want: true}, + {name: "grouped", src: "package p\nimport (\n\t\"fmt\"\n\t\"encoding/json\"\n)\n", want: true}, + {name: "aliased", src: "package p\nimport j \"encoding/json\"\n", want: true}, + {name: "blank", src: "package p\nimport _ \"encoding/json\"\n", want: true}, + {name: "v2", src: "package p\nimport \"encoding/json/v2\"\n", want: false}, + {name: "jsontext", src: "package p\nimport \"encoding/json/jsontext\"\n", want: false}, + {name: "raw string", src: "package p\nconst s = `import \"encoding/json\"`\n", want: false}, + {name: "comment", src: "package p\n// import \"encoding/json\"\n", want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := importsJSONV1("x.go", tt.src) + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +// TestNoEncodingJSONV1Import forbids encoding/json outside pkgs/jsonx, which +// owns the v1 option set; everything else uses encoding/json/v2 and jsontext. +func TestNoEncodingJSONV1Import(t *testing.T) { + t.Parallel() + + var offenders []string + + err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + switch { + case path == filepath.Join("pkgs", "jsonx"), + d.Name() == "testdata", d.Name() == "node_modules", + path != "." && strings.HasPrefix(d.Name(), "."): + return filepath.SkipDir + } + + return nil + } + + if !strings.HasSuffix(path, ".go") { + return nil + } + + src, readErr := os.ReadFile(path) + if readErr != nil { + return readErr + } + + bad, parseErr := importsJSONV1(path, src) + if parseErr != nil { + return nil //nolint:nilerr // unparsable files are the compiler's concern + } + + if bad { + offenders = append(offenders, path) + } + + return nil + }) + require.NoError(t, err) + assert.Empty(t, offenders, "import encoding/json/v2 (+ jsontext) and pass jsonx.V1() instead of encoding/json") +} diff --git a/test/guards/sdk_source_test.go b/test/guards/sdk_source_test.go index e7303d86e3..5b73b1b763 100644 --- a/test/guards/sdk_source_test.go +++ b/test/guards/sdk_source_test.go @@ -2,7 +2,8 @@ package guards_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "os" @@ -15,6 +16,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -80,16 +83,16 @@ func downloadSDKDirs(mods []sdkModule) (map[string]string, error) { out, runErr := cmd.Output() dirs := map[string]string{} - dec := json.NewDecoder(bytes.NewReader(out)) + dec := jsontext.NewDecoder(bytes.NewReader(out)) - for dec.More() { + for dec.PeekKind() != 0 { var r struct { Path string `json:"Path"` Version string `json:"Version"` Dir string `json:"Dir"` Error string `json:"Error"` } - if err := dec.Decode(&r); err != nil { + if err := json.UnmarshalDecode(dec, &r, jsonx.V1()); err != nil { return nil, err } diff --git a/test/integration/acm_test.go b/test/integration/acm_test.go index b66b887248..8236b85b7b 100644 --- a/test/integration/acm_test.go +++ b/test/integration/acm_test.go @@ -8,7 +8,8 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/base64" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "encoding/pem" "io" "math/big" @@ -20,13 +21,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func acmPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, @@ -57,7 +59,7 @@ func acmReadJSON(t *testing.T, resp *http.Response) map[string]any { defer resp.Body.Close() var m map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &m, jsonx.V1())) return m } @@ -218,7 +220,7 @@ func TestIntegration_ACM_ImportCertificate(t *testing.T) { importedARN := struct { CertificateArn string `json:"CertificateArn"` }{} - require.NoError(t, json.Unmarshal([]byte(importBody), &importedARN)) + require.NoError(t, json.Unmarshal([]byte(importBody), &importedARN, jsonx.V1())) // Describe the imported cert descImportedResp := acmPost(t, "DescribeCertificate", map[string]any{"CertificateArn": importedARN.CertificateArn}) diff --git a/test/integration/apigateway_test.go b/test/integration/apigateway_test.go index e3874a005a..f79703523c 100644 --- a/test/integration/apigateway_test.go +++ b/test/integration/apigateway_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +11,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func apigwPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) @@ -46,7 +48,7 @@ func apigwReadJSON(t *testing.T, resp *http.Response) map[string]any { defer resp.Body.Close() var m map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&m)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &m, jsonx.V1())) return m } diff --git a/test/integration/appsync_test.go b/test/integration/appsync_test.go index 108c515adb..f3592ec079 100644 --- a/test/integration/appsync_test.go +++ b/test/integration/appsync_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -255,7 +256,7 @@ func executeGraphQL( body["variables"] = variables } - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, gqlEndpoint, bytes.NewReader(bodyBytes)) @@ -284,7 +285,7 @@ func executeGraphQL( ) var result map[string]any - require.NoError(t, json.Unmarshal(respBytes, &result)) + require.NoError(t, json.Unmarshal(respBytes, &result, jsonx.V1())) return result } diff --git a/test/integration/awsconfig_test.go b/test/integration/awsconfig_test.go index 91ca4d57dc..ee579dbd68 100644 --- a/test/integration/awsconfig_test.go +++ b/test/integration/awsconfig_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func awsconfigPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) diff --git a/test/integration/azurearm_test.go b/test/integration/azurearm_test.go index 1289bb97b6..6be7a08b56 100644 --- a/test/integration/azurearm_test.go +++ b/test/integration/azurearm_test.go @@ -20,7 +20,8 @@ package integration_test import ( "crypto/tls" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "strings" "testing" @@ -29,6 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -79,7 +81,7 @@ func armDecodeJSON(t *testing.T, resp *http.Response) map[string]any { t.Helper() var body map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &body, jsonx.V1())) return body } @@ -96,7 +98,7 @@ func TestIntegration_AzureARM_MetadataAndToken(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) var doc map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&doc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &doc, jsonx.V1())) assert.Equal(t, "gopherstack", doc["name"]) assert.NotEmpty(t, doc["resourceManagerEndpoint"]) diff --git a/test/integration/azureservicebus_test.go b/test/integration/azureservicebus_test.go index b31dbb5727..5b36dcac16 100644 --- a/test/integration/azureservicebus_test.go +++ b/test/integration/azureservicebus_test.go @@ -16,7 +16,7 @@ package integration_test // namespace's Brokered Messaging API. import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -97,7 +98,7 @@ func TestIntegration_AzureServiceBus_QueueSendPeekLockComplete(t *testing.T) { var bp sbBrokerProperties - require.NoError(t, json.Unmarshal([]byte(resp.Header.Get("Brokerproperties")), &bp)) + require.NoError(t, json.Unmarshal([]byte(resp.Header.Get("Brokerproperties")), &bp, jsonx.V1())) require.NotEmpty(t, bp.MessageID) require.NotEmpty(t, bp.LockToken) diff --git a/test/integration/chaos_test.go b/test/integration/chaos_test.go index ccb8436914..916a88eb66 100644 --- a/test/integration/chaos_test.go +++ b/test/integration/chaos_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -19,6 +19,7 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -103,7 +104,7 @@ func newDynamoDBClientAt(t *testing.T, ep string) *dynamodb.Client { func postChaosRules(t *testing.T, ep string, rules any) { t.Helper() - body, err := json.Marshal(rules) + body, err := json.Marshal(rules, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext( @@ -128,7 +129,7 @@ func postChaosRules(t *testing.T, ep string, rules any) { func postChaosEffects(t *testing.T, ep string, effects chaosNetworkEffects) { t.Helper() - body, err := json.Marshal(effects) + body, err := json.Marshal(effects, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext( diff --git a/test/integration/cloudwatchlogs_test.go b/test/integration/cloudwatchlogs_test.go index a955255ca4..98aba361ba 100644 --- a/test/integration/cloudwatchlogs_test.go +++ b/test/integration/cloudwatchlogs_test.go @@ -4,7 +4,7 @@ import ( "bytes" "compress/gzip" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "testing" "time" @@ -16,6 +16,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // decompressGzip decompresses gzip-compressed bytes. @@ -349,7 +351,7 @@ func TestIntegration_CloudWatchLogs_SubscriptionFilter_KinesisDelivery(t *testin // Verify the payload matches the CloudWatch Logs subscription format. var payload map[string]any - require.NoError(t, json.Unmarshal(decompressed, &payload)) + require.NoError(t, json.Unmarshal(decompressed, &payload, jsonx.V1())) assert.Equal(t, "DATA_MESSAGE", payload["messageType"]) assert.Equal(t, groupName, payload["logGroup"]) assert.Equal(t, logStreamName, payload["logStream"]) diff --git a/test/integration/cognito_test.go b/test/integration/cognito_test.go index 8c032dbfc1..12a06d052f 100644 --- a/test/integration/cognito_test.go +++ b/test/integration/cognito_test.go @@ -3,7 +3,7 @@ package integration_test import ( "crypto/rsa" "encoding/base64" - "encoding/json" + "encoding/json/v2" "fmt" "io" "math/big" @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -122,7 +123,7 @@ func TestIntegration_CognitoIDP_FullFlow(t *testing.T) { Alg string `json:"alg"` } `json:"keys"` } - require.NoError(t, json.Unmarshal(jwksBody, &jwks), "JWKS should be valid JSON") + require.NoError(t, json.Unmarshal(jwksBody, &jwks, jsonx.V1()), "JWKS should be valid JSON") require.Len(t, jwks.Keys, 1, "JWKS should have exactly one key") assert.Equal(t, "RSA", jwks.Keys[0].Kty) assert.Equal(t, "sig", jwks.Keys[0].Use) diff --git a/test/integration/cosmosdb_test.go b/test/integration/cosmosdb_test.go index 6e092acb70..31efe7000a 100644 --- a/test/integration/cosmosdb_test.go +++ b/test/integration/cosmosdb_test.go @@ -1,13 +1,15 @@ package integration_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // cosmosDBDevMasterKey is the real Cosmos DB Local Emulator's published @@ -81,7 +83,7 @@ func TestIntegration_CosmosDB_DatabaseContainerDocumentLifecycle(t *testing.T) { } doc := testDoc{ID: "doc1", PK: "partA", Value: 42} - body, err := json.Marshal(doc) + body, err := json.Marshal(doc, jsonx.V1()) require.NoError(t, err) pk := azcosmos.NewPartitionKeyString("partA") @@ -95,7 +97,7 @@ func TestIntegration_CosmosDB_DatabaseContainerDocumentLifecycle(t *testing.T) { require.NoError(t, err) var gotDoc testDoc - require.NoError(t, json.Unmarshal(readResp.Value, &gotDoc)) + require.NoError(t, json.Unmarshal(readResp.Value, &gotDoc, jsonx.V1())) assert.Equal(t, doc, gotDoc) // Query via SQL. @@ -113,7 +115,7 @@ func TestIntegration_CosmosDB_DatabaseContainerDocumentLifecycle(t *testing.T) { for _, item := range page.Items { var qd testDoc - require.NoError(t, json.Unmarshal(item, &qd)) + require.NoError(t, json.Unmarshal(item, &qd, jsonx.V1())) if qd.ID == "doc1" { found = true @@ -125,7 +127,7 @@ func TestIntegration_CosmosDB_DatabaseContainerDocumentLifecycle(t *testing.T) { // UpsertItem (replaces existing doc1). upserted := testDoc{ID: "doc1", PK: "partA", Value: 100} - upsertBody, err := json.Marshal(upserted) + upsertBody, err := json.Marshal(upserted, jsonx.V1()) require.NoError(t, err) _, err = containerClient.UpsertItem(ctx, pk, upsertBody, nil) @@ -135,7 +137,7 @@ func TestIntegration_CosmosDB_DatabaseContainerDocumentLifecycle(t *testing.T) { require.NoError(t, err) var afterUpsert testDoc - require.NoError(t, json.Unmarshal(readResp.Value, &afterUpsert)) + require.NoError(t, json.Unmarshal(readResp.Value, &afterUpsert, jsonx.V1())) assert.Equal(t, 100, afterUpsert.Value) // DeleteItem. diff --git a/test/integration/ecrpublic_test.go b/test/integration/ecrpublic_test.go index 06fb878e0f..411e38fd53 100644 --- a/test/integration/ecrpublic_test.go +++ b/test/integration/ecrpublic_test.go @@ -3,7 +3,7 @@ package integration_test import ( "crypto/sha256" "encoding/hex" - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -13,6 +13,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -102,7 +104,7 @@ func TestIntegration_ECRPublic_RepositoryImageLifecycle(t *testing.T) { "mediaType": ecrPublicManifestMediaType, "config": map[string]any{"mediaType": ecrPublicConfigMediaType, "digest": configDigest}, "layers": []map[string]any{{"digest": layerDigest}}, - }) + }, jsonx.V1()) require.NoError(t, err) put, err := client.PutImage(ctx, &ecrpublicsdk.PutImageInput{ @@ -117,7 +119,7 @@ func TestIntegration_ECRPublic_RepositoryImageLifecycle(t *testing.T) { "schemaVersion": 2, "mediaType": ecrPublicIndexMediaType, "manifests": []map[string]any{{"digest": childDigest}}, - }) + }, jsonx.V1()) require.NoError(t, err) _, err = client.PutImage(ctx, &ecrpublicsdk.PutImageInput{ diff --git a/test/integration/eks_irsa_test.go b/test/integration/eks_irsa_test.go index b980cad68e..38744c8512 100644 --- a/test/integration/eks_irsa_test.go +++ b/test/integration/eks_irsa_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "io" "net/http" "os" @@ -22,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -33,7 +35,7 @@ type irsaToken struct { func mintIRSAToken(t *testing.T, cluster string, body map[string]any) irsaToken { t.Helper() - raw, err := json.Marshal(body) + raw, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) resp, err := testclient.New(t).Post(endpoint+"/_gopherstack/eks/"+cluster+"/serviceaccount-token", @@ -47,7 +49,7 @@ func mintIRSAToken(t *testing.T, cluster string, body map[string]any) irsaToken require.Equal(t, http.StatusOK, resp.StatusCode, string(out)) var tok irsaToken - require.NoError(t, json.Unmarshal(out, &tok)) + require.NoError(t, json.Unmarshal(out, &tok, jsonx.V1())) return tok } @@ -100,7 +102,7 @@ func TestIntegration_EKS_IRSA(t *testing.T) { defer disc.Body.Close() var discDoc map[string]any - require.NoError(t, json.NewDecoder(disc.Body).Decode(&discDoc)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(disc.Body), &discDoc, jsonx.V1())) assert.Equal(t, issuer, discDoc["issuer"]) provider, err := iamClient.CreateOpenIDConnectProvider(ctx, &iamsdk.CreateOpenIDConnectProviderInput{ diff --git a/test/integration/elasticsearch_test.go b/test/integration/elasticsearch_test.go index 3218788db5..769dfe4d3b 100644 --- a/test/integration/elasticsearch_test.go +++ b/test/integration/elasticsearch_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -79,7 +81,7 @@ func doElasticsearchRequest(t *testing.T, method, path string, body any) (int, m var reqBody io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) @@ -99,7 +101,7 @@ func doElasticsearchRequest(t *testing.T, method, path string, body any) (int, m var result map[string]any - if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &result, jsonx.V1()); err != nil { result = nil } diff --git a/test/integration/firehose_delivery_test.go b/test/integration/firehose_delivery_test.go index a270dc2cdf..4e7b019c07 100644 --- a/test/integration/firehose_delivery_test.go +++ b/test/integration/firehose_delivery_test.go @@ -4,7 +4,7 @@ import ( "bytes" "context" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "log/slog" "net/http" @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" pkglogger "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" "github.com/blackbirdworks/gopherstack/pkgs/testclient" @@ -73,7 +74,7 @@ func (keepOrDropLambdaInvoker) InvokeFunction( payload []byte, ) ([]byte, int, error) { var event transformEvent - if err := json.Unmarshal(payload, &event); err != nil { + if err := json.Unmarshal(payload, &event, jsonx.V1()); err != nil { return nil, 500, err } @@ -87,7 +88,7 @@ func (keepOrDropLambdaInvoker) InvokeFunction( out[i] = transformRecord{RecordID: rec.RecordID, Result: result, Data: rec.Data} } - resp, err := json.Marshal(map[string]any{"records": out}) + resp, err := json.Marshal(map[string]any{"records": out}, jsonx.V1()) if err != nil { return nil, 500, err } @@ -440,7 +441,7 @@ func TestIntegration_Firehose_HTTP_UpdateDestination(t *testing.T) { doPost := func(action string, body any) (int, string) { t.Helper() - bodyBytes, marshalErr := json.Marshal(body) + bodyBytes, marshalErr := json.Marshal(body, jsonx.V1()) require.NoError(t, marshalErr) req, reqCreateErr := http.NewRequestWithContext( t.Context(), http.MethodPost, srv.URL, diff --git a/test/integration/firehose_test.go b/test/integration/firehose_test.go index 79c5815720..155a48777a 100644 --- a/test/integration/firehose_test.go +++ b/test/integration/firehose_test.go @@ -3,7 +3,7 @@ package integration_test import ( "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -11,13 +11,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func firehosePost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) diff --git a/test/integration/fis_test.go b/test/integration/fis_test.go index 09879c9e05..0f8bf49784 100644 --- a/test/integration/fis_test.go +++ b/test/integration/fis_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "net/http" "testing" "time" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -32,7 +34,7 @@ func fisRequest( var bodyReader *bytes.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) } else { @@ -55,7 +57,7 @@ func fisBody(t *testing.T, resp *http.Response, out any) { defer func() { _ = resp.Body.Close() }() - require.NoError(t, json.NewDecoder(resp.Body).Decode(out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), out, jsonx.V1())) } // ---------------------------------------- @@ -309,7 +311,7 @@ func TestIntegration_FIS_ExperimentLifecycle(t *testing.T) { defer func() { _ = getResp.Body.Close() }() - if err := json.NewDecoder(getResp.Body).Decode(&getResult); err != nil { + if err := json.UnmarshalDecode(jsontext.NewDecoder(getResp.Body), &getResult, jsonx.V1()); err != nil { return false } @@ -591,7 +593,11 @@ func TestIntegration_FIS_KinesisThroughputException(t *testing.T) { return false } - if decodeErr := json.NewDecoder(getResp.Body).Decode(&getResult); decodeErr != nil { + if decodeErr := json.UnmarshalDecode( + jsontext.NewDecoder(getResp.Body), + &getResult, + jsonx.V1(), + ); decodeErr != nil { return false } @@ -642,7 +648,11 @@ func TestIntegration_FIS_KinesisThroughputException(t *testing.T) { return false } - if decodeErr := json.NewDecoder(getResp.Body).Decode(&getResult); decodeErr != nil { + if decodeErr := json.UnmarshalDecode( + jsontext.NewDecoder(getResp.Body), + &getResult, + jsonx.V1(), + ); decodeErr != nil { return false } diff --git a/test/integration/gcp_iam_test.go b/test/integration/gcp_iam_test.go index 1c21758a5f..3248783535 100644 --- a/test/integration/gcp_iam_test.go +++ b/test/integration/gcp_iam_test.go @@ -1,7 +1,7 @@ package integration_test import ( - "encoding/json" + "encoding/json/v2" "testing" iam "cloud.google.com/go/iam/admin/apiv1" @@ -11,6 +11,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/fieldmaskpb" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func newIAMAdminClient(t *testing.T) *iam.IamClient { @@ -57,7 +59,7 @@ func TestIntegration_GCP_IAM_ServiceAccountAndKey(t *testing.T) { ClientEmail string `json:"client_email"` } - require.NoError(t, json.Unmarshal(key.GetPrivateKeyData(), &kf)) + require.NoError(t, json.Unmarshal(key.GetPrivateKeyData(), &kf, jsonx.V1())) assert.Equal(t, "service_account", kf.Type) assert.Equal(t, sa.GetEmail(), kf.ClientEmail) diff --git a/test/integration/glacier_test.go b/test/integration/glacier_test.go index abd34e56a0..2d4761795f 100644 --- a/test/integration/glacier_test.go +++ b/test/integration/glacier_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "net/http" "testing" "time" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -212,7 +213,7 @@ func glacierAddTags(t *testing.T, vaultName string, tags map[string]string) erro Tags map[string]string `json:"Tags"` } - payload, err := json.Marshal(addTagsReq{Tags: tags}) + payload, err := json.Marshal(addTagsReq{Tags: tags}, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, diff --git a/test/integration/iot_test.go b/test/integration/iot_test.go index 52d96d6ad5..1809c7f436 100644 --- a/test/integration/iot_test.go +++ b/test/integration/iot_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -16,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -26,7 +28,7 @@ func iotRequest(t *testing.T, method, path string, body any) *http.Response { var reqBody io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) @@ -93,7 +95,7 @@ func TestIntegration_IoT_ThingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]string - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, thingName, out["thingName"]) assert.NotEmpty(t, out["thingArn"]) assert.NotEmpty(t, out["thingId"]) @@ -110,7 +112,7 @@ func TestIntegration_IoT_ThingLifecycle(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.Equal(t, thingName, out["thingName"]) }, }, @@ -197,7 +199,7 @@ func TestIntegration_IoT_PolicyAndRule(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) // AWS GetTopicRule wraps fields under a "rule" object with "ruleArn" at top level. rule, ok := out["rule"].(map[string]any) require.True(t, ok, "response should contain a 'rule' object") @@ -246,7 +248,7 @@ func TestIntegration_IoT_DescribeEndpoint(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode) var out map[string]string - require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &out, jsonx.V1())) assert.NotEmpty(t, out["endpointAddress"]) }) } diff --git a/test/integration/lambda_esm_test.go b/test/integration/lambda_esm_test.go index 8ed06edc41..89bba917e8 100644 --- a/test/integration/lambda_esm_test.go +++ b/test/integration/lambda_esm_test.go @@ -2,7 +2,7 @@ package integration_test import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http/httptest" "strconv" @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/container" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc/portalloctest" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -207,7 +208,7 @@ func TestIntegration_Lambda_SQS_ESM(t *testing.T) { "Code": map[string]string{"ImageUri": lambdaEchoImage}, "Role": "arn:aws:iam::000000000000:role/lambda-test", "Timeout": int(esmInvokeTimeout.Seconds()), - }) + }, jsonx.V1()) require.NoError(t, err) createFnResp, err := doLambdaRequest(ctx, "POST", diff --git a/test/integration/lambda_new_ops_test.go b/test/integration/lambda_new_ops_test.go index 1fb7cf5574..8bb2f65475 100644 --- a/test/integration/lambda_new_ops_test.go +++ b/test/integration/lambda_new_ops_test.go @@ -3,7 +3,7 @@ package integration_test import ( "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "log/slog" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc/portalloctest" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -58,7 +59,7 @@ func createLambdaFunction(ctx context.Context, t *testing.T, serverURL, fnName s "PackageType": "Image", "Code": map[string]string{"ImageUri": "test:latest"}, "Role": "arn:aws:iam::000000000000:role/test", - }) + }, jsonx.V1()) require.NoError(t, err) resp, err := doLambdaRequest(ctx, http.MethodPost, @@ -133,7 +134,7 @@ func TestLambda_AddPermission(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - body, err := json.Marshal(tt.body) + body, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) targetFn := fnName @@ -153,7 +154,7 @@ func TestLambda_AddPermission(t *testing.T) { require.NoError(t, readErr) var out map[string]any - require.NoError(t, json.Unmarshal(rawBody, &out)) + require.NoError(t, json.Unmarshal(rawBody, &out, jsonx.V1())) assert.NotEmpty(t, out["Statement"]) } }) @@ -174,7 +175,7 @@ func TestLambda_AddPermission_DuplicateStatementId(t *testing.T) { "StatementId": "AllowS3", "Action": "lambda:InvokeFunction", "Principal": "s3.amazonaws.com", - }) + }, jsonx.V1()) require.NoError(t, err) url := fmt.Sprintf("%s/2015-03-31/functions/%s/policy", server.URL, fnName) @@ -210,7 +211,7 @@ func TestLambda_GetAccountSettings(t *testing.T) { require.NoError(t, err) var out lambdapkg.AccountSettingsOutput - require.NoError(t, json.Unmarshal(respBody, &out)) + require.NoError(t, json.Unmarshal(respBody, &out, jsonx.V1())) require.NotNil(t, out.AccountLimit) assert.Positive(t, out.AccountLimit.ConcurrentExecutions) @@ -238,7 +239,7 @@ func TestLambda_CodeSigningConfig(t *testing.T) { }, }, "Description": "test config", - }) + }, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -251,7 +252,7 @@ func TestLambda_CodeSigningConfig(t *testing.T) { require.NoError(t, err) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRespBody, &createOut)) + require.NoError(t, json.Unmarshal(createRespBody, &createOut, jsonx.V1())) cscData, ok := createOut["CodeSigningConfig"].(map[string]any) require.True(t, ok, "expected CodeSigningConfig in response") @@ -279,13 +280,13 @@ func TestLambda_CodeSigningConfig(t *testing.T) { require.NoError(t, err) var listOut map[string]any - require.NoError(t, json.Unmarshal(listBody, &listOut)) + require.NoError(t, json.Unmarshal(listBody, &listOut, jsonx.V1())) configs, ok := listOut["CodeSigningConfigs"].([]any) require.True(t, ok) assert.Len(t, configs, 1) // Put function code signing config - putBody, err := json.Marshal(map[string]any{"CodeSigningConfigArn": cscARN}) + putBody, err := json.Marshal(map[string]any{"CodeSigningConfigArn": cscARN}, jsonx.V1()) require.NoError(t, err) putResp, err := doLambdaRequest(ctx, http.MethodPut, @@ -307,7 +308,7 @@ func TestLambda_CodeSigningConfig(t *testing.T) { require.NoError(t, err) var getFnCSCOut map[string]any - require.NoError(t, json.Unmarshal(getFnCSCBody, &getFnCSCOut)) + require.NoError(t, json.Unmarshal(getFnCSCBody, &getFnCSCOut, jsonx.V1())) assert.Equal(t, cscARN, getFnCSCOut["CodeSigningConfigArn"]) // List functions by code signing config @@ -364,7 +365,7 @@ func TestLambda_CapacityProvider(t *testing.T) { "SubnetIds": []string{"subnet-1"}, "SecurityGroupIds": []string{"sg-1"}, }, - }) + }, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -377,7 +378,7 @@ func TestLambda_CapacityProvider(t *testing.T) { require.NoError(t, err) var createOut map[string]any - require.NoError(t, json.Unmarshal(createRespBody, &createOut)) + require.NoError(t, json.Unmarshal(createRespBody, &createOut, jsonx.V1())) cpData, ok := createOut["CapacityProvider"].(map[string]any) require.True(t, ok, "expected CapacityProvider in response") @@ -410,7 +411,7 @@ func TestLambda_CapacityProvider(t *testing.T) { require.NoError(t, err) var listOut map[string]any - require.NoError(t, json.Unmarshal(listBody, &listOut)) + require.NoError(t, json.Unmarshal(listBody, &listOut, jsonx.V1())) providers, ok := listOut["CapacityProviders"].([]any) require.True(t, ok) assert.Len(t, providers, 1) @@ -418,7 +419,7 @@ func TestLambda_CapacityProvider(t *testing.T) { // Update capacity provider updateBody, err := json.Marshal(map[string]any{ "CapacityProviderScalingConfig": map[string]any{"MaxVCpuCount": 200}, - }) + }, jsonx.V1()) require.NoError(t, err) updateResp, err := doLambdaRequest(ctx, http.MethodPut, @@ -432,7 +433,7 @@ func TestLambda_CapacityProvider(t *testing.T) { require.NoError(t, err) var updateOut map[string]any - require.NoError(t, json.Unmarshal(updateRespBody, &updateOut)) + require.NoError(t, json.Unmarshal(updateRespBody, &updateOut, jsonx.V1())) cpUpdated, ok := updateOut["CapacityProvider"].(map[string]any) require.True(t, ok) scalingConfig, ok := cpUpdated["CapacityProviderScalingConfig"].(map[string]any) @@ -453,7 +454,7 @@ func TestLambda_CapacityProvider(t *testing.T) { require.NoError(t, err) var delOut map[string]any - require.NoError(t, json.Unmarshal(delRespBody, &delOut)) + require.NoError(t, json.Unmarshal(delRespBody, &delOut, jsonx.V1())) cpDeleted, ok := delOut["CapacityProvider"].(map[string]any) require.True(t, ok, "expected CapacityProvider in response") assert.Contains(t, cpDeleted["CapacityProviderArn"], "test-provider") @@ -497,7 +498,7 @@ func TestLambda_FunctionUrlConfig_2021(t *testing.T) { createLambdaFunction(ctx, t, server.URL, fnName) // Create function URL config via 2021 path - createBody, err := json.Marshal(map[string]any{"AuthType": "NONE"}) + createBody, err := json.Marshal(map[string]any{"AuthType": "NONE"}, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -518,7 +519,7 @@ func TestLambda_FunctionUrlConfig_2021(t *testing.T) { require.NoError(t, err) var cfg lambdapkg.FunctionURLConfig - require.NoError(t, json.Unmarshal(getBody, &cfg)) + require.NoError(t, json.Unmarshal(getBody, &cfg, jsonx.V1())) assert.Equal(t, "NONE", cfg.AuthType) // List function URL configs via 2021 path @@ -532,13 +533,13 @@ func TestLambda_FunctionUrlConfig_2021(t *testing.T) { require.NoError(t, err) var listOut map[string]any - require.NoError(t, json.Unmarshal(listBody, &listOut)) + require.NoError(t, json.Unmarshal(listBody, &listOut, jsonx.V1())) urlConfigs, ok := listOut["FunctionUrlConfigs"].([]any) require.True(t, ok) assert.Len(t, urlConfigs, 1) // Update function URL config via 2021 path - updateBody, err := json.Marshal(map[string]any{"AuthType": "AWS_IAM"}) + updateBody, err := json.Marshal(map[string]any{"AuthType": "AWS_IAM"}, jsonx.V1()) require.NoError(t, err) updateResp, err := doLambdaRequest(ctx, http.MethodPut, diff --git a/test/integration/lambda_test.go b/test/integration/lambda_test.go index 61c5494e7e..be2efb94dd 100644 --- a/test/integration/lambda_test.go +++ b/test/integration/lambda_test.go @@ -4,7 +4,7 @@ import ( "archive/tar" "bytes" "context" - "encoding/json" + "encoding/json/v2" "fmt" "io" "log/slog" @@ -23,6 +23,7 @@ import ( dockerapifilters "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/filters" dockerapiimage "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/image" dockerclient "github.com/blackbirdworks/gopherstack/internal/dockercompat/client" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -163,7 +164,7 @@ func TestLambdaIntegration_Invoke_DockerEchoContainer(t *testing.T) { "Code": map[string]string{"ImageUri": lambdaEchoImage}, "Role": "arn:aws:iam::000000000000:role/lambda-test", "Timeout": 15, // 15s gives enough time for container cold start in CI - }) + }, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, server.URL+"/2015-03-31/functions", @@ -277,7 +278,7 @@ func buildEchoLambdaImage(ctx context.Context, t *testing.T) { Error string `json:"error"` } - if jsonErr := json.Unmarshal([]byte(line), &msg); jsonErr == nil && msg.Error != "" { + if jsonErr := json.Unmarshal([]byte(line), &msg, jsonx.V1()); jsonErr == nil && msg.Error != "" { t.Skipf("Docker image build failed (infrastructure error — skipping test): %s", msg.Error) } } diff --git a/test/integration/lambda_url_test.go b/test/integration/lambda_url_test.go index 5b2d90431a..ed78d18226 100644 --- a/test/integration/lambda_url_test.go +++ b/test/integration/lambda_url_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "fmt" "io" "log/slog" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/portalloc/portalloctest" "github.com/blackbirdworks/gopherstack/pkgs/service" @@ -57,7 +58,7 @@ func TestLambdaFunctionURL_CreateGetDelete(t *testing.T) { "PackageType": "Image", "Code": map[string]string{"ImageUri": "test:latest"}, "Role": "arn:aws:iam::000000000000:role/test", - }) + }, jsonx.V1()) require.NoError(t, err) resp, err := doLambdaRequest(ctx, http.MethodPost, server.URL+"/2015-03-31/functions", @@ -67,7 +68,7 @@ func TestLambdaFunctionURL_CreateGetDelete(t *testing.T) { require.Equal(t, http.StatusCreated, resp.StatusCode) // Create function URL config - urlBody, err := json.Marshal(map[string]any{"AuthType": "NONE"}) + urlBody, err := json.Marshal(map[string]any{"AuthType": "NONE"}, jsonx.V1()) require.NoError(t, err) urlResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -80,7 +81,7 @@ func TestLambdaFunctionURL_CreateGetDelete(t *testing.T) { var cfg lambdapkg.FunctionURLConfig respBody, err := io.ReadAll(urlResp.Body) require.NoError(t, err) - require.NoError(t, json.Unmarshal(respBody, &cfg)) + require.NoError(t, json.Unmarshal(respBody, &cfg, jsonx.V1())) assert.NotEmpty(t, cfg.FunctionURL, "FunctionURL should not be empty") assert.Equal(t, "NONE", cfg.AuthType) @@ -100,7 +101,7 @@ func TestLambdaFunctionURL_CreateGetDelete(t *testing.T) { var getCfg lambdapkg.FunctionURLConfig getBody, err := io.ReadAll(getResp.Body) require.NoError(t, err) - require.NoError(t, json.Unmarshal(getBody, &getCfg)) + require.NoError(t, json.Unmarshal(getBody, &getCfg, jsonx.V1())) assert.Equal(t, cfg.FunctionURL, getCfg.FunctionURL) // Delete function URL config diff --git a/test/integration/lambda_zip_test.go b/test/integration/lambda_zip_test.go index 0178191953..4a195a7864 100644 --- a/test/integration/lambda_zip_test.go +++ b/test/integration/lambda_zip_test.go @@ -4,7 +4,7 @@ import ( "archive/zip" "bytes" "encoding/base64" - "encoding/json" + "encoding/json/v2" "io" "log/slog" "net/http" @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" pkglogger "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" lambdapkg "github.com/blackbirdworks/gopherstack/services/lambda" @@ -75,7 +76,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { "Handler": "index.handler", "Code": map[string]string{"ZipFile": zipB64}, "Role": "arn:aws:iam::000000000000:role/test", - }) + }, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -87,7 +88,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { var created lambdapkg.FunctionConfiguration body, err := io.ReadAll(createResp.Body) require.NoError(t, err) - require.NoError(t, json.Unmarshal(body, &created)) + require.NoError(t, json.Unmarshal(body, &created, jsonx.V1())) assert.Equal(t, "zip-crud-fn", created.FunctionName) assert.Equal(t, lambdapkg.PackageTypeZip, created.PackageType) assert.Equal(t, "python3.12", created.Runtime) @@ -103,7 +104,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { getBody, err := io.ReadAll(getResp.Body) require.NoError(t, err) var getOut lambdapkg.GetFunctionOutput - require.NoError(t, json.Unmarshal(getBody, &getOut)) + require.NoError(t, json.Unmarshal(getBody, &getOut, jsonx.V1())) assert.Equal(t, lambdapkg.PackageTypeZip, getOut.Configuration.PackageType) require.NotNil(t, getOut.Code) assert.Equal(t, "S3", getOut.Code.RepositoryType) // Zip locations use S3 repository type @@ -120,7 +121,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { } listBody, err := io.ReadAll(listResp.Body) require.NoError(t, err) - require.NoError(t, json.Unmarshal(listBody, &listOut)) + require.NoError(t, json.Unmarshal(listBody, &listOut, jsonx.V1())) require.Len(t, listOut.Functions, 1) assert.Equal(t, "zip-crud-fn", listOut.Functions[0].FunctionName) @@ -128,7 +129,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { newZipB64 := makeZipPayload(t, "index.py", `def handler(event, context): return "v2"`) updateCodeBody, err := json.Marshal(map[string]any{ "ZipFile": newZipB64, - }) + }, jsonx.V1()) require.NoError(t, err) updateCodeResp, err := doLambdaRequest(ctx, http.MethodPut, server.URL+"/2015-03-31/functions/zip-crud-fn/code", "application/json", @@ -142,7 +143,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { "Runtime": "python3.13", "Handler": "main.handler", "Description": "updated", - }) + }, jsonx.V1()) require.NoError(t, err) updateCfgResp, err := doLambdaRequest(ctx, http.MethodPut, server.URL+"/2015-03-31/functions/zip-crud-fn/configuration", "application/json", @@ -154,7 +155,7 @@ func TestIntegration_Lambda_Zip_CRUD(t *testing.T) { var updatedCfg lambdapkg.FunctionConfiguration cfgBody, err := io.ReadAll(updateCfgResp.Body) require.NoError(t, err) - require.NoError(t, json.Unmarshal(cfgBody, &updatedCfg)) + require.NoError(t, json.Unmarshal(cfgBody, &updatedCfg, jsonx.V1())) assert.Equal(t, "python3.13", updatedCfg.Runtime) assert.Equal(t, "main.handler", updatedCfg.Handler) @@ -235,7 +236,7 @@ func TestIntegration_Lambda_Zip_ValidationErrors(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bodyJSON, err := json.Marshal(tt.body) + bodyJSON, err := json.Marshal(tt.body, jsonx.V1()) require.NoError(t, err) resp, err := doLambdaRequest(ctx, http.MethodPost, @@ -278,7 +279,7 @@ func TestIntegration_Lambda_Zip_S3Code(t *testing.T) { "S3Key": "functions/my-fn.zip", }, "Role": "arn:aws:iam::000000000000:role/test", - }) + }, jsonx.V1()) require.NoError(t, err) createResp, err := doLambdaRequest(ctx, http.MethodPost, @@ -297,7 +298,7 @@ func TestIntegration_Lambda_Zip_S3Code(t *testing.T) { getBody, err := io.ReadAll(getResp.Body) require.NoError(t, err) var getOut lambdapkg.GetFunctionOutput - require.NoError(t, json.Unmarshal(getBody, &getOut)) + require.NoError(t, json.Unmarshal(getBody, &getOut, jsonx.V1())) assert.Equal(t, "nodejs20.x", getOut.Configuration.Runtime) require.NotNil(t, getOut.Code) assert.Equal(t, "S3", getOut.Code.RepositoryType) diff --git a/test/integration/opensearch_test.go b/test/integration/opensearch_test.go index 0031681007..dbd84d42ab 100644 --- a/test/integration/opensearch_test.go +++ b/test/integration/opensearch_test.go @@ -2,7 +2,8 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "fmt" "io" "net/http" @@ -11,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -23,7 +25,7 @@ func doOpenSearchRequest(t *testing.T, method, path string, body any) (int, map[ var reqBody io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) reqBody = bytes.NewReader(b) @@ -43,7 +45,7 @@ func doOpenSearchRequest(t *testing.T, method, path string, body any) (int, map[ var result map[string]any - if err = json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &result, jsonx.V1()); err != nil { result = nil } diff --git a/test/integration/pipes_sqs_lambda_test.go b/test/integration/pipes_sqs_lambda_test.go index 2c409e1c84..0c0319095d 100644 --- a/test/integration/pipes_sqs_lambda_test.go +++ b/test/integration/pipes_sqs_lambda_test.go @@ -2,7 +2,7 @@ package integration_test import ( "context" - "encoding/json" + "encoding/json/v2" "log/slog" "net/http/httptest" "strings" @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/logger" "github.com/blackbirdworks/gopherstack/pkgs/service" pipespkg "github.com/blackbirdworks/gopherstack/services/pipes" @@ -219,7 +220,7 @@ func TestIntegration_Pipes_SQS_To_Lambda(t *testing.T) { EventSource string `json:"eventSource"` } `json:"Records"` } - require.NoError(t, json.Unmarshal(payload, &event)) + require.NoError(t, json.Unmarshal(payload, &event, jsonx.V1())) assert.NotEmpty(t, event.Records, "payload should have at least one record") assert.Equal(t, "aws:sqs", event.Records[0].EventSource) diff --git a/test/integration/pipes_test.go b/test/integration/pipes_test.go index badca5b3eb..e4cfe31c4a 100644 --- a/test/integration/pipes_test.go +++ b/test/integration/pipes_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func pipesRequest(t *testing.T, method, path string, body any) *http.Response { if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } diff --git a/test/integration/ram_test.go b/test/integration/ram_test.go index 2c311260e9..18a47025d1 100644 --- a/test/integration/ram_test.go +++ b/test/integration/ram_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func ramRequest(t *testing.T, method, path string, body any) *http.Response { if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -91,7 +92,7 @@ func TestIntegration_RAM_UpdateResourceShare(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "create body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) shareARN := createResult["resourceShare"].(map[string]any)["resourceShareArn"].(string) @@ -116,7 +117,7 @@ func TestIntegration_RAM_DeleteResourceShare(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "create body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) shareARN := createResult["resourceShare"].(map[string]any)["resourceShareArn"].(string) @@ -139,7 +140,7 @@ func TestIntegration_RAM_AssociateResourceShare(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) shareARN := createResult["resourceShare"].(map[string]any)["resourceShareArn"].(string) @@ -164,7 +165,7 @@ func TestIntegration_RAM_TagResource(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) shareARN := createResult["resourceShare"].(map[string]any)["resourceShareArn"].(string) diff --git a/test/integration/rdsdata_test.go b/test/integration/rdsdata_test.go index 6e064344df..da5dc8d7e2 100644 --- a/test/integration/rdsdata_test.go +++ b/test/integration/rdsdata_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func rdsdataRequest(t *testing.T, path string, body any) *http.Response { if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -96,7 +97,7 @@ func TestIntegration_RDSData_TransactionLifecycle(t *testing.T) { // Parse the transaction ID. var beginResult map[string]any - require.NoError(t, json.Unmarshal([]byte(beginBody), &beginResult)) + require.NoError(t, json.Unmarshal([]byte(beginBody), &beginResult, jsonx.V1())) txID, ok := beginResult["transactionId"].(string) require.True(t, ok, "transactionId should be a string") require.NotEmpty(t, txID) @@ -135,7 +136,7 @@ func TestIntegration_RDSData_RollbackTransaction(t *testing.T) { require.Equal(t, http.StatusOK, beginResp.StatusCode, "body: %s", beginBody) var beginResult map[string]any - require.NoError(t, json.Unmarshal([]byte(beginBody), &beginResult)) + require.NoError(t, json.Unmarshal([]byte(beginBody), &beginResult, jsonx.V1())) txID, ok := beginResult["transactionId"].(string) require.True(t, ok) require.NotEmpty(t, txID) diff --git a/test/integration/redshiftdata_test.go b/test/integration/redshiftdata_test.go index 8bac0ad4ab..cd0a36967d 100644 --- a/test/integration/redshiftdata_test.go +++ b/test/integration/redshiftdata_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func redshiftDataRequest(t *testing.T, target string, body any) *http.Response { if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -62,7 +63,7 @@ func TestIntegration_RedshiftData_ExecuteStatement(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var respObj map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &respObj)) + require.NoError(t, json.Unmarshal([]byte(body), &respObj, jsonx.V1())) assert.NotEmpty(t, respObj["Id"], "expected statement ID in response") } @@ -79,7 +80,7 @@ func TestIntegration_RedshiftData_DescribeStatement(t *testing.T) { require.Equal(t, http.StatusOK, execResp.StatusCode, "body: %s", execBody) var execObj map[string]any - require.NoError(t, json.Unmarshal([]byte(execBody), &execObj)) + require.NoError(t, json.Unmarshal([]byte(execBody), &execObj, jsonx.V1())) id := execObj["Id"].(string) @@ -89,7 +90,7 @@ func TestIntegration_RedshiftData_DescribeStatement(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var descObj map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &descObj)) + require.NoError(t, json.Unmarshal([]byte(body), &descObj, jsonx.V1())) assert.Equal(t, id, descObj["Id"]) assert.Equal(t, "FINISHED", descObj["Status"]) } @@ -107,7 +108,7 @@ func TestIntegration_RedshiftData_GetStatementResult(t *testing.T) { require.Equal(t, http.StatusOK, execResp.StatusCode, "body: %s", execBody) var execObj map[string]any - require.NoError(t, json.Unmarshal([]byte(execBody), &execObj)) + require.NoError(t, json.Unmarshal([]byte(execBody), &execObj, jsonx.V1())) id := execObj["Id"].(string) @@ -149,7 +150,7 @@ func TestIntegration_RedshiftData_BatchExecuteStatement(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var respObj map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &respObj)) + require.NoError(t, json.Unmarshal([]byte(body), &respObj, jsonx.V1())) assert.NotEmpty(t, respObj["Id"]) } diff --git a/test/integration/resourcegroups_test.go b/test/integration/resourcegroups_test.go index 6dea026c60..a5d485cf53 100644 --- a/test/integration/resourcegroups_test.go +++ b/test/integration/resourcegroups_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func resourceGroupsPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) diff --git a/test/integration/resourcegroupstaggingapi_test.go b/test/integration/resourcegroupstaggingapi_test.go index ef2941bd90..f3d41d1862 100644 --- a/test/integration/resourcegroupstaggingapi_test.go +++ b/test/integration/resourcegroupstaggingapi_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -21,7 +22,7 @@ import ( func taggingAPIPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) @@ -115,7 +116,7 @@ func TestIntegration_TaggingAPI_TagResources(t *testing.T) { // Decode the response and assert no failures. var tagOut map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &tagOut)) + require.NoError(t, json.Unmarshal([]byte(body), &tagOut, jsonx.V1())) assert.Empty(t, tagOut["FailedResourcesMap"], "expected no tag failures") // Verify via GetResources that the tagged queue appears when filtering by tag. diff --git a/test/integration/route53resolver_test.go b/test/integration/route53resolver_test.go index 182c4bb681..48b50ebc55 100644 --- a/test/integration/route53resolver_test.go +++ b/test/integration/route53resolver_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func route53resolverPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) @@ -91,7 +92,7 @@ func TestIntegration_Route53Resolver_GetEndpoint(t *testing.T) { var createBody map[string]any createData, _ := io.ReadAll(createResp.Body) createResp.Body.Close() - require.NoError(t, json.Unmarshal(createData, &createBody)) + require.NoError(t, json.Unmarshal(createData, &createBody, jsonx.V1())) ep, _ := createBody["ResolverEndpoint"].(map[string]any) id, _ := ep["Id"].(string) @@ -120,7 +121,7 @@ func TestIntegration_Route53Resolver_DeleteEndpoint(t *testing.T) { var createBody map[string]any createData, _ := io.ReadAll(createResp.Body) createResp.Body.Close() - require.NoError(t, json.Unmarshal(createData, &createBody)) + require.NoError(t, json.Unmarshal(createData, &createBody, jsonx.V1())) ep, _ := createBody["ResolverEndpoint"].(map[string]any) id, _ := ep["Id"].(string) @@ -179,7 +180,7 @@ func TestIntegration_Route53Resolver_DeleteRule(t *testing.T) { var createBody map[string]any createData, _ := io.ReadAll(createResp.Body) createResp.Body.Close() - require.NoError(t, json.Unmarshal(createData, &createBody)) + require.NoError(t, json.Unmarshal(createData, &createBody, jsonx.V1())) rule, _ := createBody["ResolverRule"].(map[string]any) id, _ := rule["Id"].(string) @@ -206,7 +207,7 @@ func TestIntegration_Route53Resolver_GetRule(t *testing.T) { var createBody map[string]any createData, _ := io.ReadAll(createResp.Body) createResp.Body.Close() - require.NoError(t, json.Unmarshal(createData, &createBody)) + require.NoError(t, json.Unmarshal(createData, &createBody, jsonx.V1())) rule, _ := createBody["ResolverRule"].(map[string]any) id, _ := rule["Id"].(string) diff --git a/test/integration/s3_eventbridge_test.go b/test/integration/s3_eventbridge_test.go index 0e2cbd2738..4349ab6718 100644 --- a/test/integration/s3_eventbridge_test.go +++ b/test/integration/s3_eventbridge_test.go @@ -1,7 +1,8 @@ package integration_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "testing" "time" @@ -16,6 +17,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestIntegration_S3_NotificationToEventBridge verifies that when EventBridgeConfiguration @@ -144,37 +147,37 @@ func TestIntegration_S3_NotificationToEventBridge(t *testing.T) { }, 10*time.Second, 500*time.Millisecond, "should receive EventBridge event in SQS within 10s") // Validate the EventBridge event envelope. - var event map[string]json.RawMessage - require.NoError(t, json.Unmarshal([]byte(receivedMsg), &event), "event must be valid JSON") + var event map[string]jsontext.Value + require.NoError(t, json.Unmarshal([]byte(receivedMsg), &event, jsonx.V1()), "event must be valid JSON") var source string - require.NoError(t, json.Unmarshal(event["source"], &source)) + require.NoError(t, json.Unmarshal(event["source"], &source, jsonx.V1())) assert.Equal(t, "aws.s3", source) var detailType string - require.NoError(t, json.Unmarshal(event["detail-type"], &detailType)) + require.NoError(t, json.Unmarshal(event["detail-type"], &detailType, jsonx.V1())) assert.Equal(t, "Object Created", detailType) // Validate the event detail. - var detail map[string]json.RawMessage - require.NoError(t, json.Unmarshal(event["detail"], &detail)) + var detail map[string]jsontext.Value + require.NoError(t, json.Unmarshal(event["detail"], &detail, jsonx.V1())) - var bucketDetail map[string]json.RawMessage - require.NoError(t, json.Unmarshal(detail["bucket"], &bucketDetail)) + var bucketDetail map[string]jsontext.Value + require.NoError(t, json.Unmarshal(detail["bucket"], &bucketDetail, jsonx.V1())) var bucketName string - require.NoError(t, json.Unmarshal(bucketDetail["name"], &bucketName)) + require.NoError(t, json.Unmarshal(bucketDetail["name"], &bucketName, jsonx.V1())) assert.Equal(t, bucket, bucketName) - var objectDetail map[string]json.RawMessage - require.NoError(t, json.Unmarshal(detail["object"], &objectDetail)) + var objectDetail map[string]jsontext.Value + require.NoError(t, json.Unmarshal(detail["object"], &objectDetail, jsonx.V1())) var key string - require.NoError(t, json.Unmarshal(objectDetail["key"], &key)) + require.NoError(t, json.Unmarshal(objectDetail["key"], &key, jsonx.V1())) assert.Equal(t, objectKey, key) var reason string - require.NoError(t, json.Unmarshal(detail["reason"], &reason)) + require.NoError(t, json.Unmarshal(detail["reason"], &reason, jsonx.V1())) assert.Equal(t, "PutObject", reason) t.Logf("S3→EventBridge event delivered: source=%s detail-type=%s bucket=%s key=%s reason=%s", diff --git a/test/integration/s3_notification_test.go b/test/integration/s3_notification_test.go index 3ce2a51233..887d6cf38a 100644 --- a/test/integration/s3_notification_test.go +++ b/test/integration/s3_notification_test.go @@ -1,7 +1,8 @@ package integration_test import ( - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "strings" "testing" "time" @@ -14,6 +15,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // TestIntegration_S3_NotificationToSQS verifies that S3 bucket notifications @@ -117,41 +120,45 @@ func TestIntegration_S3_NotificationToSQS(t *testing.T) { }, 10*time.Second, 500*time.Millisecond, "should receive SQS notification within 10s") // Validate the notification payload format. - var payload map[string]json.RawMessage - require.NoError(t, json.Unmarshal([]byte(receivedMsg), &payload), "notification body must be valid JSON") + var payload map[string]jsontext.Value + require.NoError( + t, + json.Unmarshal([]byte(receivedMsg), &payload, jsonx.V1()), + "notification body must be valid JSON", + ) records, ok := payload["Records"] require.True(t, ok, "notification must contain a Records array") - var recordList []map[string]json.RawMessage - require.NoError(t, json.Unmarshal(records, &recordList)) + var recordList []map[string]jsontext.Value + require.NoError(t, json.Unmarshal(records, &recordList, jsonx.V1())) require.NotEmpty(t, recordList) record := recordList[0] var eventSource string - require.NoError(t, json.Unmarshal(record["eventSource"], &eventSource)) + require.NoError(t, json.Unmarshal(record["eventSource"], &eventSource, jsonx.V1())) assert.Equal(t, "aws:s3", eventSource) var eventName string - require.NoError(t, json.Unmarshal(record["eventName"], &eventName)) + require.NoError(t, json.Unmarshal(record["eventName"], &eventName, jsonx.V1())) assert.True(t, strings.HasPrefix(eventName, "s3:ObjectCreated:"), "eventName should be ObjectCreated") - var s3Block map[string]json.RawMessage - require.NoError(t, json.Unmarshal(record["s3"], &s3Block)) + var s3Block map[string]jsontext.Value + require.NoError(t, json.Unmarshal(record["s3"], &s3Block, jsonx.V1())) - var bucketBlock map[string]json.RawMessage - require.NoError(t, json.Unmarshal(s3Block["bucket"], &bucketBlock)) + var bucketBlock map[string]jsontext.Value + require.NoError(t, json.Unmarshal(s3Block["bucket"], &bucketBlock, jsonx.V1())) var bucketName string - require.NoError(t, json.Unmarshal(bucketBlock["name"], &bucketName)) + require.NoError(t, json.Unmarshal(bucketBlock["name"], &bucketName, jsonx.V1())) assert.Equal(t, bucket, bucketName) - var objectBlock map[string]json.RawMessage - require.NoError(t, json.Unmarshal(s3Block["object"], &objectBlock)) + var objectBlock map[string]jsontext.Value + require.NoError(t, json.Unmarshal(s3Block["object"], &objectBlock, jsonx.V1())) var key string - require.NoError(t, json.Unmarshal(objectBlock["key"], &key)) + require.NoError(t, json.Unmarshal(objectBlock["key"], &key, jsonx.V1())) assert.Equal(t, objectKey, key) t.Logf("S3 notification delivered: eventName=%s bucket=%s key=%s", eventName, bucketName, key) diff --git a/test/integration/scheduler_test.go b/test/integration/scheduler_test.go index 1a1c5a58d0..b4ac70736e 100644 --- a/test/integration/scheduler_test.go +++ b/test/integration/scheduler_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func schedulerPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) diff --git a/test/integration/secretsmanager_rotation_test.go b/test/integration/secretsmanager_rotation_test.go index 2c694c4496..b2d016e017 100644 --- a/test/integration/secretsmanager_rotation_test.go +++ b/test/integration/secretsmanager_rotation_test.go @@ -2,7 +2,7 @@ package integration_test import ( "context" - "encoding/json" + "encoding/json/v2" "io" "net" "net/http/httptest" @@ -23,6 +23,7 @@ import ( dockerapibuild "github.com/blackbirdworks/gopherstack/internal/dockercompat/api/types/build" dockerclient "github.com/blackbirdworks/gopherstack/internal/dockercompat/client" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/container" "github.com/blackbirdworks/gopherstack/pkgs/logger" @@ -384,7 +385,7 @@ func buildRotationLambdaImage(ctx context.Context, t *testing.T) { Error string `json:"error"` } - if jsonErr := json.Unmarshal([]byte(line), &event); jsonErr == nil && event.Error != "" { + if jsonErr := json.Unmarshal([]byte(line), &event, jsonx.V1()); jsonErr == nil && event.Error != "" { buildErrorMsg = event.Error break diff --git a/test/integration/servicediscovery_test.go b/test/integration/servicediscovery_test.go index 129815659c..51fcb962fd 100644 --- a/test/integration/servicediscovery_test.go +++ b/test/integration/servicediscovery_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func servicediscoveryRequest(t *testing.T, op string, body any) *http.Response { if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -71,7 +72,7 @@ func TestIntegration_ServiceDiscovery_NamespaceLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) opID, ok := createResult["OperationId"].(string) require.True(t, ok) require.NotEmpty(t, opID) @@ -82,7 +83,7 @@ func TestIntegration_ServiceDiscovery_NamespaceLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, opResp.StatusCode, "body: %s", opBody) var opResult map[string]any - require.NoError(t, json.Unmarshal([]byte(opBody), &opResult)) + require.NoError(t, json.Unmarshal([]byte(opBody), &opResult, jsonx.V1())) operation := opResult["Operation"].(map[string]any) targets := operation["Targets"].(map[string]any) nsID, ok := targets["NAMESPACE"].(string) @@ -114,7 +115,7 @@ func TestIntegration_ServiceDiscovery_ServiceLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) svcData := createResult["Service"].(map[string]any) svcID, ok := svcData["Id"].(string) require.True(t, ok) @@ -162,14 +163,14 @@ func TestIntegration_ServiceDiscovery_UpdateHttpNamespace(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) opID := createResult["OperationId"].(string) // Get the operation to find the namespace ID. opResp := servicediscoveryRequest(t, "GetOperation", map[string]any{"OperationId": opID}) opBody := servicediscoveryReadBody(t, opResp) var opResult map[string]any - require.NoError(t, json.Unmarshal([]byte(opBody), &opResult)) + require.NoError(t, json.Unmarshal([]byte(opBody), &opResult, jsonx.V1())) nsID := opResult["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) // Update the namespace. @@ -193,13 +194,13 @@ func TestIntegration_ServiceDiscovery_UpdatePrivateDnsNamespace(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) opID := createResult["OperationId"].(string) opResp := servicediscoveryRequest(t, "GetOperation", map[string]any{"OperationId": opID}) opBody := servicediscoveryReadBody(t, opResp) var opResult map[string]any - require.NoError(t, json.Unmarshal([]byte(opBody), &opResult)) + require.NoError(t, json.Unmarshal([]byte(opBody), &opResult, jsonx.V1())) nsID := opResult["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) updateResp := servicediscoveryRequest(t, "UpdatePrivateDnsNamespace", map[string]any{ @@ -222,13 +223,13 @@ func TestIntegration_ServiceDiscovery_UpdatePublicDnsNamespace(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) opID := createResult["OperationId"].(string) opResp := servicediscoveryRequest(t, "GetOperation", map[string]any{"OperationId": opID}) opBody := servicediscoveryReadBody(t, opResp) var opResult map[string]any - require.NoError(t, json.Unmarshal([]byte(opBody), &opResult)) + require.NoError(t, json.Unmarshal([]byte(opBody), &opResult, jsonx.V1())) nsID := opResult["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) updateResp := servicediscoveryRequest(t, "UpdatePublicDnsNamespace", map[string]any{ @@ -251,7 +252,7 @@ func TestIntegration_ServiceDiscovery_UpdateService(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) svcID := createResult["Service"].(map[string]any)["Id"].(string) updateResp := servicediscoveryRequest(t, "UpdateService", map[string]any{ @@ -281,7 +282,7 @@ func TestIntegration_ServiceDiscovery_ServiceAttributesLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) svcID := createResult["Service"].(map[string]any)["Id"].(string) // UpdateServiceAttributes. @@ -332,7 +333,7 @@ func TestIntegration_ServiceDiscovery_UpdateInstanceCustomHealthStatus(t *testin require.Equal(t, http.StatusOK, createResp.StatusCode, "body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) svcID := createResult["Service"].(map[string]any)["Id"].(string) regResp := servicediscoveryRequest(t, "RegisterInstance", map[string]any{ @@ -374,13 +375,13 @@ func TestIntegration_ServiceDiscovery_DiscoverInstancesRevision(t *testing.T) { require.Equal(t, http.StatusOK, createNsResp.StatusCode, "body: %s", createNsBody) var createNsResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createNsBody), &createNsResult)) + require.NoError(t, json.Unmarshal([]byte(createNsBody), &createNsResult, jsonx.V1())) opID := createNsResult["OperationId"].(string) opResp := servicediscoveryRequest(t, "GetOperation", map[string]any{"OperationId": opID}) opBody := servicediscoveryReadBody(t, opResp) var opResult map[string]any - require.NoError(t, json.Unmarshal([]byte(opBody), &opResult)) + require.NoError(t, json.Unmarshal([]byte(opBody), &opResult, jsonx.V1())) nsID := opResult["Operation"].(map[string]any)["Targets"].(map[string]any)["NAMESPACE"].(string) createSvcResp := servicediscoveryRequest(t, "CreateService", map[string]any{ diff --git a/test/integration/sesv2_test.go b/test/integration/sesv2_test.go index 8cb337a866..56df2741dd 100644 --- a/test/integration/sesv2_test.go +++ b/test/integration/sesv2_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "net/url" @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -21,7 +22,7 @@ func sesv2Do(t *testing.T, method, path string, body any) *http.Response { var bodyReader io.Reader if body != nil { - b, err := json.Marshal(body) + b, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) bodyReader = bytes.NewReader(b) @@ -62,7 +63,7 @@ func TestIntegration_SESv2_CreateEmailIdentity(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, "EMAIL_ADDRESS", out["IdentityType"]) assert.Equal(t, true, out["VerifiedForSendingStatus"]) } @@ -83,7 +84,7 @@ func TestIntegration_SESv2_GetEmailIdentity(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, "EMAIL_ADDRESS", out["IdentityType"]) } @@ -105,7 +106,7 @@ func TestIntegration_SESv2_ListEmailIdentities(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) identities, ok := out["EmailIdentities"].([]any) require.True(t, ok) @@ -165,7 +166,7 @@ func TestIntegration_SESv2_SendEmail(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.NotEmpty(t, out["MessageId"]) } @@ -197,7 +198,7 @@ func TestIntegration_SESv2_GetConfigurationSet(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, name, out["ConfigurationSetName"]) } @@ -218,7 +219,7 @@ func TestIntegration_SESv2_ListConfigurationSets(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) sets, ok := out["ConfigurationSets"].([]any) require.True(t, ok) @@ -267,7 +268,7 @@ func TestIntegration_SESv2_URLEncodedIdentity(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "GET with percent-encoded identity should succeed; body: %s", body) var out map[string]any - require.NoError(t, json.Unmarshal([]byte(body), &out)) + require.NoError(t, json.Unmarshal([]byte(body), &out, jsonx.V1())) assert.Equal(t, "EMAIL_ADDRESS", out["IdentityType"]) // DELETE with percent-encoded path. diff --git a/test/integration/sns_roundtrip_test.go b/test/integration/sns_roundtrip_test.go index 0a13441dcf..de8a8d75ee 100644 --- a/test/integration/sns_roundtrip_test.go +++ b/test/integration/sns_roundtrip_test.go @@ -1,7 +1,7 @@ package integration_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -12,6 +12,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // snsEnvelope is the JSON body SNS delivers to a subscribed SQS queue @@ -64,7 +66,7 @@ func TestIntegration_SNS_AddRemovePermissionRoundTrip(t *testing.T) { require.NoError(t, err) var policy snsPolicyDocument - require.NoError(t, json.Unmarshal([]byte(attrsOut.Attributes["Policy"]), &policy)) + require.NoError(t, json.Unmarshal([]byte(attrsOut.Attributes["Policy"]), &policy, jsonx.V1())) var granted *snsPolicyStatement @@ -90,7 +92,7 @@ func TestIntegration_SNS_AddRemovePermissionRoundTrip(t *testing.T) { require.NoError(t, err) var policyAfter snsPolicyDocument - require.NoError(t, json.Unmarshal([]byte(attrsOut2.Attributes["Policy"]), &policyAfter)) + require.NoError(t, json.Unmarshal([]byte(attrsOut2.Attributes["Policy"]), &policyAfter, jsonx.V1())) for _, stmt := range policyAfter.Statement { assert.NotEqual(t, label, stmt.Sid, "RemovePermission should drop the statement from Policy") @@ -244,7 +246,7 @@ func TestIntegration_SNS_PublishBatchRoundTrip(t *testing.T) { for _, msg := range recvOut.Messages { var env snsEnvelope - require.NoError(t, json.Unmarshal([]byte(aws.ToString(msg.Body)), &env)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(msg.Body)), &env, jsonx.V1())) assert.Equal(t, topicARN, env.TopicArn) priority := "" diff --git a/test/integration/sns_sqs_cross_test.go b/test/integration/sns_sqs_cross_test.go index 9a42496ac2..ba08b9eb91 100644 --- a/test/integration/sns_sqs_cross_test.go +++ b/test/integration/sns_sqs_cross_test.go @@ -1,7 +1,7 @@ package integration_test import ( - "encoding/json" + "encoding/json/v2" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -12,6 +12,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) func TestIntegration_SNS_SQS_CrossService(t *testing.T) { @@ -71,7 +73,7 @@ func TestIntegration_SNS_SQS_CrossService(t *testing.T) { // Message body should be SNS envelope JSON var envelope map[string]any - require.NoError(t, json.Unmarshal([]byte(*recvOut.Messages[0].Body), &envelope)) + require.NoError(t, json.Unmarshal([]byte(*recvOut.Messages[0].Body), &envelope, jsonx.V1())) assert.Equal(t, "Notification", envelope["Type"]) // Cleanup @@ -286,7 +288,7 @@ func TestIntegration_SNS_SQS_RedrivePolicy(t *testing.T) { // DLQ receives the same body that was attempted to the failed queue. // Without RawMessageDelivery=true, that body is the SNS envelope JSON. var dlqEnvelope map[string]any - require.NoError(t, json.Unmarshal([]byte(*recvOut.Messages[0].Body), &dlqEnvelope)) + require.NoError(t, json.Unmarshal([]byte(*recvOut.Messages[0].Body), &dlqEnvelope, jsonx.V1())) assert.Equal(t, "Notification", dlqEnvelope["Type"]) assert.Equal(t, msgBody, dlqEnvelope["Message"]) diff --git a/test/integration/stepfunctions_asl_test.go b/test/integration/stepfunctions_asl_test.go index a511cc442b..b33b479ff1 100644 --- a/test/integration/stepfunctions_asl_test.go +++ b/test/integration/stepfunctions_asl_test.go @@ -1,7 +1,7 @@ package integration_test import ( - "encoding/json" + "encoding/json/v2" "fmt" "testing" "time" @@ -12,6 +12,8 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) // passThroughDefinition is a minimal ASL state machine that passes input through. @@ -335,7 +337,7 @@ func TestIntegration_StepFunctions_FullExecution(t *testing.T) { assert.Equal(t, "SUCCEEDED", string(descOut.Status)) var output map[string]any - require.NoError(t, json.Unmarshal([]byte(aws.ToString(descOut.Output)), &output)) + require.NoError(t, json.Unmarshal([]byte(aws.ToString(descOut.Output)), &output, jsonx.V1())) require.Contains(t, output, "original", "output should contain the original input field") assert.Equal(t, "data", output["original"]) meta, ok := output["meta"].(map[string]any) diff --git a/test/integration/swf_test.go b/test/integration/swf_test.go index 9301f160ea..f5679ccf73 100644 --- a/test/integration/swf_test.go +++ b/test/integration/swf_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,13 +10,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) func swfPost(t *testing.T, action string, body any) *http.Response { t.Helper() - bodyBytes, err := json.Marshal(body) + bodyBytes, err := json.Marshal(body, jsonx.V1()) require.NoError(t, err) req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, endpoint, bytes.NewReader(bodyBytes)) diff --git a/test/integration/verifiedpermissions_test.go b/test/integration/verifiedpermissions_test.go index 26f7cabf2d..31bbc1f750 100644 --- a/test/integration/verifiedpermissions_test.go +++ b/test/integration/verifiedpermissions_test.go @@ -2,7 +2,7 @@ package integration_test import ( "bytes" - "encoding/json" + "encoding/json/v2" "io" "net/http" "testing" @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -20,7 +21,7 @@ func verifiedPermissionsPost(t *testing.T, action string, body any) *http.Respon if body != nil { var err error - bodyBytes, err = json.Marshal(body) + bodyBytes, err = json.Marshal(body, jsonx.V1()) require.NoError(t, err) } @@ -72,7 +73,7 @@ func TestIntegration_VerifiedPermissions_PolicyStoreLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createResp.StatusCode, "create body: %s", createBody) var createResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createBody), &createResult)) + require.NoError(t, json.Unmarshal([]byte(createBody), &createResult, jsonx.V1())) storeID, ok := createResult["policyStoreId"].(string) require.True(t, ok, "policyStoreId should be a string") require.NotEmpty(t, storeID) @@ -127,7 +128,7 @@ func TestIntegration_VerifiedPermissions_PolicyLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createStoreResp.StatusCode, "create store body: %s", createStoreBody) var storeResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createStoreBody), &storeResult)) + require.NoError(t, json.Unmarshal([]byte(createStoreBody), &storeResult, jsonx.V1())) storeID := storeResult["policyStoreId"].(string) // Create a policy. @@ -143,7 +144,7 @@ func TestIntegration_VerifiedPermissions_PolicyLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createPolicyResp.StatusCode, "create policy body: %s", createPolicyBody) var policyResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createPolicyBody), &policyResult)) + require.NoError(t, json.Unmarshal([]byte(createPolicyBody), &policyResult, jsonx.V1())) policyID := policyResult["policyId"].(string) require.NotEmpty(t, policyID) @@ -198,7 +199,7 @@ func TestIntegration_VerifiedPermissions_PolicyTemplateLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createStoreResp.StatusCode, "create store body: %s", createStoreBody) var storeResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createStoreBody), &storeResult)) + require.NoError(t, json.Unmarshal([]byte(createStoreBody), &storeResult, jsonx.V1())) storeID := storeResult["policyStoreId"].(string) // Create a policy template. @@ -211,7 +212,7 @@ func TestIntegration_VerifiedPermissions_PolicyTemplateLifecycle(t *testing.T) { require.Equal(t, http.StatusOK, createTplResp.StatusCode, "create template body: %s", createTplBody) var tplResult map[string]any - require.NoError(t, json.Unmarshal([]byte(createTplBody), &tplResult)) + require.NoError(t, json.Unmarshal([]byte(createTplBody), &tplResult, jsonx.V1())) templateID := tplResult["policyTemplateId"].(string) require.NotEmpty(t, templateID) diff --git a/test/internal/tofu/download.go b/test/internal/tofu/download.go index b08d0492f8..74096f6076 100644 --- a/test/internal/tofu/download.go +++ b/test/internal/tofu/download.go @@ -8,7 +8,8 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "io" @@ -16,6 +17,8 @@ import ( "os" "runtime" "strings" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) var ( @@ -48,7 +51,7 @@ func DownloadBinary(ctx context.Context, logf func(format string, args ...any)) } `json:"versions"` } - if err = json.NewDecoder(resp.Body).Decode(&api); err != nil { + if err = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &api, jsonx.V1()); err != nil { return "", fmt.Errorf("decoding OpenTofu version list: %w", err) } diff --git a/test/terraform/azure/cosmosdb_test.go b/test/terraform/azure/cosmosdb_test.go index de81ca4702..af77bb373d 100644 --- a/test/terraform/azure/cosmosdb_test.go +++ b/test/terraform/azure/cosmosdb_test.go @@ -1,7 +1,7 @@ package azure_test import ( - "encoding/json" + "encoding/json/v2" "os" "os/exec" "strings" @@ -10,6 +10,8 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const azureCosmosFixture = ` @@ -150,7 +152,7 @@ func TestTerraform_Azure_CosmosDB(t *testing.T) { require.NoError(t, err) pk := azcosmos.NewPartitionKeyString("p1") - item, err := json.Marshal(map[string]any{"id": "item1", "pk": "p1", "email": "a@example.com"}) + item, err := json.Marshal(map[string]any{"id": "item1", "pk": "p1", "email": "a@example.com"}, jsonx.V1()) require.NoError(t, err) ctx := t.Context() @@ -182,7 +184,7 @@ func TestTerraform_Azure_CosmosDB(t *testing.T) { func mustJSON(t *testing.T, v any) []byte { t.Helper() - b, err := json.Marshal(v) + b, err := json.Marshal(v, jsonx.V1()) require.NoError(t, err) return b @@ -192,7 +194,7 @@ func stripSystemFields(t *testing.T, raw []byte) string { t.Helper() var m map[string]any - require.NoError(t, json.Unmarshal(raw, &m)) + require.NoError(t, json.Unmarshal(raw, &m, jsonx.V1())) for k := range m { if strings.HasPrefix(k, "_") { diff --git a/test/terraform/azure/eventgrid_test.go b/test/terraform/azure/eventgrid_test.go index 45cf2d2606..76ea1c018a 100644 --- a/test/terraform/azure/eventgrid_test.go +++ b/test/terraform/azure/eventgrid_test.go @@ -5,7 +5,7 @@ import ( "context" "crypto/tls" "crypto/x509" - "encoding/json" + "encoding/json/v2" "io" "net/http" "os" @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/azureauth" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" ) const ( @@ -146,7 +147,7 @@ func armCallAPI(ctx context.Context, t *testing.T, c *http.Client, method, path, var out map[string]any - require.NoError(t, json.Unmarshal(raw, &out)) + require.NoError(t, json.Unmarshal(raw, &out, jsonx.V1())) return out } @@ -209,7 +210,7 @@ func verifyEventGridRoundTrip(t *testing.T) { var got map[string]any - require.NoError(t, json.Unmarshal([]byte(text), &got)) + require.NoError(t, json.Unmarshal([]byte(text), &got, jsonx.V1())) assert.Equal(t, "from-terraform", got["id"]) assert.Equal(t, "App.Thing", got["eventType"]) assert.Contains(t, got["topic"], "Microsoft.EventGrid/topics/"+azureEventGridTopicName) diff --git a/test/terraform/azure/servicebus_test.go b/test/terraform/azure/servicebus_test.go index 7d24f16945..5fe5016a7c 100644 --- a/test/terraform/azure/servicebus_test.go +++ b/test/terraform/azure/servicebus_test.go @@ -11,7 +11,7 @@ package azure_test import ( - "encoding/json" + "encoding/json/v2" "io" "net/http" "strings" @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -162,7 +163,7 @@ func verifyServiceBusQueueRoundTrip(t *testing.T) { assert.Equal(t, messageBody, string(gotBody)) var bp sbBrokerProperties - require.NoError(t, json.Unmarshal([]byte(resp.Header.Get("Brokerproperties")), &bp)) + require.NoError(t, json.Unmarshal([]byte(resp.Header.Get("Brokerproperties")), &bp, jsonx.V1())) require.NotEmpty(t, bp.LockToken) location := resp.Header.Get("Location") diff --git a/test/terraform/terraform_test.go b/test/terraform/terraform_test.go index 066923c93d..3656410958 100644 --- a/test/terraform/terraform_test.go +++ b/test/terraform/terraform_test.go @@ -5,7 +5,8 @@ import ( "bytes" "context" "embed" - "encoding/json" + "encoding/json/jsontext" + "encoding/json/v2" "errors" "fmt" "hash/fnv" @@ -152,6 +153,7 @@ import ( "github.com/stretchr/testify/require" "github.com/blackbirdworks/gopherstack/pkgs/httpclient" + "github.com/blackbirdworks/gopherstack/pkgs/jsonx" "github.com/blackbirdworks/gopherstack/pkgs/testclient" ) @@ -689,7 +691,7 @@ func downloadTofuBinary(logger *slog.Logger) (string, error) { ID string `json:"id"` } `json:"versions"` } - err = json.NewDecoder(resp.Body).Decode(&api) + err = json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &api, jsonx.V1()) if err != nil { return "", fmt.Errorf("decoding OpenTofu version list: %w", err) } @@ -3558,7 +3560,7 @@ func TestTerraform_SESv2(t *testing.T) { "email identity %q should exist after terraform apply", email) var identityOut map[string]any - require.NoError(t, json.NewDecoder(resp.Body).Decode(&identityOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp.Body), &identityOut, jsonx.V1())) assert.Equal(t, "EMAIL_ADDRESS", identityOut["IdentityType"]) // Verify configuration set was created. @@ -3574,7 +3576,7 @@ func TestTerraform_SESv2(t *testing.T) { "configuration set %q should exist after terraform apply", cfgName) var cfgOut map[string]any - require.NoError(t, json.NewDecoder(resp2.Body).Decode(&cfgOut)) + require.NoError(t, json.UnmarshalDecode(jsontext.NewDecoder(resp2.Body), &cfgOut, jsonx.V1())) assert.Equal(t, cfgName, cfgOut["ConfigurationSetName"]) }, }, @@ -4706,7 +4708,7 @@ func TestTerraform_CloudControl(t *testing.T) { for _, rd := range out.ResourceDescriptions { var props map[string]any propsJSON := []byte(aws.ToString(rd.Properties)) - if unmarshalErr := json.Unmarshal(propsJSON, &props); unmarshalErr == nil { + if unmarshalErr := json.Unmarshal(propsJSON, &props, jsonx.V1()); unmarshalErr == nil { if props["LogGroupName"] == expectedName { foundIdentifier = aws.ToString(rd.Identifier) @@ -6612,7 +6614,7 @@ func TestTerraform_ServerlessRepo(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected 200 OK, body: %s", string(body)) var result map[string]any - require.NoError(t, json.Unmarshal(body, &result)) + require.NoError(t, json.Unmarshal(body, &result, jsonx.V1())) assert.Equal(t, appName, result["name"]) }, }, @@ -6668,7 +6670,7 @@ func TestTerraform_Shield(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected 200 OK, body: %s", string(respBody)) var result map[string]string - require.NoError(t, json.Unmarshal(respBody, &result)) + require.NoError(t, json.Unmarshal(respBody, &result, jsonx.V1())) assert.Equal(t, "ACTIVE", result["SubscriptionState"]) }, }, @@ -6786,7 +6788,7 @@ func TestTerraform_Textract(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected 200 OK, body: %s", string(respBody)) var result map[string]any - require.NoError(t, json.Unmarshal(respBody, &result)) + require.NoError(t, json.Unmarshal(respBody, &result, jsonx.V1())) blocks, ok := result["Blocks"].([]any) assert.True(t, ok, "expected Blocks in response") assert.NotEmpty(t, blocks, "expected non-empty Blocks") @@ -6848,7 +6850,7 @@ func TestTerraform_TimestreamWrite(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected 200 OK, body: %s", string(respBody)) var result map[string]any - require.NoError(t, json.Unmarshal(respBody, &result)) + require.NoError(t, json.Unmarshal(respBody, &result, jsonx.V1())) db, ok := result["Database"].(map[string]any) assert.True(t, ok, "expected Database in response") assert.Equal(t, databaseName, db["DatabaseName"]) @@ -7105,7 +7107,7 @@ func TestTerraform_Wafv2(t *testing.T) { assert.Equal(t, http.StatusOK, resp.StatusCode, "expected 200 OK, body: %s", string(respBody)) var result map[string]any - require.NoError(t, json.Unmarshal(respBody, &result)) + require.NoError(t, json.Unmarshal(respBody, &result, jsonx.V1())) webACLs, ok := result["WebACLs"].([]any) require.True(t, ok)